Skip to content

Defining Algorithms

Quantum algorithms are constructed from functions, which are the functional building blocks that implement quantum logic, and the data flow between them.

Calling a Function

As in classical programming, calling a function requires specifying its name and its parameters. The following example calls a state preparation function, which is a built-in function developed by Classiq. There are multiple such functions, listed in the Built-In Function Library. You can also define your own functions, as described in the Function section.

To call a function using the textual interface, list it under the body field and pass on its parameters under the function_params field of the function.

{
  "functions": [
    {
      "name": "main",
      "body": [{
          "function": "StatePreparation",
          "function_params": {
            "probabilities": [0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125]
          }
        }
      ]
    }
  ]
}

In the SDK, create the StatePreparation object, which represents the parameters of the function, and then pass it on to the StatePreparation method of the Model object.

from classiq import Model, synthesize, show
from classiq.builtin_functions import StatePreparation

model = Model()

probabilities = (0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125)
sp_params = StatePreparation(probabilities=probabilities)

model.StatePreparation(sp_params)

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

You can also determine the name of a call, e.g., to distinguish multiple calls to the same function. Do it by setting the name field of the call in the textual interface or the call_name argument in the SDK.

Defining the Data Flow

Define the data flow between the functions by connecting an output of a function to an input of another function.

The following example connects the output of the state preparation function to the input of the quantum Fourier transform (QFT) function.

To define a connection using the textual model, name the connection "sp_out", and list it under the outputs of StatePreparation and the inputs of QFT.

{
  "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],
            "error_metric": { "KL": {"upper_bound": 0.3}}
          },
          "outputs": "sp_out"
        },
        {
          "function": "QFT",
          "function_params": {
            "num_qubits": 3
          },
          "inputs": "sp_out"
        }
      ]
    }
  ]
}

The outputs of a called function in the SDK are returned in a dictionary. The keys of the dictionary are the function output names. The values of the dictionary are Wire objects that represent the outputs.

The inputs of a function are also defined in a dictionary and passed to the in_wires argument. Similarly, the keys of the dictionary are the input names and the values are a Wire object. Passing a Wire object that represents an output to the input of another function connects the two.

from classiq import Model, synthesize, show
from classiq.builtin_functions import QFT, StatePreparation
from classiq.builtin_functions.state_preparation import Metrics
from classiq.builtin_functions.range_types import NonNegativeFloatRange

model = Model()

num_qubits = 3
probabilities = (0.5, 0.1, 0.2, 0.005, 0.015, 0.12, 0.035, 0.025)
sp_params = StatePreparation(
    probabilities=probabilities,
    error_metric={Metrics.KL: NonNegativeFloatRange(upper_bound=0.3)},
)

x = model.StatePreparation(sp_params)["OUT"]

qft_params = QFT(num_qubits=num_qubits)
model.QFT(qft_params, in_wires=x)

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

The state preparation function has a single pre-defined output, and the QFT function has a single pre-defined input. In this case, the Classiq platform allows skipping the explicit specification of each, requiring only to define the connection. The following example shows how to handle functions with multiple inputs/outputs. See how to split a single input/output into multiple ones using register slicing.

The following example implements the addition of two prepared states. The states are prepared using the StatePreparation function and the functions' outputs are added using the Adder function. Use the left_arg and right_arg inputs of the adder function to connect each state preparation output into its corresponding addend. Generally, each function has a set of predefined input and output names.

{
  "functions": [
    {
      "name": "main",
      "body": [{
          "function": "StatePreparation",
          "function_params": {
              "probabilities": [0.4, 0.05, 0.2, 0.05, 0.3, 0.0, 0.0, 0.0],
              "error_metric": {"KL": {"upper_bound": 0.2}}
          },
          "outputs": "sp_output_a",
          "name": "state_preparation_1"
      },
      {
          "function": "StatePreparation",
          "function_params": {
              "probabilities": [0.5, 0.1, 0.2, 0.005, 0.015, 0.12, 0.035, 0.025],
              "error_metric": {"KL": {"upper_bound": 0.2}}
          },
          "outputs": "sp_output_b",
          "name": "state_preparation_2"
      },
      {
          "function": "Adder",
          "function_params": {
              "left_arg": { "size": 3 },
              "right_arg": { "size": 3 }
          },
          "inputs": {
              "left_arg": "sp_output_a",
              "right_arg": "sp_output_b"
          },
          "name": "adder"
      }]
    }
  ]
}
from classiq import Model, RegisterUserInput, synthesize, show
from classiq.builtin_functions import StatePreparation, Adder
from classiq.builtin_functions.state_preparation import Metrics
from classiq.builtin_functions.range_types import NonNegativeFloatRange

model = Model()
num_qubits = 3

probabilities_a = (0.4, 0.05, 0.2, 0.05, 0.3, 0.0, 0.0, 0.0)
error_metric_a = {Metrics.KL: NonNegativeFloatRange(upper_bound=0.2)}
sp_params_a = StatePreparation(
    probabilities=probabilities_a,
    error_metric=error_metric_a,
)
x = model.StatePreparation(sp_params_a, call_name="state_preparation_1")["OUT"]

probabilities_b = (0.5, 0.1, 0.2, 0.005, 0.015, 0.12, 0.035, 0.025)
error_metric_b = {Metrics.KL: NonNegativeFloatRange(upper_bound=0.2)}
sp_params_b = StatePreparation(
    probabilities=probabilities_b,
    error_metric=error_metric_b,
)
y = model.StatePreparation(sp_params_b, call_name="state_preparation_2")["OUT"]

adder_arg = RegisterUserInput(size=num_qubits)
adder_params = Adder(
    left_arg=x.to_register_user_input(), right_arg=y.to_register_user_input()
)
model.Adder(adder_params, in_wires=[x, y], call_name="adder")

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

The output quantum program is shown below at the functional level.

 Adder example