Backend adapters

Four worked implementations of the Executor contract. Application code never imports these; the CLI selects one from its flag.

Note

netqasm and cunqa are imported at module level by their adapters and are mocked when these docs are built, since neither can be installed in a public CI runner. Signatures and docstrings are accurate; the types they borrow from those packages are not resolved into links.

CUNQA

The reference backend: HPC emulation through virtual QPUs, and the only adapter implementing every communication primitive. See CUNQA backend.

Executor and configuration

Executor adapter for the CUNQA backend.

This module provides an implementation of the Executor interface for running applications with the CUNQA backend.

A NetQMPI run needs one vQPU per rank. There are two ways to get them, and the configuration picks between them:

  • Attach (the default): the vQPUs are already up, raised by the user before the netqmpi command with qraise, and the run takes the ones it needs from a single family. The allocation outlives the run, so several programs can be launched against the same vQPUs without paying for a SLURM job each time, and a family larger than the run is fine — the vQPUs no rank is using are kept busy with a trivial circuit, since the family’s executor runs a round only once every one of its vQPUs has submitted something.

  • Raise (qraise: true in the config): the adapter raises the vQPUs itself, runs, and drops them again. The vQPU definition to raise them with is the backend setting, so the qubit budget of the run is under the user’s control.

class netqmpi.runtime.adapters.cunqa.cunqa_executor.CunqaRunConfig[source]

Bases: RunConfig

Extension of RunConfig with CUNQA-specific execution parameters.

Variables:
  • qraise (bool) – Whether to raise the vQPUs for this run and drop them afterwards. When False (the default), the run attaches to vQPUs that are already up.

  • backend (str | None) – Path to the vQPU definition file the vQPUs are raised with, which is what fixes their qubit budget. Only meaningful when qraise is enabled; None leaves the choice to CUNQA’s own default.

  • simulator (str) – Simulator backing each vQPU. Raise-mode only.

  • time (str) – Wall-clock reservation for the SLURM job, as D-HH:MM:SS or HH:MM:SS. Raise-mode only.

  • family (str | None) – Family the vQPUs belong to. Selects which of the running vQPUs to attach to, or names the family to raise.

  • co_located (bool) – Whether the vQPUs are reachable from other nodes (CUNQA’s co-located mode) rather than only from the node they run on (hpc mode).

qraise: bool = False
backend: str | None = None
simulator: str = 'Munich'
time: str = '00:10:00'
family: str | None = None
co_located: bool = True
__init__(shots=1024, qraise=False, backend=None, simulator='Munich', time='00:10:00', family=None, co_located=True)
Parameters:
Return type:

None

class netqmpi.runtime.adapters.cunqa.cunqa_executor.CunqaExecutorAdapter[source]

Bases: Executor

Executor adapter for the CUNQA backend.

This adapter enables execution through CUNQA while conforming to the common interface defined by Executor.

__init__(size, config=None)[source]

Initialize the CUNQA executor adapter.

Parameters:
  • size (int) – Number of available CUNQA nodes.

  • config (CunqaRunConfig) – Backend-specific configuration parameters.

create_circuit(num_qubits, num_clbits, comm)[source]

Create a CUNQA circuit adapter.

Parameters:
  • num_qubits (int) – Number of qubits in the circuit.

  • num_clbits (int) – Number of classical bits in the circuit.

  • comm (CunqaCommunicator) – Communicator associated with the circuit.

Returns:

A CunqaCircuitAdapter instance.

Return type:

CunqaCircuitAdapter

build_apps(file, size)[source]

Build one application wrapper per rank from the provided file.

Parameters:
  • file (str) – Path to the file containing the main entry point.

  • size (int) – Number of ranks to instantiate.

Returns:

A collection of wrapped application callables, one per rank.

Return type:

Any

run(apps)[source]

Execute the provided applications on the CUNQA backend.

The applications are invoked one after the other to build their circuits; the last rank to leave its with comm: block triggers the joint translation and the actual run.

Parameters:

apps (Any) – Applications to execute.

Raises:

Exception – Propagates any exception raised while the ranks build their circuits or while the circuits are executed.

Return type:

None

Circuit adapter

Adapter for the CUNQA backend circuit.

This module implements the Circuit interface for CUNQA circuits.

Local operations are translated one by one, exactly as the abstract Circuit dispatch expects. Collective ones cannot: CUNQA’s telegate helpers (cunqa.qc_protocols.cat_entangler() and cunqa.qc_protocols.cat_disentangler()) write instructions into every participating circuit in a single call, so they can only run once all the ranks are known and each of them has been translated up to the matching call. translate_group() performs that joint pass, walking every rank’s operation stream and stopping at the collective calls to expand them in one go.

The rooted transfers (qscatter, qgather) sit in between: they are collective for the user, since every rank has to call them, but each rank’s half is a sequence of ordinary teledata blocks that CUNQA pairs up by tag at run time, so they need no joint expansion.

netqmpi.runtime.adapters.cunqa.cunqa_circuit.PROTOCOL_CLREG = 'netqmpi_protocol'

Name of the classical register holding the correction bits of the distributed protocols, kept apart from the user’s own register.

class netqmpi.runtime.adapters.cunqa.cunqa_circuit.CunqaCircuitAdapter[source]

Bases: Circuit

Circuit adapter for the CUNQA backend.

This class wraps a CunqaCircuit instance and exposes the common interface defined by the abstract Circuit base class.

__init__(num_qubits, num_clbits, comm)[source]

