Skip to content

sim() cannot accept a TickCircuit, so a Guppy program cannot be idle-stripped before simulation #444

Description

@ciaranra

The DEM builder and the simulator disagree about idle noise for the same experiment, and the workflow that would reconcile them is not expressible.

The divergence

DetectorErrorModel.builder().with_idle_after_2q(d) strips runtime-emitted idles first. The default is deliberate — python/quantum-pecos/src/pecos/qec/dem.py:311:

# Inserting a uniform idle convention on top of runtime-emitted idles
# would double-count idle noise, so insertion implies stripping first.
strip_traced_idles = idle_after_2q_duration is not None

general_noise().with_idle_after_2q(d) has no such protection, because a noise model decorates a gate stream and cannot remove gates from it. Both of these fire:

  • crates/pecos-engines/src/noise/general.rs:575 — applies idle faults to every real GateType::Idle in the stream.
  • crates/pecos-engines/src/noise/general.rs:1278 — separately inserts idle faults after every two-qubit gate.

So if the Selene runtime plugin emitted its own Idle gates during lowering, the simulator applies idle noise twice where the DEM applies it once. Configuring the same experiment for both surfaces silently gives different physics.

The workflow that would fix it, and where it stops

Three of four hops already work:

from pecos.tracing import trace_program_to_tick_circuit

tc = trace_program_to_tick_circuit(prog, 2)   # Guppy -> TickCircuit via the QIS trace
tc.remove_identity()                          # strip runtime-emitted idles
tc.insert_idle_after_two_qubit_gates(1.0)     # apply the uniform convention once

The fourth does not:

sim(tc)
TypeError: program must be a Qasm, Qis, Hugr, or PhirJson instance

What exists already

exp/pecos-neo can do this in Rust today — impl SimNeoInput for pecos_quantum::TickCircuit at exp/pecos-neo/src/tool/simulation.rs:633, and &TickCircuit at :640. DagCircuit and CommandQueue are likewise accepted. But sim_neo is not exported to Python from pecos, so the capability is unreachable from where users write this.

Proposal

Let sim() accept a TickCircuit (and plausibly DagCircuit), likely via a TickCircuitEngine alongside the existing Qasm/Qis/Hugr/PhirJson inputs. The circuit is what every other input lowers to, so this adds an entry point rather than a concept.

This is preferable to giving the engines noise model a strip-then-insert flag: circuit transformation is a circuit-layer concern, and having a noise model mutate the gate stream is the design that was rejected for exp/pecos-neo in PR #420 (see pecos-docs/design/neo-noise-event-emission.md).

Acceptance

  • sim(tick_circuit) runs, with the same noise-model and backend configuration as other inputs.
  • A test showing that stripping before insertion produces different noise from not stripping — i.e. the double-count is real and the pipeline avoids it.
  • A test showing the DEM and the simulator agree for the same experiment when both strip once and insert once.

Found while documenting with_runtime in PR #420. Not fixed there: that PR does not touch sim()s accepted inputs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpythonPull requests that update python coderustPull requests that update rust codeseverity:highBroken functionality or loud failure in a supported path

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions