Skip to content

Propagate every run-time failure in pecos-neo as an error instead of printing or panicking - #712

Draft
ciaranra wants to merge 2 commits into
devfrom
neo-error-propagation
Draft

Propagate every run-time failure in pecos-neo as an error instead of printing or panicking#712
ciaranra wants to merge 2 commits into
devfrom
neo-error-propagation

Conversation

@ciaranra

@ciaranra ciaranra commented Sep 4, 2026

Copy link
Copy Markdown
Member

Summary

Makes every run-time failure inside a pecos-neo simulation surface as Err(PecosError) from sim_neo(), SimNeoBuilder::run(), Simulation::run() and the pecos::sim() facade's neo route. Before this change a failing classical engine was printed to stderr and reported as a completed shot, a simulator that could not execute a gate panicked, and the tool scheduler had no way to carry an error at all.

Stacked on #711 (base branch neo-delete-engines-module); retarget to dev once that merges.

What changed

  • CommandSource::{next_commands, reset, shot_results}, ProgramRunner::run_shot and DynProgramRunner::run_shot return Result. ClassicalEngineAdapter returns the wrapped engine's error instead of printing it; QuantumEngineProgramRunner no longer panics on reset, processing or outcome decoding. A continuation requested without the previous batch's outcomes is now an error rather than a silent completion.
  • ExecutionError converts into PecosError::Processing (message preserved) and gains a QubitArity variant for injected gates whose target count does not match their arity.
  • Noise- and event-handler-injected gates that the backend cannot execute return ExecutionError through apply_noise_response and CircuitRunner::apply_noise instead of asserting. Event handlers are user closures with no build-time validation, so this was a reachable run-time panic.
  • System::run, Schedule::{run_stage, run_shots}, Tool::{run, run_shots, run_shots_on} and Simulation::run are fallible; the schedule stops at the first error. Startup-stage engine and backend construction failures propagate the same way, and startup no longer consumes the program source before its fallible build, so a Simulation re-run after a failed start returns the error again instead of panicking.
  • Parallel Monte Carlo returns the error of the lowest-indexed failing worker (collected in index order, independent of completion order). The qubit count is established once per run from a single probe source so all workers agree; workers still build their own sources.
  • Importance sampling, path enumeration and subset simulation only accept static circuits, so a gate they cannot execute is now rejected at build() beside the existing configuration validators (same loud pattern, pinned with should_panic tests). subset_simulation(0) is rejected there too instead of underflowing. Each sampler's gate-support predicate lives in its own module and is pinned by an exhaustive agreement test that runs the real executor for every GateType variant; GateType::ALL is enforced complete at compile time.
  • The pecos::sim() facade propagates with ?; the pecos-rslib-exp bindings map the error to PyRuntimeError. Doc examples that discarded the result now use ? or expect.

Build-time configuration validation that panics inside SimNeoBuilder::build() is unchanged and out of scope. run_parallel_importance uses the separate importance runner, which has no fallible operation on that path.

Tests

exp/pecos-neo/tests/error_propagation_test.rs (nine tests). Each was run against the pre-change tree first and observed to fail for the stated reason:

  • sequential classical continuation error: previously Ok with a printed error, now Err carrying the engine's message
  • sequential non-Clifford rotation on sparse_stab(): previously a panic in ProgramRunner::run_shot, now Err
  • parallel classical continuation error over four workers: previously Ok, now Err
  • startup error on every run: previously a panic on the second run(), now Err twice
  • event-handler-injected non-Clifford gate: previously an assertion in execute_noise_gate, now Err
  • importance sampling, path enumeration and subset simulation with a non-Clifford gate, and subset_simulation(0): previously panics inside the executors, now rejected at build()

Plus three agreement tests (supports_agrees_with_executor_for_every_gate_type) in the sampler modules, each shown to fail when one arm is removed from its predicate.

Verification

cargo fmt --all -- --check
cargo clippy --locked -p pecos-neo --all-targets --all-features -- -D warnings
cargo clippy --locked -p pecos-neo --all-targets -- -D warnings
cargo clippy --locked -p pecos -p pecos-rslib-exp -p benchmarks --all-targets --all-features -- -D warnings
cargo test --locked -p pecos-neo --all-features
cargo test --locked -p pecos --features neo --test neo_routing_test --test neo_emission_test --test neo_v6_example_sweep_test --test neo_equivalence_matrix_test --test neo_hugr_routing_test
cargo check --locked --workspace --all-targets --all-features

All green on a cold build (changed crates cleaned first). The Python wheel cannot be rebuilt in the development environment; cargo clippy -p pecos-rslib-exp is the compile gate for the bindings and the behaviour is exercised by CI.

Base automatically changed from neo-delete-engines-module to dev September 6, 2026 06:09
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