Initialize the CUNQA circuit adapter.

The underlying CunqaCircuit starts with no communication qubit and no protocol classical bits: how many are needed only becomes known once the application has been traced, so they are added by prepare() right before the instructions are emitted.

Parameters:
  • num_qubits (int) – Number of qubits in the circuit.

  • num_clbits (int) – Number of classical bits in the circuit.

  • comm (CunqaCommunicator) – Communicator associated with the circuit.

property cunqa_circuit: cunqa.circuit.CunqaCircuit

Return the underlying CUNQA circuit.

Returns:

The wrapped CunqaCircuit.

prepare()[source]

Reserve on the CUNQA circuit the resources the trace asked for.

Adds the communication qubits and the protocol classical register sized by the trace. Must run before any instruction is emitted, since the protocol register has to sit after the user’s own bits.

Calling it twice is a no-op: a second call would append a further register and quietly renumber the protocol bits.

Return type:

None

comm_qubit_of(slot)[source]

Return the CUNQA index of one of this circuit’s comm-qubit slots.

Parameters:

slot (int) – Communication-qubit slot reserved by a protocol.

Returns:

The communication qubit index in the CUNQA circuit.

Return type:

int

translate(op)[source]

Dispatch an operation to its corresponding translation method.

Parameters:

op (Operation) – Operation to translate.

Returns:

The translated CUNQA instruction or instructions.

Raises:

TypeError – If the operation type is unknown.

Return type:

Any

netqmpi.runtime.adapters.cunqa.cunqa_circuit.idle_circuit(index)[source]

Build the trivial circuit sent to a vQPU that no rank is using.

CUNQA runs one executor per family of vQPUs, and every round that executor waits for a circuit from each vQPU of the family before it runs anything: a vQPU left out does not sit idle, it holds up the whole family. A run with fewer ranks than the family has vQPUs therefore submits this circuit to each of the spare ones, which does nothing, is over immediately, and whose counts are discarded.

Parameters:

index (int) – Position of the spare vQPU, used to give the circuit an id of its own.

Returns:

A one-qubit circuit holding a single measurement.

Return type:

cunqa.circuit.CunqaCircuit

netqmpi.runtime.adapters.cunqa.cunqa_circuit.check_transfers(adapters)[source]

Check that every point-to-point transfer of the group has both halves.

A qsend and its qrecv are paired at run time by the tag both sides derive from the ranks involved, so a transfer whose other half was never traced is not an error CUNQA can report: the vQPU that made the call simply waits for a partner that never comes, and the run hangs with nothing to show for it. Reading the group’s own records is enough to see it coming, and to say which rank is left waiting for what.

Only the backend-agnostic records are read, so any backend that pairs its transfers by tag can use this as it stands.

Parameters:

adapters (Dict[int, CunqaCircuitAdapter]) – Circuit adapter of every rank, keyed by rank.

Raises:

RuntimeError – If a qsend has no matching qrecv, or a qrecv no matching qsend.

Return type:

None

netqmpi.runtime.adapters.cunqa.cunqa_circuit.translate_group(adapters)[source]

Translate the circuits of a whole group of ranks into CUNQA circuits.

Every rank’s operation stream is drained until it reaches a collective call. Once all the participants of a collective are waiting on it, the call is expanded into all of their circuits at once and they resume. This mirrors what the ranks would do if they really ran side by side, while keeping each circuit’s instructions in program order.

Parameters:

adapters (Dict[int, CunqaCircuitAdapter]) – Circuit adapter of every rank, keyed by rank.

Returns:

The translated CUNQA circuits, ordered by rank.

Raises:

RuntimeError – If a collective names a rank outside the group, or if the ranks block on collectives that never match — the trace equivalent of a deadlock.

Return type:

List[cunqa.circuit.CunqaCircuit]

Communicator

Concrete BaseCommunicator backed by the CUNQA runtime.

This communicator adapts the backend-specific communication layer to the backend-agnostic QMPICommunicator interface.

CUNQA takes the whole distributed program at once — every rank’s circuit is submitted together and the vQPUs resolve the communication directives between them at run time. The ranks therefore do not execute as they are traced: each of them records its circuits, and the last one to leave its with comm: block triggers the joint translation and the actual run.

class netqmpi.runtime.adapters.cunqa.cunqa_communicator.CunqaSession[source]

Bases: object

State shared by every rank of a single NetQMPI run.

Holds what only makes sense for the program as a whole: the vQPUs, the run configuration, the communicators that have already finished tracing, and the results once they are back.

The results of a distributed program are the results of every rank — a single rank’s counts say nothing about the correlations the program was written to produce — so they are kept here, whole, and shared with all the communicators rather than split among them.

Variables:
  • size – Number of ranks in the run.

  • qpus – vQPUs backing the ranks, ordered by rank.

  • idle_qpus – vQPUs of the same family that no rank is using.

  • config – Run configuration shared by every rank.

  • communicators – Communicator of each rank, keyed by rank.

  • finished – Ranks that are done tracing.

  • results – Counts of every rank, keyed by rank, once the run is over.

__init__(size, qpus, config, idle_qpus=None)[source]

Initialize the session.

Parameters:
  • size (int) – Number of ranks in the run.

  • qpus (List[cunqa.qpu.QPU]) – vQPUs backing the ranks, ordered by rank.

  • config (RunConfig) – Run configuration shared by every rank.

  • idle_qpus (List[cunqa.qpu.QPU] | None) – vQPUs of the same family that no rank is using, and which have to be kept busy anyway so the family’s executor is not left waiting for them.

