First Distributed Execution

Once CUNQA is installed (Module Set Up), the basic workflow to use it is:

  1. Raise the desired QPUs with the command qraise.

  2. Run circuits on the QPUs:

    • Connect to the QPUs through the python API.

    • Define the circuits to execute.

    • Execute the circuits on the QPUs.

    • Obtain the results.

  3. Drop the raised QPUs with the command qdrop. ..

    ❗ Important: Please, note that steps 1-4 of the Installation section have to be done every time CUNQA wants to be used.

1. qraisecommand

The qraise command raises as many QPUs as desired. Each QPU can be configured by the user to have a personalized backend. There is a help FLAG with a quick guide of how this command works:

qraise --help
  1. The only two mandatory FLAGS of qraise are the number of QPUs, set up with -n or --num_qpus and the maximum time the QPUs will be raised, set up with -t or --time. So, for instance, the command .. code-block:

    qraise -n 4 -t 01:20:30
    

    will raise four QPUs during at most 1 hour, 20 minutes and 30 seconds. The time format is hh:mm:ss.

πŸ“˜ Note: By default, all the QPUs will be raised with AerSimulator as the background simulator and IdealAer as the background backend. That is, a backend of 32 qubits, all connected and without noise.

  1. The simulator and the backend configuration can be set by the user through qraise FLAGs:

Set simulator:

qraise -n 4 -t 01:20:30 --sim=Munich

The command above changes the default simulator by the mqt-ddsim simulator. Currently, CUNQA only allows two simulators: --sim=Aer and --sim=Munich.

Set FakeQmio:

qraise -n 4 -t 01:20:30 --fakeqmio=<path/to/calibrations/file>

The --fakeqmio FLAG raises the QPUs as simulated QMIOs. If no <path/to/calibrations/file> is provided, last calibrations of de QMIO are used. With this FLAG, the background simulator is AerSimulator.

Set personalized backend:

qraise -n 4 -t 01:20:30 --backend=<path/to/backend/json>

The personalized backend has to be a json file with the following structure:

{"backend":{"name": "BackendExample", "version": "0.0", "n_qubits": 32,"url": "", "is_simulator": true, "conditional": true, "memory": true, "max_shots": 1000000, "description": "", "basis_gates": [], "custom_instructions": "", "gates": [], "coupling_map": []}, "noise": {}}

πŸ“˜ Note: The β€œnoise” key must be filled with a json with noise instructions supported by the chosen simulator.

❗ Important: Several qraise commands can be executed one after another to raise as many QPUs as desired, each one having its own configuration, independently of the previous ones. The getQPUs() method presented in the section below will collect all the raised QPUs.

2. Python Program Example

Once the QPUs are raised, they are ready to execute any quantum circuit. The following script shows a basic workflow.

⚠️ Warning: To execute the following python example it is needed to load the Qiskit module:

In QMIO:

module load qmio/hpc gcc/12.3.0 qiskit/1.2.4-python-3.9.9

In FT3:

module load cesga/2022 gcc/system qiskit/1.2.4
# Python Script Example

import os
import sys

# Adding pyhton folder path to detect modules
INSTALL_PATH = os.getenv("INSTALL_PATH")
sys.path.insert(0, INSTALL_PATH)

# Let's get the raised QPUs
from cunqa.qpu import getQPUs

qpus  = getQPUs() # List of raised QPUs
for q in qpus:
    print(f"QPU {q.id}, name: {q.backend.name}, backend: {q.backend.simulator}, version: {q.backend.version}.")

# Let's create a circuit to run in our QPUs
from qiskit import QuantumCircuit

N_QUBITS = 2 # Number of qubits
qc = QuantumCircuit(N_QUBITS)
qc.h(0)
qc.cx(0,1)
qc.measure_all()

# Time to run
qpu0 = qpu[0] # Select one of the raise QPUs

job = qpu0.run(qc, transpile = True, shots = 1000)

result = job.result() # Get the result of the execution

counts = result.get_counts()

It is not mandatory to run a QuantumCircuit from Qiskit. The .run method also supports OpenQASM 2.0 with the following structure:

{"instructions":"OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[2];\ncreg c[2];\nh q[0];\ncx q[0], q[1];\nmeasure q[0] -> c[0];\nmeasure q[1] -> c[1];" , "num_qubits": 2, "num_clbits": 4, "quantum_registers": {"q": [0, 1]}, "classical_registers": {"c": [0, 1], "other_measure_name": [2], "meas": [3]}}

and json format with the following structure:

{"instructions": [{"name": "h", "qubits": [0], "params": []},{"name": "cx", "qubits": [0, 1], "params": []}, {"name": "rx", "qubits": [0], "params": [0.39528385768119634]}, {"name": "measure", "qubits": [0], "memory": [0]}], "num_qubits": 2, "num_clbits": 4, "quantum_registers": {"q": [0, 1]}, "classical_registers": {"c": [0, 1], "other_measure_name": [2], "meas": [3]}}

3. qdrop command

Once the work is finished, the raised QPUs should be dropped in order to not monopolize computational resources.

The qdrop command can be used to drop all the QPUs raised with a single qraise by passing the corresponding qraise SLURM_JOB_ID:

qdrop SLURM_JOB_ID

Note that the SLURM_JOB_ID can be obtained, for instance, executing the squeue command.

To drop all the raised QPUs, just execute:

qdrop --all

ACKNOWLEDGEMENTS