Skip to content

Composite Functions

Composite functions are functions whose implementations are not described at the gate level, but at the functional level. This is another level of abstraction supported by the Classiq platform: the functions you add to your circuit can themselves be composed of smaller functions. The synthesis engine then decomposes the bigger functions into smaller ones, allowing for optimization at the gate level.

Defining the Logic Flow

You define the logic flow implemented by a composite function in a similar fashion to defining the entire circuit. Thus, you list the different function calls, their parameters, and wiring outputs to inputs. In addition, set the inputs and outputs of the composite function, and state the inner wires to which they are connected. The Python SDK does this automatically, but you must provide it manually in the textual model.

In the textual model, list the function calls under the body field of the function definition.

{
  "functions": [
    {
      "name": "main",
      "body": []
    },
    {
      "name": "my_composite_function",
      "body": [{
          "function": "StatePreparation",
          "function_params": {
              "probabilities": [0.5, 0.1, 0.2, 0.005, 0.015, 0.12, 0.035, 0.025]
          },
          "outputs": "sp_out_wire"
      },
      {
          "function": "QFT",
          "function_params": { "num_qubits": 3 },
          "inputs": "sp_out_wire",
          "outputs": "COMP_OUT_out"
      }],
      "port_declarations": {
          "COMP_OUT": {"name": "COMP_OUT", "direction": "output", "size": {"expr": 3}}
      }
    }
  ]
}

In the SDK, use the FunctionGenerator object. The methods for adding function calls are equivalent to the methods of the Model object.

from classiq import QReg, FunctionGenerator
from classiq.builtin_functions import QFT, StatePreparation

function_generator = FunctionGenerator(function_name="my_composite_function")
x = QReg(size=3)

probabilities = (0.5, 0.1, 0.2, 0.005, 0.015, 0.12, 0.035, 0.025)
sp_params = StatePreparation(
    probabilities=probabilities,
)

sp_output_dict = function_generator.StatePreparation(sp_params)

qft_params = QFT(num_qubits=3)
function_generator.QFT(qft_params, in_wires={"IN": sp_output_dict["OUT"]})

You can use your own functions within the composite function. Call the functions as described in the basic usage section. The functions must be defined in the same library. When using the SDK, use the include_library()method of FunctionGenerator before calling functions defined in the library.

Defining Inputs and Outputs

The previous section discusses defining the logic flow between the inner function calls. Composite functions can also connect to other functions, i.e., its inputs and outputs. To define an input or output, choose its name and connect it to an input or an output of one of the inner function calls. Thus, when you call this function within a circuit, any wire connected to an input or output of the composite function is "redirected" to the input or output of the inner call.

The following example defines a subtraction function that has two inputs, a and b; and one output, difference.

In the textual model, add an input or an output by defining a wire that only connects on one side. The Classiq engine deduces that this wire represents an input or an output, and takes the specified wire name as its name.

{
  "functions": [
    {
      "name": "main",
      "body": []
    },
    {
      "name": "my_subtractor_function",
      "body": [{
          "function": "Negation",
          "function_params": {
              "arg": {"size": 3},
              "inplace": true
          },
          "inputs": { "arg": "subtrahend_in" },
          "outputs": { "negated": "negation_output_wire" }
      },
      {
          "function": "Adder",
          "function_params": {
              "left_arg": {"size": 3},
              "right_arg": {"size": 4}
          },
          "inputs": {
              "left_arg": "minuend_in",
              "right_arg": "negation_output_wire"
          },
          "outputs": { "sum": "difference_out" }
      }],
      "port_declarations": {
          "minuend": {"name": "minuend", "direction": "input", "size": {"expr": 3}},
          "subtrahend": {"name": "subtrahend", "direction": "input", "size": {"expr": 3}},
          "difference": {"name": "difference", "direction": "output", "size": {"expr": 5}}
      }
    }
  ]
}

Define the inputs and outputs in the SDK using the create_inputs() and set_outputs() methods of FunctionGenerator:

  • create_inputs() gets a dictionary as input, where the keys are the input names and the values are QReg type. The function returns a dictionary, where the keys are the input names, and the values are QReg objects of the requested type and size that can be used as an input of an inner function call.
  • set_outputs() gets a dictionary as input, where the keys are the output names and the values are QRegs returned by adding function calls to the FunctionGenerator instance.