Return type:

None

class netqmpi.runtime.adapters.cunqa.cunqa_communicator.CunqaCommunicator[source]

Bases: QMPICommunicator

CUNQA-backed communicator for a single rank.

This class provides the communicator implementation used by the CUNQA backend and is injected into QMPICommunicator.

Parameters:
  • rank – Numeric index of the current rank.

  • size – Total number of ranks in the communicator.

  • qpu – vQPU backing this rank.

  • config – Run configuration.

  • session – State shared with the other ranks of the run.

__init__(rank, size, qpu, config, session=None)[source]

Initialize the communicator.

Parameters:
  • rank (int) – Numeric index of the current rank.

  • size (int) – Total number of ranks in the communicator.

  • qpu (cunqa.qpu.QPU) – vQPU backing this rank.

  • config (RunConfig) – Run configuration.

  • session (CunqaSession) – State shared with the other ranks of the run. When omitted, a single-rank session is created.

Return type:

None

property session: CunqaSession

Return the state shared with the other ranks of the run.

Returns:

The session this communicator belongs to.

NetQASM / SquidASM

Low-level quantum-network simulation. See NetQASM / SquidASM backend.

Executor and configuration

NetQASM backend adapter.

This module implements the full Executor contract for the NetQASM simulator, including circuit creation, application construction, and simulation execution.

This is the only file in the NetQASM adapter layer allowed to import from netqasm.*.

class netqmpi.runtime.adapters.netqasm.netqasm_executor.NetQASMRunConfig[source]

Bases: RunConfig

Extension of RunConfig with NetQASM-specific simulation parameters.

Variables:
  • netqasm_major (int) – Major NetQASM release this run expects, 2 by default. The --netqasm1.0 flag sets it to 1. It selects an environment rather than an adapter: the API this backend uses is the same in both releases, so the value is checked against what is installed and the run stops early if they disagree.

  • shots (int) – Number of times the program is simulated. Overrides the generic default of 1024, which is wrong by two orders of magnitude for this backend: SquidASM simulates the whole network once per shot, at roughly a second each on a two-rank program, so the generic default would take a quarter of an hour and look like a hang. Raise it with --shots when the statistics matter more than the wait.

  • formalism (netqasm.runtime.settings.Formalism) – Quantum state formalism to use in the simulation.

  • network_config (Any | None) – Network configuration describing the simulated topology. If None, the default topology is used.

  • log_cfg (Any | None) – NetQASM log configuration controlling per-rank instruction logging.

netqasm_major: int = 2
shots: int = 50
formalism: netqasm.runtime.settings.Formalism
enable_logging: bool = True
hardware: str = 'generic'
post_function: Callable | None = None
network_config: Any | None = None
log_cfg: Any | None = None
argv = None
roles: str = 'roles.yaml'
__init__(shots=50, netqasm_major=2, formalism=<factory>, enable_logging=True, hardware='generic', post_function=None, network_config=None, log_cfg=None, roles='roles.yaml')
Parameters:
  • shots (int)

  • netqasm_major (int)

  • formalism (netqasm.runtime.settings.Formalism)

  • enable_logging (bool)

  • hardware (str)

  • post_function (Callable | None)

  • network_config (Any | None)

  • log_cfg (Any | None)

  • roles (str)

Return type:

None

class netqmpi.runtime.adapters.netqasm.netqasm_executor.NetQASMExecutorAdapter[source]

Bases: Executor

Executor implementation for the NetQASM backend.

This adapter handles circuit creation, application construction, and simulation execution for the NetQASM runtime.

__init__(size, config=None)[source]

Initialize the NetQASM executor adapter.

Parameters:
  • size (int) – Number of available NetQASM nodes.

  • config (NetQASMRunConfig) – NetQASM-specific configuration.

Raises:

RuntimeError – If the installed NetQASM is not the major release the configuration asks for.

Return type:

None

create_circuit(num_qubits, num_clbits, comm)[source]

Create a NetQASM circuit adapter.

Parameters:
  • num_qubits (int) – Number of qubits in the circuit.

  • num_clbits (int) – Number of classical bits in the circuit.

  • comm (NetQASMCommunicator) – Communicator bound to the circuit.

Returns:

A NetQASMCircuitAdapter instance.

Return type:

Circuit

build_apps(file, size)[source]

Load a file and build a NetQASM application instance.

The resulting application instance contains one program per rank, each wrapping the user main function with an injected Environment.

Parameters:
  • file (str) – Path to the NetQMPI Python file.

  • size (int) – Number of parallel quantum nodes.

  • argv_file – Optional YAML file containing per-rank input arguments.

  • roles_cfg_file – Path to the roles configuration file.

Returns:

A ApplicationInstance ready to be passed to run().

Raises:

ValueError – If file is None or does not point to a Python file.

Return type:

Any

run(apps)[source]

Run an application instance through the NetQASM simulator.

Parameters:
  • app_instance – Application instance returned by build_apps().

  • apps (Any)

Return type:

None

Circuit adapter

Circuit adapter for the NetQASM eager-execution model.

