Skip to content

Refactor: decouple Run/Protocol from Client, add RandomTraps sampling, detection_rate, and round-count optimisation#22

Draft
jemappellesami wants to merge 41 commits into
qat-inria:mainfrom
jemappellesami:randomtraps
Draft

Refactor: decouple Run/Protocol from Client, add RandomTraps sampling, detection_rate, and round-count optimisation#22
jemappellesami wants to merge 41 commits into
qat-inria:mainfrom
jemappellesami:randomtraps

Conversation

@jemappellesami
Copy link
Copy Markdown
Collaborator

@jemappellesami jemappellesami commented May 27, 2026

Summary

Builds on dummyless3.

Refactors the canvas-sampling procedure to support the RandomTraps verification protocol, in which the client samples a fresh random multi-qubit trap at each test round rather than drawing from a pre-computed set. Decouples Run and VerificationProtocol from Client. Adds detection_rate to the protocol interface and a new util_rounds module for computing optimal round counts from security parameters.


What changed

RandomTraps and the sample_test_run interface

In the RandomTraps protocol ([KKLM+22, §6.3.1](https://arxiv.org/pdf/2206.00631)), the client samples a uniformly random non-empty subset of nodes at each test round and uses it as a single multi-qubit trap. The set of possible test runs is the powerset of V — it cannot be stored or pre-computed, so the prior approach of indexing a fixed list at round time does not work.

VerificationProtocol gains a new abstract method:

def sample_test_run(self, graph, test_runs, rng) -> TestRun
Protocol Behaviour
FK12 samples uniformly from the pre-computed list
Dummyless samples uniformly from the pre-computed list
RandomTraps generates a fresh random non-empty trap from the graph on each call

sample_canvas now routes through self.protocol.sample_test_run(...) instead of directly indexing test_runs. For RandomTraps, create_test_runs returns [].

This also fixes a prior bug where trap size was sampled from [0, n), allowing empty traps.


detection_rate property on VerificationProtocol

All protocols now expose a detection_rate property:

Protocol Rate Notes
FK12 1 / n_colors set after create_test_runs
Dummyless 1/14 hard-coded for brickwork states
RandomTraps 1/2 any non-trivial deviation is detected with probability ½, matching FK12 on bipartite graphs

Note: this is preparatory. A follow-up PR will use detection_rate to compute the threshold automatically on the client side.


Decoupling Run and VerificationProtocol from Client

Run no longer holds a Client reference. The execution method is renamed from delegate to accept, following the visitor pattern:

# Before
run.delegate(backend, noise_model, rng)

# After
run.accept(executor, backend, noise_model, rng)

ComputationRun and TestRun forward to two new methods on Client:

  • Client.execute_computation_run(backend, noise_model, rng)
  • Client.execute_test_run(test_run, backend, noise_model, rng)

The clifford_structure tableau is no longer stored on Client. Instead, get_graph_clifford_structure(graph) (moved from client.py to verifying.py) is called on-demand inside build_stabilizer. This is valid because MBQC verification protocols depend only on the graph entanglement structure, not on client-specific state.

Signatures that changed:

# VerificationProtocol
create_test_runs(client, rng)  →  create_test_runs(graph, rng)

# TestRun
TestRun(client=client, traps=traps)  →  TestRun(graph=graph, nqubits=n, traps=traps)

New module: veriphix/util_rounds.py

Implements analytic bounds and optimizers for round-count design, built on Hoeffding-based security bounds from [SO26](https://arxiv.org/abs/2601.07111) (Appendix B, itself building on [LMKO21](https://arxiv.org/abs/2109.04042)). Those references bound the security error given fixed round counts (d, s, w) and a noise level; util_rounds.py inverts this direction — given a security target ε and protocol parameters, find the round counts that achieve it. This is the practically meaningful direction: in deployment, one fixes the desired security level and asks for the minimum experimental overhead.

The core bound (bounds_corrected_delta) is split between a test side and a computation side, with delta_tilde and lambda_phi as free parameters that trade off between the two.

optimize_with_robustness_constraint
Given security target ε and minimum robustness ρ_min (the noise level the protocol must tolerate), finds the minimum total rounds d + s satisfying both.

maximize_robustness_under_budget
The dual problem: given a fixed round budget d + s and security target ε, finds the maximum tolerated noise level w/s.

Both have _over_lambda variants that jointly optimize over lambda_phi in addition to delta_tilde.


Minor changes

  • FK12 — proper-colouring validation of manual_colouring moved from create_test_runs into __init__, so invalid colourings fail at construction time.
  • MaliciousNoiseModel — removed a duplicate self.nodes assignment and stale commented-out code.
  • .github/workflows/ruff.yml — ruff pinned to 0.15.12 in both lint and format steps.

Why

RandomTraps requires the client to independently re-sample a trap for every test round. The old interface forced pre-computation of a fixed list, which is both semantically incorrect (samples are not independent) and infeasible (the powerset of V cannot be stored). The sample_test_run abstraction resolves this without special-casing RandomTraps anywhere in the canvas loop.

Removing the Client dependency from Run and VerificationProtocol is the structural prerequisite: a TestRun built only from the graph can be constructed on-the-fly by any protocol, without a bound Client at setup time.


Testing

Test What it checks
test_random_general_traps_average_detection_rate Lemma 8 — for any fixed Pauli deviation support, a random trap detects it with empirical rate ≈ ½
test_random_traps_performance End-to-end with depolarising and malicious noise; detection rate over 30 rounds falls in [0.35, 0.65]
test_designed_rounds_noiseless Round counts from optimize_with_robustness_constraint feed correctly into TrappifiedSchemeParameters; all traps pass with an honest server
test_designed_rounds_bqp_circuit BQP circuits under malicious noise at prob = ρ_min / 2 still pass traps and return the correct majority-vote answer

All existing tests updated to the new accept and TestRun(graph=..., nqubits=..., traps=...) APIs.


Risks / Notes

  • RandomTraps.create_test_runs returns [] — this is intentional. Client calls create_test_runs at initialization, so client.test_runs is always correctly populated: a non-empty list for FK12 and Dummyless, and an empty list for RandomTraps (where sampling happens on-the-fly through sample_test_run at canvas time). The only risk is code that calls protocol.create_test_runs directly and expects a non-empty result for RandomTraps.
  • Lazy detection_rate — for FK12 and Dummyless, the rate is set inside create_test_runs. Since Client calls this at initialization, detection_rate is valid by the time it is accessible through a Client instance. Reading it directly on a bare protocol object before create_test_runs has been called returns a stale default, not the graph-specific value.
  • Statistical flakiness — detection-rate tests use 30–100 samples with bounds [0.35, 0.65]. Seeding or increasing sample counts may be needed if CI proves unstable on some seeds.

PR description drafted with [Claude Code](https://claude.ai/code) from the git diff and manual author annotations.

jemappellesami and others added 30 commits April 28, 2026 15:43
…s, noise comment, benchmark dedup, disconnected-graph rank assertion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants