Appendix D: isq-Core Documentation¶
isQ-core Python Package Installation and Usage Guide¶
Environment¶
python == 3.8
Installation¶
Direct installation using pip
pip install isqopen
Offline installation from source code
git clone https://gitee.com/arclight_quantum/isq-core/
python setup.py build
python setup.py install
Hardware Support¶
isQ-core supports the 12-qubit quantum hardware on the Innovation Academy Cloud Platform. To use quantum hardware, you need to register for an account and password on this cloud platform.
isQ-core supports hardware connected through AWS Braket. You can use corresponding quantum hardware by setting the hardware number and S3 bucket. For offline use, please first install aws-braket-sdk
, register an AWS account, enable Braket permissions, and apply for an S3 bucket. When using the isQ Cloud Platform, the platform provides AWS usage permissions, and running consumes platform credits.
Usage¶
Import isqopen using import isq
Device Usage¶
Users need to first define the Device to use, then write the isQ-core program and call the Device's run
interface to execute it. For Device initialization parameters, please refer to the API documentation. LocalDevice
returns measurement statistics as a dict
. QcisDevice
and AwsDevice
return ISQTask, and you can get results through the result
interface (if waiting time exceeds max_wait_time, an empty dictionary is returned; you can check execution status through the state
attribute). Usage examples are as follows:
from isq import LocalDevice, QcisDevice, AwsDevice
isq_str = '''
qbit a, b;
X(a);
CNOT(a, b);
M(a);
M(b);
'''
# local device
ld = LocalDevice(shots = 200)
# returns dict
ld_res = ld.run(isq_str)
print(ld_res)
#qcis device
qd = QcisDevice(user = "xxx", passwd = "xxx", max_wait_time = 60)
#returns ISQTask
task = qd.run(file = './test.isq')
#output status and results
print(task.state)
print(task.result())
import logging
logger = logging.getLogger() # initialize logging class
logger.setLevel(logging.CRITICAL)
#aws device
from isq import TaskState
ad = AwsDevice(shots=100, device_arn = "arn:aws:braket:::device/qpu/ionq/ionQdevice", s3 = ("amazon-braket-arclight", "simulate"), max_wait_time=10, logger = logger)
task = ad.run(isq_str)
print(task.state)
#wait for results until execution completes
while task.state == TaskState.WAIT:
task.result()
print(task.result())
Device provides the compile_to_ir
interface, which can compile isq to openqasm/qcis. Example:
'''
isQ-core -> openqasm2.0
qreg size matches the number of qubits defined in isQ-core
creg size matches the number of measured bits
Measurement results are stored sequentially starting from creg[0] according to measurement order
'''
ld = LocalDevice()
ir = ld.compile_to_ir(file = './test.isq', target="openqasm")
print(ir)
isq_str = '''
qbit a,b;
H(a);
CNOT(a,b);
M(a);
M(b);
'''
ld = LocalDevice()
ir = ld.compile_to_ir(isq_str, target="qcis")
print(ir)
Quantum Kernel Functions¶
isQ-core provides the function decorator isq.qpu
. Through this decorator, you can define a quantum kernel function. This decorator has the following parameters:
Parameter | Type | Description | Default |
---|---|---|---|
device | Device | Quantum backend, can be LocalDevice , QcisDevice , AwsDevice |
None |
Functions decorated with this decorator need to use isQ-core syntax in the form of comments inside the function. Quantum kernel functions are called in the same way as Python functions, and their return results are consistent with using the Device's run
function.
#Use simulator to run and prepare Bell state
import isq
from isq import LocalDevice, QcisDevice, AwsDevice
ld = LocalDevice()
@isq.qpu(ld)
def bell_sim():
'''
qbit a, b;
X(a);
CNOT(a, b);
M(a);
M(b);
'''
res = bell_sim()
print(res)
ir = ld.get_ir()
print(ir)
#Use 12-bit quantum hardware to run and prepare Bell state
qd = QcisDevice(user = "xxx", passwd = "xxx")
@isq.qpu(qd)
def bell_qcis():
'''
qbit a, b;
X(a);
CNOT(a, b);
M(a);
M(b);
'''
task = bell_qcis()
res = task.result()
print(res)
Parameterized Circuits¶
isQ-core supports writing parameterized circuits and passing specific parameter values during compilation and execution. Users can directly use parameters when writing circuits and pass specific values of same-name parameters in the Device's compilation and execution interface, for example:
isq_str = '''
qbit a, b;
RX(theta[0], a);
RY(theta[1], b);
M(a);
M(b);
'''
from isq import LocalDevice
# local device
ld = LocalDevice(shots = 200)
# returns dict
ld_res = ld.run(isq_str, theta = [0.2, 0.4])
print(ld_res)
Or through quantum kernel functions, determine parameter names when defining kernel functions and pass specific values when calling functions:
#Parameterized quantum kernel function
import isq
from isq import LocalDevice
ld = LocalDevice()
@isq.qpu(ld)
def test(theta, a):
'''
qbit t[5];
if (a < 5){
X(t[0]);
}else{
RX(theta[1],t[1]);
}
M(t[1]);
'''
res = test([0.1, 0.2], 3)
Custom Unitary Gates¶
You can use the quantumCor.addGate
function to declare custom unitary gates outside the kernel. These unitary gates can be directly used in all kernels (unitary operations). Declaring unitary gates with the same name inside the kernel will override them internally. Custom unitary gates require two parameters: gate name (str) and gate matrix representation (list/np.array). Example:
from isq import quantumCor
import isq
a = [[0.5+0.8660254j,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]
quantumCor.addGate("Rs", a)
ld = LocalDevice()
@isq.qpu(ld)
def test():
'''
qbit a, b;
...
Rs(a, b);
...
'''
Circuit Diagram Generation¶
isQ-core can print circuit display diagrams of instruction sets through the Device's draw_circuit
function. Since it's generated from compilation results, circuit diagrams only include basic gate sets, and user-defined gates will be displayed after decomposition. Rotation angles for RX, RY, RZ are not displayed by default. If needed, you can set the showparam
parameter.
from isq import LocalDevice
#Define quantum circuit
isq_str = '''
qbit a,b;
H(a);
CNOT(a,b);
RX(2.333, a);
M(a);
M(b);
'''
ld = LocalDevice()
ld.run(isq_str)
ld.draw_circuit()
ld.draw_circuit(True)
Automatic Differentiation¶
isQ-core can perform automatic differentiation on parameterized circuits and use optimizers for parameter optimization. LocalDevice
has a built-in probs interface that performs parameterized compilation at the compilation stage to generate parameterized circuits (currently only supports parameterized RX, RY, RZ gates). These parameters are the ones that need optimization and should be wrapped with optv
when passed to the interface. During the simulation stage, it returns probability values of measured bits (no measurement is performed, probabilities are returned directly).
isQ-core provides an optimizer
based on gradient descent method. Its opt
interface accepts user-defined loss functions and parameters to be optimized, returning optimized parameters and current loss function value.
Example:
import isq
from isq import LocalDevice, optv
#Define quantum circuit
isq_str = '''
qbit q[2];
RX(theta[0], q[0]);
RY(theta[1], q[1]);
M(q[0]);
'''
#Define device
ld = LocalDevice()
#Define loss function
def loss(params):
#Use probs function for parameterized compilation and simulation, where parameters to be optimized are passed after being wrapped with optv
c = ld.probs(isq_str, theta = optv(params))
return c[0] - c[1]
#Define optimizer
opt = isq.optimizer(0.01)
#Get optimized parameters and current loss function calculation value
newp, cost = opt.opt(loss, [0.2, 0.4])
print(newp, cost)
Custom Optimizer¶
isQ-core provides automatic differentiation interface grad
to enable users to customize optimizers based on differentiation results. grad
accepts a loss function and the positions of parameters to be differentiated in the loss function as inputs, returning a callable type. When you pass the parameters required for the calculation function, it returns the differentiation results of the parameters. Example:
import isq
from isq import LocalDevice, optv
#Define quantum circuit
isq_str = '''
qbit q[2];
RX(theta[0], q[0]);
RY(theta[1], q[1]);
M(q[0]);
'''
#Define device
ld = LocalDevice()
#Define calculation function
def calc(params):
#Use probs function for parameterized compilation and simulation, where parameters to be differentiated are passed after being wrapped with optv
c = ld.probs(isq_str, theta = optv(params))
return c[0] - c[1]
#Define grad and call it, differentiating with respect to the 0th parameter
g = isq.grad(calc, [0])
d = g([0.2, 0.4])
print(d)
Batch Processing¶
isQ-core provides batch processing functionality for automatic differentiation through jax's vmap. This functionality is less efficient than the above grad
interface for single differentiation runs, but has significant advantages in batch processing.
To use this, you need to first install the jax
environment, and add the mod
parameter when using the LocalDevice
's probs
interface in the loss function, setting it to 1 (default is 0). Differentiation uses jax's built-in grad function, and batch processing uses jax's vmap function. Example:
from isq import LocalDevice
from jax import grad, vmap
from jax import numpy as jnp
#Define quantum circuit
isq_str = '''
qbit q[2];
RX(theta[0], q[0]);
RY(theta[1], q[1]);
M(q[0]);
'''
#Define device
ld = LocalDevice()
#Define calculation function
def calc(params):
#Use probs function for parameterized compilation and simulation, where parameters to be optimized are passed after being wrapped with optv, mod value is 1, using jax.numpy for calculation
c = ld.probs(isq_str, mod = 1, theta = optv(params))
return c[0] - c[1]
theta = jnp.array([[0.2, 0.4] for _ in range(10)])
#Differentiation
g = grad(calc)
#vmap batch processing, indexing by axis 0, outputting differentiation results for each partition. In this example, it partitions by rows and outputs 10 results
c = jax.vmap(g)
d = c(theta)
print(d)
isQ-core Syntax Description¶
The isQ-core programming language includes the following operations:
1.1 Custom qubit Variables¶
isQ-core supports users to define qubit variables (only supports defining global variables). It can be a single variable or a qubit array (note that in isQ, the keyword corresponding to qubit is qbit
). The definition format is as follows:
Where ID
is the variable name, consisting of letters and numbers, such as a, b2. Variable names cannot be repeated; array variable format is ID[NUMBER]
, where NUMBER
is a number, such as a[3], b1[10]; each line can define single or multiple variables/arrays. All qubit variables are initialized to |0⟩ by default.
Example:
qbit a;
qbit b[3];
qbit q, p[5];
Note: When compiling to QCIS instruction set, qubits correspond to physical bits in order, such as in the above example a->Q1, b[0]->Q2
1.2 Custom qcouple Variables¶
isQ-core supports users to define qcouple variables (only supports defining global variables). qcouple refers to two coupled quantum bits. The definition format is as follows:
The definition of qcouple is similar to qubit. When using, qcouple can only be used for two-qubit gates, such as CNOT, CZ.
Example:
qcouple c;
qcouple qc[3];
qcouple q, p[5];
...
CZ(c);
...
Note: When compiling to QCIS instruction set, qcouple corresponds to coupled physical bit pairs in order, such as in the above example c->Q67(Q1, Q7), qc[0]->Q68(Q1, Q8)
1.3 Classical Variable Definition¶
isQ-core supports users to define classical variables through var
(only supports defining global variables). The definition format is as follows:
Classical variables need to be given initial values when defined. These classical variables can generally be used as parameters for rotation gates.
Example:
qbit q[66];
var x = 1.3;
var theta = {1.2, 1.4};
...
RX(x, q[0]);
RXY(theta[0], theta[1], q[5]);
...
1.4 Custom Unitary Gates¶
isQ-core allows users to define unitary gates that meet the following three conditions:
- Gate size is a power of 2
- Defined at the beginning of the program
- Does not conflict with basic gate names
Example:
defgate Rs = [
0.5+0.8660254j,0,0,0;
0,1,0,0;
0,0,1,0;
0,0,0,1
]
1.5 Unitary Operations¶
Before explaining unitary operations, let's introduce a concept: qbundle. qbundle represents a group of qubits within a qubit array, expressed as follows:
qbundle
has two expression forms, where ID
is the qubit variable name, which must be a previously defined array variable, and idx
is an index, which can be a constant or an expression constructed with arithmetic operations. In the first expression form, a series of indices represent this group of qubits. For example, if we previously defined t[10]
, we can use t[1,3,5,7]
to represent the four qubits t[1],t[3],t[5],t[7]
. The second expression form is similar to Python's range, using start/stop
to represent start/end indices (excluding stop), and step
to represent step size, defaulting to 1. For example, t[1:4]
represents t[1],t[2],t[3]
.
Unitary operations apply unitary gates to qubits. The definition format is as follows:
GateID
is the unitary gate name. Current basic gates include: H,X,Y,Z,S,T,SD,TD,X2P,X2M,Y2P,Y2M,CX, CY, CZ,CNOT,RX,RY,RZ,RXY
. n is the number of qubits the gate acts on (CZ, CNOT are 2, all other gates are 1). Unitary gates can act on qubit variables (form one) or qbundles (form two). When acting on qbundles, the lengths of qbundle_i must be consistent. The semantics is that for each j, execute GateID<qbundle_{1,j},…,qbundle_{n,j}>.
Example:
H(q);
H(p[1+2]);
CNOT(q, p[1]);
CNOT(p[1:3], p[2:4]); // = CNOT<p[1], p[2]>, CNOT<p[2], p[3]>
RX(0.5, q);
1.6 Measurement Operations¶
Measure qubits. The definition format is similar to the unitary operations above:
Where the semantics of measuring qbundle is to measure each qubit in the qbundle.
Example:
M(q);
M(p[1]);
M(p[1:4]); // = M(p[1]), M(p[2]), M(p[3]);
1.7 for Loops¶
To facilitate users in performing some loop unitary operations and simplifying code, isQ-core provides support for for loops. The definition format is as follows:
Where cid
is the loop increment variable name, start,stop,step
are consistent with the definition in qbundle
, start/stop
represent start/end values (excluding stop), and step
represents step size. forloopBody
can contain unitary operations, measurement operations, for loops, and if conditional statements. In forloopBody
, you can use expressions with cid as indices to access qubit variables.
Example:
for i in 1:3{
H(t[i]);
CZ(w[i],w[i+1]);
}
1.8 if Conditional Statements¶
isQ-core provides if conditional branch statements. The definition format is as follows:
or
Where expression
is a computable arithmetic expression, such as constants, int variables, or their arithmetic operations, and asso
is a comparison operator. Currently supports >,>=,<,<=,==,!=
. ifbody
and elseBody
are consistent with forloopBody
.
1.9 Comments¶
isQ-core provides single-line comment functionality. Add // before the content that needs to be commented.
isQ-core API Documentation¶
quantumCor¶
addGate
¶
Static function, register user-defined unitary gates
Parameter | Type | Description | Default |
---|---|---|---|
name | str | Unitary gate name | - |
val | [np.array, list] | Unitary gate matrix representation | - |
getGate
¶
Static function, get the current set of registered unitary gates
getMapping
¶
Static function, implement quantum bit mapping (QcisDevice has already called this interface to implement mapping for Innovation Academy's 12-bit quantum hardware)
Parameter | Type | Description | Default |
---|---|---|---|
qbit_num | int | Number of physical bits | - |
topo | list | Hardware topology structure | - |
isq_ir | str | Compiled isq_ir | - |
Device¶
run
¶
Compile + run, select corresponding compilation results based on backend
Parameter | Type | Description | Default |
---|---|---|---|
isq_str | str | isq source code | '' |
file | str | isq external file, when both isq_str and file exist, file content takes priority | None |
**kwargs | - | isq parameters | - |
compile_to_ir
¶
Compile
Parameter | Type | Description | Default |
---|---|---|---|
isq_str | str | isq source code | '' |
file | str | isq external file, when both isq_str and file exist, file content takes priority | None |
target | str | Compilation target, currently supports isq , qcis , openqasm |
isq |
**kwargs | - | isq parameters | - |
get_ir
¶
Get the compilation result of the current run
draw_circuit
¶
Print the circuit diagram of the currently compiled ir
Parameter | Type | Description | Default |
---|---|---|---|
showparam | bool | Whether to display RX, RY, RZ angles | False |
LocalDevice¶
__init__
Initialize
Parameter | Type | Description | Default |
---|---|---|---|
shots | int | Number of runs | 100 |
max_wait_time | int | Maximum wait time for task queries | 60 |
name | str | Backend name | 'local_device' |
mode | str | Simulation mode, fast /normal |
fast |
logger | Logger | Logger object | logging.getLogger(name) |
probs
Get amplitudes (no measurement, directly return probabilities)
Parameter | Type | Description | Default |
---|---|---|---|
isq_str | str | isq source code | '' |
file | str | isq external file, when both isq_str and file exist, file content takes priority | None |
mod | int | Data mode adopted, 0 —autograd.numpy, 1 —jax.numpy |
0 |
**kwargs | - | isq parameters, when used with optimizer, parameters to be optimized should be wrapped with optv | - |
QcisDevice¶
__init__
Initialize
Parameter | Type | Description | Default |
---|---|---|---|
user | str | Innovation Academy platform username | - |
passwd | str | Innovation Academy platform password | - |
shots | int | Number of runs | 100 |
max_wait_time | int | Maximum wait time for task queries | 60 |
name | str | Backend name | 'qcis_device' |
logger | Logger | Logger object | logging.getLogger(name) |
AwsDevice¶
__init__
Initialize
Parameter | Type | Description | Default |
---|---|---|---|
device_arn | str | AWS QPU, see braket documentation for optional values | - |
s3 | tuple | AWS S3 bucket, when using cloud platform, no need to fill in, use platform-provided bucket | - |
shots | int | Number of runs | 100 |
max_wait_time | int | Maximum wait time for task queries | 60 |
name | str | Backend name | 'aws_device' |
logger | Logger | Logger object | logging.getLogger(name) |
ISQTask¶
__init__
¶
Initialize
Parameter | Type | Description | Default |
---|---|---|---|
task_id | Union[int, str] | Task ID | - |
task_type | TaskType | Task type | - |
state | TaskState | Task state | - |
device | Device | Associated device | - |
**kwargs | - | Other parameters | - |
result
¶
Get results. When task state is TaskState.COMPLETE
, directly return results. When state is TaskState.WAIT
, start querying results. When query time exceeds the maximum wait time set by the associated device, the task is still queuing or running, return empty dictionary; otherwise, return query results.
cancel
¶
When using AwsDevice to generate tasks, you can cancel tasks through this interface (currently QcisDevice tasks cannot be cancelled)
TaskState¶
- WAIT
- COMPLETE
- FAIL
- CANCEL
TaskType¶
- QCSI
- AWS
optimizer¶
__init__
¶
Initialize
Parameter | Type | Description | Default |
---|---|---|---|
lr | float | Learning rate | - |
argnum | list | Positions of parameters to optimize | [0] |
opt
¶
Gradient descent optimization of parameters
Parameter | Type | Description | Default |
---|---|---|---|
fun | function | Loss function | - |
params | list | Parameters to optimize | - |
grad¶
__init__
¶
Initialize
Parameter | Type | Description | Default |
---|---|---|---|
fun | function | Loss function | - |
argnum | list | Positions of parameters to optimize | [0] |