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 to happen at the gate level.
Defining the Logic Flow¶
Defining the logic flow implemented by a composite function is done in a similar fashion to defining the entire circuit. Thus, it requires listing the different function calls, their parameters, and wiring outputs to inputs. In addition, one has to set the inputs and outputs of the composite function, and state the inner wires they're connected to. This is done automatically in the Python SDK, but must be provided manually in the textual model.
In the textual model, this is done by listing the function calls under the
logic_flow
field of the function definition.
{
"function_library": {
"name": "my_library",
"functions": [{
"name": "my_composite_function",
"logic_flow": [{
"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": "qft_out_wire"
}],
"input_decls" : {},
"output_decls": {"COMP_OUT": {"wire": "qft_out_wire", "reg": {"size": 3}}}
}]
}
}
In the SDK, this is done using 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"]})
Using your own functions within the composite function is also possible. Calling the
functions is done in the same way as described in the
basic usage section.
The functions you use must be defined in the same library. When using the SDK, you must
use the include_library()
method of FunctionGenerator
before callings
functions defined in the library.
Defining Inputs and Outputs¶
The previous section discusses defining the logic flow between the inner function calls. We also want to define the way the composite function connects to other functions, i.e., its inputs and outputs. For now, defining an input or output simply requires choosing its name, and connecting it to an input or an output, respectively, of one of the inner function calls. Thus, when calling this function within a circuit, any wire connected to an input or output of the composite function, would be "redirected" to the input or output of the inner call, as defined.
In the following example, we define a subtraction function that has two inputs, a
and
b
, and one output, difference
.
In the textual model, adding an input or an output is done by defining a wire that only connects on one side. It is automatically deduced that this wire represents an input or an output, and the specified wire name is chosen as its name.
{
"function_library": {
"name": "my_library",
"functions": [{
"name": "my_subtractor_function",
"logic_flow": [{
"function": "Negation",
"function_params": {
"arg": {"size": 3},
"inplace": true
},
"inputs": {
"in_arg": "subtrahend_wire"
},
"outputs": {
"negated": "negation_output_wire"
}
},
{
"function": "Adder",
"function_params": {
"left_arg": {"size": 3},
"right_arg": {"size": 4}
},
"inputs": {
"left_arg": "minuend_wire",
"right_arg": "negation_output_wire"
},
"outputs": {
"sum": "difference_wire"
}
}],
"input_decls": {
"minuend": {"wire": "minuend_wire", "reg": {"size": 3}},
"subtrahend": {"wire": "subtrahend_wire", "reg": {"size": 3}}
},
"output_decls": {"difference": {"wire": "difference_wire", "reg": {"size": 5}}}
}]
}
}
Defining the inputs and outputs in the SDK this is done using the create_inputs()
and set_outputs()
methods of FunctionGenerator
:
create_inputs()
gets as input a dictionary, 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 as input a dictionary, 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={"in_arg": input_dict["subtrahend"]}
)
adder_outputs = function_generator.Adder(
adder_params,
in_wires={
"left_arg": input_dict["minuend"],
"negated": negation_outputs["negated"],
},
)
function_generator.set_outputs({"difference": adder_outputs[adder_params.output_name]})
Using Composite Functions¶
Once we define a composite function, we can then add it to a function library and use it in our circuits.
{
"function_library": {
"name": "my_library",
"functions": [{
"name": "my_subtractor_function",
"logic_flow": [{
"function": "Negation",
"function_params": {
"arg": {"size": 3},
"inplace": true
},
"inputs": {
"in_arg": "subtrahend_wire"
},
"outputs": {
"negated": "negation_output_wire"
}
},
{
"function": "Adder",
"function_params": {
"left_arg": {"size": 3},
"right_arg": {"size": 4}
},
"inputs": {
"left_arg": "minuend_wire",
"right_arg": "negation_output_wire"
},
"outputs": {
"sum": "difference_wire"
}
}],
"input_decls": {
"minuend": {"wire": "minuend_wire", "reg": {"size": 3}},
"subtrahend": {"wire": "subtrahend_wire", "reg": {"size": 3}}
},
"output_decls": {"difference": {"wire": "difference_wire", "reg": {"size": 5}}}
}]
},
"logic_flow": [{
"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"}
}]
}
In the SDK, we first need to add it to create an instance of FunctionDefinition
, by using
the to_function_definition()
method.
from classiq import (
Model,
FunctionGenerator,
FunctionLibrary,
RegisterUserInput,
QUInt,
)
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={"in_arg": input_dict["subtrahend"]}
)
adder_outputs = function_generator.Adder(
adder_params,
in_wires={
"left_arg": input_dict["minuend"],
"negated": 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"]})
circuit = model.synthesize()
circuit.show_interactive()
The circuit created by this example is shown below.