NetQASM does not provide a circuit object: each instruction is dispatched to the simulator as soon as it is invoked on a netqasm.sdk.qubit.Qubit. Therefore, this adapter:

  1. Allocates a Qubit array in __init__ using the active connection exposed by self._comm.

  2. Overrides the gate methods of the base Circuit so that each method first delegates to super() to record the operation in the OperationContainer, and then executes the corresponding NetQASM SDK call immediately.

  3. Overrides the inter-rank communication primitives (qsend, qrecv, qscatter, qgather, expose, and unexpose) with the concrete teleportation and collective protocols implemented through the NetQASM SDK.

  4. Keeps translate() as a no-op, since execution has already taken place eagerly.

  5. Flushes the connection in build() and returns the qubit array together with the classical measurement results.

class netqmpi.runtime.adapters.netqasm.netqasm_circuit.NetQASMCircuitAdapter[source]

Bases: Circuit

Eager-execution circuit adapter for NetQASM.

This adapter executes operations immediately on live NetQASM qubits while still recording them through the base Circuit interface.

Variables:
  • _qubits – Live NetQASM qubits allocated at construction time.

  • _results – Classical measurement results indexed by classical bit.

__init__(num_qubits, num_clbits, comm)[source]

Initialize the NetQASM circuit adapter.

Parameters:
  • num_qubits (int) – Number of qubits to allocate.

  • num_clbits (int) – Number of classical result slots.

  • comm (NetQASMCommunicator) – Communicator providing the active NetQASM connection.

Return type:

None

reset_round()[source]

Clear the state a simulated round leaves behind.

Only the qubits are per-round. The emitted operations are closures that look their qubits up when they run, so one translation serves every shot; re-translating each time simply grew the list by a full copy of the program.

Return type:

None

property translated_ops: List[Any]

The operations emitted for this circuit, in program order.

translate(op)[source]

Dispatch an operation to its corresponding translation method.

Parameters:

op (Operation) – Operation to translate.

Returns:

The translated backend instruction or instructions.

Raises:

TypeError – If the operation type is unknown.

Return type:

Any

create_ghz()[source]

Create a GHZ state across all ranks.

Returns:

The local qubit belonging to the distributed GHZ state.

Return type:

netqasm.sdk.Qubit

Communicator

Concrete BaseCommunicator backed by the NetQASM SDK.

This is the only communicator module allowed to import from netqasm.*. It provides the low-level resource management delegated by the backend-agnostic QMPICommunicator facade, including connections, EPR sockets, and classical sockets.

class netqmpi.runtime.adapters.netqasm.netqasm_communicator.NetQASMCommunicator[source]

Bases: QMPICommunicator

NetQASM-backed communicator for a single rank.

This class provides the backend-specific communication resources used by the NetQASM runtime adapter, including the NetQASM connection, EPR sockets, and lazily created classical sockets.

Parameters:
  • rank – Numeric index of the current rank.

  • size – Total number of ranks in the communicator.

  • _config – NetQASM application configuration associated with this rank.

netqasm_circuits = []
communicators: List[NetQASMCommunicator] = []

Every rank’s communicator for the current run, so the state that a single simulated round leaves behind can be cleared before the next.

__init__(rank, size, config)[source]

Initialize the NetQASM communicator.

Parameters:
  • rank (int) – Numeric index of the current rank.

  • size (int) – Total number of ranks in the communicator.

  • _config – NetQASM application configuration associated with this rank.

  • config (NetQASMRunConfig)

Return type:

None

property epr_sockets: Dict[str, Dict[str, netqasm.sdk.EPRSocket]]

Return the EPR sockets indexed by rank name.

Returns:

A nested mapping of EPR sockets.

classmethod reset_run()[source]

Forget the programs and communicators of a finished run.

Return type:

None

get_socket(my_rank, other_rank)[source]

Return a classical socket between two ranks.

A fresh one every time, deliberately. These used to be cached for the life of the communicator, which survives the network they were opened on: the second shot then reached for a socket belonging to a torn-down network and failed with “Socket is not connected so cannot send”. A socket is cheap, and it belongs to one connection.

Parameters:
  • my_rank (int) – Rank requesting the socket.

  • other_rank (int) – Rank at the other endpoint of the socket.

Returns:

A classical socket connecting the two ranks.

Return type:

netqasm.sdk.external.Socket

get_epr_socket(my_rank, other_rank)[source]

Return the EPR socket between two ranks.

Parameters:
  • my_rank (int) – Rank requesting the socket.

  • other_rank (int) – Rank at the other endpoint of the socket.

Returns:

The EPR socket connecting the two ranks.

Raises:

RuntimeError – If the requested EPR socket does not exist.

Return type:

netqasm.sdk.EPRSocket

flush()[source]

Flush the underlying NetQASM connection.

Return type:

None

create_qubit()[source]

Create a new qubit on the underlying NetQASM connection.

Returns:

A newly allocated NetQASM qubit.

property connection: netqasm.sdk.external.NetQASMConnection

Return the underlying NetQASM connection.

Returns:

The active NetQASM connection.

Qiskit Aer

Shot-based circuit simulation on one monolithic circuit. See Qiskit Aer backend.

Executor

Executor adapter for Qiskit AerSimulator.

This module provides the AerExecutorAdapter implementation of the Executor interface for running NetQMPI applications on Qiskit’s AerSimulator backend.

class netqmpi.runtime.adapters.aer.aer_executor.AerExecutorAdapter[source]

Bases: Executor

Executor adapter that runs NetQMPI apps on Qiskit’s AerSimulator.

Owns the single global QuantumCircuit every rank writes into. It is built by lay_out() once the ranks have finished tracing, because only then are the widths they each asked for known; a rank’s slice is sized to its own request rather than to a width assumed common to all.

