Skip to content

Commit c0700b0

Browse files
Stephanie Wehnerclaude
andcommitted
Switch examples to explicit flush(); add eventBased examples and docs
## Examples: use explicit flush() instead of with-block for multi-round programs All new-sdk examples now open explicitly, call to execute queued operations, read measurement results, then call — rather than wrapping everything in a block. The pattern works for single-round programs but makes it hard for students to extend to more realistic applications. To do classical processing between quantum rounds — measure, read the result, and apply a correction gate based on it — students need to call mid-circuit before the connection closes. The explicit pattern teaches this from the start, and scales naturally to multi-round protocols: sim_conn = NetQASMConnection("Alice", epr_sockets=[epr_socket]) q = Qubit(sim_conn) m = q.measure() sim_conn.flush() # executes all queued operations; m is now readable result = int(m) # classical decision — more quantum ops can follow sim_conn.close() The example demonstrates this end-to-end: it measures a qubit mid-circuit, makes a classical decision, and applies a correction gate based on the result — a pattern that is impossible with the plain block. ## New: event-based examples Four new examples in implement a state-machine pattern for protocols that interleave classical negotiation with quantum operations: - — basic quantum ping-pong between two nodes - — adds a classical handshake before each quantum round - — correlated random number generation via entanglement - — same, with classical verification of correlations **nativeMode/graphState**: added selecting the backend; the default stabilizer backend raises for (used for Rz(−π/2) = S†). Updated accordingly. ## Documentation - reorganised into three levels (new-sdk, event-based, native-mode) with a short description of when to use each, and toctrees for all example families including the new event-based ones - (new): RST pages for all four event-based examples - (new): RST pages for all new-sdk examples - : removed stale warnings, updated config snippets from the old .cfg format to the current JSON format ## Tests - (new): verifies that makes measurement results readable mid-circuit, including multi-round programs with classical branching between flush calls - : added a clarifying comment explaining why merge tests belong in SimulaQron (not NetQASM) and what the tests actually exercise ## Core simulator fixes **flush() / _wait_for_done now works correctly** - gains a field so the client's loop can unblock for the right subroutine instead of spinning forever - Errors are deferred via and raised after exits, so can still run cleanly - and populate from , which sets before each subroutine **Shutdown race condition fixed** - Removed from in ; the decorator was triggering a premature reactor shutdown (causing mid-measurement) while also raising a **ProjectQ backend: numpy serialization** - now wraps values with ; Twisted PB cannot serialize and was silently producing objects on the receiving end, breaking nativeMode/teleport **QuTiP backend: probability clamping** - clamps and renormalises probabilities before to prevent from floating-point rounding after multi-qubit gate sequences (affected nativeMode/graphState) **Network process management** - Fixed typo → in - no longer hangs: then as fallback; removed duplicate call in Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c92e704 commit c0700b0

75 files changed

Lines changed: 3967 additions & 799 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# SimulaQron Development Conventions
2+
3+
## Hard Requirements
4+
5+
### Examples
6+
- Examples MUST always use separate files per node (e.g. `nodeTest-client.py`, `nodeTest-server.py`), never single-file programs. Students must be able to run each node on a separate machine.
7+
- Follow the `new-sdk/template-client-server` structure: two node scripts + `run.sh` + `terminate.sh` + config JSONs.
8+
- All nodes should print their own output (not rely on return values visible only to the test harness).
9+
10+
### Connection Pattern
11+
- Prefer creating a `NetQASMConnection` once and reusing it with `flush()` for multi-round quantum programs, rather than opening/closing with `with` each round.
12+
- `conn.flush()` is the sync point that makes measurement results readable via `int(m)`.
13+
14+
### Backends
15+
- Use `stabilizer` backend by default in examples and tests unless non-Clifford gates are needed.
16+
- `projectq` is deprecated / hard to install. Prefer `qutip` when full state simulation is needed.
17+
18+
## File Conventions
19+
- **Examples:** `examples/new-sdk/` — all new examples use the new SDK
20+
- **Tests:** `tests/slow/sdk/` — SDK-level integration tests
21+
- **Core code:** `simulaqron/` — simulator source
22+
- **Docs:** `docs/` — Sphinx documentation (currently outdated, being rewritten)
23+
24+
## Virtual Environment
25+
- The project venv is at `.venv/` — activate with `source .venv/bin/activate`
26+
- Install with `pip install -e .` (skip `[test]` if projectq build fails)

