Skip to content

HUGR engine overhaul: work-queue interpreter with full control flow#336

Closed
ciaranra wants to merge 394 commits into
devfrom
integrate-merge
Closed

HUGR engine overhaul: work-queue interpreter with full control flow#336
ciaranra wants to merge 394 commits into
devfrom
integrate-merge

Conversation

@ciaranra

@ciaranra ciaranra commented Jul 6, 2026

Copy link
Copy Markdown
Member

Summary

Rebuilds the HUGR engine (crates/pecos-hugr) into a work-queue interpreter that executes guppy-compiled HUGR programs directly: CFGs (including loop cycles), Conditionals, TailLoops, function Calls, higher-order scans, and flattened DFGs, with measurement round-trips over ByteMessage.

Highlights

  • Typed handler contract: HandlerOutcome/ClassicalOutcome (Processed / Defer / Fault) with fail-loud semantics throughout — starved nodes surface in a completion-time stall report, faults poison execution, and completion runs a reachability audit (executed_containers).
  • Control flow: two-phase container activation (all resets before any readiness check), a single node_settled predicate backing readiness and all completion checks, iterative CFG transitions with cascade-tagged measurement-replay edges (fill-only, purge-on-reactivation), and fill-only post-measurement repair for Call arguments and TailLoop initial inputs.
  • Value model: canonical sign-extended int storage per op width (matches ConstInt::value_s), Euclidean div/mod with unsigned divisors per the HUGR spec, structural DFG-boundary tracing for classical values and qubits with depth caps, and a dedicated conditional-payload map (ends virtual-port aliasing of real output ports).
  • Performance: set-mirrored WorkQueue, Arc<Hugr> (guppy test tree 91s → 34s), O(log δ) xorshift64 RNG jump-ahead via GF(2) matrix powers (bidirectional).
  • Guards: per-command qubit-capacity checks in both shared quantum dispatchers including MZ-batching lookahead; batch scheduling counts builder messages so handler-emitted commands are never dropped.
  • Tests: guppy tree at 484 passed / 0 xfail, including a 14-program semantic sweep, a 20-seed generative fuzzer vs a Python reference, Selene reference parity for divergent classical semantics, and end-to-end pins for previously silent failure modes.

Verification

Eleven adversarial review rounds (blind multi-arm panels: Codex cross-model + fresh subagents, fused by merit), each folded in with gates: cargo fmt, clippy -D warnings (both feature configs), full workspace tests, guppy pytest tree, repo-wide lint. Final battery: 3,918 Python tests + 10,479 workspace Rust tests, zero failures (local cuQuantum excluded — 5 environmental failures reproduce identically on dev; CI is authoritative there).

Notes for reviewers

🤖 Generated with Claude Code

