SDK¶
The user-facing layer. Application code depends on these classes and nothing else, which is what makes a NetQMPI program backend-agnostic.
Environment¶
Environment object injected into every user main() function.
This class acts as the bridge between the runtime layer, which sets up the multi-process execution environment and selects the backend, and the SDK layer, which provides the user-facing programming API.
By depending only on this class, user applications remain fully backend-agnostic and do not need to import any concrete executor or backend adapter.
- class netqmpi.sdk.environment.Environment[source]¶
Bases:
objectRuntime context injected into every user
main()function.This class encapsulates two responsibilities:
Communication, through the
QMPICommunicatorexposed by thecommproperty.Circuit creation, through
create_circuit(), which delegates to the backend-specificExecutorselected by the runtime.
Example
- def main(env: Environment = None):
rank = env.comm.rank circuit = env.create_circuit(num_qubits=2, num_clbits=1)
- with env.comm:
…
- Parameters:
comm – Communicator associated with this rank.
executor – Executor responsible for creating backend-specific circuit instances.
- __init__(comm, executor)[source]¶
Initialize the environment.
- Parameters:
comm (QMPICommunicator) – Communicator associated with this rank.
executor (Executor) – Executor responsible for creating backend-specific circuit instances.
- Return type:
None
- property comm: QMPICommunicator¶
Return the communicator associated with this rank.
- Returns:
The rank communicator.
Communicator¶
High-level MPI-style communicator.
This module defines the backend-agnostic communicator interface exposed
to user application code through Environment.comm. Concrete
backend implementations are injected by the runtime or executor layer.
No backend-specific package (such as netqasm or cunqa) is
imported here.
- class netqmpi.sdk.communicator.QMPICommunicator[source]¶
Bases:
ABCBackend-agnostic facade for rank-based communication.
This class exposes the communication interface required by user code and by
Circuit, while delegating the backend-specific behavior to concrete subclasses.It provides:
rankandsizeproperties.Context-manager support for connection lifecycle handling.
Utility helpers for rank naming and neighbor traversal.
- property size: int¶
Return the total number of ranks in the communicator.
- Returns:
The communicator size.
- qsend(circuit, qubits, dest_rank)[source]¶
Send a qubit to the destination rank using teleportation.
- qscatter(circuit, qubits, root)[source]¶
Scatter the qubits of the root among the other ranks.
Collective call: every rank of the communicator has to reach it. The root passes its whole buffer, split into one chunk per other rank in rank order; each of those ranks passes the local qubits its chunk lands on. The transfers move the qubits, and unlike
MPI_Scatterthe root keeps no chunk: it ends the call holding none of what it scattered.
- qgather(circuit, qubits, root)[source]¶
Gather the qubits of every rank into the root.
Collective call, like
MPI_Gather, and the mirror image ofqscatter(): the root passes the whole buffer the chunks land on, every other rank the qubits it contributes. Here too the qubits are moved, so the contributors are left with theirs back in|0⟩.
- expose(circuit, qubit, ranks, root=None)[source]¶
Open a telegate window sharing a control qubit across ranks.
Collective call: every rank in
[root] + ranksmust reach it. The root lends the state ofqubitto the other participants, each of which gets back the index of a local communication qubit carrying that control until the matchingunexpose().- Parameters:
- Returns:
The qubit index this rank must use as control, or
Noneif it does not take part in the window.- Return type:
int | None
- unexpose(circuit, ranks, root=None)[source]¶
Close the telegate window opened by the matching
expose().
Circuit¶
Base abstraction for quantum circuits.
This module defines the contract that all circuit adapters must follow. It provides a backend-agnostic circuit representation based on generic operations and exposes the abstract hooks required by concrete backend implementations.
Qubit indices span two ranges. Indices below Circuit.num_qubits
address the data qubits the user asked for; indices from there on address
the communication qubits the runtime reserves for distributed
protocols, and are only ever produced by Circuit.expose(). Both
ranges are accepted by the gate API, so a control qubit borrowed from a
remote rank is used exactly like a local one.
- class netqmpi.sdk.circuit.Circuit[source]¶
Bases:
ABCAbstract base class representing a quantum circuit.
This class provides:
An
OperationContainerstoring operations according to the Composite pattern.A fluent gate API (
h,cx,rx,measure, etc.) that appends operations to the container and returnsselffor chaining.Abstract hooks
translate()andbuild()that concrete backend adapters must implement.
- Variables:
num_qubits – Number of qubits in the circuit.
num_clbits – Number of classical bits in the circuit.
- __init__(num_qubits, num_clbits, comm)[source]¶
Initialize the circuit.
- Parameters:
num_qubits (int) – Number of qubits in the circuit.
num_clbits (int) – Number of classical bits in the circuit.
comm (QMPICommunicator) – Communicator associated with the circuit.
- Return type:
None
- property num_qubits: int¶
Return the number of qubits in the circuit.
- Returns:
The number of qubits.
- property num_clbits: int¶
Return the number of classical bits in the circuit.
- Returns:
The number of classical bits.
- property num_comm_qubits: int¶
Return how many communication qubits the circuit needs.
This is the largest number of communication qubits held at the same time by the distributed protocols traced so far, and it is only final once the circuit has been fully traced.
- Returns:
The number of communication qubits to reserve on the backend.
- property num_protocol_clbits: int¶
Return how many classical bits the distributed protocols need.
These bits carry the correction outcomes of teledata/telegate and are additional to the
num_clbitsrequested by the user, so a protocol never clobbers a user measurement.- Returns:
The number of protocol classical bits to reserve on the backend.
- property ops: OperationContainer¶
Return the root operation container.
- Returns:
The operation container storing the circuit operations.
- property comm: QMPICommunicator¶
Return the communicator associated with the circuit.
- Returns:
The circuit communicator.
- comm_qubit(slot)[source]¶
Return the circuit-wide index of a communication-qubit slot.
Communication qubits are addressed right after the data qubits, so the value returned here can be handed to any gate of the fluent API just like a data qubit index.
- cp(control, target, theta)[source]¶
Apply a controlled phase gate.
Generalises
cs()(theta = pi/2) andct()(theta = pi/4), which is what the rotations of a QFT are made of.
- measure_all()[source]¶
Measure every qubit into the classical bit of the same index.
- Returns:
The current circuit instance.
- Raises:
ValueError – If there are fewer classical bits than qubits.
- Return type:
- qsend(qubits, dest_rank)[source]¶
Send qubits to another rank.
The backend adapter decides the concrete transfer protocol; each qubit is transferred by its own protocol block, which borrows one communication qubit and two protocol classical bits for as long as the transfer lasts.
- qscatter(qubits, root)[source]¶
Scatter the qubits of the root among the other ranks.
Collective call: every rank of the communicator must reach it. The root passes its whole buffer, which is split into one chunk per other rank in rank order, and each of those ranks passes the local qubits its chunk is to land on — as many as the root reserved for it.
Unlike
MPI_Scatter, the root keeps no chunk of its own: the buffer is shared out among the other ranks only, so a root scattering two qubits over two ranks is left holding none of them. Qubits are moved, not copied: the whole buffer is teleported away, and the root’s qubits are back in|0⟩once the call returns. The qubits a chunk lands on must be in|0⟩when the call is reached, as they must be for a plainqrecv(): whatever they held is not saved anywhere, the transfer destroys it.- Parameters:
- Returns:
The local qubits holding this rank’s chunk – the ones passed in on every rank but the root, and an empty list on the root, which keeps nothing.
- Raises:
IndexError – If a qubit index is not a data qubit of this rank.
ValueError – If the root is not a rank of the communicator, if it is the only rank, if the buffer is empty, or if it does not split evenly among the other ranks.
- Return type:
- qgather(qubits, root)[source]¶
Gather the qubits of every rank into the root.
Collective call, like
MPI_Gather, and the mirror image ofqscatter(): the root passes the whole buffer the chunks are to land on — its own chunk, at positionroot, already holding its contribution — and every other rank passes the local qubits it contributes.Qubits are moved here as well, so once the call is over the contributors are left with theirs back in
|0⟩and only the root holds the gathered data. The slots the root gathers into — every one of its buffer but its own chunk — must be in|0⟩when the call is reached, exactly as for a plainqrecv().- Parameters:
- Returns:
The whole buffer on the root, this rank’s contribution elsewhere.
- Raises:
IndexError – If a qubit index is not a data qubit of this rank.
ValueError – If the root is not a rank of the communicator, if the buffer is empty, or if the root’s buffer does not split evenly among the ranks.
- Return type:
- expose(qubit, ranks, root=None)[source]¶
Open a telegate window sharing a control qubit across ranks.
This is a collective call: every rank in
[root] + ranksmust reach it, exactly as they all reach anMPI_Bcast. Therootlends the state ofqubitto the other participants, which each receive it on a communication qubit of their own and can then use it as a local control until the matchingunexpose().- Parameters:
- Returns:
The qubit index to use as control on this rank —
qubititself on the root, the freshly reserved communication qubit on every receiver — orNoneif this rank does not take part.- Raises:
IndexError – If the root exposes something other than a data qubit.
ValueError – If the participant list is empty or names no receiver.
- Return type:
int | None
- unexpose(ranks, root=None)[source]¶
Close the telegate window opened by the matching
expose().Collective as well: the same ranks that opened the window must close it. The communication qubit and the protocol classical bits it held are returned to the pool, so a later window can reuse them.
- Parameters:
- Returns:
The current circuit instance.
- Raises:
RuntimeError – If no matching expose window is open.
- Return type:
Resources¶
Reusable index pools for the resources a distributed protocol borrows.
Communication qubits and the classical bits used by the correction rounds of teledata/telegate are scarce, backend-managed resources: they are taken when a protocol block opens and given back when it closes, so two blocks that never overlap in time can share the same physical resource.
This module provides the tiny allocator both the circuit layer and the backend adapters rely on to agree, without any inter-rank communication, on which slot each protocol block uses.
- class netqmpi.sdk.resources.IndexPool[source]¶
Bases:
objectAllocator of small non-negative indices with reuse.
Indices are handed out from a free list first and only then from a fresh counter, which keeps the total footprint at the maximum number of simultaneously held indices rather than the total number of acquisitions.
Example:
pool = IndexPool() a = pool.acquire(2) # [0, 1] pool.release(a) b = pool.acquire(1) # [0] -- reused pool.size # 2
- property size: int¶
Return how many distinct indices the pool ever handed out.
- Returns:
The high-water mark of the allocator.
- acquire(count=1)[source]¶
Reserve
countindices.- Parameters:
count (int) – Number of indices to reserve.
- Returns:
The reserved indices, in ascending order.
- Raises:
ValueError – If count is not strictly positive.
- Return type:
Operations¶
The operation model recorded by a circuit and consumed by a backend adapter.
Every class can be imported directly from netqmpi.sdk.operations.
Base operation¶
Abstract base for all quantum operations (Command pattern).
- class netqmpi.sdk.operations.operation.Operation[source]¶
Bases:
ABCAbstract base class for all quantum operations.
Follows the Command pattern: each subclass encapsulates all the information needed to describe a single quantum action, keeping it independent of any backend.
- Variables:
qubits (List[int]) – Qubit indices this operation acts on.
Container¶
Composite container for quantum operations.
- class netqmpi.sdk.operations.container.OperationContainer[source]¶
Bases:
OperationComposite container for quantum operations.
Implements the Composite pattern: it can hold both leaf
Operationinstances and nestedOperationContainerobjects, allowing circuits to be built hierarchically.flatten()produces a depth-first iterator over every leafOperationin insertion order.Example:
ops = OperationContainer() ops.add(Gate('H', [0])).add(Measure(0, 0)) sub = OperationContainer() sub.add(Gate('X', [1])) ops.add_circuit(sub) for op in ops.flatten(): print(op)
- __init__()[source]¶
- Parameters:
qubits – Qubit indices this operation acts on.
- Raises:
TypeError – If qubits is not a list of integers.
- Return type:
None
- property children: List[Operation | OperationContainer]¶
Direct children, leaves and sub-containers alike, in insertion order.
Unlike
flatten(), this keeps the nesting: a sub-container comes out whole, so a caller dispatching on the operation type still sees what kind of block it is instead of only its leaves.
Gates¶
Unitary gate operations (Command pattern).
- class netqmpi.sdk.operations.gate.Gate[source]¶
Bases:
OperationGeneric unitary single gate (H, X, RZ, U3, …).
- Variables:
- class netqmpi.sdk.operations.gate.ControlledGate[source]¶
Bases:
OperationGeneric controlled gate.
Wraps one or more target
Gateobjects behind a set of control qubits. The overall qubit list iscontrols + all target qubits.- Variables:
- class netqmpi.sdk.operations.gate.ClassicalControlledGate[source]¶
Bases:
OperationGate conditioned on the value of one or more classical bits.
Unlike
ControlledGate(whose controls are qubits), here the controls are classical bit indices — typically the results of prior measurements. The gate fires when all listed cbits equal 1.The
qubitsproperty returns only the target qubits (the cbits are classical and therefore not part of the quantum register).- Variables:
Non-unitary operations¶
Non-unitary quantum operations: Measure, Reset, Barrier.
- class netqmpi.sdk.operations.non_unitary.Measure[source]¶
Bases:
OperationMeasurement — collapses a qubit and stores the outcome in a classical bit.
- Variables:
- class netqmpi.sdk.operations.non_unitary.Reset[source]¶
Bases:
OperationReset — unconditionally sets a qubit back to
|0⟩.- Variables:
qubit (int) – Qubit index to reset.
Communication operations¶
Inter-rank communication primitives as first-class Operations.
Each class encodes the intent of a distributed quantum operation.
The concrete backend adapter is responsible for implementing the
protocol (e.g. teleportation, GHZ) inside Circuit.translate(op).
All classes inherit from Operation,
so they flow through OperationContainer
and flatten() exactly like any gate or measurement.
Three families of primitives live here:
Point-to-point operations (
QSend,QRecv), which every rank can translate on its own because the backend emits an independent instruction block on each side.Rooted transfers (
RootedTransfersubclasses such asQScatterandQGather), which every rank must call but which expand, on each of them, into the point-to-point transfers above. They are containers holding those transfers, so a backend that can send and receive a qubit gets them for free.Collective operations (
CollectiveOperationsubclasses such asExposeandUnexpose), whose backend expansion writes instructions into every participating circuit at once and therefore can only be emitted when all participants have reached the matching call. Each participant carries the resources it contributes to the protocol (a communication-qubit slot, protocol classical bits) plus atagthat is identical across ranks, so the runtime can pair the calls up without any trace-time communication.
- class netqmpi.sdk.operations.qmpi.CollectiveOperation[source]¶
Bases:
OperationBase class for operations that must be expanded jointly by all ranks.
A collective operation is recorded independently by every participating rank, but the backend can only translate it once all participants are sitting on the matching call. Two records match when they have the same type and the same
tag.- Variables:
- __init__(qubits, rank, ranks, tag)[source]¶
- Parameters:
- Raises:
ValueError – If ranks is empty or tag is not a string.
- Return type:
None
- class netqmpi.sdk.operations.qmpi.QSend[source]¶
Bases:
OperationSend local qubits to a remote rank.
The protocol (e.g. teleportation) is chosen by the backend adapter.
- Variables:
- __init__(qubits, dest_rank, comm_slot=None, clbits=None, tag=None)[source]¶
- Parameters:
- Raises:
ValueError – If qubits is empty or dest_rank is negative.
- Return type:
None
- class netqmpi.sdk.operations.qmpi.QRecv[source]¶
Bases:
OperationReceive qubits from a remote rank into local qubit slots.
- Variables:
- __init__(qubits, src_rank, comm_slot=None, clbits=None, tag=None)[source]¶
- Parameters:
qubits (List[int]) – Local qubit indices to receive into.
len(qubits)determines how many qubits are expected.src_rank (int) – Rank of the sending process.
comm_slot (int | None) – Communication-qubit slot reserved for the transfer.
clbits (List[int] | None) – Protocol classical bits reserved for the transfer.
tag (str | None) – Identifier shared with the matching
QSend.
- Raises:
ValueError – If qubits is empty or src_rank is negative.
- Return type:
None
- class netqmpi.sdk.operations.qmpi.RootedTransfer[source]¶
Bases:
OperationContainerBase class for the rooted collectives built out of teledata.
QScatterandQGathermove qubits between one root and every other rank. Because a quantum state cannot be copied, they can only be built out of transfers that consume the source qubit: each of them expands into oneQSendper qubit leaving this rank and oneQRecvper qubit arriving, and the record keeps those children so the backends translate the collective through the very same point-to-point path they already implement.These records are deliberately not
CollectiveOperationinstances. A collective in that sense is one whose backend expansion writes into every participating circuit at once, and so has to wait for all the ranks; here each side is an ordinary point-to-point transfer that the runtime pairs up by tag, so every rank can be translated on its own.- Variables:
- __init__(rank, root, ranks, qubits)[source]¶
- Parameters:
- Raises:
ValueError – If the participant list is empty, if it does not contain both rank and root, or if qubits is empty.
- Return type:
None
- class netqmpi.sdk.operations.qmpi.QScatter[source]¶
Bases:
RootedTransferScatter the qubits held by the root among the other ranks.
MPI_Scatterwith qubits instead of bytes, save for one thing: the root keeps no chunk of its own. Its buffer is split into one chunk per other rank, in rank order, and handing a qubit over means moving it, so the whole buffer is teleported away and the root is left with its qubits back in|0⟩. After the call the data it scattered lives on the receivers alone.- Variables:
- class netqmpi.sdk.operations.qmpi.QGather[source]¶
Bases:
RootedTransferGather the qubits of every rank into the root.
The mirror image of
QScatter, andMPI_Gatherwith qubits instead of bytes: rank r contributes its chunk, and the root ends up holding all of them in rank order. Here too the transfer moves the qubits, so once the call is over the contributors are left with theirs back in|0⟩and only the root holds the data.- Variables:
- class netqmpi.sdk.operations.qmpi.Expose[source]¶
Bases:
CollectiveOperationOpen a telegate window sharing a control qubit across ranks.
The root rank lends the state of one of its data qubits to every other participant, which receives it on a local communication qubit and can then apply locally-controlled gates with it. The backend realises this with a shared GHZ state (cat-entangler); the window is closed by the matching
Unexpose.Every participant records its own
Expose, holding only the resources it contributes: one communication-qubit slot and the protocol classical bits used for the corrections (len(ranks) - 1bits on the root, one bit on each receiver).- Variables:
rank (int) – Rank owning this record.
root (int) – Rank that exposes its data qubit.
ranks (List[int]) – Participants, root first.
data_qubit (int) – Exposed data qubit (root only, else
None).comm_slot (int) – Local communication-qubit slot.
clbits (List[int]) – Local protocol classical bits.
tag (str) – Identifier shared by every participant.
- __init__(rank, root, ranks, tag, comm_slot, clbits, data_qubit=None)[source]¶
- Parameters:
- Raises:
ValueError – If the participant list is inconsistent with root, or if the root does not provide a data qubit.
- Return type:
None
- class netqmpi.sdk.operations.qmpi.Unexpose[source]¶
Bases:
CollectiveOperationClose a telegate window opened by
Expose.Carries the very same resources as the
Exposeit closes, so the backend can emit the cat-disentangler (comm-qubit measurement and the phase correction on the root’s data qubit) without re-deriving them.- Variables:
rank (int) – Rank owning this record.
root (int) – Rank that exposed its data qubit.
ranks (List[int]) – Participants, root first.
data_qubit (int) – Exposed data qubit (root only, else
None).comm_slot (int) – Local communication-qubit slot.
clbits (List[int]) – Local protocol classical bits.
tag (str) – Identifier shared by every participant.
- __init__(rank, root, ranks, tag, comm_slot, clbits, data_qubit=None)[source]¶