docs/Examples.rst

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,45 @@
1-
Programming via SimulaQron's native Python Twisted Interface (specific to SimulaQron)
2-
=====================================================================================
1+
SimulaQron Programming Examples
2+
================================
33

4-
One way to program SimulaQron is directly via its 'native interface' using Twisted.
5-
This means writing a client program connecting directly to the local virtual quantum node, and issuing instructions
6-
to such simulated quantum hardware. Programming SimulaQron in its native interface is evidently Python specific, and
7-
meant primarily as an internal interface allowing one to explore higher level abstractions built on top of it.
8-
One such abstraction is the NetQASM interface.For programming in a universal, i.e., not Python specific interface
9-
see :doc:`NetQASM`.
4+
SimulaQron offers three ways to write quantum network programs, from highest-level to lowest-level:
105

11-
The examples below assume that you have already made your way through :doc:`GettingStarted`: you have the virtual
12-
node servers up and running, and ran the simple example of generating correlated randomness. Further examples can
13-
also be found in examples/nativeMode.
6+
1. **New SDK** (``examples/new-sdk/``) — The recommended approach using the NetQASM SDK.
7+
Programs use ``NetQASMConnection`` and ``EPRSocket`` for quantum operations, and
8+
``SimulaQronClassicalClient``/``SimulaQronClassicalServer`` for classical messaging.
9+
Start here if you are new to SimulaQron.
1410

15-
.. warning:: Update the link to the CQC interface.
11+
2. **Event-based** (``examples/eventBased/``) — Builds on the new SDK by adding a state-machine
12+
pattern for classical messaging. Each node defines states, message handlers, and a dispatch
13+
table. This is the recommended pattern for protocols that interleave classical negotiation
14+
with quantum operations.
1615

17-
.. note:: The 'native' mode is not the recommended way to program applications for SimulaQron, instead use the
18-
`NetQASM <https://softwarequtech.github.io/CQC-Python/index.html>`_ interface.
16+
3. **Native mode** (``examples/nativeMode/``) — The low-level Twisted interface that talks
17+
directly to SimulaQron's virtual quantum nodes. This is Python-specific and more verbose,
18+
but gives full control over the simulation backend.
19+
20+
The examples below assume that you have already made your way through :doc:`GettingStarted`:
21+
you have the virtual node servers up and running.
22+
23+
.. toctree::
24+
:maxdepth: 2
25+
:caption: New SDK examples:
26+
27+
new-sdk/Overview
28+
new-sdk/Template
29+
new-sdk/CorrRNG
30+
new-sdk/Teleport
31+
new-sdk/ExtendGHZ
32+
new-sdk/MidCircuitLogic
33+
34+
.. toctree::
35+
:maxdepth: 2
36+
:caption: Event-based examples:
37+
38+
event-based/Overview
39+
event-based/PingPong
40+
event-based/PolitePingPong
41+
event-based/QuantumCorrRNG
42+
event-based/QuantumCorrRNGVerified
1943

2044
.. toctree::
2145
:maxdepth: 2
@@ -25,6 +49,3 @@ also be found in examples/nativeMode.
2549
native-mode/Template
2650
native-mode/Teleport
2751
native-mode/GraphState
28-
29-
30-

