Skip to content

Default HybridEngine to the Rust classical interpreter, with parity fixes and #345 engine fail-fast#347

Merged
ciaranra merged 14 commits into
devfrom
phir-engine-fail-fast
Jul 11, 2026
Merged

Default HybridEngine to the Rust classical interpreter, with parity fixes and #345 engine fail-fast#347
ciaranra merged 14 commits into
devfrom
phir-engine-fail-fast

Conversation

@ciaranra

@ciaranra ciaranra commented Jul 10, 2026

Copy link
Copy Markdown
Member

Fixes #345.

This PR does three things: (1) defaults HybridEngine to the Rust classical interpreter with full boundary parity to the Python one, (2) applies the #345 engine fail-fast/correctness fixes, and (3) revives a tree of orphaned integration tests found along the way.

⚠️ Behavior change: default classical interpreter is now Rust

HybridEngine's default classical interpreter is now the Rust-backed RustPhirClassicalInterpreter, replacing the pure-Python PhirClassicalInterpreter. It is behaviorally equivalent for valid programs and is differentially validated against the Python interpreter (~3,200 fuzzed programs, a QASM-generated-PHIR differential, and exact exception-type parity tests).

Escape hatch: to restore the previous behavior, construct the engine with HybridEngine(cinterp="python").

The Rust interpreter's boundary behavior was brought to parity so the switch is not silent:

  • phir_validate now schema-validates on the Rust path (it was previously inert).
  • Unconvertible program inputs (e.g. PyPHIR) raise a clear TypeError instead of a cryptic AttributeError.
  • Exception types match Python: division by zero → ZeroDivisionError; reading or writing an undefined classical variable → KeyError.
  • The Rust core is now strict about undeclared classical variables (assignment, ffcall return, measurement result) — raising KeyError rather than silently auto-creating them. This matches the previous Python default, so it does not regress existing production behavior.
  • check_ffc fails fast when a program calls a foreign function the supplied object lacks, or when no foreign object is supplied.
  • run_multisim works with to_dict-capable foreign objects (e.g. WasmForeignObject): the Rust interpreter now supports detaching its foreign object (foreign_obj = None) for pickling, as the multiprocessing helper requires.
  • With phir_validate=True (the default), the Rust interpreter needs pecos.typing from quantum-pecos; standalone pecos_rslib installs get a clear ImportError naming the requirement (or set phir_validate=False).
  • PhirJsonEngine measurement writes beyond a register's declared size now fail fast (previously silently masked between declared size and backing width), and conflicting variable redefinitions (same name, different type/size) are rejected.

Rust API note: PecosError gains a RuntimeUndefinedVariable variant — source-breaking for downstream code that exhaustively matches the enum (0.x pre-release).

Known deferred divergences that do not affect the default path (documented for follow-up): custom op_processors that emit block structures, the separate PhirJsonEngine engine's remaining leniency, unreachable Rust-only operators, PyPHIR program objects (clear TypeError; use a dict/JSON or cinterp="python"), and residual exception-type differences on malformed-program paths.

Engine fixes (#345)

All three were pre-existing on dev and confined to secondary/engine/fallback paths (the primary interpreter surface was already sound).

  1. Invalid variable definitions fail fast. PhirJsonEngine::from_json and the exec loop propagate handle_variable_definition errors (? instead of let _ =), so e.g. a signed size == 64 register errors at construction instead of being swallowed and silently recreated as i32. add_quantum_variable is now idempotent (skip-if-exists) like add_classical_variable, so only genuine definition errors propagate (the constructor-pre-define + loop-re-define pattern relied on tolerating the benign re-define).
  2. Out-of-range measurement writes fail fast. store_measurement_result returns Result and propagates add_variable/set_bit errors instead of log-and-continue.
  3. No-return measurement index is absolute across batches. A per-shot measurements_applied counter (reset in reset()) keeps the legacy measurement_N vars and the no-return m fallback correct when a shot's measurements split across batches. The primary explicit-return path is unchanged.

Three Rust regression tests added.

Revived orphaned integration tests

crates/pecos-phir-json/tests/ had ~13 integration test files organised into subdirectories, but with no top-level tests/*.rs harness and no [[test]] entries — so cargo never compiled or ran them (they'd silently rotted; #339 even edited some assuming they ran). Moved them back to top-level so cargo runs them, and reconciled expectations that had drifted against #339's value/result representations (ExprValue; register results are now BitVec, not Data::U32 — same values, new form). Verified every reconciled value against runtime output (no papering-over). One test was reading a signed register via as_u64 (raw bits) instead of as_i64 (signed value) — fixed the test, not the code (as_u64/as_i64 are deliberately distinct views and load-bearing across rendering/export/parity). Also cleaned up the pre-existing clippy lints these never-linted files carried.

Net: ~41 previously-dormant integration tests now run in CI.

Verification

pecos-phir-json: 91 lib + full integration tests pass, clippy (1.97) and fmt clean. Against the rebuilt extension: 203 PHIR/parity Python tests (incl. exact exception-type + undeclared-variable parity), 1036 pecos-rslib (non-performance), and the engine/hybrid/interp surface all pass. The default switch went through a 2-arm deep-review plus two adversarial re-review rounds; findings and deferred items are recorded in the design/review notes.

@ciaranra ciaranra changed the title Fail fast on invalid PHIR definitions and out-of-range measurements (#345) Default HybridEngine to the Rust classical interpreter, with parity fixes and #345 engine fail-fast Jul 10, 2026
@ciaranra ciaranra merged commit afaa608 into dev Jul 11, 2026
85 checks passed
@ciaranra ciaranra deleted the phir-engine-fail-fast branch July 11, 2026 00:21
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.

PHIR classical engine: error-swallowing and fail-fast gaps in secondary execution paths

1 participant