Skip to content

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)
In the SDK, add functions to the library using the 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.

 Controlled not example

The circuit shown below is outputted at the functional level.

 Controlled not example

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 a FunctionDeclaration 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)