docs/event-based/Overview.rst

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
Event-Based Programming Overview
2+
================================
3+
4+
The event-based examples show how to structure quantum network protocols using
5+
a **state machine** pattern. This is the recommended approach for protocols that
6+
interleave classical negotiation with quantum operations.
7+
8+
Prerequisites: you should understand the :doc:`../new-sdk/Overview` first.
9+
10+
The state machine pattern
11+
-------------------------
12+
13+
Each node's behaviour is defined by:
14+
15+
1. **States** — string constants naming each phase of the protocol
16+
2. **Handlers** — async functions that execute when a specific message arrives in
17+
a specific state. Each handler performs an action and returns the next state.
18+
3. **Dispatch table** — a dictionary mapping ``(current_state, message)`` to handler.
19+
This *is* the state machine.
20+
4. **Event loop** — reads messages, looks up the transition, calls the handler.
21+
22+
Example structure::
23+
24+
# States
25+
STATE_WAITING_ACCEPT = "WAITING_ACCEPT"
26+
STATE_DONE = "DONE"
27+
28+
# Handler
29+
async def handle_yes(writer: StreamWriter) -> str:
30+
# ... do something ...
31+
return STATE_DONE
32+
33+
# Dispatch table
34+
DISPATCH = {
35+
(STATE_WAITING_ACCEPT, "yes"): handle_yes,
36+
}
37+
38+
# Event loop
39+
async def run_alice(reader, writer):
40+
state = STATE_WAITING_ACCEPT
41+
while state != STATE_DONE:
42+
data = await reader.read(255)
43+
msg = data.decode("utf-8")
44+
handler = DISPATCH.get((state, msg))
45+
if handler is None:
46+
continue # ignore unknown messages
47+
state = await handler(writer)
48+
49+
Why this pattern?
50+
-----------------
51+
52+
- **Clear protocol structure**: the dispatch table maps directly to a state
53+
transition diagram, making the protocol easy to read and verify.
54+
- **Extensible**: adding a new state or message is just adding a handler and
55+
a dispatch entry — no changes to the event loop.
56+
- **Quantum integration**: handlers can create ``NetQASMConnection``, perform
57+
quantum operations, and ``flush()`` — keeping quantum code isolated in
58+
focused handler functions.
59+
60+
Examples
61+
--------
62+
63+
The examples progress from purely classical to quantum:
64+
65+
.. list-table::
66+
:header-rows: 1
67+
:widths: 30 70
68+
69+
* - Example
70+
- What it teaches
71+
* - :doc:`PingPong`
72+
- Basic event loop with simple if/else message handling
73+
* - :doc:`PolitePingPong`
74+
- Full state machine pattern with dispatch table
75+
* - :doc:`QuantumCorrRNG`
76+
- Adding quantum operations (EPR + measure) to event handlers
77+
* - :doc:`QuantumCorrRNGVerified`
78+
- Multi-state protocol: negotiate, quantum, then classical verification

