Skip to content

Advance Usage

Multiple Implementations

In order to fully utilize the strength of the Classiq engine, we allow the user to add multiple different implementations for the same function.

The simplest example is two implementations of a controlled-Z gate.

Example: Controlled-Z Gate with Two Implementations

{
    "constraints": {
      "max_width": 20,
      "max_depth": 100
    },
    "function_library":{
        "name": "my_library",
        "functions": [{
            "name": "my_controlled_z_gate",
            "implementations": [{
                "name": "option_1",
                "serialized_circuit": "OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[2];\nh q[1];\n cx q[0], q[1];\nh q[1];",
                "input_registers": [{
                    "name": "input_1",
                    "qubits": [0]
                },
                {
                    "name": "input_2",
                    "qubits": [1]
                }],
                "output_registers": [{
                    "name": "output_1",
                    "qubits": [0]
                },
                {
                    "name": "output_2",
                    "qubits": [1]
                }]
            },
            {
                "name": "option_2",
                "serialized_circuit": "OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[2];\nh q[1];\n cx q[0], q[1];\nh q[1];",
                "input_registers": [{
                    "name": "input_1",
                    "qubits": [1]
                },
                {
                    "name": "input_2",
                    "qubits": [0]
                }],
                "output_registers": [{
                    "name": "output_1",
                    "qubits": [1]
                },
                {
                    "name": "output_2",
                    "qubits": [0]
                }]
            }]
        }]
    }
}
from typing import Tuple
from classiq import qfunc, QReg, QASM_INTRO


@qfunc
def my_controlled_z_gate(control: QReg[1], target: QReg[1]) -> Tuple[QReg[1], QReg[1]]:
    return (
        QASM_INTRO
        + """qreg q[2];
h q[1];
cx q[0], q[1];
h q[1];"""
    )


@my_controlled_z_gate.add_implementation
def my_other_controlled_z_gate(
    target: QReg[1], control: QReg[1]
) -> Tuple[QReg[1], QReg[1]]:
    # The same QASM code
    return (
        QASM_INTRO
        + """qreg q[2];
h q[1];
cx q[0], q[1];
h q[1];"""
    )

Each entry of the input_registers and output_registers must match all implementations of the same function in its name and number of qubits.

The Classiq synthesis engine is then able to choose the optimal implementation based on the circuit constraints and optimization criteria.

Auxiliaries and Uncomputation

It is often required to use auxiliary qubits which are neither input nor outputs of the function. In those cases we allow our users to define functions which use such auxiliaries.

NOTE: All auxiliary_registers are assumed to be initialized as zero and returned to zero at the end of the function. If some registers are not returned to zero at the end of the computation, please use the zero_input_registers field to declare them.

The following code introduces a simple ripple adder [1] as a function.

Example: Ripple Adder

{
    "constraints": {
      "max_width": 20,
      "max_depth": 100
    },
    "function_library":{
        "name": "my_library",
        "functions": [{
            "name": "init_a_register_to_2",
            "implementations": [{
                "serialized_circuit": "OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[2];\nx q[1];",
                "output_registers": [{
                    "name": "init_2_output",
                    "qubits": [0,1]
                }],
                "zero_input_registers": [{
                    "name": "zero_input",
                    "qubits": [0,1]
                }]
            }]
        },
        {
            "name": "my_ripple_adder",
            "implementations": [{
                "serialized_circuit": "OPENQASM 2.0;\ninclude \"qelib1.inc\";\ngate maj a,b,c\n{\n  cx c,b;\n  cx c,a;\n  ccx a,b,c;\n}\ngate uma a,b,c\n{\n  ccx a,b,c;\n  cx c,a;\n  cx a,b;\n}\nqreg q[6];\nmaj q[0],q[1],q[2];\nmaj q[2],q[3],q[4];\ncx q[4],q[5];\numa q[2],q[3],q[4];\numa q[0],q[1],q[2];",
                "input_registers": [{
                    "name": "input_a",
                    "qubits": [2,4]
                },
                {
                    "name": "input_b",
                    "qubits": [1,3]
                }],
                "output_registers": [{
                    "name": "output_a",
                    "qubits": [2,4]
                },
                {
                    "name": "output_a_plus_b",
                    "qubits": [1,3,5]
                }],
                "zero_input_registers": [{
                    "name": "zero_input",
                    "qubits": [5]
                }],
                "auxiliary_registers": [{
                    "name": "auxiliary",
                    "qubits": [0]
                }]
            }]
        }]
    },
    "logic_flow": [{
        "function": "CustomFunction",
        "function_params": {
            "name": "init_a_register_to_2"
        },
        "outputs": {
            "init_2_output": "wire_a"
          }
    },
    {
        "function": "CustomFunction",
        "function_params": {
            "name": "init_a_register_to_2"
        },
        "outputs": {
            "init_2_output": "wire_b"
          }
    },
    {
        "function": "CustomFunction",
        "function_params": {
            "name": "my_ripple_adder"
        },
        "inputs": {
            "input_a": "wire_a",
            "input_b": "wire_b"
        }
    }]
}
from classiq.interface.generator.functions import (
    FunctionData,
    FunctionImplementation,
    Register,
)

