QiLab is a C++20 framework for simulating quantum internet networks with a focus on command routing, entanglement coordination, and pluggable data transport. It integrates the Stim stabilizer simulator and provides runners for simulation, entanglement, and data transmission.
QiLab stands in for a quantum device. It speaks the QNodeOS instruction set, so any quantum network operating system that uses that instruction set can drive it. QNodeOS is not included with QiLab, and QiLab's license grants no rights to QNodeOS. See the QNodeOS notice.
A two-node remote state preparation run, then four nodes running entanglement-based key distribution in parallel. For the interactive version, download docs/demo/qilab_demo.html and open it in a browser (Space pauses, F toggles fullscreen).
- ConfigurationEngine: loads and validates the YAML node/link configuration (
ConfigurationEngine) - DataTypes: wire-format command/response structs shared across the pipeline (
DataTypes) - Simulation: Stim-backed adapters behind a pluggable interface, driven by a manager that owns all per-node state (
Simulator/SimulatorAdapters,Simulator/SimulatorManager) - Noise models: per-qubit decoherence and entanglement-generation noise, configurable per node/link (
Simulator/NoiseModels) - Entanglement: request matching, batched success/failure, and delayed execution (
Simulator/EntanglementManager,Simulator/SimulatorRunners/entanglement_runner) - Messaging: lock-free broker routing commands and responses between runner threads (
MessageBroker) - Transport: pluggable handlers for SPI/mock comms (
DataTransmission) - Logging & Instrumentation: a real-time-safe logger, low-overhead profiler, and structured data-acquisition sink (
logger)
src/
ConfigurationEngine/ # YAML config loading, parsing, model parsers
DataTransmission/ # Transmission handlers, processor, runner
DataTypes/ # Wire command/response structs
logger/ # StandardLogger, Profiler, DataAcquisition
MessageBroker/ # Thread-safe broker and ring buffer
Simulator/
SimulatorAdapters/ # ISimulator implementations (Stim-backed, mock)
SimulatorManager/ # Per-node dispatch, timing, noise application
EntanglementManager/ # Request matching, batching, delay
NoiseModels/ # Qubit decoherence + entanglement-generation noise
SimulatorRunners/ # Threads driving simulator/entanglement flows
utils/ # Thread utilities and version info
application.cpp # Entry point wiring runners together
docs/ # Config guide, schema, architecture diagrams, release docs
scripts/ # Log-analysis and release helper scripts
test/
Unit/ # Single class/function, all collaborators mocked/stubbed
Functional/ # Multiple real components wired together
Integration/ # Scaffold for real-hardware tests (see test/Integration/README.md)
CHANGELOG.md # Release history
Prerequisites:
- CMake 3.14+ (project minimum)
- C++20 toolchain. By default CMake cross-compiles for
aarch64-linux-gnu. Override for host builds and enable tests in one step:cmake -S . -B build -DCMAKE_C_COMPILER=$(which gcc) -DCMAKE_CXX_COMPILER=$(which g++) -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON cmake --build build -j$(nproc)
CMAKE_BUILD_TYPEdefaults toReleaseif unset.Debugadditionally forcesENABLE_LOGGING=ON.
Build options:
| Option | Default | Effect |
|---|---|---|
ENABLE_PROFILING |
ON | Compiles in the low-overhead TLS profiler |
ENABLE_LOGGING |
ON | Compiles in StandardLogger; always on regardless of build type (Debug additionally forces it on if you tried to turn it off) |
ENABLE_DATA_ACQUISITION |
ON | Compiles in the structured JSONL data-acquisition sink (see below) |
BUILD_TESTING |
ON (via include(CTest)) |
Builds the test/ targets for ctest |
Artifacts appear in build/: the qilab executable, static libraries, and (if BUILD_TESTING=ON) test binaries under build/test/.
./build/qilabRuntime notes:
application.cppwires up and starts all three runner threads (simulator, entanglement, transmission) unconditionally.- Thread pinning is controlled via the
SIMULATOR_CORE,ENTANGLEMENT_CORE, andTRANSMISSION_COREconstants inapplication.cpp. QILAB_LOG_LEVEL(env var) sets the log level (DEBUG/ERROR/OFF); defaults toDEBUGif unset.QILAB_DAQ=1(env var) enables the data-acquisition sink (see below); anything else or unset leaves it disabled.- Graceful shutdown: send SIGINT or SIGTERM; each runner's
on_abort()is called before the threads are joined.
Both instrument sinks write to fixed filenames under a directory that can be
relocated by env var. This is the interface qilab_web_control reads to offer
the files for download — the same static-file hand-off already used for
config.schema.json.
| Artifact | Default path | Override (first match wins) |
|---|---|---|
| DAQ records (JSONL) | ~/.qilab/logs/data_acquisition.jsonl |
QILAB_DAQ_PATH (full path), then QILAB_DAQ_DIR |
| Profiler durations (CSV) | ~/.qilab/logs/profile_durations.csv |
QILAB_PROFILER_PATH (full base), then QILAB_PROFILER_DIR |
| Profiler events (CSV) | ~/.qilab/logs/profile_events.csv |
same as above |
CSV columns:
profile_durations.csv—Event,Max[ns],Min[ns],Count,Avg[ns].Avg[ns]is the exact mean (total_runtime / count), so it always falls within[Min, Max]; a count of 0 reports 0.profile_events.csv—Message,TimestampNs(free-formPROF_EVENTmarkers).
The deployed systemd unit sets QILAB_DAQ_DIR and QILAB_PROFILER_DIR under
/var/log/qilab, because it runs with ProtectHome=true and cannot write $HOME.
Files are overwritten per run.
Flush behaviour:
- DAQ is flushed once per second from the main (non-real-time) thread and again after all threads join, so the JSONL is readable while a run is in progress and complete after shutdown.
- Profiling is inherently shutdown-only: each runner merges its thread-local counters into the global container as it exits, so the CSVs are written once, after every thread has joined. There is no partial-run profiling snapshot.
QiLab loads node/link configuration from ~/.qilab/config/nodes.yaml by default. A minimal default file is created automatically if none exists. Override the path with either env var (checked in this order):
QILAB_CONFIG_PATH— an explicit full path, used as-is.QILAB_CONFIG_DIR— a directory;nodes.yamlis read from inside it.
docs/config_guide.md is the authoritative reference for the full format (node types, qubit-array noise models, entanglement links, the system section). A short, current example:
nodes:
- name: alice
node_type: NV_CENTER
two_qubit_gate_noise_model:
type: DEPOLARIZING
depolarizing_probability: 0.02
qubit_arrays:
- num_qubits: 2
single_gate_delay_ns: 200
qubit_decoherence_noise_model:
type: BASIC
t2_value: 1000
gate_noise_model:
type: DEPOLARIZING
depolarizing_probability: 0.001
- name: bob
qubit_arrays:
- num_qubits: 2
single_gate_delay_ns: 200
qubit_decoherence_noise_model:
type: BASIC
t2_value: 1000
gate_noise_model:
type: DEPOLARIZING
depolarizing_probability: 0.001
entanglement_links:
- node_a: alice
node_b: bob
attempt_model:
type: BASIC
attempt_duration_ns: 1000
batch_size: 100
p_success: 0.5
rng_seed: 42
link_noise_model:
type: BASIC
p_depolarizing: 0.01
p_dephasing: 0.02
system:
transmission_cycle_time_us: 5
qubit_noise_enabled: true
entanglement_noise_enabled: trueStructured, low-overhead JSONL recording for research/experiment data — separate from the human-readable log. Compiles away entirely when ENABLE_DATA_ACQUISITION=OFF; gated at runtime by QILAB_DAQ=1 (see Run above).
#include "logger/data_acquisition_api.hpp"
DAQ_RECORD("entanglement").set("attempt", 42).set("success", true).set("fidelity", 0.91);
// Guard expensive field construction:
if (DAQ_ACTIVE()) {
DAQ_RECORD("state").set("dump", expensive_to_string());
}
DAQ_FLUSH(); // application.cpp already does this each second and at shutdownEach record auto-prefixes a ts_us (epoch microseconds) timestamp and channel name, producing one JSON object per line at the DAQ output path (see Telemetry output paths):
{"ts_us":1751371200123456,"channel":"entanglement","attempt":42,"success":true,"fidelity":0.910000}Analyze with, e.g., pandas.read_json(path, lines=True).
This is distinct from scripts/analyze_gate_noise_log.py and scripts/extract_measurements.py, which parse the plain-text StandardLogger log (not the DAQ JSONL) for per-qubit measurement statistics, noise-probability/fidelity checks, and timing analysis — see each script's --help for options.
test/ is split by scope: Unit/ (one class/function, all collaborators mocked/stubbed), Functional/ (multiple real components wired together, doubling only the outermost hardware-adapter boundary), and a scaffolded Integration/ (real transmission-processor hardware, gated behind -DQILAB_BUILD_INTEGRATION_TESTS=ON, pending hardware availability — see test/Integration/README.md).
cmake -S . -B build -DCMAKE_C_COMPILER=$(which gcc) -DCMAKE_CXX_COMPILER=$(which g++) -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON
cmake --build build -j$(nproc)
ctest --test-dir build --output-on-failureEvery test is labeled unit or functional (ctest -L unit / ctest -L functional to filter). See CLAUDE.md's Testing convention for the reference pattern to follow when adding new tests.
For running QiLab as a systemd service in production (Raspberry Pi 5 target), see docs/systemd_service_setup.md.
- Architecture diagrams:
docs/Design/*.drawio - Configuration reference: docs/config_guide.md
- Hardware bill of materials (Raspberry Pi 5 QiLab box and QNodeOS boards): docs/HwBOM_box.xlsx
- Workshop slides: docs/QiLab_Workshop_Github.pptx
- Release history: CHANGELOG.md
- Core interfaces live under
src/; headers include Doxygen comments for runners, brokers, transmission handlers, and profiling APIs.
Copyright (c) 2026, Quantum Internet Alliance, Delft University of Technology, Milen Kolev, Samuel Oslovich, Jake Smith, Stephanie Wehner.
QiLab is released under the Clear BSD License (SPDX: BSD-3-Clause-Clear). This license grants no patent rights. Every source file carries the matching SPDX header.
- What QiLab is. QiLab stands in for a quantum device. It receives and answers operations in the QNodeOS instruction set (the interface between a quantum network operating system and its quantum device). Any quantum network operating system that uses this instruction set, QNodeOS among them, can communicate with QiLab.
- Not included. QNodeOS and any other operating system are separate software. They are not part of QiLab and are not provided, distributed or sublicensed with QiLab.
- No rights granted. The QiLab license covers QiLab only. It grants no rights of any kind, express or implied, in QNodeOS or in the QNodeOS instruction set or its specifications, including copyright, patent and trademark rights. QiLab implements the device side of the instruction set solely for interoperability.
- Separate terms. You must get QNodeOS, or any other operating system you use with QiLab, from its rights holders. Its use is governed solely by its own license terms.
- Name only. The name "QNodeOS" is used here only to describe compatibility. It does not imply endorsement by the QNodeOS rights holders.
Third-party dependencies are fetched at build time and remain under their own licenses:
- Stim (v1.15.0): Apache License 2.0. Statically linked into the
qilabbinary. Release archives include its license asLICENSE.stim. - GoogleTest (tests only, not shipped): BSD 3-Clause License.
Active development project — APIs and wiring may change.

