Skip to content

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

{
    "constraints": {
      "max_width": 20,
      "max_depth": 100
    },
    "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

{
    "constraints": {
      "max_width": 20,
      "max_depth": 100
    },
    "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];",
                "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 FunctionData object is responsible for storing all the information about the function. In its initialization it requires a name and a tuple of implementations, or a single implementation. The FunctionImplementation object contains the serialized_circuit code which implements the logic of the function. 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, 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 FunctionData 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

{
    "constraints": {
      "max_width": 20,
      "max_depth": 100
    },
    "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];",
                "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

{
    "constraints": {
      "max_width": 20,
      "max_depth": 100
    },
    "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];",
                "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];",
                "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": "CustomFunction",
        "function_params": {
            "name": "init_one_qubit_to_1"
        },
        "outputs": {
            "init_1_output": "wire"
          }
    },
    {
        "function": "CustomFunction",
        "function_params": {
            "name": "my_controlled_not_gate"
        },
        "inputs": {
            "control_input": "wire"
        }
    }]
}
from typing import Tuple
from classiq import ModelDesigner, 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];"


function_library = FunctionLibrary(
    init_one_qubit_to_1, my_controlled_not_gate, name="my_library"
)

model_designer = ModelDesigner()
model_designer.include_library(function_library)

control = QReg(size=1)
model_designer.init_one_qubit_to_1(out_wires=control)
model_designer.my_controlled_not_gate(
    in_wires={"control": control},
)

circuit = model_designer.synthesize()
circuit.show()

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 ModelDesigner's method, as shown in the example above; or by using the apply method, as shown in the example below.

output_dict_1 = model_designer.apply(function_name="init_one_qubit_to_1")
model_designer.apply(
    function_name="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.

 Controlled not example

The circuit shown below is outputted at the functional level.

 Controlled not example

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 a FunctionData 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.

{
    "constraints": {
      "max_width": 20,
      "max_depth": 100
    },
    "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];",
                "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];",
                "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": "CustomFunction",
        "function_params": {
            "name": "init_one_qubit_to_1"
        },
        "outputs": {
            "init_1_output": "wire"
          }
    },
    {
        "function": "CustomFunction",
        "function_params": {
            "name": "my_controlled_not_gate"
        },
        "inputs": {
            "control_input": "wire"
        }
    }]
}
from typing import Tuple
from classiq import ModelDesigner, 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];"


function_library = FunctionLibrary(
    init_one_qubit_to_1, my_controlled_not_gate, name="my_library"
)

model_designer = ModelDesigner()
model_designer.include_library(function_library)

control = QReg(size=1)
model_designer.init_one_qubit_to_1(out_wires=control)
model_designer.my_controlled_not_gate(
    in_wires={"control": control},
)

circuit = model_designer.synthesize()
circuit.show()