run() launches every rank in a separate thread. build_apps() installs a threading.Barrier on AerCommunicator so that __exit__ can synchronise all threads before and after the simulation.

__init__(size, config=None)[source]

Initialize the AerSimulator executor adapter.

Parameters:
  • size (int) – Number of parallel ranks to simulate.

  • config (AerSimulatorConfig) – AerSimulator-specific configuration. Defaults to AerSimulatorConfig with its built-in defaults.

Return type:

None

create_circuit(num_qubits, num_clbits, comm)[source]

Create an AerCircuitAdapter for one rank.

No space is reserved here. The ranks may ask for registers of different widths — a qscatter root holds one qubit per receiver while each receiver holds one — so a rank’s slice cannot be placed from its rank index and its own width alone; doing that overlapped the slices and silently corrupted the program. lay_out() places them all once every rank has finished tracing.

Thread-safe: multiple ranks may call this simultaneously.

Parameters:
  • num_qubits (int) – Number of qubits for this rank’s circuit slice.

  • num_clbits (int) – Number of classical bits for this rank’s circuit slice.

  • comm (AerCommunicator) – Communicator associated with this rank.

Returns:

An AerCircuitAdapter whose slice is placed later.

Return type:

AerCircuitAdapter

lay_out(groups)[source]

Build the global circuit and give every rank its slice.

Slices are laid out group by group and, within a group, in rank order, so the bit layout of the resulting histogram is deterministic and independent of the order in which the rank threads happened to reach create_circuit().

Parameters:

groups (List[List[AerCircuitAdapter]]) – The circuits of each distributed program, rank-ordered within each group.

Return type:

None

build_apps(file, size)[source]

Build one callable wrapper per rank and install the sync barrier.

Creates all AerCommunicator instances and then installs a threading.Barrier on the class so that every rank’s __exit__ can synchronise before the simulation runs.

Parameters:
  • file (str) – Path to the NetQMPI Python script defining main().

  • size (int) – Number of ranks to instantiate.

Returns:

A list of zero-argument callables, one per rank.

Return type:

List[Any]

run(apps)[source]

Launch every rank in a separate thread and wait for all to finish.

Running ranks concurrently is required so that the threading.Barrier in AerCommunicator.__exit__ can synchronise them: all N threads must reach the barrier for any of them to proceed past it.

Parameters:

apps (List[Any]) – List of callables returned by build_apps().

Raises:

Exception – Whatever the designated thread raised while translating or simulating, re-raised here once every rank has been released.

Return type:

None

Configuration

Backend-specific configuration for Qiskit AerSimulator runs.

class netqmpi.runtime.adapters.aer.aer_run_config.AerSimulatorConfig[source]

Bases: RunConfig

Extension of RunConfig with Qiskit AerSimulator-specific fields.

Variables:
  • shots (int) – Number of simulation shots.

  • transfer_mode (str) –

    Qubit transfer protocol for qsend/qrecv. Only "swap" exists today; "teleport" is accepted by the dataclass but raises NotImplementedError at translation time.

    "swap" moves the state with a SWAP straight across the global register. That is unphysical — no entanglement is consumed, no classical correction is sent, and nothing can go wrong — which is exactly what makes this backend useful as a correctness reference: a wrong answer here is a bug in the translation, never decoherence. It is not a model of a quantum network, and fidelities measured on it say nothing about one.

    Implementing "teleport" is only worth doing together with an Aer noise model. On a noiseless simulator a teleportation circuit returns exactly what the SWAP returns, just with more gates and a pair of ancillas per transfer, so on its own it would add cost without adding information.

  • seed_simulator (int | None) – Optional RNG seed for reproducible simulations.

shots: int = 1024
transfer_mode: str = 'swap'
seed_simulator: int | None = None
__init__(shots=1024, transfer_mode='swap', seed_simulator=None)
Parameters:
  • shots (int)

  • transfer_mode (str)

  • seed_simulator (int | None)

Return type:

None

Circuit adapter

Circuit adapter for Qiskit AerSimulator.

Translates SDK operations into Qiskit gates appended directly to a shared global QuantumCircuit owned by the executor. Every local qubit index is shifted by the rank’s offset before being written to the global register.

class netqmpi.runtime.adapters.aer.aer_circuit.AerCircuitAdapter[source]

Bases: Circuit

Circuit adapter that writes operations into a shared global QuantumCircuit.

Each rank owns a contiguous slice [qubit_offset, qubit_offset + num_qubits) of the global qubit register and the analogous slice of the classical register. All translate methods map local indices to global indices before appending gates.

A rank’s slice is not sized or placed until every rank has finished tracing: the ranks may ask for registers of different widths — a qscatter root holds one qubit per receiver while the receivers hold one each — so where a slice starts cannot be known from the rank index alone. assign_slice() fills the offsets in once the layout is settled.

A communication qubit belongs to no slice at all. It addresses a control another rank has lent through an open expose window, and _global() resolves it to that rank’s data qubit for as long as the window is open.

__init__(num_qubits, num_clbits, comm)[source]

Initialize the AerCircuitAdapter.

Parameters:
  • num_qubits (int) – Number of qubits for this rank’s circuit slice.

  • num_clbits (int) – Number of classical bits for this rank’s circuit slice.

  • comm (AerCommunicator) – Communicator owning this rank.

Return type:

None

assign_slice(global_circuit, qubit_offset, clbit_offset)[source]

Place this rank’s slice in the global circuit.

Called once every rank has finished tracing, so that the widths each of them asked for are all known and the slices can be laid out without overlapping.

