From 95eef838b6be7e6099e0226969456d581132ebda Mon Sep 17 00:00:00 2001 From: ciaranra Date: Tue, 25 Aug 2026 02:21:08 -0600 Subject: [PATCH] docs(design): specify event-driven noise and leakage channels --- design/event-driven-noise-model.md | 674 ++++++++++++++ design/leakage-aware-stochastic-channels.md | 936 ++++++++++++++++++++ 2 files changed, 1610 insertions(+) create mode 100644 design/event-driven-noise-model.md create mode 100644 design/leakage-aware-stochastic-channels.md diff --git a/design/event-driven-noise-model.md b/design/event-driven-noise-model.md new file mode 100644 index 000000000..d44343061 --- /dev/null +++ b/design/event-driven-noise-model.md @@ -0,0 +1,674 @@ +# RFC: Event-Driven Noise Modeling + +Status: draft for discussion. + +Scope: event-driven noise placement in `pecos-engines`, compatibility with +`GeneralNoiseModel`, Python bindings, PECOS and Selene runtime transport, and +differential conformance. + +Related channel specification: +[Leakage-Aware Stochastic Channels](leakage-aware-stochastic-channels.md). + +Implementation experience: the experimental `pecos-neo` `ComposableNoiseModel`, +`NoiseEvent`, typed signals, and `GeneralNoiseModelBuilder`. + +Conformance foundation: [PECOS #545](https://github.com/PECOS-packages/PECOS/pull/545). + +## Summary + +This RFC proposes an event-driven execution layer for PECOS noise models. Ideal +gates and other runtime operations become typed event anchors. Users may also +insert generic, serializable trigger anchors into the command stream. Ordered +noise rules attach channels or gate actions to supported phases of selected +events. + +The event-driven model is a new implementation boundary, not an immediate +replacement for the production `GeneralNoiseModel`. A thin compatibility facade +will compile existing general-noise parameters into event rules and delegate all +execution to the compiled event-driven model. The implementation becomes the +production default only after exact seeded compatibility, semantic conformance, +performance, and runtime-integration requirements pass a separate review. + +## Motivation + +The existing general noise model provides a useful collection of physical and +effective error parameters, but placement and execution are encoded in one +specialized implementation. Adding fixed before/after vectors for every channel +family, arity, gate family, and runtime marker would create separate ordering and +composition rules for each new feature. + +An event-driven model separates: + +- what a channel does; +- which runtime occurrence selects it; +- when it runs relative to that occurrence; +- whether an ideal gate executes, is suppressed, or is replaced; and +- how a semantic noise specification is lowered for a runtime and simulator. + +This supports the leakage-aware channels in the related RFC, a compatibility +implementation of general noise, and later extensions such as transport markers, +calibration boundaries, measurement groups, and explicitly timed operations. + +## Goals + +- Treat program and runtime gates as typed event anchors. +- Support generic serializable triggers without representing them as fake gates. +- Provide typed preparation, measurement, reset, idle, batch, and circuit events + required by general-noise semantics. +- Attach heterogeneous ordered noise actions before, at, or after supported + events. +- Resolve execute, suppress, and replace gate behavior explicitly. +- Prevent accidental recursive noise on generated operations. +- Give overlapping selectors a deterministic total order. +- Preserve simultaneous runtime-batch information needed by crosstalk and idle + handling. +- Compile human-readable event names to efficient runtime identifiers. +- Reimplement general noise through a thin compatibility facade rather than + maintaining two long-term definitions of its semantics. +- Validate compatibility using independent oracles and legacy/event-driven + Selene plugins. + +## Non-goals + +- Switching existing users to the new implementation when this RFC is accepted. +- Encoding arbitrary simulator-native Kraus or density-matrix operations. +- Treating transport/runtime source as the generation origin of an operation. +- Carrying arbitrary type-erased Rust values across Python, process, or Selene + plugin boundaries. +- Making every custom Selene operation a PECOS trigger. +- Defining recursive noise on generated gates in the first implementation. + +## Architecture + +The design separates five layers: + +1. `NoiseParameters` describe physical or effective noise intent. +2. `NoiseRule` binds a channel or event action to a selector and phase. +3. `ApproximationPolicies` explicitly authorize semantic transformations. +4. `CompiledEventNoiseModel` contains indexed executable handlers. +5. `NoiseCompilationReport` records preserved, approximated, and unsupported + behavior. + +`GeneralNoiseParameters` are a compatibility-oriented specialization of +`NoiseParameters`. The general-noise facade compiles them using a fixed +compatibility profile. + +Compilation targets execution capabilities, not only the quantum-state +simulator: + +```python +target = NoiseCompilationTarget( + runtime=runtime.capabilities(), + simulator=simulator.capabilities(), + dem=None, +) + +compiled_noise, report = specification.compile_for(target) +``` + +PECOS classically tracks leakage above the computational-state simulator, so a +simulator without native qutrit support does not require a leakage approximation. +Runtime trigger transport, injected-gate support, measurement dependencies, and +DEM lowering are separate capabilities reported by the target. + +## Typed Event Algebra + +Built-in event semantics must be represented with typed payloads. Opaque bytes +are reserved for versioned generic-trigger metadata and must not carry ordinary +gate angles, measurement outcomes, idle durations, or timing. + +The conceptual event envelope is: + +```rust +pub struct EventEnvelope<'a> { + pub key: EventKey, + pub phase: EventPhase, + pub targets: &'a [QubitId], + pub timing: Option, + pub origin: OperationOrigin, + pub payload: EventPayload<'a>, +} + +pub enum EventKey { + Gate(GateId), + Preparation, + Measurement, + Reset, + Idle, + Batch, + Circuit, + Trigger(CompiledTriggerId), +} + +pub enum EventPhase { + Before, + At, + After, +} + +pub enum EventPayload<'a> { + Gate { + gate_type: GateType, + angles: &'a [Angle64], + }, + Preparation, + Measurement { + outcomes: Option<&'a [MeasurementOutcome]>, + }, + Reset, + Idle { + duration: TimeUnits, + }, + Batch { + operations: &'a [OperationDescriptor<'a>], + }, + Circuit { + num_qubits: usize, + }, + Trigger { + stable_id: &'a TriggerId, + metadata_version: u32, + metadata: &'a [u8], + }, +} + +pub enum OperationOrigin { + Program, + Noise, + Replacement, +} +``` + +The exact Rust representation may use specialized borrowed event types rather +than one large enum. The semantic requirements are: + +- phase is explicit; +- built-in payloads are typed; +- target qubits and timing are available without parsing metadata; +- measurement outcomes exist only when the selected phase makes them available; +- idle duration remains a typed time quantity; and +- generation origin is distinct from the runtime transport that delivered a + program operation. + +For example, a gate originating in a Guppy program and delivered through Selene +has `OperationOrigin::Program`. Selene is transport context, not a competing +origin value. + +Each event kind declares its valid phases and payload invariants. The initial +matrix is: + +| Event | Supported phases | Required information | +| --- | --- | --- | +| Gate | before, after | ID, type, targets, angles, timing | +| Preparation | after | prepared targets | +| Measurement | before, after | targets; outcomes after only | +| Reset | after | reset targets | +| Idle | at | targets and duration | +| Batch | before, after | ordered operation descriptors and timing | +| Circuit | before, after | qubit count | +| Trigger | before, after | stable ID, targets, versioned metadata | + +Construction or compilation rejects a rule selecting an unsupported phase. + +## Stable and Compiled Identifiers + +`GateId` and `TriggerId` occupy separate namespaces. A trigger cannot collide +with a physical gate even when their human-readable names are equal. + +Python may construct a trigger from a namespaced string: + +```python +trigger = TriggerId("helios.transport_complete") +``` + +The stable string or an equivalent stable UUID is serialized. A compact +`CompiledTriggerId`, such as a process-local integer, is created while compiling +the noise model and is never used as the persistent identity of the trigger. + +Compiled dispatch tables are keyed by event kind, compiled identifier, phase, +and arity. Event dispatch must not scan every configured channel or repeatedly +compare strings. + +## Rules, Actions, and Total Ordering + +Public construction APIs remain typed, while compiled rules use one heterogeneous +action representation: + +```rust +pub struct NoiseRule { + pub selector: EventSelector, + pub phase: EventPhase, + pub action: NoiseAction, + registration_index: u64, +} + +pub enum NoiseAction { + OneQubitChannel(OneQubitNoiseChannel), + TwoQubitChannel(TwoQubitNoiseChannel), + GateAction(GateAction), + PreparationAction(PreparationAction), + MeasurementAction(MeasurementAction), + ResetAction(ResetAction), + IdleAction(IdleAction), + BatchAction(BatchAction), + CircuitAction(CircuitAction), +} +``` + +Trait-erased executable actions are also acceptable internally. A generic +`NoiseRule` alone is insufficient because it cannot form the required +heterogeneous ordered sequence. + +Every rule receives a monotonically increasing `registration_index` when added +to the specification. For one event phase, all matching broad and specific +selectors are merged and executed in ascending registration order. Selector +specificity does not implicitly change precedence. + +For example, if a two-qubit catch-all rule is registered before an RZZ-specific +rule, it executes first for RZZ even though the second selector is more specific. +Compilation may pre-merge common selector combinations, but it must preserve the +same total order. + +Compatibility-generated rules receive explicit registration positions from the +compatibility profile. User-added compatibility methods append at the documented +locations. Iteration order must not depend on Python dictionary hashing. + +Public gate selectors match `OperationOrigin::Program` by default. The first +implementation does not expose selectors that recursively apply ordinary gate +noise to `Noise` or `Replacement` origins. + +Illustrative Python construction is: + +```python +noise = EventDrivenNoiseModelBuilder() + +noise.on_gate("RZZ").before(incoming_recovery).after(outgoing_leakage) +noise.on_trigger("helios.transport_complete").after(transport_recovery) +``` + +A declarative rule-list API is equally acceptable if it makes ownership and +ordering clearer. + +## Gate Lifecycle and Disposition + +The gate lifecycle is: + +1. Dispatch matching before-gate rules in total registration order. +2. Resolve the gate disposition and any scheduled effects. +3. Execute the original gate, suppress it, or execute its replacement body. +4. Dispatch matching after-gate rules in total registration order. + +The conceptual disposition is: + +```rust +pub enum GateDisposition { + Execute, + Suppress, + Replace(ReplacementBody), +} +``` + +Multiple suppression requests are idempotent. The initial model rejects at +compile time any configuration in which more than one replacement-producing +rule can match the same gate event. It also rejects overlap between a possible +replacement and a separately configured suppression rule unless one composite +action defines their precedence explicitly. This conservative rule can be +relaxed later with a reviewed composition algebra. + +Noise- and replacement-originated gates do not recursively emit program-gate +noise events. Recursive processing is outside the first implementation. This +provenance rule prevents emission replacements and injected Paulis from +accidentally receiving another copy of gate noise. + +After-gate rules run after the resolved body, including when the original body +was suppressed or replaced. They observe state changes from before rules and the +resolved body but cannot retroactively execute a suppressed ideal gate. + +## Generic Trigger Lifecycle + +A generic trigger has no ideal quantum body: + +```text +before-trigger rules -> inert trigger anchor -> after-trigger rules +``` + +Triggers are neither suppressible nor replaceable. They may target zero, one, +two, or more qubits, but a channel attached to a trigger must accept that arity. +Arity compatibility is checked during compilation. + +The trigger is consumed by the event layer and is never forwarded to the +computational-state simulator. + +## Runtime Batches and Simultaneous Operations + +Runtime adapters must preserve batch boundaries rather than immediately flattening +every batch into an unmarked sequential gate stream. The normalized lifecycle is: + +1. Dispatch the before-batch event with the complete ordered operation descriptor + list and timing. +2. Build per-operation anchors in stable source order. +3. Apply per-operation before-phase effects and resolve dispositions in stable + source order using the compatibility profile's documented ordering. +4. Submit the resolved ideal/replacement operation batch to the simulator. +5. Dispatch per-operation after rules in stable source order. +6. Dispatch the after-batch event. + +Batch-level channels handle behavior that depends on the full simultaneous set, +including measurement crosstalk victim selection. Per-operation ordering is a +deterministic sampling convention; it does not assert that disjoint ideal gates +occur at different physical times. + +The first implementation rejects overlapping ideal operations on the same qubit +within one simultaneous batch unless the runtime defines a supported meaning. +Compatibility lowering must reproduce the production model's crosstalk victim +ordering, idle insertion, and RNG consumption for Selene batches. + +An explicit generic trigger occupies its own ordered command position or runtime +batch/barrier in the first cross-runtime format. A trigger mixed into a batch of +simultaneous gates would otherwise have no unambiguous before/after relationship +with its peers. + +## General-Noise Compatibility Facade + +The long-term public relationship is: + +```text +GeneralNoiseParameters + | + v +GeneralNoiseCompatibilityCompiler + | + v +CompiledEventNoiseModel +``` + +The compatibility-facing model is deliberately thin: + +```rust +pub struct GeneralNoiseModel { + parameters: GeneralNoiseParameters, + compiled: CompiledEventNoiseModel, + report: NoiseCompilationReport, +} +``` + +It contains no independent sampling, gate-processing, leakage, or crosstalk +logic. It validates and retains the original parameters, invokes the +compatibility compiler, delegates seeding, reset, message handling, and execution +to the compiled model, and exposes the compilation report and parameters for +inspection. + +During migration, explicit legacy and event-driven build paths remain available: + +```text +parameters.build_legacy() +parameters.build_event_driven() +``` + +The event-driven path compiles the same defaults, validation, scaling, weighted +samplers, composite outer coins, leakage behavior, gate replacement, crosstalk, +idle placement, and noiseless-gate controls. The compatibility compiler must not +split one legacy composite sampling decision into independently sampled event +rules. + +Exact seeded output and RNG-stream parity are requirements for the compatibility +facade. A difference requires an explicitly reviewed compatibility change; it is +not waived merely because sampled distributions are close. Independently +constructed event-driven models promise semantic/distributional correctness but +do not promise the legacy model's random-number consumption order. + +The production `GeneralNoiseModel` remains the default until a separate review +confirms: + +- exact compatibility tests pass; +- independent semantic tests pass; +- performance and allocation targets pass; +- runtime and wrapper support is ready; +- downcast/introspection compatibility is addressed; and +- release and deprecation plans are documented. + +## Compatibility Gate Convenience Methods + +Arity-specific methods remain useful for discovery: + +```python +noise = ( + GeneralNoiseModelBuilder() + .add_p2_transition_channel_before_gate(recovery) + .add_p2_transition_channel_after_gate(joint_recovery) +) +``` + +They compile to rules selecting all gates of the corresponding arity and append +at the compatibility profile's documented location. They do not create a second +hook implementation. Gate-specific and trigger-specific placement uses the +general event rule API. + +## PECOS and Selene Trigger Transport + +PECOS should add a serializable trigger command containing: + +- stable `TriggerId`; +- ordered target qubits; +- metadata schema version; and +- serialized metadata bytes. + +Rust typed signals may remain an in-process extension mechanism, but their +`TypeId` and type-erased payload are not a stable cross-language trigger format. + +Selene already carries tagged custom operations. A prototype may reserve a +coordinated, namespaced custom tag whose versioned payload contains the same +trigger fields. Other custom operations continue to be rejected. The reserved +tag and payload schema must be shared rather than privately chosen by each +plugin. If generic triggers become common, a first-class Selene trigger operation +should replace the provisional custom encoding. + +## Leakage and Simulator Capabilities + +Classically tracked leakage is part of PECOS's execution layer and is available +with every computational-state simulator backend. Strict/default compilation +preserves that model. Replacing leakage with computational-subspace depolarizing +noise requires an explicit approximation policy and compilation-report entry. + +Trigger support, injected gates, outcome-dependent measurement effects, and DEM +coverage are independent capabilities and must not be conflated with native +simulator leakage support. + +## Differential Conformance + +The device-neutral Selene general-noise plugin and conformance suite in +[PECOS #545](https://github.com/PECOS-packages/PECOS/pull/545) provide the +acceptance harness. Two plugin implementations accept the same immutable +`GeneralNoiseParameters` and share runtime-operation parsing and translation: + +```text +GeneralNoiseParameters + +-- legacy GeneralNoiseModel plugin + +-- event-driven general-noise plugin +``` + +Only model construction differs. This isolates noise-model behavior from adapter +differences. + +Conformance has three levels: + +1. Run the independent analytic basis-state and qutrit-reference cases against + both plugins. These prevent a shared PECOS defect from being accepted as + parity. +2. Compare output distributions over multiple seeds for every noise family and + representative combined configurations. +3. Run exact seeded differential traces comparing simulator operations, Boolean + and leakage-valued outcomes, leakage state, suppression, replacement, idle + insertion, crosstalk ordering, and subsequent RNG behavior. + +Level 3 is mandatory for the compatibility facade. Level 2 is the appropriate +equivalence guarantee for independently composed models that do not claim legacy +RNG ordering. + +The PR #545 mutation audit should be repeated against the event-driven plugin. +Additional mutations should cover: + +- broad/specific handler reordering; +- lost targets or timing; +- flattened batch boundaries; +- recursive generated-gate processing; +- trigger forwarding; +- incorrect origin tagging; and +- conflicting gate dispositions. + +Generic triggers have no legacy equivalent and therefore use focused semantic, +serialization, runtime, and performance tests rather than legacy parity tests. + +## Performance Requirements + +- Compile stable names to compact runtime IDs. +- Index handlers by event kind, identifier, phase, and arity. +- Pre-merge common broad and specific selector lists while retaining registration + order. +- Avoid per-event parsing of built-in payloads or trigger identifiers. +- Avoid allocations for no-op events and common one- and two-qubit actions. +- Preserve fast outer-probability checks inside channels. +- Preserve deterministic iteration independent of hash-map implementation. +- Benchmark the event-driven compatibility facade against the production general + noise model on representative QEC workloads before changing defaults. + +## Serialization and Introspection + +Serialization preserves: + +- semantic noise parameters; +- approximation policies; +- event selectors and phases; +- rule registration order; +- stable gate and trigger identifiers; +- trigger targets and versioned metadata; and +- the selected compatibility profile version. + +Process-local compiled IDs, cached samplers, and dispatch-table layout are not +serialized as semantic identities. + +The compatibility facade exposes its original parameters and compilation report. +Any existing code that downcasts to the concrete production `GeneralNoiseModel` +must be inventoried before the default implementation changes. + +## Alternatives Considered + +### Continue adding fixed fields and hook vectors + +Rejected as the long-term architecture. Convenience methods may remain, but they +compile into event rules rather than creating bespoke execution paths. + +### Maintain two independent general-noise implementations + +Rejected. Separate implementations are useful during migration, but the +parameter schema and compatibility behavior have one specification. After +qualification, the public facade delegates to the event-driven implementation. + +### Represent triggers as fake gates + +Rejected. Triggers have no ideal unitary body and must not participate in gate-set +validation, decomposition, replacement, or simulator execution. + +### Match event names as strings during shots + +Rejected. Human-readable names are construction and serialization identifiers; +compiled dispatch uses compact typed IDs. + +### Use selector specificity as implicit precedence + +Rejected. A global registration order is easier to compose and does not silently +reorder a later broad rule ahead of an earlier specific rule or vice versa. + +### Require only statistical parity for the compatibility facade + +Rejected. It would silently break seeded reproducibility for existing users. +Distributional parity remains sufficient for models that do not claim legacy +compatibility. + +## Minimum Test Matrix + +### Event validation + +- Supported and unsupported phase/event combinations. +- Gate and trigger namespace separation. +- Stable trigger serialization versus process-local compiled IDs. +- Trigger metadata versions and malformed payload rejection. +- Rule/channel arity compatibility. +- Overlapping replacement-rule rejection. +- Replacement/suppression overlap rejection. + +### Dispatch and lifecycle + +- Broad and specific rules merge by registration order. +- Before rules observe earlier before-rule state changes. +- After rules run after execute, suppress, and replace dispositions. +- Generated operations do not recursively receive program-gate noise. +- Measurement outcomes are available only after measurement. +- Idle handlers receive typed durations. +- Trigger anchors remain inert and are not forwarded. + +### Batch behavior + +- Batch boundaries and timing survive runtime normalization. +- Per-operation anchors use stable source order. +- Batch-level measurement crosstalk sees the complete measured set. +- Overlapping target operations are rejected when unsupported. +- Trigger barriers cannot be ambiguously mixed with simultaneous gates. + +### General-noise compatibility + +- Defaults and validation match the production builder. +- Fixed seeds produce identical operations, outcomes, leakage state, and subsequent + RNG behavior. +- Every scale, weighted sampler, emission/replacement path, seepage path, idle + family, crosstalk mode, and noiseless-gate control is covered. +- Empty/noiseless models retain the production fast path. +- Rust, Python, PECOS runtime, and Selene runtime configurations agree. + +## Implementation Sequence + +1. Finalize typed event, phase, timing, origin, batch, and stable-ID contracts. +2. Implement indexed heterogeneous dispatch with global registration ordering. +3. Implement gate disposition validation and non-recursive generated-operation + provenance. +4. Add a serializable PECOS trigger command and focused runtime tests. +5. Implement the event-driven model behind an explicit non-default build path. +6. Compile `GeneralNoiseParameters` through the compatibility profile without + changing composite sampling decisions. +7. Add the event-driven Selene plugin and run PR #545's independent, + distributional, seeded, and mutation conformance suites. +8. Add the provisional coordinated Selene custom-trigger transport. +9. Add Python bindings, serialization, introspection, and compilation reporting. +10. Benchmark representative QEC workloads and optimize compiled dispatch. +11. Review a production-default change separately after every acceptance criterion + passes. + +## Open Questions + +1. Should the provisional Selene trigger transport use a reserved `Custom` + operation, or should a first-class Selene trigger operation be proposed first? +2. Which versioned metadata format should generic cross-language triggers use? +3. Should event-driven support for circuit and batch selectors be public in the + first release or initially reserved for compatibility channels? +4. Which event and trigger constructs can the DEM builder represent exactly in its + first release? + +## Decision Checklist + +Before implementation begins, reviewers should explicitly agree on: + +- [ ] the typed built-in event algebra and payload invariants; +- [ ] stable versus process-local event identifiers; +- [ ] valid phases for each event kind; +- [ ] one heterogeneous action representation; +- [ ] global registration ordering across overlapping selectors; +- [ ] batch preservation and deterministic per-operation ordering; +- [ ] conservative gate-disposition conflict rejection; +- [ ] generation origin and non-recursive generated operations; +- [ ] generic trigger arity, serialization, and barrier semantics; +- [ ] the thin general-noise compatibility facade; +- [ ] exact seeded/RNG-stream compatibility requirements; +- [ ] the legacy/event-driven Selene comparison strategy; +- [ ] classically tracked leakage as the backend-independent default; +- [ ] performance acceptance criteria; and +- [ ] a separate review before changing the production default. diff --git a/design/leakage-aware-stochastic-channels.md b/design/leakage-aware-stochastic-channels.md new file mode 100644 index 000000000..8cd6db434 --- /dev/null +++ b/design/leakage-aware-stochastic-channels.md @@ -0,0 +1,936 @@ +# RFC: Leakage-Aware Stochastic Channels + +Status: draft for discussion. + +Scope: leakage-aware channel semantics, compatibility convenience methods in `GeneralNoiseModel`, +Python bindings, and downstream wrappers. + +Prototype: [PECOS #518](https://github.com/PECOS-packages/PECOS/pull/518). + +Placement architecture: [Event-Driven Noise Modeling](event-driven-noise-model.md). + +## Summary + +This RFC proposes additive, reusable noise channels for the effective qutrit state space +`{|0>, |1>, |L>}` used by PECOS leakage simulations. It separates two physically different +families: + +1. **Population-transition channels**, described by conditional probabilities + `P(destination | source)` over `0`, `1`, and `L`. +2. **Pauli-plus-leakage channels**, described by an overall application probability and relative + weights over stochastic Pauli and leakage events. + +Both families can be attached in ordered stacks before or after gate and user-defined trigger +events. Gates are automatically observable event anchors; explicit triggers can be inserted into +the command stream without pretending to be physical gates. The proposal is additive: existing +`GeneralNoiseModel` fields, p1/p2 Pauli models, leakage ratios, and gate-replacement labels retain +their current behavior during migration. + +Leakage is part of PECOS's backend-independent effective state model. PECOS classically tracks +whether each qubit is in `|L>` and lifts that behavior above the computational-state simulator, so +native simulator support for a third level is not required. Preserving this classically tracked +leakage semantics is the default. Replacing leakage with computational-subspace noise is an +explicit model approximation, not an implicit backend fallback. + +The RFC intentionally specifies semantics before implementation. PR #518 is an exploratory +prototype, not the proposed compatibility baseline. + +## Motivation + +The existing general noise model handles common Pauli, emission, preparation, measurement, idle, +and crosstalk errors well. More detailed leakage studies need reusable channels that can express: + +- leakage conditioned on a computational population, such as `0 -> L`; +- recovery such as `L -> 0`, `L -> 1`, or recovery to a configurable mixture; +- a 90% recovery attempt before or after every two-qubit gate; +- leakage on one leg of a two-qubit gate while the other leg is an identity wire; +- correlated pair-state transitions such as `0L -> L1` or `LL -> 00`; +- explicitly placed stochastic Pauli-plus-leakage events; and +- several independently sampled channels at the same event phase. + +Trying to approximate recovery by increasing the existing p2 seepage parameter is insufficient. +It does not clearly distinguish incoming leakage from leakage produced at the gate, does not expose +placement, and cannot represent general conditional population transfer. + +## Goals + +- Give physicists a recognizable conditional-transition representation. +- Preserve the familiar QEC convention of an overall error probability plus relative event + weights for Pauli-like channels. +- Make identity behavior implicit instead of requiring entries such as `"I": 0.999`. +- Support one- and two-qubit channels with explicit before/after placement. +- Permit multiple channels per event phase with deterministic insertion ordering. +- Preserve an untouched two-qubit leg without measuring or resolving it. +- Keep Rust and Python semantics aligned. +- Allow a fast outer-probability check for the low error rates typical of QEC simulation. +- Preserve classically tracked leakage on every PECOS simulator backend by default. +- Make every requested approximation explicit and report how the declared model was lowered. +- Fail loudly when a DEM builder or wrapper cannot preserve or explicitly lower the requested + semantics. + +## Non-goals + +- An arbitrary Kraus-operator or density-matrix channel interface. +- Coherence between the computational and leakage sectors. +- General `n`-qutrit correlated transition matrices in the first version. +- Changing existing p1/p2 Pauli models, crosstalk configuration, seepage, or emission behavior. +- Changing existing gate-replacement label syntax. +- Automatically approximating unsupported transition channels as Pauli noise. +- Defining correlated two-qubit Pauli channels beyond the events already representable by Pauli + strings and leakage markers. +- Defining the event-dispatch, trigger-transport, or general-noise migration architecture; those + contracts live in the related event-driven RFC. + +## Effective State Model + +PECOS represents each qubit as either: + +- an active computational qubit in the span of `|0>` and `|1>`, or +- a classically tracked leaked state `|L>`. + +This representation is lifted from the underlying simulator. The simulator continues to evolve +the active computational state, while the PECOS execution layer tracks leakage, suppresses or +modifies operations involving leaked qubits, defines leaked-qubit measurement behavior, and +handles recovery or re-preparation. Consequently, every PECOS simulator backend can support this +effective leakage model without natively representing `|L>`. + +The proposed transition interface treats this as an effective qutrit space for channel +specification. PECOS does not preserve coherent superpositions between `|L>` and the computational +subspace. + +Each proposed channel is a fixed convex mixture of trace-preserving component maps on this +effective space. Including `|L>` makes leakage and recovery trace preserving; the API does not +expose arbitrary Kraus operators even though the components can be described that way +mathematically. + +A transition conditioned on computational population can require a hidden Z-basis measurement. +For example, a channel with separate `0` and `1` rows is a population channel and can dephase a +computational superposition. The hidden outcome is consumed by the noise model and is never +returned as a program measurement. + +A channel containing only an `L` row does not inspect or measure computational states. PECOS knows +whether a qubit is leaked from its classical leakage tracker. + +## Semantic Specification, Lowering, and Approximation + +Noise configuration should describe the physical or effective model the user intends to study, +not whichever primitive operations happen to be convenient for one simulator. The architecture +should distinguish: + +1. `NoiseParameters`, which contain backend-independent channel parameters and physical intent; +2. `ApproximationPolicies`, which explicitly authorize particular semantic transformations; +3. gate placement and ordered composition in a noise-model specification; +4. a compiled noise model containing the operations executed by PECOS and its selected backend; + and +5. a compilation report identifying preserved, approximated, and unsupported features. + +The default approximation policy is strict in the sense that it authorizes no transformations. +It does **not** reject leakage on a backend without native qutrit support: classically tracked +leakage is already PECOS's standard execution semantics and is preserved by default. + +An illustrative Python shape is: + +```python +parameters = NoiseParameters( + # Population-transition and Pauli-plus-leakage channel parameters. +) + +policies = ApproximationPolicies.strict() + +spec = NoiseModelSpec( + parameters=parameters, + approximation_policies=policies, +) + +target = NoiseCompilationTarget( + runtime=runtime.capabilities(), + simulator=simulator.capabilities(), +) + +compiled_noise, report = spec.compile_for(target) +``` + +The related event-driven RFC defines `NoiseCompilationTarget` and the runtime, simulator, and DEM +capabilities represented by `target`. + +An opt-in policy may replace some or all leakage-creation events with a completely depolarizing +channel on the computational subspace: + +```python +policies = ApproximationPolicies.strict().replace_leakage_with_completely_depolarizing( + replacement_probability=1.0, +) +``` + +The final API name is subject to review, but `replacement_probability` must mean the probability +of replacing a selected leakage-creation event. It must not be ambiguously described as both a +leakage-retention probability and a replacement probability. Intermediate values must remain +numeric rather than being coerced to booleans. + +Replacing an `x -> L` creation event is distinct from specifying what happens when an +already-leaked qubit participates in a later gate. For example, skipping that gate and +depolarizing its non-leaked partner is an interaction rule in the classically tracked leakage +model; it is not the same transformation as preventing the original leakage. Policy names, +serialization, and reports must keep these operations separate. + +The compilation report should record at least the policy used, the affected channel or parameter, +the scope of the transformation, and its numerical probability. No backend, DEM builder, or +wrapper may silently replace leakage with depolarizing noise. + +This separation also provides the intended direction for other noise families. For example, a +coherent idle specification should describe its Hamiltonian or rotation rates in +`NoiseParameters`; Pauli twirling belongs in an explicit approximation policy rather than in a +second physical parameter named after the approximation. The complete coherent-idle API is +outside the scope of this RFC. + +## Placement Architecture + +The channels in this RFC attach through the event and rule system specified by +[Event-Driven Noise Modeling](event-driven-noise-model.md). That RFC owns typed events, phases, +trigger transport, handler ordering, batch semantics, gate disposition, operation origin, the +general-noise compatibility facade, and Selene differential conformance. + +This RFC owns the mathematical and operational semantics of transition and Pauli-plus-leakage +channels. Compatibility methods such as `add_p2_transition_channel_after_gate` compile to event +rules; they do not create a separate hook execution path. + +## Common Outer-Probability Convention + +Every proposed channel has an outer application probability `p`, with `0 <= p <= 1`. For a +component channel `C`, the effective channel is + +```text +C_effective = (1 - p) Identity + p C. +``` + +PECOS should sample this outer coin before doing state resolution or more expensive conditional +sampling. When the coin fails, the channel is exact identity and must not perform hidden +measurements. + +The meaning of the inner configuration differs between the two channel families and must not be +conflated: + +- Transition rows are independently normalized conditional distributions `P(y | x)`. +- Pauli-plus-leakage event weights form one normalized distribution conditioned on the outer coin + succeeding. + +## Population-Transition Channels + +### One-qubit definition + +A `TransitionChannel` contains: + +```text +TransitionChannel( + probability: float, + transitions: dict[str, dict[str, float]], +) +``` + +The nested dictionary means + +```text +transitions[source][destination] = P(destination | source, outer coin succeeded). +``` + +For example: + +```python +population_transfer = TransitionChannel( + probability=0.01, + transitions={ + "0": {"0": 0.05, "1": 0.05, "L": 0.90}, + "1": {"0": 0.10, "1": 0.80, "L": 0.10}, + "L": {"0": 0.45, "1": 0.45, "L": 0.10}, + }, +) +``` + +Each supplied source row must be nonempty, nonnegative, finite, and sum to one within numerical +tolerance. A destination omitted from a supplied row has probability zero. A source row omitted +from the dictionary is exact identity for that source. + +The row weights are probabilities, not arbitrary relative weights. Normalizing each row in the +implementation may be convenient, but accepting substantially unnormalized rows would hide user +errors and is not proposed. + +### Recovery helper + +The common leakage-recovery case should have a named constructor: + +```python +recovery = TransitionChannel.leak_recovery( + probability=0.90, + p_zero=0.50, +) +``` + +This means: + +```text +with probability 0.90: + L -> 0 with probability 0.50 + L -> 1 with probability 0.50 +otherwise: + identity +``` + +It therefore recovers 90% of leaked inputs at each configured site. The equivalent representation +with one always-selected channel is: + +```python +TransitionChannel( + probability=1.0, + transitions={"L": {"0": 0.45, "1": 0.45, "L": 0.10}}, +) +``` + +By contrast, putting the latter row inside a channel with `probability=0.01` gives only +`0.01 * 0.90 = 0.009` total recovery probability per configured site. + +To model a two-qubit gate that independently recovers 90% of the leakage present on either leg +after ordinary gate noise, attach the one-qubit helper directly to the p2 after-gate hook: + +```python +noise = GeneralNoiseModelBuilder().add_p2_transition_channel_after_gate(recovery) +``` + +After-gate placement includes leakage produced by that gate's ordinary noise. Before-gate +placement sees only leakage already present when the gate site begins. + +### Component-map interpretation + +The transition matrix denotes a fixed stochastic, trace-preserving population map on the +effective qutrit space. It is not a stochastic unitary channel: + +- `0 -> 1` and `1 -> 0` are population-conditioned transitions; +- `L -> 0` and `L -> 1` recover a leaked qubit; +- `0 -> L` and `1 -> L` are state-selective leakage; +- omitted rows are identity without state resolution. + +This distinction is why Pauli errors are specified by a separate channel family. + +## Two-Qubit Transition Channels + +`TwoQubitTransitionChannel` represents a channel attached to a two-qubit site. It has two forms: + +1. a concrete joint conditional matrix with one shared outer coin; or +2. a product of one-qubit transition channels, retaining their independent outer coins. + +No public `P2TransitionStep` type is proposed. An implementation may use an internal enum, but the +public abstraction remains a channel. + +### Concrete joint matrix + +A concrete joint map uses all pair states in `{0, 1, L}^2`: + +```python +joint = TwoQubitTransitionChannel( + probability=0.02, + transitions={ + "0L": {"L1": 0.20, "0L": 0.80}, + "LL": {"00": 0.50, "11": 0.50}, + }, +) +``` + +Every supplied pair-state row is independently normalized. Missing pair-state rows are identity. +The one outer coin is shared by the entire joint map. + +Concrete rows can require resolving either computational input. For example, selecting between a +`0L` row and a `1L` row requires a Z-basis population measurement of the first leg. + +### Identity-wire form + +When a two-qubit channel acts on only one leg, `*` denotes an identity wire: + +```python +recover_either_leg = TwoQubitTransitionChannel( + probability=1.0, + transitions={ + "*L": {"*0": 0.45, "*1": 0.45, "*L": 0.10}, + "L*": {"0*": 0.45, "1*": 0.45, "L*": 0.10}, + }, +) +``` + +The `*` contract is stronger than ordinary wildcard matching: + +- In a source, it accepts any state on that leg without resolving the computational state. +- In a destination, it carries the same subsystem through with the identity operation. +- It must occur in the same position in the source and every destination of that row. +- The identity leg must not acquire a hidden measurement dependency. +- It does not promise that an entangled partner is unaffected by a physical measurement performed + on the acted-on leg. + +Thus `"*L" -> "*0"` examines only the second leg's classical leakage flag, recovers the second leg +to zero, and performs no operation or measurement on the first leg. + +Rules for `*x` and `x*` may coexist in the same channel. If both match, they act on their respective +legs. For an `LL` input in the example above, both recovery rows are sampled independently after +the channel's one shared outer coin succeeds. + +For the first version, a dictionary is either entirely concrete or entirely in identity-wire +form. Mixing concrete pair rows with identity-wire rows in one dictionary is rejected because +overlap and precedence would otherwise be ambiguous. Users can attach a second ordered channel +when both behaviors are required. + +`**` is not proposed. Each identity-wire row must act on exactly one leg. + +### Product of one-qubit channels + +Two single-qubit channels can form an independent-leg two-qubit channel: + +```python +first = TransitionChannel.leak_recovery(0.90, p_zero=0.75) +second = TransitionChannel.leak_recovery(0.80, p_zero=0.25) +independent_pair = first * second +``` + +The product has two independent outer coins, one for each leg. This differs from constructing a +joint conditional dictionary and then supplying one outer probability. + +Named forms should also be available for clarity: + +```python +independent_pair = TwoQubitTransitionChannel.independent(first, second) +same_on_both = TwoQubitTransitionChannel.same_on_each(recovery) +``` + +### Transition-dictionary algebra + +`TransitionDict` validates transition matrices independently from channel application +probabilities. It supports: + +- `left * right` or `left.tensor(right)` for a Kronecker product; +- `after @ before` or `after.compose(before)` for sequential matrix composition; and +- `first.then(second)` as a readable application-order alias. + +Multiplying dictionaries and multiplying channels intentionally have different outer-coin +semantics: + +- `TransitionDict * TransitionDict`, wrapped in `TwoQubitTransitionChannel(p, result)`, has one + shared outer coin `p`. +- `TransitionChannel(p1, ...) * TransitionChannel(p2, ...)` retains two independent outer coins. + +The identity-wire form is a compact representation of a factorized dictionary and must retain +identity metadata through compilation. It must not be naively expanded in a way that introduces +computational-state measurements on identity legs. + +## Pauli-Plus-Leakage Channels + +Population transitions and stochastic Pauli operations should not share one event dictionary. +Paulis implement `rho -> P rho P` in the computational subspace and do not require the +population-measurement semantics of conditional transition rows. + +### One-qubit definition + +```python +faults = PauliLeakageChannel( + probability=0.001, + events={ + "X": 0.40, + "Y": 0.20, + "Z": 0.30, + "L": 0.10, + }, +) +``` + +Here the event values are nonnegative relative weights. They need not sum to one and are +normalized as a single distribution conditioned on the outer coin succeeding: + +```text +P(event i) = p * weight_i / sum(weights). +``` + +`L` means `any -> L`. A Pauli selected on an already leaked qubit is a no-op under the existing +classical-leakage model. + +Identity is represented by failure of the outer coin. A one-qubit `I` event and an all-identity +multi-qubit event are rejected, avoiding confusing configurations such as `{"I": 0.999, ...}`. + +### Two-qubit definition + +A joint two-qubit event distribution uses strings over `{I, X, Y, Z, L}`: + +```python +joint_faults = TwoQubitPauliLeakageChannel( + probability=0.002, + events={ + "IX": 0.30, + "XL": 0.20, + "LL": 0.05, + "ZZ": 0.45, + }, +) +``` + +As with transition channels, multiplying two one-qubit channels retains independent outer coins, +while constructing a joint two-qubit channel uses one shared outer coin. + +`PauliLeakageDict` may provide validated mapping and tensor-product ergonomics parallel to +`TransitionDict`, while retaining its own relative-weight normalization rules. + +The final names should parallel the transition family. No public "step" type is needed. + +## Compatibility Gate Methods and Ordered Composition + +For compatibility and discoverability, the general noise builder initially exposes four ordered +views of event rules: + +- before single-qubit gate sites; +- after single-qubit gate sites; +- before two-qubit gate sites; and +- after two-qubit gate sites. + +Each view contains typed channel variants in exact insertion order. Transition and +Pauli-plus-leakage channels must share the same underlying event-phase sequence so users can +control cross-family ordering. Separate storage that always runs one family before another is not +proposed. + +Typed builder methods append to the common stack: + +```python +noise = ( + GeneralNoiseModelBuilder() + .add_p2_pauli_leakage_channel_before_gate(leak_fault) + .add_p2_transition_channel_before_gate(recovery) + .add_p2_transition_channel_after_gate(joint_recovery) +) +``` + +In this example the before-gate leakage channel runs first and the recovery channel observes its +result. Every channel has its own outer sampling decision unless its definition explicitly shares +one coin across legs. + +Bulk replacement methods may accept lists, but they must preserve list order and replace the whole +heterogeneous event-phase sequence rather than creating family-specific ordering domains. A +generic advanced form can be added if needed: + +```python +builder.with_p2_channels_before_gate([leak_fault, recovery]) +``` + +Typed `add_*` methods remain useful for discoverability and static typing. + +These methods compile to event rules selecting all gates of the corresponding arity. More specific +gate selectors and generic triggers use the event-driven rule API. The convenience methods do not +create a second placement implementation. + +## Placement Semantics + +The related event-driven RFC defines the general-noise compatibility ordering, including composite +p1/p2 sampling, emission replacement, leaked-input suppression, and after-two-qubit idle effects. +For the channels defined here, that lifecycle has these consequences: + +- Before-gate recovery can allow an incoming leaked qubit to participate in the ideal gate. +- Before-gate leakage can suppress the ideal gate under the existing leaked-input policy. +- After-gate recovery can act on incoming leakage or leakage produced by ordinary gate noise. +- After-gate recovery cannot retroactively execute a gate that was already suppressed. +- A later channel observes state changes made by every earlier channel at the same event phase. + +## Proposed Python API + +```python +from pecos import ( + GeneralNoiseModelBuilder, + PauliLeakageChannel, + PauliLeakageDict, + TransitionChannel, + TransitionDict, + TwoQubitPauliLeakageChannel, + TwoQubitTransitionChannel, +) + +recovery = TransitionChannel.leak_recovery(0.90, p_zero=0.50) + +joint_recovery = TwoQubitTransitionChannel( + probability=1.0, + transitions={ + "*L": {"*0": 0.45, "*1": 0.45, "*L": 0.10}, + "L*": {"0*": 0.45, "1*": 0.45, "L*": 0.10}, + }, +) + +noise = ( + GeneralNoiseModelBuilder() + # A one-qubit channel supplied at a p2 hook is sampled independently on each leg. + .add_p2_transition_channel_before_gate(recovery) + # A two-qubit channel is applied according to its joint or independent definition. + .add_p2_transition_channel_after_gate(joint_recovery) +) +``` + +Proposed signatures: + +```python +class TransitionChannel: + def __init__( + self, + probability: float, + transitions: TransitionDict | dict[str, dict[str, float]], + ) -> None: ... + + @staticmethod + def leak_recovery( + probability: float, + p_zero: float = 0.5, + ) -> TransitionChannel: ... + + def __mul__( + self, + other: TransitionChannel, + ) -> TwoQubitTransitionChannel: ... + + +class TwoQubitTransitionChannel: + def __init__( + self, + probability: float, + transitions: TransitionDict | dict[str, dict[str, float]], + ) -> None: ... + + @staticmethod + def independent( + first: TransitionChannel, + second: TransitionChannel, + ) -> TwoQubitTransitionChannel: ... + + @staticmethod + def same_on_each( + channel: TransitionChannel, + ) -> TwoQubitTransitionChannel: ... + + +class GeneralNoiseModelBuilder: + def add_p1_transition_channel_before_gate( + self, + channel: TransitionChannel, + ) -> GeneralNoiseModelBuilder: ... + + def add_p1_transition_channel_after_gate( + self, + channel: TransitionChannel, + ) -> GeneralNoiseModelBuilder: ... + + def add_p2_transition_channel_before_gate( + self, + channel: TransitionChannel | TwoQubitTransitionChannel, + ) -> GeneralNoiseModelBuilder: ... + + def add_p2_transition_channel_after_gate( + self, + channel: TransitionChannel | TwoQubitTransitionChannel, + ) -> GeneralNoiseModelBuilder: ... +``` + +The Pauli-plus-leakage family follows the same arity and product conventions. + +## Proposed Rust API Shape + +Rust should expose the same concepts without exposing Python-driven names or an implementation-only +stack entry type: + +```rust +pub struct TransitionDict { /* validated representation */ } + +pub struct TransitionChannel { /* one-qutrit channel */ } + +pub enum TwoQubitTransitionChannel { + Independent { + first: TransitionChannel, + second: TransitionChannel, + }, + Joint { + probability: f64, + transitions: TransitionDict, + }, +} + +pub enum OneQubitHookChannel { + Transition(TransitionChannel), + PauliLeakage(PauliLeakageChannel), +} + +pub enum TwoQubitHookChannel { + Transition(TwoQubitTransitionChannel), + PauliLeakage(TwoQubitPauliLeakageChannel), +} + +``` + +Exact enum names are open to normal Rust API review. The required property is that the public API +speaks in terms of channels. The event-driven RFC defines the heterogeneous executable action and +rule representation. Arity should be validated before execution rather than discovered through a +failed cast in the hot path. + +`From`, `Into`, and `Mul` implementations may provide concise construction without making a +public `Step` wrapper necessary. + +## Validation Rules + +Construction should fail immediately when: + +- an outer probability is nonfinite or outside `[0, 1]`; +- a transition dictionary is empty; +- a label has the wrong arity or contains an unsupported symbol; +- a supplied conditional row is empty, negative, nonfinite, or not normalized; +- a Pauli-plus-leakage event map is empty or has no positive total weight; +- a one-qubit identity event or all-identity multi-qubit event is supplied; +- an identity-wire row does not contain exactly one `*`; +- the destination moves or removes the `*` identity position; +- concrete and identity-wire rows are mixed in one two-qubit transition dictionary; or +- an operation combines channels with incompatible arity. + +Error messages should identify the channel, source row, destination label, and violated rule. +Python construction errors should be ordinary `ValueError` or `TypeError`, not uncaught Rust panic +exceptions. + +## Scaling + +Scaling must preserve conditional normalization. Therefore scale factors can modify outer channel +probabilities but must not multiply individual transition-row probabilities or relative event +weights. + +Proposed default: + +```text +p_effective = clamp(p * global_scale * site_scale, 0, 1) +``` + +where `site_scale` is the applicable p1 or p2 scale. Conditional rows and relative event weights +remain unchanged. + +Whether the existing `leakage_scale` should additionally modify explicit `L` events is an open +question. This RFC recommends **no implicit leakage scaling for explicitly configured channels**: +their dictionaries already specify the intended leakage fraction. A separately named opt-in scale +could be introduced if experiments need to scan only explicit leakage branches. + +## Leakage Replacement and Legacy Configuration + +PECOS's default is to preserve leakage events using its classical leakage tracker. A +leakage-to-depolarizing setting changes the declared channel: a selected transition into `L` is +instead replaced by a completely depolarizing channel and no leaked state remains for a later +`L -> x` recovery channel to observe. + +Long term, this transformation should be represented by an explicit approximation policy with a +clearly named replacement probability and an entry in the compilation report. During migration, +legacy settings such as `leakage_scale` or `leak2depolar` may remain supported, but their adapters +must translate their documented numeric convention into that policy. In particular, an API using +a leakage-retention probability must convert it to a replacement probability rather than merely +renaming the field. + +Python and Selene wrappers must preserve intermediate numeric values. They must not narrow a +probability to a boolean where only the two endpoints remain representable. Serialization should +use one unambiguous convention, even if compatibility constructors accept legacy aliases. + +An explicit transition channel containing `x -> L` is subject to the configured replacement +policy at its own ordered location. If the event is replaced, later channels see a computational +qubit; if it is preserved, later channels see `L`. This ordering must be visible and tested. + +## Execution and DEM Requirements + +The PECOS execution layer consuming these channels must: + +- provide classically tracked leakage independently of the selected computational-state + simulator; +- preserve ordered channel application; +- make the outer-probability no-op path measurement-free; +- avoid resolving omitted transition rows; +- avoid resolving an identity-wire leg; +- apply population measurements only to legs whose source rows distinguish `0` from `1`; +- preserve deterministic sampling for a fixed seed and configuration; and +- report unsupported operations rather than silently dropping them. + +The underlying computational-state simulator is not required to represent `|L>` or expose native +qutrit operations. The execution layer must prevent an active-state operation from being applied +where the classical leakage state says it should be suppressed or replaced. + +A DEM builder must not silently replace arbitrary transition channels with depolarizing noise. +Exact support may require hidden-measurement branch replay similar to measurement crosstalk. Until +supported, the DEM builder should identify the unsupported channel and fail loudly or report it in +an explicit coverage result. An explicitly requested leakage-replacement approximation may be +used during DEM lowering, but it must be included in that result. + +## Serialization and Wrapper Requirements + +The plain nested dictionary is the canonical human-facing transition representation. Rust may +compile it into a denser or factorized form, but `to_dict()` should preserve the user's validated +definition, including identity-wire labels. + +Wrappers should expose: + +- the same outer probability as the core channel; +- the same conditional-row validation; +- ordered before/after event placement; +- both one- and two-qubit channel types; +- numeric rather than boolean-only leakage-conversion settings; and +- clear rejection for capabilities the wrapper cannot forward. + +The Selene PECOS wrapper should forward channel objects or a stable serialized schema rather than +reinterpreting transition probabilities as legacy seepage fields. Generic event and trigger +transport requirements live in the related event-driven RFC. + +## Relationship to Crosstalk + +PECOS crosstalk models already use labels such as `0 -> L`, `1 -> L`, and population flips. Their +conditional semantics should eventually share the same validated transition representation and +component-map definitions proposed here. Reusing one parser and sampler vocabulary would prevent +the gate-channel and crosstalk paths from assigning different meanings to the same label. + +This RFC does not replace crosstalk placement or victim selection. Crosstalk remains responsible +for deciding which non-gate qubits are affected; a reusable transition channel describes what +happens after a victim is selected. + +## Performance Expectations + +- Sample the outer coin first. +- Precompile labels and conditional samplers when the noise model is built. +- Do not allocate transition dictionaries in the per-gate hot path. +- Preserve sparse omitted-row identity behavior. +- Compile identity-wire rules into factorized leg-local samplers or equivalent metadata; do not + infer the identity leg's computational state. +- Keep deterministic iteration and sampling order independent of Python dictionary hashing. + +## Backward Compatibility + +When no new channels are configured, generated noise operations and RNG consumption must remain +unchanged for a fixed seed. + +This RFC does not reserve `*` globally. Its identity-wire meaning is scoped to transition-state +labels. Existing uses of `*` in other configuration namespaces, including any gate-replacement +labels, remain unchanged. + +The new types and builder methods are additive. Existing p1/p2 Pauli models remain the preferred +interface for ordinary gate-local Pauli noise. + +Compatibility convenience methods such as `add_p2_transition_channel_after_gate` compile into +event rules. Their public behavior does not depend on exposing event-dispatch internals. + +## Alternatives Considered + +### Increase p2 seepage + +Rejected as a general solution. It cannot express placement, input-conditioned output mixtures, +or recovery of leakage generated at a particular point in gate processing. + +### One dictionary containing transitions and Paulis + +Rejected for the initial API. Conditional population rows and stochastic unitary events have +different normalization and coherence semantics. Combining them makes both harder to explain and +validate. + +### One generic `ChannelDict` for every channel family + +Rejected for the initial API. Typed `TransitionDict` and Pauli event dictionaries have different +normalization rules and support different algebra. They can share low-level mapping ergonomics, +but a common untyped container would permit configurations whose meaning depends on which +constructor consumes them. + +### Arbitrary Kraus operators + +Deferred. They are mathematically general but do not map naturally onto PECOS's classically lifted +leakage representation or every DEM builder. The proposed channels cover the immediate QEC use +cases with explicit operational semantics. + +### Expand `*L` into `0L`, `1L`, and `LL` + +Rejected as a semantic implementation strategy. Selecting among those concrete rows can require +resolving the first qubit and would violate the identity-wire contract. + +### Public two-qubit "step" objects + +Rejected. Independent-leg and joint maps are both channels. A step is an internal representation +of one ordered stack entry, not a concept users should need to construct. + +### Fixed ordering between channel families + +Rejected. Separate family-specific vectors create surprising behavior when users call append +methods in a different order. All explicit channels at one event phase should share insertion +ordering. + +## Minimum Test Matrix + +### Validation + +- Valid and invalid outer probabilities. +- Normalized, unnormalized, empty, negative, and nonfinite rows. +- Missing rows remain identity. +- Invalid labels and arity mismatches. +- Identity-wire position mismatch and `**` rejection. +- Concrete/wildcard mixing rejection. + +### Semantics + +- `L -> 0`, `L -> 1`, `0 -> L`, `1 -> L`, and population flips. +- A leakage-only row does not measure a computational input. +- A computationally conditioned row performs only the required hidden measurement. +- `*L` never measures or resolves the first leg. +- `L*` never measures or resolves the second leg. +- `*L` and `L*` both apply to `LL` with the specified shared-coin semantics. +- Concrete joint pair transitions cover all nine source states. +- Independent products retain separate outer coins. + +### Placement and composition + +- Before recovery can enable ideal-gate execution. +- Before leakage can suppress ideal-gate execution. +- After recovery observes leakage produced by ordinary gate noise. +- Multiple channels run in insertion order across channel families. +- Existing after-two-qubit idle noise remains last. + +### Compatibility + +- Empty channel stacks preserve existing bytes and RNG streams. +- Rust and Python configurations produce identical seeded behavior. +- Every simulator backend preserves the same classically tracked leakage semantics. +- Strict/default policy preserves leakage rather than rejecting or replacing it. +- Explicit leakage replacement uses the requested numeric probability and is reported. +- Leakage-creation replacement and leaked-partner interaction remain distinguishable. +- Serialization round-trips identity-wire definitions. +- Unsupported DEM and wrapper paths fail loudly. + +## Implementation Sequence After RFC Approval + +1. Add validated channel value types and pure sampler tests. +2. Define the semantic specification, approximation-policy, and compilation-report boundaries. +3. Integrate the channel families with the executable action and rule interfaces accepted by the + event-driven RFC. +4. Implement transition execution and measurement-dependency tracking. +5. Add Python bindings, type stubs, and compatibility convenience methods. +6. Add Selene wrapper forwarding without narrowing numeric leakage replacement probabilities. +7. Add user documentation, end-to-end seeded tests, and channel performance benchmarks. +8. Add explicit DEM coverage and approximation reporting before attempting branch replay. + +The prototype in PR #518 may supply test cases and implementation ideas, but code should be +reworked against the accepted public model rather than merged by default. + +## Open Questions + +1. Should explicit `L` events ignore `leakage_scale`, as recommended here, or have a separately + named scan multiplier? +2. What public names and serialized form should replace the ambiguous legacy + `leakage_scale`/`leak2depolar` conventions? +3. Should bulk `with_*_channels_*` methods accept a heterogeneous list, or should the first release + provide only ordered `add_*` methods? +4. Should identity-wire syntax remain limited to two-qubit channels in the first release? +5. What numerical tolerance should transition-row normalization use across Rust and Python? +6. Which transition subset can the existing DEM machinery support exactly in its first release? + +## Decision Checklist + +Before implementation resumes, reviewers should explicitly agree on: + +- [ ] the two-family split; +- [ ] outer probability and inner normalization semantics; +- [ ] hidden-measurement behavior for computational rows; +- [ ] the `*` identity-wire contract; +- [ ] overlap semantics for `*x` and `x*`; +- [ ] no public step abstraction; +- [ ] one ordered heterogeneous sequence per selected event phase; +- [ ] classically tracked leakage as the backend-independent default; +- [ ] explicit and reported leakage-replacement policy semantics; +- [ ] scaling and legacy `leakage_scale`/`leak2depolar` migration; +- [ ] additive backward compatibility; and +- [ ] initial simulator, runtime-wrapper, and DEM support boundaries.