Skip to content

HW-aware Synthesis of MCX

View on GitHub

This tutorial consists of two parts, 1. How to create a 10-CONTROL MCX gate with Classiq 2. Classiq's Hardware-Aware Synthesis capability

1. How to create a 10-CONTROL MCX gate with Classiq

To create an MCX gate with 10 control qubits using Classiq, we will first define a quantum function called my_mcx, whose arguments are an array of qubits (of any size) to be used as control, and a single qubit argument to be used as the target.

# importing all the necessary libraries from classiq

from math import pi

from classiq import *
# define MCX quantum function


@qfunc
def my_mcx(ctrl: QArray[QBit], target: QBit) -> None:
    control(ctrl, lambda: X(target))

Then, to create an MCX gate with 10 control qubits we will create a quantum main function that will simply execute our my_mcx function with 10 qubits allocated to the control argument.

# define the MCX parameters within the quantum 'main' function


@qfunc
def main(ctrl: Output[QArray[QBit]], target: Output[QBit]) -> None:
    allocate(10, ctrl)
    allocate(1, target)
    my_mcx(ctrl, target)

To build our model we will use the create_model function:

# build a model
model = create_model(main)

To constrain a circuit to only 20 qubits and optimize for circuit depth, we pass the max width and optimization parameter to a Constraints object and update our model

from classiq import write_qmod

constraints = Constraints(
    max_width=20, optimization_parameter=OptimizationParameter.DEPTH
)
model = set_constraints(model, constraints)

write_qmod(model, "mcx_10_ctrl_depth")

We can now synthesize our model, create a quantum program and view it:

qprog = synthesize(model)
show(qprog)
Opening: http://localhost:4200/circuit/0a38090c-f137-4276-893b-6f9a21f47668?version=0.0.0

Additionally, To get the transpiled circuit from our qprog object and print its depth:

circuit = QuantumProgram.from_qprog(qprog)
print(f"Synthesized MCX depth is {circuit.transpiled_circuit.depth}")
Synthesized MCX depth is 52

2. Classiq's Hardware-Aware Synthesis capability

In the above section we have seen how to create a MCX circuit, add constraints to it and optimize its depth in Classiq. In this section we will check the hardware-aware synthesis capability of Classiq. In this example we will implement and synthesize a simple MCX (Multiple Control-X) circuit with two different HW-aware configuration settings using Classiq's synthesis engine.

The two different fictitious hardware created here demonstrates how to insert your own custom-designed machine. For comparison, we create two types of hardware with cx, u basis gates. The difference between them manifests in the connectivity map: one has grid connectivity while the other has star connectivity.

The two fictitious hardware created are using two different simulators: Classiq's Simulator and IonQ's Simulator. We will compare the depth of the circuit and we will optimise the number of cnot gates as it is important in quantum computation because they cause more errors and are very expensive.

# define MCX quantum function


@qfunc
def my_mcx(ctrl: QArray[QBit], target: QBit) -> None:
    control(ctrl, lambda: X(target))
# define the MCX parameters within the quantum 'main' function


@qfunc
def main(ctrl: Output[QArray[QBit]], target: Output[QBit]) -> None:
    allocate(12, ctrl)
    allocate(1, target)
    my_mcx(ctrl, target)
# build a model
model = create_model(main)
# define the hardware parameters
max_width = 18

# Grid Connectivity
grid_connectivity = [
    [0, 1],
    [1, 2],
    [2, 3],
    [3, 4],
    [4, 5],  # Row 1
    [6, 7],
    [7, 8],
    [8, 9],
    [9, 10],
    [10, 11],  # Row 2
    [12, 13],
    [13, 14],
    [14, 15],
    [15, 16],
    [16, 17],  # Row 3
    [0, 6],
    [6, 12],  # Column 1
    [1, 7],
    [7, 13],  # Column 2
    [2, 8],
    [8, 14],  # Column 3
    [3, 9],
    [9, 15],  # Column 4
    [4, 10],
    [10, 16],  # Column 5
    [5, 11],
    [11, 17],  # Column 6
]

# Star Connectivity
star_connectivity = [[0, i] for i in range(1, max_width - 1)]
# Plotting Grid Connectivity
import matplotlib.pyplot as plt
import networkx as nx

G = nx.Graph()
G.add_edges_from(grid_connectivity)