Parameters:
  • global_circuit (QuantumCircuit) – The circuit shared by every rank.

  • qubit_offset (int) – Global index where this rank’s qubits start.

  • clbit_offset (int) – Global index where this rank’s classical bits start.

Return type:

None

lend_control(slot, control)[source]

Point a communication-qubit slot at the control another rank lent.

Parameters:
  • slot (int) – Communication-qubit slot reserved by the expose.

  • control (int) – Global index of the root’s exposed data qubit.

Return type:

None

release_control(slot)[source]

Close a slot opened by lend_control().

Parameters:

slot (int) – Communication-qubit slot the window reserved.

Return type:

None

property qubit_offset: int

Global index where this rank’s slice of the register starts.

property clbit_offset: int

Global index where this rank’s classical bits start.

emit_transfer(op, source, target)[source]

Move one qubit from a sender’s slot to a receiver’s slot.

In swap mode the move is an unphysical SWAP straight across the global register: no entanglement is consumed and no noise is introduced, which is what makes this backend a correctness reference rather than a model of a network.

Parameters:
  • op (QSend) – The send half of the transfer, for error reporting.

  • source (int) – Global index of the sender’s qubit.

  • target (int) – Global index of the receiver’s qubit.

Raises:

NotImplementedError – When transfer_mode is "teleport".

Return type:

None

translate(op)[source]

Dispatch an operation and return the shared global QuantumCircuit.

Parameters:

op (Operation) – Operation to translate.

Returns:

The shared global QuantumCircuit after appending the operation.

Raises:

TypeError – If the operation type is unknown.

Return type:

Any

netqmpi.runtime.adapters.aer.aer_circuit.BLOCKING = (<class 'netqmpi.sdk.operations.qmpi.QSend'>, <class 'netqmpi.sdk.operations.qmpi.QRecv'>, <class 'netqmpi.sdk.operations.qmpi.CollectiveOperation'>)

they need a partner rank to be sitting on the matching call before anything can be written out.

Type:

Operations a rank cannot emit on its own

netqmpi.runtime.adapters.aer.aer_circuit.translate_group(adapters)[source]

Translate the circuits of a whole group of ranks into the global circuit.

Aer runs every rank inside a single QuantumCircuit, so the order in which instructions are appended is the order in which they execute. Translating one rank fully and then the next therefore only works when the program’s cross-rank dependencies happen to follow rank order: a chain 0 -> 1 -> 2 survives it, while a control that returns to rank 0 between hops does not, and the run then produces a wrong answer with no error raised.

This pass instead interleaves the ranks the way they would really run. Each rank advances through its own operations until it reaches something it cannot emit alone — a transfer, or a collective — and that call is expanded once every rank it involves is waiting on it, after which they all resume. The result is an emission order that respects every dependency the program expressed.

Pairing a qsend with its qrecv also supplies what a single-rank pass cannot: the receiver’s own qubit index. The transfer moves the state from the sender’s slot to the slot the receiver asked for, instead of assuming both sides chose the same local index. An expose likewise needs the root’s record for the qubit being lent and each receiver’s for the slot it is lent into.

Parameters:

adapters (Dict[int, AerCircuitAdapter]) – Circuit adapter of every rank, keyed by rank.

Raises:

RuntimeError – If the ranks block on calls that never match — the trace-time equivalent of a deadlock — or if a matched transfer disagrees on how many qubits it moves.

Return type:

None

Communicator

Communicator adapter for Qiskit AerSimulator.

Manages the context lifecycle for a single rank. The global QuantumCircuit is owned by AerExecutorAdapter; this class coordinates the barrier synchronization that ensures all ranks have finished building their circuits before the simulation runs, and that all ranks receive results before any of them continue past the with env.comm: block.

class netqmpi.runtime.adapters.aer.aer_communicator.AerCommunicator[source]

Bases: QMPICommunicator

AerSimulator-backed communicator for a single rank.

All N ranks run concurrently in separate threads. __exit__ uses a threading.Barrier to synchronise them:

  1. Every rank finishes building its circuit ops and reaches the barrier.

  2. One designated thread translates all the ranks’ circuits jointly — interleaving them so that transfers pair up and cross-rank dependencies survive — and runs the simulation.

  3. All threads are released with results available and continue past the with env.comm: block simultaneously.

The barrier and class-level communicator list are reset after the last rank exits so the adapter is reusable within the same process.

Parameters:
  • rank – Numeric index of the current rank.

  • size – Total number of ranks.

  • config – AerSimulator-specific configuration.

  • executor – Executor that owns the global QuantumCircuit.

communicators: List[AerCommunicator] = []
__init__(rank, size, config, executor)[source]

Initialize the communicator.

Parameters:
  • rank (int) – Numeric index of the current rank.

  • size (int) – Total number of ranks in the communicator.

  • config (AerSimulatorConfig) – AerSimulator-specific configuration.

  • executor (AerExecutorAdapter) – Executor that owns the global QuantumCircuit.

Return type:

None

Qoala

Quantum-internet node execution environment, simulation only. See Qoala backend.

Executor and configuration

Executor adapter for the Qoala backend (simulation only).

Qoala is a NetSquid-based simulator of Qoala-spec quantum-internet nodes; there is no real-hardware execution path, so this backend is explicitly simulation-only and must not be treated as on par with a physical deployment.

