Discrete Event Simulator

Quantum network protocols are usually not "apply these gates in order and stop." They wait for messages, reserve resources, react to timeouts, and run in parallel with other control loops. That is why QuantumSavory uses discrete-event simulation for protocol logic.

The two main building blocks are:

  • @resumable from ResumableFunctions.jl, which defines a coroutine-like process that can suspend and resume;
  • @process from ConcurrentSim.jl, which schedules that process on the simulation clock.

Why Discrete Event Simulation Fits LOCC

LOCC protocols are driven by events:

  • a Bell pair becomes available,
  • a classical message arrives,
  • a timeout expires,
  • or a slot lock is released.

Those events do not happen at the same time, and several protocol components may be active at once. Discrete-event execution lets each protocol wait only on the events it cares about.

@resumable Processes

A @resumable function can @yield a condition and continue only when that condition is satisfied.

@resumable function swapper(net, node)
    mb = messagebuffer(net, node)
    msg = @yield querydelete_wait!(mb, :swap_request)

    # local quantum work happens only after the message arrives
    return msg
end

This style is useful because the protocol code reads in the same order as the logical protocol: wait, receive, act.

@process Starts A Running Simulation Process

Defining a resumable function is not enough. To make it participate in the simulation, schedule it with @process.

sim = Simulation()
@process swapper(net, 2)

Once scheduled, the process runs whenever the events it is waiting on become ready.

AbstractProtocol Is The Reusable Protocol Pattern

Plain @resumable functions are supported, but QuantumSavory uses a more structured convention for reusable protocols: a callable struct subtype of AbstractProtocol.

struct MySwapperProt <: AbstractProtocol
    sim::Simulation
    net::RegisterNet
    node::Int
    alice::Int
    charlie::Int
end

@resumable function (prot::MySwapperProt)()
    (; sim, net, node, alice, charlie) = prot
    mb = messagebuffer(net, node)
    @yield querydelete_wait!(mb, :swap_request)

    a = query(net[node], EntanglementCounterpart, alice, ❓)
    b = query(net[node], EntanglementCounterpart, charlie, ❓)

    @yield lock(a.slot) & lock(b.slot)
    # strictly speaking, we should be checking that
    # by the time the locks are acquired
    # alice and charlie still have the properties we have queried for
    # (someone else might have consumed the entanglement in between)
    x, y = LocalEntanglementSwap()(a.slot, b.slot)
    unlock(a.slot)
    unlock(b.slot)
    return x, y
end

This pattern is useful because configuration and runtime context stay packaged with the protocol. It is easier to pass around, store, visualize, and reuse than a large free function with many arguments.

Common Wait Conditions

The most important wait sources are:

  • timeout(sim, delay) for waiting a fixed amount of simulated time;
  • onchange(register) or onchange(register, Tag) for register changes;
  • onchange(messagebuffer) for incoming classical messages;
  • query_wait(...) for wait-until-query-succeeds;
  • querydelete_wait!(...) for wait-until-query-succeeds-and-consume;
  • lock(regref) for resource acquisition.

The query_wait helpers are especially useful because they combine the common "wait for change, query again, repeat" pattern into one call. query_wait on its own does not lock a register or its tags – if the protocol will consume a tag, use querydelete_wait! or re-query after acquiring locks.

Condition Combinators

Wait conditions can be combined directly.

@yield lock(q1) & lock(q2)
@yield onchange(mb) | timeout(sim, 10.0)

& means all conditions must become ready. | means any one of them is enough to resume the process.

This is what makes concurrent control logic concise. A protocol can express "wait until both resources are free" or "wait for a message, but only up to a deadline" without manually writing an event loop.

Locks Are Part Of Protocol Logic

Protocols often compete for the same memory slot or communication qubit. QuantumSavory therefore treats locks as ordinary waitable conditions inside the same event system.

That matters because concurrency control is not a separate concern in a network simulation. It directly changes which quantum resources are available and when.

Where The Metadata Plane Fits

Discrete-event execution and the metadata plane are designed to work together. Protocols usually wait on metadata changes or message-buffer queries, then act on the matching quantum resources.

That is why the protocol code can stay modular: one component publishes a fact, another waits for it, and neither needs an explicit handle to the other's internal state machine.

Where To Go Next