Skip to content

qnetbench.backends

The reference execution engine, and the replay seam every simulator backend fills.

Narrative guide: Backends and Extending the suite.

The replay seam

A supply backend implements exactly one method, _make_supply(node, peer), returning the delivered-pair stream for an edge.

qnetbench.backends.replay

Shared machinery for entanglement-supply backends.

A supply backend lets an external simulator own the entanglement-generation physics (delivery timing + fidelity) and replays that supply through the verified reference engine, which runs the local quantum ops and classical protocol. Both the SeQUeNCe and NetSquid backends are ~1-method subclasses of ReplayBackend: they only implement _make_supply. This is the "adding a backend is easy" property the suite's adoption goal depends on.

Supply dataclass

Supply(
    inter_arrivals: list[float],
    fidelities: list[float],
    classical_latency: float,
)

A replayable entanglement supply between one pair of nodes.

inter_arrivals[i] is the simulated-seconds gap before delivery i (the first is measured from the reservation/window start); fidelities[i] is that pair's delivered fidelity. classical_latency is the one-way classical delay.

inter_arrivals instance-attribute

inter_arrivals: list[float]

fidelities instance-attribute

fidelities: list[float]

classical_latency instance-attribute

classical_latency: float

ReplayBackend

ReplayBackend(
    topology: Topology,
    seed: int,
    arbitration: str = "native",
    policy: Policy | None = None,
)

Bases: ReferenceBackend

A ReferenceBackend whose entanglement physics is replayed from a pre-generated per-edge Supply. Subclasses implement _make_supply.

Source code in qnetbench/backends/replay.py
def __init__(
    self,
    topology: Topology,
    seed: int,
    arbitration: str = "native",
    policy: Policy | None = None,
) -> None:
    super().__init__(topology, seed=seed, arbitration=arbitration, policy=policy)
    # Pre-generate every edge's supply eagerly, on the caller's (main) thread,
    # before any application role thread is spawned. Some simulators keep
    # process-global state that misbehaves when driven from worker threads.
    self._cursors: dict[frozenset[str], _Cursor] = {}
    for edge in topology.links:
        node, peer = sorted(edge)
        self._cursors[edge] = _Cursor(self._make_supply(node, peer))

The reference backend

qnetbench.backends.reference.backend

The reference backend: a pure-Python, dependency-free implementation of the api. It is the CI oracle and the reference against which the SeQUeNCe and NetSquid backends are checked for equivalence — not a headline deliverable, but the only backend that runs anywhere without registration.

ReferenceBackend

ReferenceBackend(
    topology: Topology,
    seed: int,
    arbitration: str = "native",
    policy: Policy | None = None,
    pair_age: float = 0.0,
    coherence_time: float = inf,
)
Source code in qnetbench/backends/reference/backend.py
def __init__(
    self,
    topology: Topology,
    seed: int,
    arbitration: str = "native",
    policy: Policy | None = None,
    pair_age: float = 0.0,
    coherence_time: float = math.inf,
) -> None:
    self.topology = topology
    self.seed = seed
    self.arbitration = arbitration
    self.policy = policy
    # Staleness model: every delivered pair has been held `pair_age` seconds,
    # decohering its fidelity toward the maximally-mixed 1/4 with time constant
    # `coherence_time`. pair_age=0 / coherence_time=inf means fresh pairs.
    self.pair_age = pair_age
    self.coherence_time = coherence_time
    self.engine = Engine()
    seq = np.random.SeedSequence(seed)
    # Link sampling and quantum measurement must not share a stream. Link
    # sampling draws two values per delivered pair (_sample_pairs), while a
    # backend that replays a pre-generated supply draws none; sharing one
    # generator therefore leaves every subsequent measurement at a different
    # position in the stream, and outcomes stop being comparable across
    # backends at a fixed seed. Separate streams make replay outcome-preserving,
    # which is what Goal G4 asserts.
    phys_seed, register_seed = seq.spawn(2)
    self._phys_rng = np.random.default_rng(phys_seed)
    self.register = Register(np.random.default_rng(register_seed))
    self._channels: dict[tuple[NodeId, NodeId], _ClassicalChannel] = {}
    # Single-qubit transmission rendezvous (qsend blocks until qrecv), one
    # sender/receiver in flight per directed edge — keeps the register small.
    self._qsend_pending: dict[tuple[NodeId, NodeId], tuple[Process, int]] = {}
    self._qrecv_waiting: dict[tuple[NodeId, NodeId], tuple[Process, list[int]]] = {}
    self._waiting: dict[frozenset[str], _Waiter] = {}
    self._req_ids = itertools.count()
    self._events: list[Event] = []
    # Deterministic, independent per-node RNG for application randomness.
    node_seeds = seq.spawn(len(topology.nodes) + 1)[1:]
    self._node_rng = {
        node: np.random.default_rng(s)
        for node, s in zip(topology.nodes, node_seeds, strict=True)
    }