Like the NetQASM adapter, the whole N-node network runs inside a single Python process as one NetSquid discrete-event simulation (one ProcNode context per rank), not as N operating-system processes. Each rank compiles its circuit to a .iqoala program; when all ranks are ready, run_simulation() builds the Qoala network, submits one batch (of shots iterations) per node, pairs the remote PIDs for entanglement, runs the simulation and returns a per-rank measurement histogram.

This is the only module in the Qoala adapter allowed to import qoala.* / netsquid. Those imports are performed lazily inside run_simulation() so that merely selecting the backend (and building apps) does not pull in the heavy NetSquid runtime until a simulation actually runs.

class netqmpi.runtime.adapters.qoala.qoala_executor.QoalaQDeviceConfig[source]

Bases: object

Hardware parameters of a single node’s quantum device (the Qoala analogue of SquidASM’s qdevice_cfg). Applied uniformly to every node.

Durations are in nanoseconds. T1 == T2 == 0 means “no memory noise” (Qoala’s convention for a perfect qubit); depolarising probabilities of 0 mean noiseless gates. The defaults therefore describe a perfect qdevice.

Note (documented limitation): init_time and measure_time are exposed independently by building the topology per-instruction. Depolarising noise is applied to the single-/two-qubit gates only; INSTR_INIT and INSTR_MEASURE carry their own duration but no depolarising error, so there is no separate readout-flip model here.

Variables:
  • t1 (float) – Amplitude-damping time (ns). 0 disables amplitude damping.

  • t2 (float) – Dephasing time (ns). 0 disables dephasing. Requires t2 <= 2 * t1 when both are non-zero.

  • single_qubit_gate_time (float) – Duration (ns) of single-qubit gates.

  • two_qubit_gate_time (float) – Duration (ns) of two-qubit gates.

  • init_time (float) – Duration (ns) of qubit initialization.

  • measure_time (float) – Duration (ns) of measurement.

  • single_qubit_gate_depolar_prob (float) – Depolarising probability of single-qubit gates.

  • two_qubit_gate_depolar_prob (float) – Depolarising probability of two-qubit gates.

t1: float = 0.0
t2: float = 0.0
single_qubit_gate_time: float = 5000.0
two_qubit_gate_time: float = 200000.0
init_time: float = 5000.0
measure_time: float = 5000.0
single_qubit_gate_depolar_prob: float = 0.0
two_qubit_gate_depolar_prob: float = 0.0
classmethod from_dict(data)[source]

Build a config from a plain dict, rejecting unknown keys.

Parameters:

data (Dict[str, Any])

Return type:

QoalaQDeviceConfig

__init__(t1=0.0, t2=0.0, single_qubit_gate_time=5000.0, two_qubit_gate_time=200000.0, init_time=5000.0, measure_time=5000.0, single_qubit_gate_depolar_prob=0.0, two_qubit_gate_depolar_prob=0.0)
Parameters:
Return type:

None

class netqmpi.runtime.adapters.qoala.qoala_executor.QoalaRunConfig[source]

Bases: RunConfig

Extension of RunConfig with Qoala/NetSquid simulation parameters.

Variables:
  • num_qubits_per_node (int | None) – Physical qubits exposed by every node. If None, it is inferred from the compiled circuits (user qubits plus a teleportation scratch slot).

  • link_duration (float) – EPR-pair generation time (ns) for the links.

  • qnos_instr_time (float) – Duration (ns) of a single quantum-processor instruction.

  • hw_config (netqmpi.runtime.adapters.qoala.qoala_executor.QoalaQDeviceConfig | None) – Per-node qdevice hardware parameters. If None, a perfect qdevice (no memory/gate noise) is used.

  • link_fidelity (float) – Fidelity of the generated EPR pairs to the ideal Bell state, in [0.25, 1.0]. 1.0 (default) uses perfect links; values below 1.0 use a depolarising link with prob_max_mixed = (4/3)(1 - link_fidelity).

  • seed (int | None) – Optional NetSquid random seed for reproducible runs.

num_qubits_per_node: int | None = None
qnos_instr_time: float = 1000.0
hw_config: QoalaQDeviceConfig | None = None
seed: int | None = None
classmethod from_dict(data)[source]

Build a Qoala run config from a dict, translating the nested hardware block into a QoalaQDeviceConfig.

Parameters:

data (Dict[str, Any]) – Merged config settings for the Qoala backend.

Returns:

A QoalaRunConfig instance.

Return type:

QoalaRunConfig

__init__(shots=1024, num_qubits_per_node=None, link_duration=1000.0, qnos_instr_time=1000.0, hw_config=None, link_fidelity=1.0, seed=None)
Parameters:
Return type:

None

class netqmpi.runtime.adapters.qoala.qoala_executor.QoalaExecutorAdapter[source]

Bases: Executor

Executor adapter for the Qoala simulator.

Handles circuit creation and per-rank application construction, and owns the shared simulation driver invoked once all ranks have compiled their program.

__init__(size, config=None)[source]

Initialize the Qoala executor adapter.

Parameters:
  • size (int) – Number of parallel quantum nodes to simulate.

  • config (QoalaRunConfig) – Qoala-specific configuration. Defaults to QoalaRunConfig with its built-in defaults.

Return type:

None

create_circuit(num_qubits, num_clbits, comm)[source]

Create a Qoala circuit adapter.

Parameters:
  • num_qubits (int) – Number of qubits in the circuit.

  • num_clbits (int) – Number of classical bits in the circuit.

  • comm (QoalaCommunicator) – Communicator associated with the circuit.

Returns:

A QoalaCircuitAdapter instance.

