跳转至

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:

qbit \ ID; \\ qbit \ ID[NUMBER]; \\ qbit \ ID_1,…, ID_n[NUMBER];

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:

qcouple \ ID; \\ qcouple \ ID[NUMBER]; \\ qcouple \ ID_1,…, ID_n[NUMBER];

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:

var \ ID = NUMBER; \\\ var \ ID[n] = \{NUMBER_1,...,NUMBER_n\};

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:

ID[idx_1, idx_2,..., idx_n] \\\ ID[start:stop:step]

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 (qid_1,…,qid_n) \\\ GateID (qbundle_1,…,qbundle_n) \\\ GateID (theta, qid) \\\ GateID (theta, qbundle) \\\ GateID (phi, theta, qid) \\\ GateID (phi, theta, qbundle)

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:

M (qid) \\\ M (qbundle)

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:

for \ cid \ in \ start:stop:step\{ \\\ forloopBody \\\ \}

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:

if (expression \ asso \ expression )\{ \\\ ifBody \\\ \}

or

if (expression \ asso \ expression )\{ \\\ ifBody \\\ \}else\{ elseBody \}

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]