Introduction to Cirq with Q5

Q5 is a 5 qubit Quantum Computer that is co-developed by VTT and IQM. It uses superconducting transmon qubits in a star shaped topology. Q5’s natives gates consist of the phased-rx and controlled-z gates. This architecture is called Adonis by IQM.

In this tutorial running on Q5 is demonstrated using the Cirq framework. You can also run on Q5 using Qiskit with the qiskit-on-iqm adapter, and this is described in a separate notebook.

Setup

This notebook uses the following requirements.

cirq-iqm==15.4
iqm-client==20.17
networkx

Using Q5 with Cirq

First we import cirq-on-iqm which is needed to run on Q5 with cirq. You can read the user guide here.

import cirq
import matplotlib.pyplot as plt
import networkx as nx
import numpy as np
from cirq.contrib.svg import SVGCircuit
from iqm.cirq_iqm.iqm_sampler import IQMSampler
from iqm.cirq_iqm.optimizers import simplify_circuit

Then connection to the backend is simple! For this we point the IQMSampler to the quantum computer’s URL.

import os

os.environ["IQM_TOKEN"] = "<TOKEN>"
QX_URL = "https://qx.vtt.fi/api/devices/q5"
sampler = IQMSampler(QX_URL)
device = sampler.device

We can also specify a calibration_set_id and whether to run with max_circuit_duration_over_t2 or not. The max_circuit_duration_over_t2 option when set to 1.0 disqualifies any circuits that are too long compared to the coherence time of the qubits.

from iqm.iqm_client import CircuitCompilationOptions, HeraldingMode

sampler = IQMSampler(
    QX_URL,
    calibration_set_id="1b76cb23-64ec-4c6f-af4f-cf2c33a96526",
    compiler_options=CircuitCompilationOptions(
        max_circuit_duration_over_t2=None, heralding_mode=HeraldingMode.ZEROS
    ),
)

Now that we have connected to Q5, we can query for some information about Q5!

print(f"Native operations: {device.metadata.gateset}")
print(f"Number of qubits: {device.qubits}")
print(f"Coupling map: {device.metadata.nx_graph.edges}")

The topology can be visualized with networkx.

G = nx.Graph()
G.add_edges_from(device.metadata.nx_graph.edges)
node_labels = {node: str(node) for node in G.nodes}
nx.draw(
    G,
    labels=node_labels,
    node_color="skyblue",
    node_size=500,
    font_size=10,
    with_labels=True,
)

Constructing and executing quantum circuits

Circuits are constructed in Cirq by decomposing and routing them for the target topology. Additionally, you can run some simple optimization routines to get better performance for your circuit.

q1, q2 = cirq.NamedQubit("Alice"), cirq.NamedQubit("Bob")
circuit = cirq.Circuit()
circuit.append(cirq.H(q1))
circuit.append(cirq.CNOT(q1, q2))
circuit.append(cirq.measure(q1, q2, key="m"))

SVGCircuit(circuit)

First we’ll transpile the circuit into Q5’s native gates

decomposed_circuit = device.decompose_circuit(circuit)
SVGCircuit(decomposed_circuit)

Then we route the circuit based on Q5’s topology

routed_circuit, initial_mapping, final_mapping = device.route_circuit(
    decomposed_circuit
)
SVGCircuit(routed_circuit)

By printing the initial mapping we can see how the qubit names have beemn translated into the names of the qubits physically on Q5 and how the original qubit names were routed onto the device.

print(initial_mapping)

This circuit can be executed on Q5, but as an additional step we can simplify the circuit, using cirq-on-iqm’s built in optimizers.

simplified_circuit = simplify_circuit(routed_circuit)
SVGCircuit(simplified_circuit)

The circuits can then be executed by calling sampler.run. Additionally, a folding function can be passed to process the sampled measurement results and convert the results into a format suitable for plotting for example.

def fold_func(x: np.ndarray) -> str:
    """Fold the measured bit arrays into strings."""
    return "".join(map(lambda x: chr(x + ord("0")), x))


result = sampler.run(simplified_circuit, repetitions=100)
# print(result.measurements['m'])
print(result.histogram(key="m", fold_func=fold_func))

A histogram of the results can be plotted using plot_state_histogram.

def binary_labels(num_qubits):
    return [bin(x)[2:].zfill(num_qubits) for x in range(2**num_qubits)]


cirq.plot_state_histogram(result, plt.subplot(), tick_label=binary_labels(2))

Additional metadata about the executed job can also be found.

print("Job ID: ", result.metadata.job_id)  # Retrieving the submitted job id
print(
    "Calibration Set ID: ", result.metadata.calibration_set_id
)  # Retrieving the current calibration set id.

Batch execution

Q5 also allows for batches of circuits to be submitted with 1 call to the quantum computer. A batch is simply a list of circuits. This is often faster than executing circuits individually, however, circuits will still be executed sequentially. On Q5 currently you can only place a maximum of 20 circuits in one batch. All circuits in a batch are executed with the same number of shots. The maximum number of shots per circuit is 100,000.

With cirq this is implemented via the run_iqm_batch method of sampler.

Batch submission of circuits allows parameterized circuits to be executed using the cirq-resolve_parameters function.

circuit_list = []

q1, q2 = cirq.NamedQubit("Alice"), cirq.NamedQubit("Bob")
circuit_1 = cirq.Circuit()
circuit_1.append(cirq.H(q1))
circuit_1.append(cirq.CNOT(q1, q2))
circuit_1.append(cirq.measure(q1, q2, key="m"))

SVGCircuit(circuit_1)

circuit_2 = cirq.Circuit()
circuit_2.append(cirq.H(q1))
circuit_2.append(cirq.CNOT(q2, q1))
circuit_2.append(cirq.measure(q1, q2, key="m"))

SVGCircuit(circuit_2)

routed_circuit_1, _, _ = device.route_circuit(device.decompose_circuit(circuit_1))
routed_circuit_2, _, _ = device.route_circuit(device.decompose_circuit(circuit_2))

circuit_list.append(routed_circuit_1)
circuit_list.append(routed_circuit_2)

results = sampler.run_iqm_batch(circuit_list, repetitions=10)

for result in results:
    print(result.histogram(key="m"))

Summary

In this notebook we have demonstrated how to connect and run circuits on Q5 with Cirq using the cirq-on-iqm adapter. Executing on Q50 can be done in a similiar way just by adjusting the connection URL.