Example: Using VTT QX to Submit Jobs

Step 1: Installing Python library dependencies

This notebook requires a Python virtual environment with following libraries installed. Please refer to the Devices page; Q5 and Q50, or check VTT QX to find the latest library versions.

The following packages listed below could be saved as requirements.txt:

iqm-client==20.17
qiskit-iqm==15.6
matplotlib
pylatexenc

First, create a virtual Python environment with

conda create -n vttqx python=3.11 pip
conda activate vttqx

Then install the dependencies with

pip install iqm-client==20.17 qiskit-iqm==15.6 matplotlib pylatexenc

If you prefer using uv for faster installs and reproducible builds:

# Initialize a new project
uv init

# This creates a pyproject.toml file for project configuration
# Then installs dependencies, which will be recorded
uv add iqm-client==20.17 qiskit-iqm==15.6 matplotlib pylatexenc

# Or install from existing requirements
uv pip install -r requirements.txt

Starting Jupyter Notebook

Install Jupyter in your conda environment:

conda activate vttqx
pip install notebook

# Start Jupyter Notebook
jupyter notebook

Install and run Jupyter in your uv-managed environment:

# First, activate your uv environment
source vttqx/bin/activate  # On Windows: vttqx\Scripts\activate

# Install Jupyter
uv pip install notebook

# Start Jupyter Notebook
jupyter notebook

Then we can import the IQM client library and the qiskit Python objects to define a quantum circuit

import os

from iqm.qiskit_iqm import IQMProvider
from qiskit import QuantumCircuit, transpile

Step 2: Define your quantum circuit

Next, we define a quantum circuit that prepares a so called Bell state. In the first line we instantiate a QuantumCircuit object, that allocates two qubits. Further more, we give it the name Bell pair circuit.

circuit = QuantumCircuit(2, name="Bell pair circuit")
circuit.h(0)  # Apply a Hadamard gate to the first qubit
circuit.cx(
    0, 1
)  # Apply a CNOT gate withfirst qubit as control qubit and second as target qubit
circuit.measure_all()  # Finally, we add a measurement operation measuring all qubits
circuit.draw(output="mpl")

Step 3. How to submit to VTT QX?

To submit to VTT QX, we have to instantiate the IQMProvider, which takes a server URL as an argument. The URL should expose all needed API endpoints to fetch information about the target quantum computer. In the picture below, it is shown how to fetch the server URL for the Q5 quantum computer in the VTT QX UI.

server_url = "https://qx.vtt.fi/api/devices/q5"
provider = IQMProvider(server_url)

Generate project token

The provider allows us to generate a qiskit Backend object, that describes the quantum architecture that we are targeting. Knowing the details of the target quantum architecture is important for transpilation of our quantum circuits to the native gates and topology of the target device.

To instantiate the backend, the provider will make a call to the server_url, requesting information about the backend. VTT QX requires, that this call is authenticated. A user authenticates itself to VTT QX, by attaching a project token to each request. The project token can be copied from the dashboard in VTT QX.

Select the project as shown in the picture above. Next, copy the token following the instructions above

Alternatively, you can generate a new project token by authenticating with an existing active project token. This method is useful when you already have access to a project and want to rotate or generate additional tokens. You can do this by using the following curl command:

curl 'https://qx.vtt.fi/api/projects/{project_id}/token' \
  -X POST \
  -H 'Authorization: Bearer <active_project_token>' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json'

Finally, paste the token into the cell below. The token is exposed to the IQM client via the environment variable IQM_TOKEN.

# Set the VTT QX token
os.environ["IQM_TOKEN"] = "<token-here>"

# Note, the token can also be passed directly to the client with out setting IQM_TOKEN
# provider = IQMProvider(server_url, token="<token-here>")

Now, we can generate a backend object, which allows us to print more information about our target device

backend = provider.get_backend()

print(f"Native operations: {backend.operation_names}")
print(f"Number of qubits: {backend.num_qubits}")
print(f"Coupling map: {backend.coupling_map}")

Before executing the circuit, we need to make sure that it only consists of native gates supported by the device

transpiled_circuit = transpile(circuit, backend)

Finally, we can also submit the quantum circuit that we defined at the beginning and print the results

for _ in range(1):
    job = backend.run(transpiled_circuit)
    counts = job.result().get_counts()
    print(counts)

Step 4: Inspecting job results in VTT QX

VTT QX exposes additional information about the job in the UI.

Optional: Submitting with Cirq

IQM Cirq user guide can be found here: IQM-Cirq

⚠️ ⚠️ Cirq is not as well supported by IQM as Qiskit ⚠️ ⚠️

Compatible versions:

cirq-iqm 15.3
cirq-core 1.4.1
import cirq
from iqm.cirq_iqm import Adonis

adonis = Adonis()

# Print metadata about the chip
print(adonis.metadata.qubit_set)
print(adonis.metadata.gateset)
print(adonis.metadata.nx_graph)
# Define a test circuit with 2 qubits
q1, q2 = cirq.NamedQubit("Alice"), cirq.NamedQubit("Bob")
circuit = cirq.Circuit()
circuit.append(cirq.X(q1))
circuit.append(cirq.H(q2))
circuit.append(cirq.CNOT(q1, q2))
circuit.append(cirq.measure(q1, q2, key="m"))
print(circuit)
from iqm.cirq_iqm.iqm_sampler import IQMSampler

# Decompose circuit to native gate set
decomposed_circuit = adonis.decompose_circuit(circuit)
# Map logical qubits to physical qubits and insert SWAP gates where needed
routed_circuit_1, initial_mapping, final_mapping = adonis.route_circuit(
    decomposed_circuit
)

# Use the `IQMSampler` class to perform authenticated circuit submission
sampler = IQMSampler(server_url)
result = sampler.run(routed_circuit_1, repetitions=10)
print(result.measurements["m"])