# Define the positions of the nodes in a 3x6 grid
pos = {}
for i in range(3):
    for j in range(6):
        pos[i * 6 + j] = (j, -i)  # (x, y) coordinates

# Plot the graph
plt.figure(figsize=(10, 6))
nx.draw(
    G,
    pos,
    with_labels=True,
    node_size=500,
    node_color="skyblue",
    font_size=16,
    font_color="black",
    edge_color="gray",
)
plt.title("3x6 Grid Connectivity Map")
plt.show()

png

# plotting Star Connectivity
import numpy as np

G = nx.Graph()
G.add_edges_from(star_connectivity)

# Define the positions of the nodes in a star configuration
pos = {}
pos[0] = (0, 0)  # Central node
angle_step = 360 / (len(star_connectivity))
for i in range(1, 18):
    angle = angle_step * (i - 1)
    radians = np.deg2rad(angle)
    pos[i] = (np.cos(radians), np.sin(radians))

# Plot the graph
plt.figure(figsize=(10, 10))
nx.draw(
    G,
    pos,
    with_labels=True,
    node_size=500,
    node_color="skyblue",
    font_size=16,
    font_color="black",
    edge_color="gray",
)
plt.title("Star Connectivity Map")
plt.show()

png

# Grid Preferences
preferences_grid = Preferences(
    custom_hardware_settings=CustomHardwareSettings(
        basis_gates=["cx", "u"],
        connectivity_map=grid_connectivity,
    ),
    random_seed=-1,
)


# Star Preferences
preferences_star = Preferences(
    custom_hardware_settings=CustomHardwareSettings(
        basis_gates=["cx", "u"],
        connectivity_map=star_connectivity,
    ),
    random_seed=-1,
)
# define synthesis engine constraints
constraints = Constraints(optimization_parameter="cx", max_width=max_width)
# define models with different preferences
model = set_constraints(model, constraints)
model_grid = set_preferences(model, preferences_grid)
model_star = set_preferences(model, preferences_star)


# write models to files
write_qmod(model_grid, "hardware_aware_mcx_grid")
write_qmod(model_star, "hardware_aware_mcx_star")
qprog_grid = synthesize(model_grid)
show(qprog_grid)
Opening: http://localhost:4200/circuit/74f4c802-631d-4f39-b68b-3b95397e67bd?version=0.0.0
circuit_grid = QuantumProgram.from_qprog(qprog_grid)

print(
    f"Synthesized MCX cx-count for grid connectivity is {circuit_grid.transpiled_circuit.count_ops['cx']}"
)
circuit_grid.show()
Synthesized MCX cx-count for grid connectivity is 117
Opening: http://localhost:4200/circuit/74f4c802-631d-4f39-b68b-3b95397e67bd?version=0.0.0
qprog_star = synthesize(model_star)
show(qprog_star)
Opening: http://localhost:4200/circuit/d47721d8-f737-4e07-be84-ad204e5df08d?version=0.0.0
circuit_star = QuantumProgram.from_qprog(qprog_star)

print(
    f"Synthesized MCX cx-count for star connectivity is {circuit_star.transpiled_circuit.count_ops['cx']}"
)
circuit_star.show()
Synthesized MCX cx-count for star connectivity is 114
Opening: http://localhost:4200/circuit/d47721d8-f737-4e07-be84-ad204e5df08d?version=0.0.0
print(
    f"Synthesized Depth for grid connectivity is {circuit_grid.transpiled_circuit.depth}"
)
Synthesized Depth for grid connectivity is 82
print(
    f"Synthesized Depth for star connectivity is {circuit_star.transpiled_circuit.depth}"
)
Synthesized Depth for star connectivity is 191

Conclusion: Given the connectivity of the hardware and its preferences the engince optimizes the constraints of the model. In this case the constraits were max_width which is the maximum number of qubits it can use and the number of cx gates. Our main aim is to minimize the number of cx gates.

With Star Connectivity the minimum number of cx gates required after transpiling the circuit is just 114 and depth of the circuit turned out to be 191.

Whereas with Grid Connectivity the minimum number of cx gates required after transpiling the circuit is 117 and depth of the circuit is 82.

Looking at the both the cases we can come to a conclusion that star connectivity is more favourable to synthesize and execute a mcx circuit.