ciaranra added 30 commits June 13, 2026 14:07
…ejections so a sourceless .classical()+neo reports the missing source, not the classical-override rejection
…rement-count tests to the circuit's num_measurements
…red GateDefinitions decompositions (gate on has_decomposition in both dispatch chains) so explicit decompositions are not silently bypassed at Clifford angles
…on/module accuracy caveat (the multi-level estimator biases upward; deep-rare-event estimates are approximate), per Codex review of the remaining-work doc
…ens like the other parsers, and the two algorithm-decoder bindings return PyErr instead of panicking on empty segments or missing boundary-gate fields
…64 in the descriptor bindings (+ debug_assert and obs_bits helper at the apply site), and correct the predecoder zero-alloc docstring
… default to a single unbiased level and require explicit .allow_biased_multilevel() to engage the (biased) multi-level path, fail-fast at build otherwise; mirrored in the Python binding
…imulation: descending-sorted scores must take the survivor threshold at index p0*n (keep top p0), not (1-p0)*n (which kept the bottom and made each level's conditional ~1-p0 instead of ~p0); add a discriminating threshold test plus the previously-missing analytical-unbiasedness guard for the multi-level path
…ors on >64 observables (observable_dem_output_mask returns Result) instead of silently dropping ids >=64, and the per-shot truth-mask helper takes the validated mask
ciaranra added 29 commits July 4, 2026 11:35
… case expansion, TailLoop expand/continue, Call frames) onto one two-phase ContainerActivation mechanism that structurally enforces reset-before-readiness
…ation reset current_block and transitions mid-flight)
…es queue at expansion (CFGs gated on readiness like Calls), body/case completion waits for them, and completed loops/CFGs run their parent-container hooks
…ed container region must be processed or provably inert, catching starvation invisible to queue bookkeeping; Call-frame resets invalidate the previous invocation's records
…ed variants (Euclidean q,r pair; m=0 faults unchecked and error-Sums checked), and itobool/ifrombool, pinned by a builder-driven executor test
… values store canonically sign-extended per width, unsigned reads mask, shifts drop past-width bits, rotates/popcnt/clz/ctz work within N bits, and widen_u zero-extends from the source width
…rame machinery for both CFG and plain-dataflow function bodies, with LoadFunction producing first-class FuncRef values), parse result labels from typed String args instead of Debug scraping, and flip conditional_x to assert clean completion with its captured result array
…driver/engine batch disagreement) instead of silently dropping them
… for pure-classical programs (results key 'return' / 'return_N'), un-xfailing the two return-capture tests
…arity, array comprehensions with literal sizes, measure_array instead of move-out-of-subscript) -- the guppy tree now has zero xfails
…rams checked against a plain-Python reference evaluator via the X-anchor trick
…ns no longer execute or clobber returns), symmetric Call/scan frame ownership with cross wake-ups, unsigned width conversions reinterpret canonical storage at the source width (iwiden_u/inarrow_u/imin_u/imax_u), and error Sums carry the spec's error payload
…oop control resolution gates on body completion, nodes_inside_cases rebuilds on reset, poison surfaces at loop entry, bare DFG containers fault loudly, pending_conditionals is a set, pending_func_calls is a deduped FIFO, dead Tag audit exemption removed
…, RandomInt/RandomIntBounded store canonically at int<5> with unsigned bound reads, RandomAdvance faults on backtracking and unboundedly large deltas, result_uint/result_array_uint bit-reinterpret canonical storage, and unresolved symbolic angles fault instead of defaulting to identity
…s not-ready (one-shot input copiers no longer fire early), case payload offsets derive from the type row, loop-continue clears stale just-input wires, LoadConstant children count in block emptiness/completion with extra pending-branch wake-ups, and empty-block/scan-passthrough chains iterate instead of recursing
…anned functions resolve load_nat), trunc_s/u range-check and canonicalize at the op's width, iu_to_s/is_to_u fault out of range per spec, shadowed divergent arms deleted (float comparisons, convert aliases, nonexistent bit-count ops) with fmax/fmin/fpow confirmed live, prelude.exit faults explicitly, and ConstUsize/ConstError constants parse
…onditional/QFT/Hadamard tests assert real distributions, backend-unavailable skips honestly, Bell/Grover pins exact), the two skip-marker constructs un-skipped (RUS while loop reports via result(), deep circuit passes), interference test pins the correct 50/50 physics, and the Python-vs-HUGR divergent regimes (negative divisors, logical shifts, past-width shifts) pinned with spec-derived expectations
… every deferred op kind, not just bool reads) and document pending_conditionals accurately
…ins, dedup on push) and the loaded Hugr lives behind an Arc so per-round and per-wave handles stop deep-cloning the graph -- guppy tree wall time drops ~60%
…utures resolve to Int (preserving leak values) instead of collapsing to Bool
…near over GF(2), so advancement is a 64x64 bit-matrix power and backtracking uses the 2^64-1 period -- the step ceiling and backtracking fault are gone, pinned by jump-vs-stepping and round-trip tests
…dispatch, naming the op and capacity instead of failing opaquely inside the simulator
…ead now validates each consumed command's qubits, via a shared check_qubit_capacity helper on both dispatchers
…fore block-output propagation (stale-iteration values starve loudly), block/CFG payload offsets derive from type-level arity, unknown transition successors poison instead of spinning past the ceiling, extension Faults skip the completion cascades, synchronously-completed scans no longer re-park as false stalls, NewRNGContext reads the seed's canonical bit pattern, pass-through ops defer on missing inputs, DFG containers flatten explicitly, and impossible scan-launch states fault loudly
…ence (bool-encoded: int result reporting is unlinked locally, and hugr-core's ishr const-folder breaks on constant past-width shifts -- both noted)
…(processed and no active container owns it) backs readiness and all four completion checks, and all_predecessors_ready becomes a method, ending the map-threading at eleven call sites
… qubit tracing: a DFG node's outputs resolve at its Output child, its Input node at the outer wires), closing the documented classical-DFG residual with a nested-DFG pinning test
…th purge-on-reactivation retention and post-replay resolver re-runs, the classical executor resolves inputs through the tracing layer (pinned end-to-end: a nested-DFG op previously deferred forever), qubit tracing crosses DFG boundaries with depth-capped recursion, Call arguments and conditional virtual payloads clear stale state before rewrite, and the fail-loud edges close (Engine::process with pending quantum work, gate modifiers, the gate-emitter catch-all, the empty-queue stall check, port-keyed return capture, global_phase deferral) with capacity-guard dispatcher tests replacing the vacuous allocation test
…ad through the DFG-tracing layer (a gate on a DFG output ran on a phantom qubit), conditional Sum payloads move to a dedicated map ending the real-port aliasing, TailLoop/Call inputs clear stale state and gain fill-only post-measurement repair, block re-activation resets descend flattened DFG interiors, replay edges carry cascade ids so within-cascade chains survive purging and only the latest cascade refills, return capture is positional at collection with signature-derived arity, and batch scheduling counts builder messages so handler-emitted commands are never dropped
@ciaranra ciaranra closed this Jul 6, 2026
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.

1 participant