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 areQReg
type. The function returns a dictionary, where the keys are the input names, and the values areQReg
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 areQReg
s returned by adding function calls to theFunctionGenerator
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.