Return type:

QoalaCircuitAdapter

build_apps(file, size)[source]

Build one callable wrapper per rank from the provided script.

Parameters:
  • file (str) – Path to the NetQMPI Python script defining main().

  • size (int) – Number of ranks to instantiate.

Returns:

A list of zero-argument callables, one per rank.

Return type:

List[Any]

run(apps)[source]

Run every rank’s main. The joint simulation is triggered by the last rank leaving its with comm block (see run_simulation()).

Parameters:

apps (List[Any]) – Callables returned by build_apps().

Return type:

None

run_simulation(registry)[source]

Build the Qoala network and run one simulation for all ranks.

Parameters:

registry (Dict[int, Tuple[QoalaProgramSpec, QoalaCommunicator]]) – Mapping rank -> (program spec, communicator) gathered as each rank left its with comm block.

Returns:

Mapping rank -> {bitstring: count} with the measurement histogram for each rank (empty for ranks that measure nothing).

Return type:

Dict[int, Dict[str, int]]

Circuit adapter

Circuit adapter for the Qoala backend (simulation only).

Qoala programs are structured very differently from an eager gate stream: a program is a list of host-code basic blocks (typed CL/CC/QL/QC) that invoke local routines (NetQASM subroutines) and request routines (EPR generation). This adapter therefore acts as a small compiler that walks the flat NetQMPI OperationContainer and emits the textual .iqoala representation of one program per rank.

The generated text is parsed into a QoalaProgram by qoala_executor (the only module that imports qoala.*). Keeping this file free of qoala imports means the whole .iqoala generation is pure-Python and independently testable, and it preserves the lazy-import contract used by the other backends.

Compilation rules (see docs/design/qoala-backend.md for the full mapping):

  • Consecutive local gates / measurements are accumulated into a single local routine, flushed as a QL block whenever a communication boundary (qsend/qrecv) is reached or at the end of the circuit.

  • qsend becomes the teleportation sender triad: a QC EPR-create request, a QL Bell-state-measurement routine, and a CL block sending the two correction bits.

  • qrecv becomes the teleportation receiver triad: a QC EPR-receive request, two CC blocks receiving the correction bits, and a QL block applying the Pauli corrections. The received qubit lands directly in the target virtual-qubit slot.

Scope (v1): local gates, measure, qsend and qrecv. Every other inter-rank primitive raises NotImplementedError, mirroring the CUNQA adapter.

class netqmpi.runtime.adapters.qoala.qoala_circuit.QoalaProgramSpec[source]

Bases: object

Backend-neutral description of the .iqoala program built for one rank.

Variables:
  • iqoala_text (str) – Full serialized .iqoala program, ready to be parsed by QoalaParser.

  • program_input (dict) – Mapping of program parameter names to values (remote node ids), consumed as a ProgramInput by the executor.

  • num_qubits (int) – Minimum number of physical qubits the node must expose for this program (user qubits plus one teleportation scratch slot).

  • outputs (List[Tuple[int, str]]) – Ordered (clbit_index, host_var_name) pairs describing which host variables are returned via return_result, used to rebuild a measurement bitstring.

iqoala_text: str
program_input: dict
num_qubits: int
outputs: List[Tuple[int, str]]
__init__(iqoala_text, program_input, num_qubits, outputs=<factory>)
Parameters:
Return type:

None

class netqmpi.runtime.adapters.qoala.qoala_circuit.QoalaCircuitAdapter[source]

Bases: Circuit

Compiles a NetQMPI circuit into a textual Qoala program (simulation only).

The adapter does not execute anything: it records operations through the base Circuit fluent API and, on build_program(), walks them to emit the .iqoala text for this rank.

__init__(num_qubits, num_clbits, comm)[source]

Initialize the Qoala circuit adapter.

Parameters:
  • num_qubits (int) – Number of user qubits in the circuit.

  • num_clbits (int) – Number of classical bits in the circuit.

  • comm (QoalaCommunicator) – Communicator providing rank/size and rank naming helpers.

Return type:

None

build_program()[source]

Compile the recorded operations into a QoalaProgramSpec.

Returns:

The .iqoala text plus the metadata needed to run it and to reconstruct measurement results.

Return type:

QoalaProgramSpec

Communicator

Concrete QMPICommunicator for the Qoala backend (simulation only).

Following the NetQASM adapter’s model, all ranks run in the same process and the joint NetSquid simulation is deferred until every rank has left its with comm block. Each rank compiles its circuit to a .iqoala program on __exit__ and registers it; when the last rank registers, the executor builds the Qoala network and runs one simulation for all ranks at once.

This module imports no qoala package: program text is produced by QoalaCircuitAdapter and the simulation is driven by QoalaExecutorAdapter.

class netqmpi.runtime.adapters.qoala.qoala_communicator.QoalaCommunicator[source]

Bases: QMPICommunicator

Qoala-backed communicator for a single rank.

The communicator only orchestrates: it does not execute quantum operations (the circuit is deferred and compiled to a Qoala program). It collects one program per rank and, once all size ranks are ready, asks the executor to run the joint simulation and stores the per-rank measurement histogram in results.

__init__(rank, size, config, executor)[source]

Initialize the Qoala communicator.

Parameters:
  • rank (int) – Numeric index of the current rank.

  • size (int) – Total number of ranks in the communicator.

  • config (Any) – Backend configuration (QoalaRunConfig).

  • executor (QoalaExecutorAdapter) – Executor that owns the shared simulation driver.

Return type:

None