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
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.
Then connection to the backend is simple! For this we point the IQMSampler
to the quantum computer’s URL.
import os
"IQM_TOKEN"] = "<TOKEN>"
os.environ[= "https://qx.vtt.fi/api/devices/q5"
QX_URL = IQMSampler(QX_URL) sampler
= sampler.device 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
= IQMSampler(
sampler
QX_URL,="1b76cb23-64ec-4c6f-af4f-cf2c33a96526",
calibration_set_id=CircuitCompilationOptions(
compiler_options=None, heralding_mode=HeraldingMode.ZEROS
max_circuit_duration_over_t2
), )
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
.
= nx.Graph()
G
G.add_edges_from(device.metadata.nx_graph.edges)= {node: str(node) for node in G.nodes}
node_labels
nx.draw(
G,=node_labels,
labels="skyblue",
node_color=500,
node_size=10,
font_size=True,
with_labels )
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.
= cirq.NamedQubit("Alice"), cirq.NamedQubit("Bob")
q1, q2 = cirq.Circuit()
circuit
circuit.append(cirq.H(q1))
circuit.append(cirq.CNOT(q1, q2))="m"))
circuit.append(cirq.measure(q1, q2, key
SVGCircuit(circuit)
First we’ll transpile the circuit into Q5’s native gates
= device.decompose_circuit(circuit)
decomposed_circuit SVGCircuit(decomposed_circuit)
Then we route the circuit based on Q5’s topology
= device.route_circuit(
routed_circuit, initial_mapping, final_mapping
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.
= simplify_circuit(routed_circuit)
simplified_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))
= sampler.run(simplified_circuit, repetitions=100)
result # 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)]
=binary_labels(2)) cirq.plot_state_histogram(result, plt.subplot(), tick_label
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
= cirq.NamedQubit("Alice"), cirq.NamedQubit("Bob")
q1, q2 = cirq.Circuit()
circuit_1
circuit_1.append(cirq.H(q1))
circuit_1.append(cirq.CNOT(q1, q2))="m"))
circuit_1.append(cirq.measure(q1, q2, key
SVGCircuit(circuit_1)
= cirq.Circuit()
circuit_2
circuit_2.append(cirq.H(q1))
circuit_2.append(cirq.CNOT(q2, q1))="m"))
circuit_2.append(cirq.measure(q1, q2, key
SVGCircuit(circuit_2)
= device.route_circuit(device.decompose_circuit(circuit_1))
routed_circuit_1, _, _ = device.route_circuit(device.decompose_circuit(circuit_2))
routed_circuit_2, _, _
circuit_list.append(routed_circuit_1)
circuit_list.append(routed_circuit_2)
= sampler.run_iqm_batch(circuit_list, repetitions=10)
results
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.