A Distributed Quantum Computing emulator for HPC
It is important to say that, for ensuring a correct cloning of the repository, the SSH is the one preferred. In order to get this to work one has to do:
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/SSH_KEYNow, everything is set to get the source code.
git clone [email protected]:CESGA-Quantum-Spain/cunqa.gitBefore doing any kind of compilation, the user has to define the STORE environment variable in bash. This repository will be the root for the .cunqa folder, where CUNQA is going to store several runtime files (configuration files and logging files, mainly).
export STORE=/path/to/your/storeCUNQA has a set of dependencies. They are divided in three main groups.
- Must be installed before configuration.
- Can be installed, but if they are not they will be by the configuration process.
- They will be installed by the configuration process.
From the first group, the ones that must be installed, the dependencies are the following. The versions here displayed are the ones that have been employed in the development and, therefore, that are recommended.
gcc 12.3.0
qiskit 1.2.4
CMake 3.21.0
python 3.9 (recommended 3.11)
pybind11 2.7 (recommended 2.12)
MPI 3.1
OpenMP 4.5
Boost 1.85.0
Eigen 5.0.0
Blas -
Lapack -
From the second group, the ones that will be installed if they are not yet, they are the next ones.
nlohmann JSON 3.11.3
spdlog 1.16.0
MQT-DDSIM 1.24.0
libzmq 4.3.5
cppzmq 4.11.0
CunqaSimulator 0.1.1
And, finally, the ones that will be installed.
argparse -
qiskit-aer 0.17.2 (modified version)
Now, as with any other CMake project, it can be installed using the usual directives. The CMAKE_INSTALL_PREFIX variable should be defined or will be the HOME environment variable value.
cmake -B build/ -DCMAKE_PREFIX_INSTALL=/your/installation/path
cmake --build build/ --parallel $(nproc)
cmake --install build/It is important to mention that the user can also employ Ninja to perform this task.
cmake -G Ninja -B build/ -DCMAKE_PREFIX_INSTALL=/your/installation/path
ninja -C build/ -j $(nproc)
cmake --install build/Alternatively, you can use the configure.sh file, but only after all the dependencies have been solved.
source configure.sh /your/installation/pathCunqa is available as Lmod module in CESGA. To use it all you have to do is:
- In QMIO:
module load qmio/hpc gcc/12.3.0 cunqa/0.3.1-python-3.9.9-mpi - In FT3:
module load cesga/2022 gcc/system cunqa/0.3.1
If your HPC center is interested in using it this way, EasyBuild files employed to install it in CESGA are available inside easybuild/ folder.
There has also been developed a Make directive to uninstall CUNQA if needed:
-
If you installed using the standard way:
make uninstall. -
If you installed using Ninja:
ninja uninstall.
Be sure to execute this command inside the build/ directory in both cases. An alternative is using:
cmake --build build/ --target uninstallto abstract from the installation method.
Once CUNQA is installed, the basic workflow to use it is:
- Raise the desired QPUs with the command
qraise. - 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.
- Drop the raised QPUs with the command
qdrop.
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- The only two mandatory FLAGS of
qraiseare the number of QPUs, set up with-nor--num_qpusand the maximum time the QPUs will be raised, set up with-tor--time. So, for instance, the command
qraise -n 4 -t 01:20:30will 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.
- The simulator and the backend configuration can be set by the user through
qraiseFLAGs:
Set simulator:
qraise -n 4 -t 01:20:30 --sim=MunichThe 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 get_QPUs() method presented in the section below will collect all the raised QPUs.
Once the QPUs are raised, they are ready to execute any quantum circuit. The following script shows a basic workflow to run a circuit on a single QPU.
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.9In 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
sys.path.append(os.getenv("HOME"))
# Let's get the raised QPUs
from cunqa.qutils import get_QPUs
qpus = get_QPUs(local=False) # List of all 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 = qpus[0] # Select one of the raise QPUs
job = qpu0.run(qc, transpile = True, shots = 1000) # Run the transpiled circuit
result = job.result # Get the result of the execution
counts = result.counts # Get the counts
print(f"Counts: {counts}" ) # {'00':546, '11':454}Note
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]}}
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_IDNote that the SLURM_JOB_ID can be obtained, for instance, executing the squeue command.
To drop all the raised QPUs, just execute:
qdrop --allThis work has been mainly funded by the project QuantumSpain, financed by the Ministerio de Transformación Digital y Función Pública of Spain’s Government through the project call QUANTUM ENIA – Quantum Spain project, and by the European Union through the Plan de Recuperación, Transformación y Resiliencia – NextGenerationEU within the framework of the Agenda España Digital 2026. J. Vázquez-Pérez was supported by the Axencia Galega de Innovación (Xunta de Galicia) through the Programa de axudas á etapa predoutoral (ED481A & IN606A).
Additionally, this research project was made possible through the access granted by the Galician Supercomputing Center (CESGA) to two key parts of its infrastructure. Firstly, its Qmio quantum computing infrastructure with funding from the European Union, through the Operational Programme Galicia 2014-2020 of ERDF_REACT EU, as part of theEuropean Union’s response to the COVID-19 pandemic.
Secondly, The supercomputer FinisTerrae III and its permanent data storage system, which have been funded by the NextGeneration EU 2021 Recovery, Transformation and Resilience Plan, ICT2021-006904, and also from the Pluriregional Operational Programme of Spain 2014-2020 of the European Regional Development Fund (ERDF), ICTS-2019-02-CESGA3, and from the State Programme for the Promotion of Scientific and Technical Research of Excellence of the State Plan for Scientific and Technical Research and Innovation 2013-2016 State subprogramme for scientific and technical infrastructures and equipment of ERDF, CESG15-DE-3114.
When citing the software, please cite the original CUNQA paper:
@misc{vázquezpérez2025cunqadistributedquantumcomputing,
title={CUNQA: a Distributed Quantum Computing emulator for HPC},
author={Jorge Vázquez-Pérez and Daniel Expósito-Patiño and Marta Losada and Álvaro Carballido and Andrés Gómez and Tomás F. Pena},
year={2025},
eprint={2511.05209},
archivePrefix={arXiv},
primaryClass={quant-ph},
url={https://arxiv.org/abs/2511.05209},
}