Basic Usage¶
Using a Library¶
The function library is responsible for managing the data structure where all the user-defined functions are stored, as well as storing and managing all the required data.
Creating a Library¶
The first step is creating a function library.
Example: Creating a Function Library¶
{
"function_library":{
"name": "my_library"
}
}
from classiq import FunctionLibrary
function_library = FunctionLibrary(name="my_library")
A function library may be given a name
as in the above example.
Defining a Function and Adding it to a Library¶
The function library is constructed from functions, which are the functional building blocks that implement quantum logic, and the data flow between them.
Example: Preparation of a Single Qubit in The 1 State¶
{
"function_library":{
"name": "my_library",
"functions": [{
"name": "init_one_qubit_to_1",
"implementations": [{
"serialized_circuit": "OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[1];\nx q[0];"
}],
"register_mapping": {
"output_registers": [{
"name": "output_of_init_one_qubit_to_1",
"qubits": [0]
}],
"zero_input_registers": [{
"name": "zero_input",
"qubits": [0]
}]
}
}]
}
}
from classiq import qfunc, ZeroQReg, QReg, FunctionLibrary, QASM_INTRO
@qfunc
def init_one_qubit_to_1(zero_input: ZeroQReg[1]) -> QReg[1]:
return QASM_INTRO + "qreg q[1];\nx q[0];"
function_library = FunctionLibrary(init_one_qubit_to_1, name="my_library")
In the textual interface, functions are inputted via the functions
field of the
function library.
In the SDK, one may add functions to the library using the add_function()
method.
The FunctionDefinition
object is responsible for storing all the information about the
function. In its initialization it requires a name
, a register mapping, and a tuple of
implementations, or a single implementation. The FunctionImplementation
object
contains the serialized_circuit
code which implements the logic of the function,
and additional information about the auxiliaries registers used in this implementation.
The FunctionInterfaceData
object contains the IO information of the function: its
input registers, output registers, and zero input registers.
In the example above, a single qubit is prepared in the 1 state.
The QASM_INTRO
constant is defined as the string:
OPENQASM 2.0;
include "qelib1.inc";
A generic function has inputs and outputs, which allow it to be connected to the logic
flow of the quantum circuit. In order to inform the Classiq engine of the inputs and
outputs (via the FunctionInterfaceData
object), one needs to provide Register
objects
which specify them; either as single objects or as tuples. The output_registers
field
must be explicitly specified; each Register
must contain its name
and a tuple of
qubits
in the register.
In the example above, the function only has outputs, and requires one qubit in the 0
state; this is specified via the zero_input_registers
field.
The qfunc
decorator converts a python-function to a FunctionDefinition
object.
When decorating a function with the qfunc
decorator, it is important to note that:
- The output of the function should be a
str
of valid OpenQASM2.0 or OpenQASM3.0 code - Type annotations are important. The function should declare all of its inputs, as well as its outputs, and make sure that they are of the same length and have the same number of qubits.
- For supplying an input that is going to be output, use the
ZeroQReg
type - For supplying auxillary qubits, use the
AuxQReg
type - Other types are described in Quantum Registers
- The output of the function is a string, unlike its type-hint, which states that it's a
QReg
Multiple Inputs and Outputs¶
A function may also have multiple inputs or outputs; this is seen in the next example
which implements a controlled not gate, where multiple registers are provided in the
input_registers
and output_registers
fields.
Example: Controlled-not Gate¶
{
"function_library":{
"name": "my_library",
"functions": [{
"name": "my_controlled_not_gate",
"implementations": [{
"serialized_circuit": "OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[2];\ncx q[0], q[1];"
}],
"register_mapping": {
"input_registers": [{
"name": "control_input",
"qubits": [0]
},
{
"name": "target_input",
"qubits": [1]
}],
"output_registers": [{
"name": "control_output",
"qubits": [0]
},
{
"name": "target_output",
"qubits": [1]
}]
}
}]
}
}
from typing import Tuple
from classiq import qfunc, QReg, QASM_INTRO
@qfunc
def my_controlled_not_gate(
control: QReg[1], target: QReg[1]
) -> Tuple[QReg[1], QReg[1]]:
return QASM_INTRO + "qreg q[2];\ncx q[0], q[1];"
The FunctionImplementation
object must get serialized_circuit
field which contains a
string with the instructions of the function. The serialized_circuit
field should be
provided in OpenQASM format.
Using a Function Library with the Classiq Engine¶
Functions added to a library may be used within the Classiq synthesis engine.
Example: Connecting Two Functions¶
{
"function_library":{
"name": "my_library",
"functions": [{
"name": "init_one_qubit_to_1",
"implementations": [{
"serialized_circuit": "OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[1];\nx q[0];"
}],
"register_mapping": {
"output_registers": [{
"name": "init_1_output",
"qubits": [0]
}],
"zero_input_registers": [{
"name": "zero_input",
"qubits": [0]
}]
}
},
{
"name": "my_controlled_not_gate",
"implementations": [{
"serialized_circuit": "OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[2];\ncx q[0], q[1];"
}],
"register_mapping": {
"input_registers": [{
"name": "control_input",
"qubits": [0]
},
{
"name": "target_input",
"qubits": [1]
}],
"output_registers": [{
"name": "control_output",
"qubits": [0]
},
{
"name": "target_output",
"qubits": [1]
}]
}
}]
},
"logic_flow": [{
"function": "init_one_qubit_to_1",
"function_params": {
},
"outputs": {
"init_1_output": "wire"
}
},
{
"function": "my_controlled_not_gate",
"function_params": {
},
"inputs": {
"control_input": "wire"
}
}]
}
from typing import Tuple
from classiq import Model, qfunc, QReg, ZeroQReg, FunctionLibrary, QASM_INTRO
@qfunc
def init_one_qubit_to_1(zero_input: ZeroQReg[1]) -> QReg[1]:
return QASM_INTRO + "qreg q[1];\nx q[0];"
@qfunc
def my_controlled_not_gate(
control: QReg[1], target: QReg[1]
) -> Tuple[QReg[1], QReg[1]]:
return QASM_INTRO + "qreg q[2];\ncx q[0], q[1];"
model = Model()
control = model.apply(init_one_qubit_to_1)["zero_input"]
model.apply(my_controlled_not_gate, in_wires={"control": control})
circuit = model.synthesize()
circuit.show_interactive()
In the example above, the data flow between the functions is defined by connecting an output of a function to an input of another function.
Using the SDK, calling a function can be done in two ways: either by using the name of
the function as a Model
's method, as shown in the example above; or by using the
apply
method, as shown in the example below.
output_dict_1 = model.apply(init_one_qubit_to_1)
model.apply(
my_controlled_not_gate,
in_wires={"control_input": output_dict_1["init_1_output"]},
)
In both cases, defining the data flow is done by passing the optional in_wires
argument.
In the textual model, calling a function done by adding a call to CustomFunction
in
the logic_flow
field, and setting the parameters of the function to be the name of
the function you want to call.
The output circuit is shown below at the gate level.
The circuit shown below is outputted at the functional level.
SDK Interface¶
As in the example above, the library must be included in the generator via the
include_library()
method before any function generation requests.
The FunctionLibrary
class has methods for adding and removing functions.
add_function()
gets aFunctionDefinition
object and adds a function to the library.remove_function()
removes a function from the function library.
The library also provides access to its name and to the names of the functions in the library via the following properties:
name
function_names
OpenQASM3.0 Support¶
The Classiq synthesis engine also supports user-defined functions employing OpenQASM3.0 code, and even mixed implementations (with some functions defined with OpenQASM2.0 and others with OpenQASM3.0).
For the SDK users, a special QASM3_INTRO
constant is defined as the string:
OPENQASM 3.0;
include "stdgates.inc";
Currently, the Classiq engine supports the following features of OpenQASM3.0:
- Basic gates from OpenQASM2.0 (except the
U
gate) - Quantum measurements
- Gate declarations
- Alias statements
The following features are planned to be supported in the future:
- Custom include files
U
gates- Parametrized gates and circuits
- For loops (with quantum statements only)
In the following example, the usage of OpenQASM3.0 is demonstrated.
{
"function_library":{
"name": "my_library",
"functions": [{
"name": "init_one_qubit_to_1",
"implementations": [{
"serialized_circuit": "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[1] q;\nx q[0];"
}],
"register_mapping": {
"output_registers": [{
"name": "init_1_output",
"qubits": [0]
}],
"zero_input_registers": [{
"name": "zero_input",
"qubits": [0]
}]
}
},
{
"name": "my_controlled_not_gate",
"implementations": [{
"serialized_circuit": "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[2] q;\ncx q[0], q[1];"
}],
"register_mapping": {
"input_registers": [{
"name": "control_input",
"qubits": [0]
},
{
"name": "target_input",
"qubits": [1]
}],
"output_registers": [{
"name": "control_output",
"qubits": [0]
},
{
"name": "target_output",
"qubits": [1]
}]
}
}]
},
"logic_flow": [{
"function": "init_one_qubit_to_1",
"function_params": {
},
"outputs": {
"init_1_output": "wire"
}
},
{
"function": "my_controlled_not_gate",
"function_params": {
},
"inputs": {
"control_input": "wire"
}
}]
}
from typing import Tuple
from classiq import Model, qfunc, QReg, ZeroQReg, FunctionLibrary, QASM3_INTRO
@qfunc
def init_one_qubit_to_1(zero_input: ZeroQReg[1]) -> QReg[1]:
return QASM3_INTRO + "qubit[1] q;\nx q[0];"
@qfunc
def my_controlled_not_gate(
control: QReg[1], target: QReg[1]
) -> Tuple[QReg[1], QReg[1]]:
return QASM3_INTRO + "qubit[2] q;\ncx q[0], q[1];"
model = Model()
control = model.apply(init_one_qubit_to_1)["zero_input"]
model.apply(my_controlled_not_gate, in_wires={"control": control})
circuit = model.synthesize()
circuit.show_interactive()