docs/event-based/PingPong.rst

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
Ping-Pong: Basic Event Loop
2+
===========================
3+
4+
The simplest event-based example. Alice sends a sequence of messages to Bob,
5+
who replies to each one. No state machine — just a simple if/else to choose
6+
the reply. Found in ``examples/eventBased/pingPong/``.
7+
8+
This is purely classical (no quantum operations). It demonstrates the basic
9+
async event loop pattern before adding state machines and quantum in later examples.
10+
11+
Alice (client)
12+
--------------
13+
14+
Alice sends a list of messages and prints Bob's replies::
15+
16+
async def run_alice(reader: StreamReader, writer: StreamWriter):
17+
messages = ["ping", "ping", "hello", "ping"]
18+
19+
for msg in messages:
20+
print(f"Alice: sending '{msg}'")
21+
writer.write(msg.encode("utf-8"))
22+
await writer.drain()
23+
24+
reply_data = await reader.read(255)
25+
reply = reply_data.decode("utf-8")
26+
print(f"Alice: received '{reply}'")
27+
28+
Bob (server)
29+
------------
30+
31+
Bob replies "pong" to "ping" and "no way!" to anything else::
32+
33+
async def run_bob(reader: StreamReader, writer: StreamWriter):
34+
while True:
35+
data = await reader.read(255)
36+
if not data:
37+
break # Alice disconnected
38+
39+
message = data.decode("utf-8")
40+
if message == "ping":
41+
reply = "pong"
42+
else:
43+
reply = "no way!"
44+
45+
writer.write(reply.encode("utf-8"))
46+
await writer.drain()
47+
48+
Key concepts
49+
------------
50+
51+
- **reader/writer**: The async streams for classical TCP communication.
52+
``writer.write()`` sends bytes, ``await reader.read(N)`` receives up to N bytes.
53+
- **await writer.drain()**: Ensures the write buffer is flushed to the network.
54+
- **Connection lifecycle**: Bob's loop exits when ``reader.read()`` returns empty
55+
bytes, meaning Alice has disconnected.
56+
57+
Running
58+
-------
59+
60+
::
61+
62+
cd examples/eventBased/pingPong
63+
sh run.sh
64+
65+
Expected output::
66+
67+
Alice: sending 'ping'
68+
Bob: received 'ping'
69+
Bob: sending 'pong'
70+
Alice: received 'pong'
71+
Alice: sending 'ping'
72+
...
73+
Alice: sending 'hello'
74+
Bob: received 'hello'
75+
Bob: sending 'no way!'
76+
Alice: received 'no way!'
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
Polite Ping-Pong: The State Machine Pattern
2+
============================================
3+
4+
Extends the basic ping-pong into a proper state machine with dispatch tables.
5+
Alice and Bob have a polite exchange: ping, pong, thank you, you're welcome.
6+
Found in ``examples/eventBased/politePingPong/``.
7+
8+
This is still purely classical, but introduces the **state machine pattern**
9+
that all subsequent quantum examples will use.
10+
11+
Alice's state diagram
12+
---------------------
13+
14+
::
15+
16+
(connect)
17+
│ send "ping"
18+
19+
WAITING_PONG
20+
│ recv "pong" → send "thank you"
21+
22+
WAITING_YOURE_WELCOME
23+
│ recv "you're welcome"
24+
25+
DONE
26+
27+
Alice's dispatch table
28+
----------------------
29+
30+
::
31+
32+
ALICE_DISPATCH = {
33+
(STATE_WAITING_PONG, "pong"): handle_pong,
34+
(STATE_WAITING_YOURE_WELCOME, "you're welcome"): handle_youre_welcome,
35+
}
36+
37+
Each handler is a focused function that performs one action and returns the next state::
38+
39+
async def handle_pong(writer: StreamWriter) -> str:
40+
reply = "thank you"
41+
writer.write(reply.encode("utf-8"))
42+
await writer.drain()
43+
return STATE_WAITING_YOURE_WELCOME
44+
45+
The event loop
46+
--------------
47+
48+
The event loop is generic — it works for any protocol by just changing the dispatch
49+
table and initial state::
50+
51+
async def run_alice(reader: StreamReader, writer: StreamWriter):
52+
# Initial action (before the loop)
53+
writer.write(b"ping")
54+
await writer.drain()
55+
56+
state = STATE_WAITING_PONG
57+
58+
while state != STATE_DONE:
59+
data = await reader.read(255)
60+
if not data:
61+
break
62+
msg = data.decode("utf-8")
63+
64+
handler = ALICE_DISPATCH.get((state, msg))
65+
if handler is None:
66+
print(f"no transition for '{msg}' — ignoring.")
67+
continue
68+
69+
state = await handler(writer)
70+
71+
Key concepts
72+
------------
73+
74+
- **Dispatch table = state machine**: the ``(state, message) → handler`` mapping
75+
*is* the protocol definition. Every valid transition is listed; anything not
76+
listed is automatically rejected.
77+
- **Handlers are independent**: each handler only knows about its own transition,
78+
making the code modular and easy to extend.
79+
- **The event loop is reusable**: the same loop structure works for every example
80+
that follows.
81+
82+
Bob's side
83+
----------
84+
85+
Bob uses the same pattern with his own states (``WAITING_PING``, ``WAITING_THANKS``,
86+
``DONE``) and dispatch table. See ``politeBob.py`` for the full code.
87+
88+
Running
89+
-------
90+
91+
::
92+
93+
cd examples/eventBased/politePingPong
94+
sh run.sh

0 commit comments

Comments
 (0)