backend_name class-attribute instance-attribute
backend_name: str = BACKEND_NAME
topology instance-attribute
topology = topology
seed instance-attribute
seed = seed
arbitration instance-attribute
arbitration = arbitration
policy instance-attribute
policy = policy
pair_age instance-attribute
pair_age = pair_age
coherence_time instance-attribute
coherence_time = coherence_time
engine instance-attribute
engine = Engine()
register instance-attribute
register = Register(np.random.default_rng(register_seed))
now
now() -> SimTime
Source code in qnetbench/backends/reference/backend.py
def now(self) -> SimTime:
    return self.engine.now
run
run(
    app: Application,
    cfg: dict[str, object],
    roles_to_nodes: dict[Role, NodeId],
) -> list[Event]
Source code in qnetbench/backends/reference/backend.py
def run(
    self, app: Application, cfg: dict[str, object], roles_to_nodes: dict[Role, NodeId]
) -> list[Event]:
    outcomes: dict[Role, AppOutcome] = {}

    def make_runner(role: Role, node: NodeId) -> Callable[[], None]:
        def runner() -> None:
            host = _HostImpl(self, node, self._node_rng[node])
            outcomes[role] = app.run(host, role, cfg)

        return runner

    for role in app.roles():
        node = roles_to_nodes[role]
        proc = self.engine.spawn(make_runner(role, node), name=f"{app.name}:{role}")
        self.engine.schedule(proc, 0.0)

    self.engine.run()

    missing = set(roles_to_nodes) - set(outcomes)
    if missing:
        raise RuntimeError(
            f"application {app.name!r} did not complete for roles {sorted(missing)}: "
            "a role is blocked (deadlocked on an entanglement rendezvous or a "
            "classical recv with no matching peer)."
        )

    header = RunHeader(
        t=0.0,
        api_version=_api_version(),
        app=app.name,
        backend=self.backend_name,
        arbitration=self.arbitration,
        topology=self.topology.name,
        seed=self.seed,
    )
    events: list[Event] = [header]
    for role, outcome in outcomes.items():
        events.append(
            AppOutcomeEvent(
                t=self.now(),
                role=role,
                node=roles_to_nodes[role],
                success=outcome.success,
                utility=outcome.utility,
                payload=outcome.payload,
            )
        )
    events.extend(self._events)
    events.sort(key=lambda e: e.t)
    return events

SeQUeNCe

qnetbench.backends.sequence.backend

The SeQUeNCe backend (hybrid / entanglement-supply model).

SeQUeNCe owns the entanglement-generation physics: for each edge we run a SeQUeNCe reservation and extract the delivered-pair stream (inter-arrival timing + fidelity). The shared ReplayBackend then replays that supply through the verified reference engine. Requires pip install qnetbench[sequence].

SequenceBackend

SequenceBackend(
    topology: Topology,
    seed: int,
    arbitration: str = "native",
    policy: Policy | None = None,
    window_s: float = 1.0,
)

Bases: ReplayBackend

Source code in qnetbench/backends/sequence/backend.py
def __init__(
    self,
    topology: Topology,
    seed: int,
    arbitration: str = "native",
    policy: Policy | None = None,
    window_s: float = 1.0,
) -> None:
    self.window_s = window_s
    super().__init__(topology, seed=seed, arbitration=arbitration, policy=policy)
backend_name class-attribute instance-attribute
backend_name = BACKEND_NAME
window_s instance-attribute
window_s = window_s

NetSquid

qnetbench.backends.netsquid.backend

The NetSquid backend (hybrid / entanglement-supply model).

NetSquid owns the entanglement-generation physics (a QSource elementary link with fibre delay + depolarising noise); the shared ReplayBackend replays that supply through the verified reference engine. Requires pip install qnetbench[netsquid] (NetSquid ships from https://pypi.netsquid.org).

NetSquidBackend

NetSquidBackend(
    topology: Topology,
    seed: int,
    arbitration: str = "native",
    policy: Policy | None = None,
    window_s: float = 1.0,
)

Bases: ReplayBackend

Source code in qnetbench/backends/netsquid/backend.py
def __init__(
    self,
    topology: Topology,
    seed: int,
    arbitration: str = "native",
    policy: Policy | None = None,
    window_s: float = 1.0,
) -> None:
    self.window_s = window_s
    super().__init__(topology, seed=seed, arbitration=arbitration, policy=policy)
backend_name class-attribute instance-attribute
backend_name = BACKEND_NAME
window_s instance-attribute
window_s = window_s