from classiq import FunctionGenerator, RegisterUserInput, QUInt
from classiq.builtin_functions import Adder, Negation

subtrahend = RegisterUserInput(size=3)
minuend = RegisterUserInput(size=3)
negation_params = Negation(arg=subtrahend, inplace=True)
adder_params = Adder(
    left_arg=minuend, right_arg=negation_params.outputs[negation_params.output_name]
)

function_generator = FunctionGenerator(function_name="my_subtractor_function")

input_dict = function_generator.create_inputs(
    {"minuend": QUInt[3], "subtrahend": QUInt[3]}
)

negation_outputs = function_generator.Negation(
    negation_params, in_wires={"arg": input_dict["subtrahend"]}
)
adder_outputs = function_generator.Adder(
    adder_params,
    in_wires={
        "left_arg": input_dict["minuend"],
        "right_arg": negation_outputs["negated"],
    },
)

function_generator.set_outputs({"difference": adder_outputs[adder_params.output_name]})

Using Composite Functions

After you define a composite function, add it to a function library and use it in your circuits.

{
  "functions": [
    {
      "name": "main",
      "body": [{
          "function": "StatePreparation",
          "function_params": {
              "probabilities": [0.5, 0.1, 0.2, 0.005, 0.015, 0.12, 0.035, 0.025]
          },
          "outputs": "sp_out_wire"
      },
      {
          "function": "my_subtractor_function",
          "function_params":{
          },
          "inputs": {"minuend": "sp_out_wire"}
      }]
    },
    {
      "name": "my_subtractor_function",
      "body": [{
          "function": "Negation",
          "function_params": {
              "arg": {"size": 3},
              "inplace": true
          },
          "inputs": { "arg": "subtrahend_in" },
          "outputs": { "negated": "negation_output_wire" }
      },
      {
          "function": "Adder",
          "function_params": {
              "left_arg": {"size": 3},
              "right_arg": {"size": 4}
          },
          "inputs": {
              "left_arg": "minuend_in",
              "right_arg": "negation_output_wire"
          },
          "outputs": { "sum": "difference_out" }
      }],
      "port_declarations": {
          "minuend": {"name": "minuend", "direction": "input", "size": {"expr": 3}},
          "subtrahend": {"name": "subtrahend", "direction": "input", "size": {"expr": 3}},
          "difference": {"name": "difference", "direction": "output", "size": {"expr": 5}}
      }
    }
  ]
}

In the SDK, add it to create an instance of NativeFunctionDefinition using the to_function_definition() method.

from classiq import (
    Model,
    FunctionGenerator,
    FunctionLibrary,
    RegisterUserInput,
    QUInt,
    synthesize,
    show,
)
from classiq.builtin_functions import Adder, Negation, StatePreparation

subtrahend = RegisterUserInput(size=3)
minuend = RegisterUserInput(size=3)
negation_params = Negation(arg=subtrahend, inplace=True)
adder_params = Adder(
    left_arg=minuend, right_arg=negation_params.outputs[negation_params.output_name]
)

probabilities = (0.5, 0.1, 0.2, 0.005, 0.015, 0.12, 0.035, 0.025)
sp_params = StatePreparation(probabilities=probabilities)

function_generator = FunctionGenerator(function_name="my_subtractor_function")
input_dict = function_generator.create_inputs(
    {"minuend": QUInt[3], "subtrahend": QUInt[3]}
)
negation_outputs = function_generator.Negation(
    negation_params, in_wires={"arg": input_dict["subtrahend"]}
)
adder_outputs = function_generator.Adder(
    adder_params,
    in_wires={
        "left_arg": input_dict["minuend"],
        "right_arg": negation_outputs["negated"],
    },
)
function_generator.set_outputs({"difference": adder_outputs["sum"]})

composite_function = function_generator.to_function_definition()
function_library = FunctionLibrary(composite_function)

model = Model()
model.include_library(function_library)
sp_outputs = model.StatePreparation(sp_params)
model.my_subtractor_function(in_wires={"minuend": sp_outputs["OUT"]})

quantum_program = synthesize(model.get_model())
show(quantum_program)

This is the circuit created by this example.

 Composite function example