Skip to content

Getting started with circuit synthesis

The first steps in circuit synthesis are defining the algorithms, its constraints, and your preferences. These are described in detail in the following sections.

Once your model is fully defined, it's time to synthesize it.

In the Classiq VS Code Extension, the model is described by a .qmod file.

The synthesis process is then initiated by opening the Command Palette (Ctrl+Shift+P / Command+Shift+P on Windows/Mac, respectively) and choosing the "Generate Quantum Circuit" command.

In the Classiq's Python SDK, the circuit is described by using the ModelDesigner object.

The synthesis process is then initiated by the synthesize method. Alternatively, the async method synthesize_async can be used as part of an async code (see Advanced Usage).

from classiq import ModelDesigner

model_designer = ModelDesigner()
circuit = model_designer.synthesize()

Synthesis Results

Once the synthesis process is done, the generated circuit is returned. The circuit is available in different output formats, according to the provided preferences. In addition, a static visualization of the circuit is returned.

The returned circuit maintains information about the functional structure of the circuit. However, it is not fully optimized for execution. For example, gates that are part of the implementations of different functions could be canceled in some cases. Such optimizations are performed during transpilation and the resulting circuit is returned separately.

The visualization and code of the synthesized circuit appear after synthesis. If multiple output formats are requested, the first is used for code display. The rest of the data, including all output formats, the transpiled circuit and metadata, is saved in the classiq subdirectory.

The synthesize method of ModelDesinger returns an instance of GeneratedCircuit. The instance contains the circuit in all requested formats, under the outputs field. This field is a dictionary, mapping each format to the code. Alternatively, you can access the different formats using properties, namely, qasm, qsharp, ionq etc. To see the circuit, one may use the show method. The transpiled circuit and its properties are available as attributes of the transpiled_circuit field.

Full Example - Option Pricing

Some methods of option pricing [1] begin with loading multiple quantum states onto separate registers. This is typically the first stage of the algorithm. Below we show an example of loading multiple two-qubit states. Each state preparation function uses three qubits, two for the state and an auxiliary qubit. The synthesis process selects a different auxiliary reuse strategy depending on the constraints.

{
    "constraints": {
      "max_width": 9,
      "max_depth": 100
    },
    "logic_flow": [{
        "function": "StatePreparation",
        "function_params": {
            "probabilities": [0.2, 0.3, 0.4, 0.1],
            "error_metric": {"KL": {"upper_bound": 0.01}},
            "num_qubits": 3
        },
        "name": "state_preparation_0"
    },
    {
        "function": "StatePreparation",
        "function_params": {
            "probabilities": [0.1, 0.2, 0.3, 0.4],
            "error_metric": {"KL": {"upper_bound": 0.01}},
            "num_qubits": 3
        },
        "name": "state_preparation_1"
    },
    {
        "function": "StatePreparation",
        "function_params": {
            "probabilities": [0.0, 0.1, 0.2, 0.7],
            "error_metric": {"KL": {"upper_bound": 0.01}},
            "num_qubits": 3
        },
        "name": "state_preparation_2"
    }]
}
from classiq import ModelDesigner
from classiq.builtin_functions import StatePreparation
from classiq.interface.generator.model import Constraints
from classiq.interface.generator.state_preparation import (
    Metrics,
    NonNegativeFloatRange,
)

STATE_PREPARATION_COUNT = 3
MAX_WIDTH = 9
MAX_DEPTH = 100

constraints = Constraints(max_width=MAX_WIDTH, max_depth=MAX_DEPTH)
model_designer = ModelDesigner(constraints=constraints)

probabilities = (0.1, 0.2, 0.3, 0.4)
error = {Metrics.KL: NonNegativeFloatRange(upper_bound=0.01)}
sp_params = StatePreparation(
    probabilities=probabilities, num_qubits=3, error_metric=error
)

for n in range(STATE_PREPARATION_COUNT):
    model_designer.StatePreparation(sp_params, call_name=f"state_preparation_{n}")

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

As shown in the image below, auxiliary reuse is not necessary in the case where the max_width constraint is 9 or more, where in the case of max_width=7, auxiliary reuse is required.

 Option pricing example

In this example, three states were prepared. This is a simple case, and checking all possible options can be done manually. However, if the use-case requires preparing 20 states the task becomes much harder.

When each state preparation has flexibility in the error, and different states are prepared on registers of different sizes, the problem becomes impossible to handle manually.

This is one very simple example of where describing the circuit at the functional level can help optimize over a huge amount of options, giving solutions many orders of magnitude better than can be done manually. Additional examples include functions with multiple implementation options, uncompute strategies, ordering commutative functions and sub-functions, optimizing functions for specific hardware constraints, and more.

[1] S. Chakrabarti et al, A Threshold for Quantum Advantage in Derivative Pricing, https://quantum-journal.org/papers/q-2021-06-01-463/ (2021) [2] Stephane Beauregard. 2003. Circuit for Shor's algorithm using 2n+3 qubits. Quantum Info. Comput. 3, 2 (March 2003), 175–185.