from classiq import ModelDesigner, FunctionLibrary


def define_init_2() -> FunctionData:
    init_2_qasm = """OPENQASM 2.0;
    include "qelib1.inc";
    qreg q[2];
    x q[1];"""

    init_2_data = FunctionData(
        name="init_a_register_to_2",
        implementations=FunctionImplementation(
            serialized_circuit=init_2_qasm,
            output_registers=Register(
                name="init_2_output",
                qubits=(0, 1),
            ),
            zero_input_registers=Register(
                name="zero_input",
                qubits=(0, 1),
            ),
        ),
    )
    return init_2_data


def define_ripple_adder() -> FunctionData:
    ripple_adder_qasm = """OPENQASM 2.0;
    include "qelib1.inc";
    gate maj a,b,c
    {
      cx c,b;
      cx c,a;
      ccx a,b,c;
    }
    gate uma a,b,c
    {
      ccx a,b,c;
      cx c,a;
      cx a,b;
    }
    qreg q[6];
    maj q[0],q[1],q[2];
    maj q[2],q[3],q[4];
    cx q[4],q[5];
    uma q[2],q[3],q[4];
    uma q[0],q[1],q[2];"""

    ripple_adder_data = FunctionData(
        name="my_ripple_adder",
        implementations=FunctionImplementation(
            serialized_circuit=ripple_adder_qasm,
            input_registers=(
                Register(
                    name="input_a",
                    qubits=(2, 4),
                ),
                Register(
                    name="input_b",
                    qubits=(1, 3),
                ),
            ),
            output_registers=(
                Register(
                    name="output_a",
                    qubits=(2, 4),
                ),
                Register(
                    name="output_a_plus_b",
                    qubits=(1, 3, 5),
                ),
            ),
            zero_input_registers=Register(
                name="zero_input",
                qubits=(5,),
            ),
            auxiliary_registers=Register(
                name="auxiliary",
                qubits=(0,),
            ),
        ),
    )
    return ripple_adder_data


function_library = FunctionLibrary(
    define_init_2(), define_ripple_adder(), name="my_library"
)

model_designer = ModelDesigner()
model_designer.include_library(function_library)
output_dict_init_a = model_designer.init_a_register_to_2()
output_dict_init_b = model_designer.init_a_register_to_2()
model_designer.my_ripple_adder(
    in_wires={
        "input_a": output_dict_init_a["init_2_output"],
        "input_b": output_dict_init_b["init_2_output"],
    },
)

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

The output circuit is shown below at the functional level.

 Ripple adder example

This ripple adder requires one auxiliary qubit and returns it to zero at the end of the computation [1] .

All auxiliary_registers in functions must be similarly returned to zero at the end of the computation.

[1] A. Cuccaro et al, A new quantum ripple-carry addition circuit, https://arxiv.org/abs/quant-ph/0410184 (2004)