Basic Use¶
Creating 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.
The first step is to create a function library.
Example: Creating a Function Library¶
{
"functions": [
{
"name": "main",
"body": []
}
]
}
from classiq import FunctionLibrary
function_library = FunctionLibrary()
Give a function library 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: Preparing a Single Qubit in the 1 State¶
{
"functions": [
{
"name": "main",
"body": []
},
{
"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]
}]
}
}
]
}
In the IDE, create functions in the functions
field, which is in the function_library
.
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)
add_function()
method.
The FunctionDeclaration
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 that implements the logic of the function,
and additional information about the auxiliary registers used in this implementation.
The FunctionInterfaceData
object contains the IO information of the function: its
input registers, output registers, and zero input registers.
The example above prepares a single qubit 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. To inform the Classiq engine of the inputs and
outputs (via the FunctionInterfaceData
object), provide Register
objects
that specify them; either as single objects or as tuples. You must explicitly specify the output_registers
field;
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; specified via the zero_input_registers
field.
The qfunc
decorator converts a python-function to a FunctionDeclaration
object.
When decorating a function with the qfunc
decorator, it is important to note:
- 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 and outputs, and make sure that they are of the same length and have the same number of qubits.
- When supplying an input that is going to be output, use the
ZeroQReg
type. - When supplying auxiliary qubits, use the
AuxQReg
type. - Additional types are described in Quantum Registers.
- The output of the function is a string, unlike its type-hint, which states that it is a
QReg
.
Allowing Multiple Inputs and Outputs¶
A function may also have multiple inputs or outputs as shown in the next example,
which implements a controlled not gate, and multiple registers are provided in the
input_registers
and output_registers
fields.
Example: Controlled-not Gate¶
{
"functions": [
{
"name": "main",
"body": []
},
{
"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 the serialized_circuit
field, containing 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 in the Classiq synthesis engine.
Example: Connecting Two Functions¶
{
"functions": [
{
"name": "main",
"body": [{
"function": "init_one_qubit_to_1",
"function_params": {
},
"outputs": {
"init_1_output": "wire"
}
},
{
"function": "my_controlled_not_gate",
"function_params": {
},
"inputs": {
"control_input": "wire"
}
}]
},
{
"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]
}]
}
}
]
}
from typing import Tuple
from classiq import Model, qfunc, QReg, ZeroQReg, QASM_INTRO, synthesize, show
@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})
quantum_program = synthesize(model.get_model())
show(quantum_program)
The example above defines the data flow between the functions by connecting an output of a function to an input of another function.
Using the SDK, you can call a function in two ways: using the name of
the function as a Model
's method, as shown in the example above; or 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, the data flow is defined by passing the optional in_wires
argument.
In the textual model, call a function by adding a call to CustomFunction
in
the body
field, and set the parameters of the function as the name of
the function 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 shown in the example above, you must include the library in the generator using the
include_library()
method before any function generation requests.
The FunctionLibrary
class has methods for adding and removing functions:
add_function()
gets aFunctionDeclaration
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 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 SDK users, a special QASM3_INTRO
constant is defined as the string:
OPENQASM 3.0;
include "stdgates.inc";
The Classiq engine supports these OpenQASM3.0 features:
- Basic gates from OpenQASM2.0 (except for the
U
gate) - Quantum measurements
- Gate declarations
- Alias statements
These features may be supported in the future:
- Custom include files
U
gates- Parametrized gates and circuits
- For loops (with quantum statements only)
The following example demonstrates the use of OpenQASM3.0.
{
"functions": [
{
"name": "main",
"body": [{
"function": "init_one_qubit_to_1",
"function_params": {
},
"outputs": {
"init_1_output": "wire"
}
},
{
"function": "my_controlled_not_gate",
"function_params": {
},
"inputs": {
"control_input": "wire"
}
}]
},
{
"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]
}]
}
}
]
}
from typing import Tuple
from classiq import Model, qfunc, QReg, ZeroQReg, QASM3_INTRO, synthesize, show
@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})
quantum_program = synthesize(model.get_model())
show(quantum_program)