diff --git a/.github/workflows/rust-test.yml b/.github/workflows/rust-test.yml index 3ae56f7a7..6c1537158 100644 --- a/.github/workflows/rust-test.yml +++ b/.github/workflows/rust-test.yml @@ -168,6 +168,7 @@ jobs: echo "Testing $crate..." cd "crates/$crate" && cargo clippy --locked --all-targets -- -D warnings && cd ../.. done + cargo clippy --locked -p pecos-neo --lib -- -D warnings echo "Testing pecos without default features..." cd crates/pecos && cargo clippy --locked --all-targets --no-default-features -- -D warnings && cd ../.. diff --git a/docs/experimental/composable-noise.md b/docs/experimental/composable-noise.md index a328530c4..21d80c0e1 100644 --- a/docs/experimental/composable-noise.md +++ b/docs/experimental/composable-noise.md @@ -1,5 +1,7 @@ # Composable Noise Models (pecos-neo) +Enable the `composite-noise` Cargo feature on `pecos-neo` to use composite noise primitives and patterns. + > This covers the Rust composable noise API in `pecos-neo`. For the Python noise builder API, see [Noise Model Builders](../user-guide/noise-model-builders.md). > > Source: `exp/pecos-neo/src/noise/` diff --git a/exp/pecos-neo/Cargo.toml b/exp/pecos-neo/Cargo.toml index d910dd14a..377e714ae 100644 --- a/exp/pecos-neo/Cargo.toml +++ b/exp/pecos-neo/Cargo.toml @@ -39,7 +39,7 @@ pecos-qasm.workspace = true criterion.workspace = true proptest = "1.4" # Enable our own features for doctests and integration tests -pecos-neo = { path = ".", features = ["qasm"] } +pecos-neo = { path = ".", features = ["qasm", "composite-noise"] } [[bench]] name = "hot_path" @@ -47,6 +47,7 @@ harness = false [features] default = [] +composite-noise = [] qasm = ["dep:pecos-qasm"] [lints] diff --git a/exp/pecos-neo/README.md b/exp/pecos-neo/README.md index 56ebae958..a863eec98 100644 --- a/exp/pecos-neo/README.md +++ b/exp/pecos-neo/README.md @@ -42,3 +42,10 @@ for outcome in &results.outcomes { ## Documentation See the [full documentation](docs/README.md) for examples, guides, and reference material. + +## Cargo features + +Both features are off by default. + +- `qasm`: QASM program support. +- `composite-noise`: Composite noise decision-tree primitives, builders, and patterns. diff --git a/exp/pecos-neo/benches/hot_path.rs b/exp/pecos-neo/benches/hot_path.rs index e44a5f964..d4ed36b50 100644 --- a/exp/pecos-neo/benches/hot_path.rs +++ b/exp/pecos-neo/benches/hot_path.rs @@ -1227,87 +1227,6 @@ fn bench_batch_processor(c: &mut Criterion) { group.finish(); } -/// Compare trait object dispatch vs compiled enum dispatch for composite primitives -fn bench_dispatch_comparison(c: &mut Criterion) { - use pecos_neo::noise::NoiseContext; - use pecos_neo::noise::composite::prelude::*; - use pecos_neo::noise::composite::{CompiledAction, CompiledCondition, CompiledPrimitive}; - - let mut group = c.benchmark_group("dispatch_comparison"); - - // Create equivalent noise trees: trait-object and compiled versions - - // Simple probability + pauli - let trait_simple: Box = Box::new(prob(0.01, pauli())); - let compiled_simple = CompiledPrimitive::prob( - 0.01, - CompiledPrimitive::action(CompiledAction::Pauli(PauliWeights::uniform())), - ); - - // More complex tree with conditions - let trait_complex = seq![skip_if_leaked(), prob(0.01, when_leaked(seep(), pauli())),]; - let compiled_complex = CompiledPrimitive::seq(vec![ - CompiledPrimitive::skip_if(CompiledCondition::Leaked), - CompiledPrimitive::prob( - 0.01, - CompiledPrimitive::when( - CompiledCondition::Leaked, - CompiledPrimitive::action(CompiledAction::Seep(PauliWeights::uniform())), - CompiledPrimitive::action(CompiledAction::Pauli(PauliWeights::uniform())), - ), - ), - ]); - - // Benchmark simple tree - group.bench_function("trait_simple_pauli", |b| { - let mut ctx = NoiseContext::new(); - let mut rng = PecosRng::seed_from_u64(42); - b.iter(|| black_box(trait_simple.apply(QubitId(0), &mut ctx, &mut rng))); - }); - - group.bench_function("compiled_simple_pauli", |b| { - let mut ctx = NoiseContext::new(); - let mut rng = PecosRng::seed_from_u64(42); - b.iter(|| black_box(compiled_simple.apply(QubitId(0), &mut ctx, &mut rng))); - }); - - // Benchmark complex tree - group.bench_function("trait_complex_tree", |b| { - let mut ctx = NoiseContext::new(); - let mut rng = PecosRng::seed_from_u64(42); - b.iter(|| black_box(trait_complex.apply(QubitId(0), &mut ctx, &mut rng))); - }); - - group.bench_function("compiled_complex_tree", |b| { - let mut ctx = NoiseContext::new(); - let mut rng = PecosRng::seed_from_u64(42); - b.iter(|| black_box(compiled_complex.apply(QubitId(0), &mut ctx, &mut rng))); - }); - - // Benchmark many iterations to amortize setup - group.bench_function("trait_1000_iterations", |b| { - let mut ctx = NoiseContext::new(); - let mut rng = PecosRng::seed_from_u64(42); - b.iter(|| { - for _ in 0..1000 { - black_box(trait_complex.apply(QubitId(0), &mut ctx, &mut rng)); - } - }); - }); - - group.bench_function("compiled_1000_iterations", |b| { - let mut ctx = NoiseContext::new(); - let mut rng = PecosRng::seed_from_u64(42); - b.iter(|| { - for _ in 0..1000 { - black_box(compiled_complex.apply(QubitId(0), &mut ctx, &mut rng)); - } - }); - }); - - group.finish(); -} - criterion_group!( benches, bench_noise_emission, @@ -1322,7 +1241,6 @@ criterion_group!( bench_composite_vs_channel_noise, bench_batch_filtering, bench_batch_processor, - bench_dispatch_comparison, ); criterion_main!(benches); diff --git a/exp/pecos-neo/docs/design/noise-composite.md b/exp/pecos-neo/docs/design/noise-composite.md index e156b14f3..ead3cda19 100644 --- a/exp/pecos-neo/docs/design/noise-composite.md +++ b/exp/pecos-neo/docs/design/noise-composite.md @@ -16,7 +16,6 @@ | CompositeChannel integration | Complete | `composite/channel.rs` | | CompositeNoiseModelBuilder | Complete | `composite/builder.rs` | | Geometric sampling optimization | Complete | `composite/batch.rs` | -| Compiled primitives | Complete | `composite/compiled.rs` | | Crosstalk channel | Complete | `composite/channel.rs` | | Two-stage primitives | Complete | `composite/primitive.rs` | @@ -399,13 +398,6 @@ for i in fault_mask.iter_ones() { } ``` -### Compilation - -Primitive trees can compile to optimized code: -- Eliminate virtual dispatch -- Inline small actions -- Optimal branch ordering - ## Visualization Multiple views for different needs: @@ -470,7 +462,7 @@ This design relates to existing pecos-neo infrastructure: | `GeneralNoiseModelBuilder` | A preset that builds primitive trees | Options: -1. **Internal implementation**: Primitives compile to existing channel interface +1. **Internal implementation**: Primitives integrate through the existing channel interface 2. **Alternative API**: New way to build models, coexists with channels 3. **Replacement**: Migrate to primitives, deprecate channels @@ -489,7 +481,7 @@ Recommendation: Start with option 1 (internal), validate the design works. |------|----------| | **Constrained** | Small fixed primitive set | | **Grounded** | Primitives map to physical processes | -| **Fast** | Early exit, RNG batching, compilation | +| **Fast** | Early exit, RNG batching | | **Flexible** | Primitives compose freely | | **Understandable** | Trees visualize as flowcharts | | **Tricky cases** | Outcome primitives + Crosstalk special construct | @@ -562,7 +554,6 @@ crates/pecos-neo/src/noise/ **Deliverables:** - Geometric sampling for batch processing: Complete (`composite/batch.rs`) -- Tree compilation: Complete (`composite/compiled.rs`) - RNG batching: Complete (`GeometricSampler`) - Benchmarks: Available in `benches/hot_path.rs` diff --git a/exp/pecos-neo/docs/dev/noise.md b/exp/pecos-neo/docs/dev/noise.md index 4d50919b2..1126918a5 100644 --- a/exp/pecos-neo/docs/dev/noise.md +++ b/exp/pecos-neo/docs/dev/noise.md @@ -1,5 +1,7 @@ # Noise System Usage Guide +Composite decision-tree APIs require the `composite-noise` Cargo feature on `pecos-neo`. + This guide covers practical usage of the pecos-neo noise modeling system. ## Overview @@ -310,17 +312,6 @@ let channel = CompositeChannel::new("fast_depol", prob(0.001, depolarize())) .with_filter(CompositeEventFilter::SingleQubitGate); ``` -### Compiled Primitives - -Complex primitive trees can be compiled for better performance: - -```rust -use pecos_neo::noise::composite::compiled::CompiledPrimitive; - -let primitive = seq(vec![...]); -let compiled = CompiledPrimitive::compile(&primitive); -``` - ## Debugging Noise Models ### Introspection diff --git a/exp/pecos-neo/docs/user-guides/noise.md b/exp/pecos-neo/docs/user-guides/noise.md index 9f9536d68..7b7081ba1 100644 --- a/exp/pecos-neo/docs/user-guides/noise.md +++ b/exp/pecos-neo/docs/user-guides/noise.md @@ -1,5 +1,7 @@ # Adding Noise +Enable the `composite-noise` Cargo feature on `pecos-neo` to use composite noise primitives and patterns. + ## Quickest Option One-liner on `sim_neo`: diff --git a/exp/pecos-neo/src/noise.rs b/exp/pecos-neo/src/noise.rs index 76d8b9383..8871a6b0b 100644 --- a/exp/pecos-neo/src/noise.rs +++ b/exp/pecos-neo/src/noise.rs @@ -19,6 +19,8 @@ //! - **Composed**: Build decision trees using primitives (prob, when, seq, etc.) //! - **Custom**: Implement your own channels for complete control //! +//! Decision-tree primitives are available with the `composite-noise` Cargo feature. +//! //! # Quick Start //! //! Import everything with the prelude: @@ -32,13 +34,6 @@ //! .with_measurement_error(0.02) //! .build(); //! -//! // Composed: build custom decision trees -//! let composed = NoiseModelBuilder::new() -//! .with_single_qubit_noise(seq![ -//! skip_if_leaked(), -//! prob(0.001, when_leaked(seep(), pauli())), -//! ]) -//! .build(); //! ``` //! //! Mixed approach (combine builder with custom channels): @@ -99,6 +94,7 @@ pub mod builder; pub mod category_channel; pub mod composer; +#[cfg(feature = "composite-noise")] pub mod composite; pub mod context; pub mod correlated; diff --git a/exp/pecos-neo/src/noise/builder.rs b/exp/pecos-neo/src/noise/builder.rs index 488666477..f5c760db7 100644 --- a/exp/pecos-neo/src/noise/builder.rs +++ b/exp/pecos-neo/src/noise/builder.rs @@ -15,6 +15,8 @@ //! This module provides [`NoiseModelBuilder`], the primary way to construct noise models. //! It unifies simple parameter-based configuration with composable channel construction. //! +//! Decision-tree construction requires the `composite-noise` Cargo feature. +//! //! # Philosophy //! //! The noise system is built on a few key concepts: @@ -34,18 +36,6 @@ //! .with_measurement_error(0.02) //! .build(); //! -//! // Composed: build custom decision trees -//! let composed = NoiseModelBuilder::new() -//! .with_single_qubit_noise(seq![ -//! skip_if_leaked(), -//! prob(0.001, when_leaked(seep(), pauli())), -//! ]) -//! .with_two_qubit_noise(seq![ -//! skip_if_leaked(), -//! prob(0.01, two_qubit_pauli()), -//! ]) -//! .build(); -//! //! // Mixed: combine both approaches //! let mixed = NoiseModelBuilder::new() //! .with_depolarizing(0.001, 0.01) // Simple base rates @@ -53,7 +43,9 @@ //! .build(); //! ``` +#[cfg(feature = "composite-noise")] use super::composite::Primitive; +#[cfg(feature = "composite-noise")] use super::composite::channel::{CompositeChannel, CompositeChannelBuilder}; use super::crosstalk::CrosstalkChannel; use super::idle::IdleChannel; @@ -90,19 +82,6 @@ use pecos_core::TimeScale; /// .build(); /// ``` /// -/// ## Custom composed noise -/// -/// ``` -/// use pecos_neo::noise::prelude::*; -/// -/// let model = NoiseModelBuilder::new() -/// .with_single_qubit_noise(seq![ -/// skip_if_leaked(), -/// prob(0.001, pauli()), -/// ]) -/// .build(); -/// ``` -/// /// ## Adding existing channels /// /// ``` @@ -413,7 +392,21 @@ impl NoiseModelBuilder { /// ]) /// .build(); /// ``` + /// + /// ## Custom composed noise + /// + /// ``` + /// use pecos_neo::noise::prelude::*; + /// + /// let model = NoiseModelBuilder::new() + /// .with_single_qubit_noise(seq![ + /// skip_if_leaked(), + /// prob(0.001, pauli()), + /// ]) + /// .build(); + /// ``` #[must_use] + #[cfg(feature = "composite-noise")] pub fn with_single_qubit_noise(mut self, primitive: P) -> Self { let channel = CompositeChannelBuilder::single_qubit("single_qubit", primitive); self.custom_channels.push(Box::new(channel)); @@ -425,6 +418,7 @@ impl NoiseModelBuilder { /// /// This overrides the simple p2-based configuration. #[must_use] + #[cfg(feature = "composite-noise")] pub fn with_two_qubit_noise(mut self, primitive: P) -> Self { let channel = CompositeChannelBuilder::two_qubit("two_qubit", primitive); self.custom_channels.push(Box::new(channel)); @@ -436,6 +430,7 @@ impl NoiseModelBuilder { /// /// This overrides the simple p_meas-based configuration. #[must_use] + #[cfg(feature = "composite-noise")] pub fn with_measurement_noise(mut self, primitive: P) -> Self { let channel = CompositeChannelBuilder::after_measurement("measurement", primitive); self.custom_channels.push(Box::new(channel)); @@ -447,6 +442,7 @@ impl NoiseModelBuilder { /// /// This overrides the simple p_prep-based configuration. #[must_use] + #[cfg(feature = "composite-noise")] pub fn with_preparation_noise(mut self, primitive: P) -> Self { let channel = CompositeChannelBuilder::preparation("preparation", primitive); self.custom_channels.push(Box::new(channel)); @@ -471,6 +467,7 @@ impl NoiseModelBuilder { /// .build(); /// ``` #[must_use] + #[cfg(feature = "composite-noise")] pub fn with_custom_channel( mut self, channel: CompositeChannel

, diff --git a/exp/pecos-neo/src/noise/composite.rs b/exp/pecos-neo/src/noise/composite.rs index 34eacce07..8ea0781ca 100644 --- a/exp/pecos-neo/src/noise/composite.rs +++ b/exp/pecos-neo/src/noise/composite.rs @@ -159,7 +159,6 @@ pub mod batch; pub mod batch_composite; mod builder; pub mod channel; -mod compiled; mod condition; mod primitive; mod response; @@ -176,7 +175,6 @@ pub use channel::{ BatchCompositeChannel, CompositeChannel, CompositeChannelBuilder, CompositeCrosstalkChannel, CompositeEventFilter, }; -pub use compiled::{CompiledAction, CompiledCondition, CompiledPrimitive}; pub use condition::{ Active, Always, AnyQubitLeaked, Condition, FnCondition, GateTypeIs, Leaked, Never, NotLeaked, OutcomeIs, PartnerLeaked, diff --git a/exp/pecos-neo/src/noise/composite/compiled.rs b/exp/pecos-neo/src/noise/composite/compiled.rs deleted file mode 100644 index 7dd47559e..000000000 --- a/exp/pecos-neo/src/noise/composite/compiled.rs +++ /dev/null @@ -1,761 +0,0 @@ -// Copyright 2026 The PECOS Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except -// in compliance with the License. You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software distributed under the License -// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -// or implied. See the License for the specific language governing permissions and limitations under -// the License. - -//! Compiled primitives for fast enum-based dispatch. -//! -//! This module provides an optimized representation of noise primitives that uses -//! enum dispatch instead of trait objects. The [`CompiledPrimitive`] enum covers -//! common primitive patterns with direct match-based dispatch, falling back to -//! Arc-wrapped trait objects only for custom user-defined primitives. -//! -//! # Performance -//! -//! Enum dispatch is typically 2-4x faster than trait object dispatch because: -//! - No vtable lookup -//! - Better branch prediction -//! - Better cache locality (no pointer chasing) -//! -//! # Usage -//! -//! The builder automatically compiles primitives at build time. Users don't need -//! to interact with this module directly. - -use super::Primitive; -use super::action::PauliWeights; -use super::response::CompositeResponse; -use crate::command::{GateCommand, GatePayload, GateType}; -use crate::noise::{ - NoiseContext, NoiseGateRequirement, SingleQubitEmissionWeights, TwoQubitEmissionWeights, - TwoQubitPauliWeights, -}; -use pecos_core::QubitId; -use pecos_random::PecosRng; -use rand::RngExt; -use smallvec::smallvec; -use std::sync::Arc; - -// ============================================================================ -// Compiled Action Enum -// ============================================================================ - -/// Compiled action for fast enum dispatch. -/// -/// Covers common action types with direct implementation, avoiding trait dispatch. -#[derive(Clone)] -pub enum CompiledAction { - /// No action. - Nothing, - /// Skip the current gate. - SkipGate, - /// Mark qubit as leaked. - Leak, - /// Mark qubit as unleaked. - Unleak, - /// Seep: unleak and apply random Pauli. - Seep(PauliWeights), - /// Inject a specific gate. - Inject(GateType), - /// Random Pauli with weights. - Pauli(PauliWeights), - /// Single-qubit emission (Pauli or leak). - Emission(SingleQubitEmissionWeights), - /// Two-qubit correlated Pauli. - TwoQubitPauli(TwoQubitPauliWeights), - /// Two-qubit emission. - TwoQubitEmission(TwoQubitEmissionWeights), - /// Flip measurement outcome. - FlipOutcome, - /// Force measurement outcome to specific value. - ForceOutcome(bool), - /// Mark measurement as leaked. - LeakedMeasurement, - /// Crosstalk with transitions. - CrosstalkTransitions(crate::noise::CrosstalkTransitions), - /// Fallback to Arc-wrapped trait object for custom actions. - Custom(Arc), -} - -impl CompiledAction { - fn gate_requirements(&self) -> smallvec::SmallVec<[NoiseGateRequirement; 2]> { - match self { - Self::Inject(gate_type) => { - super::action::injected_gate_requirement(*gate_type, "CompiledAction::Inject(..)") - } - Self::Custom(primitive) => primitive.gate_requirements(), - _ => smallvec::SmallVec::new(), - } - } - - /// Apply this action. - #[inline] - pub fn apply( - &self, - qubit: QubitId, - ctx: &mut NoiseContext, - rng: &mut PecosRng, - ) -> CompositeResponse { - match self { - Self::Nothing => CompositeResponse::None, - Self::SkipGate => CompositeResponse::SkipGate, - Self::Leak => { - ctx.mark_leaked(qubit); - CompositeResponse::Leak - } - Self::Unleak => { - ctx.mark_unleaked(qubit); - CompositeResponse::Unleak - } - Self::Seep(weights) => apply_seep(qubit, ctx, rng, weights), - Self::Inject(gate_type) => { - let cmd = GateCommand { - gate_type: *gate_type, - qubits: smallvec![qubit], - payload: GatePayload::Angles(smallvec![]), - }; - CompositeResponse::InjectGates(vec![cmd]) - } - Self::Pauli(weights) => apply_pauli(qubit, rng, weights), - Self::Emission(weights) => apply_emission(qubit, ctx, rng, weights), - Self::TwoQubitPauli(weights) => apply_two_qubit_pauli(qubit, ctx, rng, weights), - Self::TwoQubitEmission(weights) => apply_two_qubit_emission(qubit, ctx, rng, weights), - Self::FlipOutcome => CompositeResponse::FlipOutcome, - Self::ForceOutcome(value) => CompositeResponse::ForceOutcome(*value), - Self::LeakedMeasurement => CompositeResponse::LeakedMeasurement, - Self::CrosstalkTransitions(transitions) => { - apply_crosstalk_transitions(qubit, ctx, rng, transitions) - } - Self::Custom(prim) => prim.apply(qubit, ctx, rng), - } - } -} - -// ============================================================================ -// Compiled Condition Enum -// ============================================================================ - -/// Compiled condition for fast enum dispatch. -#[derive(Clone, Copy)] -pub enum CompiledCondition { - /// Qubit is leaked. - Leaked, - /// Qubit is not leaked. - NotLeaked, - /// Qubit is active. - Active, - /// Always true. - Always, - /// Always false. - Never, - /// Outcome equals value. - OutcomeIs(bool), - /// Gate type matches. - GateTypeIs(GateType), -} - -impl CompiledCondition { - /// Evaluate this condition. - #[inline] - #[must_use] - pub fn evaluate(&self, qubit: QubitId, ctx: &NoiseContext) -> bool { - match self { - Self::Leaked => ctx.is_leaked(qubit), - Self::NotLeaked => !ctx.is_leaked(qubit), - Self::Active => ctx.is_active(qubit), - Self::Always => true, - Self::Never => false, - Self::OutcomeIs(expected) => ctx.current_outcome() == Some(*expected), - Self::GateTypeIs(expected) => ctx - .current_gate() - .is_some_and(|info| info.gate_type == *expected), - } - } -} - -// ============================================================================ -// Compiled Primitive Enum -// ============================================================================ - -/// Compiled primitive for fast enum dispatch. -/// -/// This enum covers common primitive patterns with direct match-based dispatch. -/// Complex or custom primitives fall back to Arc-wrapped trait objects. -#[derive(Clone)] -pub enum CompiledPrimitive { - /// Terminal action. - Action(CompiledAction), - /// Probability gate: with probability p, execute inner. - Prob { - probability: f64, - inner: Box, - }, - /// Conditional: if condition, then else. - When { - condition: CompiledCondition, - then_branch: Box, - else_branch: Box, - }, - /// Weighted sample from branches. - Sample { - branches: Vec<(f64, CompiledPrimitive)>, - cumulative_weights: Vec, - }, - /// Sequential execution. - Seq(Vec), - /// Skip if condition is true. - SkipIf(CompiledCondition), - /// Fallback to Arc-wrapped trait object. - Custom(Arc), -} - -impl CompiledPrimitive { - fn gate_requirements(&self) -> smallvec::SmallVec<[NoiseGateRequirement; 2]> { - match self { - Self::Action(action) => action.gate_requirements(), - Self::Prob { inner, .. } => inner.gate_requirements(), - Self::When { - then_branch, - else_branch, - .. - } => { - let mut requirements = then_branch.gate_requirements(); - requirements.extend(else_branch.gate_requirements()); - requirements - } - Self::Sample { branches, .. } => branches - .iter() - .flat_map(|(_, primitive)| primitive.gate_requirements()) - .collect(), - Self::Seq(primitives) => primitives - .iter() - .flat_map(CompiledPrimitive::gate_requirements) - .collect(), - Self::Custom(primitive) => primitive.gate_requirements(), - Self::SkipIf(_) => smallvec::SmallVec::new(), - } - } - - /// Apply this primitive. - #[allow(clippy::missing_panics_doc)] // internal invariant: Sample always has branches - #[inline] - pub fn apply( - &self, - qubit: QubitId, - ctx: &mut NoiseContext, - rng: &mut PecosRng, - ) -> CompositeResponse { - match self { - Self::Action(action) => action.apply(qubit, ctx, rng), - Self::Prob { probability, inner } => { - if rng.random::() < *probability { - inner.apply(qubit, ctx, rng) - } else { - CompositeResponse::None - } - } - Self::When { - condition, - then_branch, - else_branch, - } => { - if condition.evaluate(qubit, ctx) { - then_branch.apply(qubit, ctx, rng) - } else { - else_branch.apply(qubit, ctx, rng) - } - } - Self::Sample { - branches, - cumulative_weights, - } => { - if branches.is_empty() { - return CompositeResponse::None; - } - let r: f64 = rng.random(); - for (i, &threshold) in cumulative_weights.iter().enumerate() { - if r < threshold { - return branches[i].1.apply(qubit, ctx, rng); - } - } - branches - .last() - .expect("CompiledPrimitive::Sample must have at least one branch") - .1 - .apply(qubit, ctx, rng) - } - Self::Seq(primitives) => { - let mut combined = CompositeResponse::None; - for prim in primitives { - let response = prim.apply(qubit, ctx, rng); - if response.skips_gate() { - return combined.combine(response); - } - combined = combined.combine(response); - } - combined - } - Self::SkipIf(condition) => { - if condition.evaluate(qubit, ctx) { - CompositeResponse::SkipGate - } else { - CompositeResponse::None - } - } - Self::Custom(prim) => prim.apply(qubit, ctx, rng), - } - } -} - -impl Primitive for CompiledPrimitive { - fn apply( - &self, - qubit: QubitId, - ctx: &mut NoiseContext, - rng: &mut PecosRng, - ) -> CompositeResponse { - CompiledPrimitive::apply(self, qubit, ctx, rng) - } - - fn describe(&self) -> String { - match self { - Self::Action(_) => "action".to_string(), - Self::Prob { probability, .. } => format!("prob({probability:.4})"), - Self::When { .. } => "when(...)".to_string(), - Self::Sample { branches, .. } => format!("sample([{} branches])", branches.len()), - Self::Seq(prims) => format!("seq([{} items])", prims.len()), - Self::SkipIf(_) => "skip_if(...)".to_string(), - Self::Custom(p) => p.describe(), - } - } - - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } - - fn gate_requirements(&self) -> smallvec::SmallVec<[NoiseGateRequirement; 2]> { - CompiledPrimitive::gate_requirements(self) - } -} - -// ============================================================================ -// Helper Functions -// ============================================================================ - -#[inline] -fn apply_pauli(qubit: QubitId, rng: &mut PecosRng, weights: &PauliWeights) -> CompositeResponse { - let r: f64 = rng.random(); - let normalized = weights.normalized(); - - let gate_type = if r < normalized.x { - GateType::X - } else if r < normalized.x + normalized.y { - GateType::Y - } else { - GateType::Z - }; - - let cmd = GateCommand { - gate_type, - qubits: smallvec![qubit], - payload: GatePayload::Angles(smallvec![]), - }; - CompositeResponse::InjectGates(vec![cmd]) -} - -#[inline] -fn apply_seep( - qubit: QubitId, - ctx: &mut NoiseContext, - rng: &mut PecosRng, - weights: &PauliWeights, -) -> CompositeResponse { - ctx.mark_unleaked(qubit); - - let r: f64 = rng.random(); - if r < 0.25 { - return CompositeResponse::Unleak; - } - - let r_scaled = (r - 0.25) / 0.75; - let normalized = weights.normalized(); - - let gate_type = if r_scaled < normalized.x { - GateType::X - } else if r_scaled < normalized.x + normalized.y { - GateType::Y - } else { - GateType::Z - }; - - let cmd = GateCommand { - gate_type, - qubits: smallvec![qubit], - payload: GatePayload::Angles(smallvec![]), - }; - - CompositeResponse::Unleak.combine(CompositeResponse::InjectGates(vec![cmd])) -} - -#[inline] -fn apply_emission( - qubit: QubitId, - ctx: &mut NoiseContext, - rng: &mut PecosRng, - weights: &SingleQubitEmissionWeights, -) -> CompositeResponse { - let r: f64 = rng.random(); - let result = weights.sample(r); - - match result { - crate::noise::SingleQubitEmissionResult::Pauli(gate_type) => { - let cmd = GateCommand { - gate_type, - qubits: smallvec![qubit], - payload: GatePayload::Angles(smallvec![]), - }; - CompositeResponse::InjectGates(vec![cmd]) - } - crate::noise::SingleQubitEmissionResult::Leaked => { - ctx.mark_leaked(qubit); - CompositeResponse::Leak - } - } -} - -#[inline] -fn apply_two_qubit_pauli( - qubit: QubitId, - ctx: &mut NoiseContext, - rng: &mut PecosRng, - weights: &TwoQubitPauliWeights, -) -> CompositeResponse { - let qubit_index = ctx.current_qubit_index(); - - let idx = if qubit_index == 0 { - let r: f64 = rng.random(); - let sampled_idx = weights.sample(r); - ctx.set_sampled_correlation(sampled_idx); - sampled_idx - } else { - ctx.sampled_correlation().unwrap_or_else(|| { - let r: f64 = rng.random(); - weights.sample(r) - }) - }; - - let (first_pauli, second_pauli) = TwoQubitPauliWeights::get_paulis(idx); - let my_pauli = if qubit_index == 0 { - first_pauli - } else { - second_pauli - }; - - if my_pauli == GateType::I { - CompositeResponse::None - } else { - let cmd = GateCommand { - gate_type: my_pauli, - qubits: smallvec![qubit], - payload: GatePayload::Angles(smallvec![]), - }; - CompositeResponse::InjectGates(vec![cmd]) - } -} - -#[inline] -fn apply_two_qubit_emission( - qubit: QubitId, - ctx: &mut NoiseContext, - rng: &mut PecosRng, - weights: &TwoQubitEmissionWeights, -) -> CompositeResponse { - let qubit_index = ctx.current_qubit_index(); - - let idx = if qubit_index == 0 { - let r: f64 = rng.random(); - let sampled_idx = weights.sample(r); - ctx.set_sampled_correlation(sampled_idx); - sampled_idx - } else { - ctx.sampled_correlation().unwrap_or_else(|| { - let r: f64 = rng.random(); - weights.sample(r) - }) - }; - - let result = TwoQubitEmissionWeights::get_result(idx); - - // Get the effect for this qubit based on index - let (my_pauli, my_leaked) = if qubit_index == 0 { - (result.first, result.first_leaked) - } else { - (result.second, result.second_leaked) - }; - - // Handle leakage - if my_leaked { - ctx.mark_leaked(qubit); - return CompositeResponse::Leak; - } - - // Handle Pauli (if any) - match my_pauli { - Some(gate_type) if gate_type != GateType::I => { - let cmd = GateCommand { - gate_type, - qubits: smallvec![qubit], - payload: GatePayload::Angles(smallvec![]), - }; - CompositeResponse::InjectGates(vec![cmd]) - } - _ => CompositeResponse::None, - } -} - -#[inline] -fn apply_crosstalk_transitions( - qubit: QubitId, - ctx: &mut NoiseContext, - rng: &mut PecosRng, - transitions: &crate::noise::CrosstalkTransitions, -) -> CompositeResponse { - // Determine current state from outcome - let is_one = ctx.current_outcome().unwrap_or(false); - - let r: f64 = rng.random(); - let result = transitions.sample(is_one, r); - - match result { - crate::noise::CrosstalkResult::NoChange => CompositeResponse::None, - crate::noise::CrosstalkResult::Flip => { - let cmd = GateCommand { - gate_type: GateType::X, - qubits: smallvec![qubit], - payload: GatePayload::Angles(smallvec![]), - }; - CompositeResponse::InjectGates(vec![cmd]) - } - crate::noise::CrosstalkResult::Leak => { - ctx.mark_leaked(qubit); - CompositeResponse::Leak - } - } -} - -// ============================================================================ -// Builder for Compiled Primitives -// ============================================================================ - -impl CompiledPrimitive { - /// Create a compiled action. - #[must_use] - pub fn action(action: CompiledAction) -> Self { - Self::Action(action) - } - - /// Create a compiled probability gate. - #[must_use] - pub fn prob(probability: f64, inner: Self) -> Self { - Self::Prob { - probability, - inner: Box::new(inner), - } - } - - /// Create a compiled conditional. - #[must_use] - pub fn when(condition: CompiledCondition, then_branch: Self, else_branch: Self) -> Self { - Self::When { - condition, - then_branch: Box::new(then_branch), - else_branch: Box::new(else_branch), - } - } - - /// Create a compiled sequence. - #[allow(clippy::missing_panics_doc)] // internal unwrap on single-element iterator - #[must_use] - pub fn seq(primitives: Vec) -> Self { - // Flatten nested sequences - let flattened: Vec = primitives - .into_iter() - .flat_map(|p| { - if let Self::Seq(inner) = p { - inner - } else { - vec![p] - } - }) - .collect(); - - // Optimize single-element sequences - if flattened.len() == 1 { - return flattened - .into_iter() - .next() - .expect("flattened has exactly 1 element"); - } - - Self::Seq(flattened) - } - - /// Create a compiled weighted sample. - #[must_use] - pub fn sample(branches: Vec<(f64, Self)>) -> Self { - if branches.is_empty() { - return Self::Action(CompiledAction::Nothing); - } - - // Compute cumulative weights - let total: f64 = branches.iter().map(|(w, _)| w).sum(); - let mut cumulative = 0.0; - let cumulative_weights: Vec = branches - .iter() - .map(|(w, _)| { - cumulative += w / total; - cumulative - }) - .collect(); - - Self::Sample { - branches, - cumulative_weights, - } - } - - /// Create a skip-if condition. - #[must_use] - pub fn skip_if(condition: CompiledCondition) -> Self { - Self::SkipIf(condition) - } - - /// Create from an Arc-wrapped primitive (fallback for custom types). - pub fn custom(prim: Arc) -> Self { - Self::Custom(prim) - } -} - -// ============================================================================ -// Tests -// ============================================================================ - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_compiled_action_nothing() { - let action = CompiledAction::Nothing; - let mut ctx = NoiseContext::new(); - let mut rng = PecosRng::seed_from_u64(42); - let response = action.apply(QubitId(0), &mut ctx, &mut rng); - assert!(matches!(response, CompositeResponse::None)); - } - - #[test] - fn test_compiled_action_pauli() { - let action = CompiledAction::Pauli(PauliWeights::uniform()); - let mut ctx = NoiseContext::new(); - let mut rng = PecosRng::seed_from_u64(42); - let response = action.apply(QubitId(0), &mut ctx, &mut rng); - assert!(matches!(response, CompositeResponse::InjectGates(_))); - } - - #[test] - fn test_compiled_primitive_prob() { - let prim = CompiledPrimitive::Prob { - probability: 1.0, - inner: Box::new(CompiledPrimitive::Action(CompiledAction::Pauli( - PauliWeights::uniform(), - ))), - }; - let mut ctx = NoiseContext::new(); - let mut rng = PecosRng::seed_from_u64(42); - let response = prim.apply(QubitId(0), &mut ctx, &mut rng); - assert!(matches!(response, CompositeResponse::InjectGates(_))); - } - - #[test] - fn test_compiled_primitive_seq() { - let prim = CompiledPrimitive::Seq(vec![ - CompiledPrimitive::Action(CompiledAction::Nothing), - CompiledPrimitive::Action(CompiledAction::Inject(GateType::X)), - ]); - let mut ctx = NoiseContext::new(); - let mut rng = PecosRng::seed_from_u64(42); - let response = prim.apply(QubitId(0), &mut ctx, &mut rng); - assert!(matches!(response, CompositeResponse::InjectGates(_))); - } - - #[test] - fn test_compiled_condition_leaked() { - let mut ctx = NoiseContext::new(); - let cond = CompiledCondition::Leaked; - - assert!(!cond.evaluate(QubitId(0), &ctx)); - ctx.mark_leaked(QubitId(0)); - assert!(cond.evaluate(QubitId(0), &ctx)); - } - - #[test] - fn test_compiled_primitive_builder() { - // Test the builder methods - let prim = CompiledPrimitive::seq(vec![ - CompiledPrimitive::skip_if(CompiledCondition::Leaked), - CompiledPrimitive::prob( - 0.5, - CompiledPrimitive::when( - CompiledCondition::NotLeaked, - CompiledPrimitive::action(CompiledAction::Pauli(PauliWeights::uniform())), - CompiledPrimitive::action(CompiledAction::Nothing), - ), - ), - ]); - - let mut ctx = NoiseContext::new(); - let mut rng = PecosRng::seed_from_u64(42); - - // Should work without panic - let _response = prim.apply(QubitId(0), &mut ctx, &mut rng); - } - - #[test] - fn test_compiled_sample() { - let prim = CompiledPrimitive::sample(vec![ - ( - 0.5, - CompiledPrimitive::action(CompiledAction::Inject(GateType::X)), - ), - ( - 0.5, - CompiledPrimitive::action(CompiledAction::Inject(GateType::Z)), - ), - ]); - - let mut ctx = NoiseContext::new(); - let mut rng = PecosRng::seed_from_u64(42); - - // Run multiple times to test distribution - let mut x_count = 0; - for _ in 0..1000 { - let response = prim.apply(QubitId(0), &mut ctx, &mut rng); - if let CompositeResponse::InjectGates(gates) = response - && gates[0].gate_type == GateType::X - { - x_count += 1; - } - } - - // Should be roughly 50/50 - let x_rate = f64::from(x_count) / 1000.0; - assert!((x_rate - 0.5).abs() < 0.1, "Expected ~50% X, got {x_rate}"); - } -} diff --git a/exp/pecos-neo/src/noise/general_builder.rs b/exp/pecos-neo/src/noise/general_builder.rs index 94c211a44..dc03ff235 100644 --- a/exp/pecos-neo/src/noise/general_builder.rs +++ b/exp/pecos-neo/src/noise/general_builder.rs @@ -58,21 +58,17 @@ use std::collections::BTreeMap; /// /// # Mixing Channel Types /// -/// You can mix traditional channels with composite channels using [`with_channel`]: +/// Add existing channels using [`with_channel`]. Composite primitives additionally +/// require the `composite-noise` feature: /// /// ```no_run /// use pecos_neo::noise::GeneralNoiseModelBuilder; -/// use pecos_neo::noise::composite::prelude::*; +/// use pecos_neo::noise::TwoQubitChannel; /// /// let model = GeneralNoiseModelBuilder::new() /// .with_p1(0.001) // Traditional 1Q channel /// .with_p_meas(0.02, 0.03) // Traditional measurement channel -/// .with_channel( // Custom composite channel for 2Q -/// CompositeChannelBuilder::two_qubit("custom_2q", seq![ -/// skip_if_leaked(), -/// prob(0.01, pauli()), -/// ]) -/// ) +/// .with_channel(TwoQubitChannel::depolarizing(0.01)) /// .build(); /// ``` /// @@ -205,16 +201,11 @@ impl GeneralNoiseModelBuilder { /// /// ```no_run /// use pecos_neo::noise::GeneralNoiseModelBuilder; - /// use pecos_neo::noise::composite::prelude::*; + /// use pecos_neo::noise::TwoQubitChannel; /// /// let model = GeneralNoiseModelBuilder::new() /// .with_p1(0.001) // Traditional single-qubit noise - /// .with_channel( // Custom composite channel - /// CompositeChannelBuilder::two_qubit("leaky_2q", seq![ - /// skip_if_leaked(), - /// prob(0.01, when_leaked(seep(), pauli())), - /// ]) - /// ) + /// .with_channel(TwoQubitChannel::depolarizing(0.01)) /// .build(); /// ``` #[must_use] diff --git a/exp/pecos-neo/src/noise/introspection.rs b/exp/pecos-neo/src/noise/introspection.rs index ad1ef5b6b..21717616d 100644 --- a/exp/pecos-neo/src/noise/introspection.rs +++ b/exp/pecos-neo/src/noise/introspection.rs @@ -23,20 +23,10 @@ //! ```no_run //! use pecos_neo::noise::prelude::*; //! -//! let noise = seq![ -//! skip_if_leaked(), -//! prob(0.01, when_leaked(seep(), pauli())), -//! ]; -//! -//! // Print the decision tree -//! println!("{}", noise.describe_tree()); -//! // Output: -//! // Seq -//! // ├─ SkipIf(Leaked) -//! // └─ Prob(0.01) -//! // └─ When(Leaked) -//! // ├─ then: seep -//! // └─ else: pauli +//! let model = NoiseModelBuilder::new() +//! .with_depolarizing(0.001, 0.01) +//! .build(); +//! println!("{}", model.describe()); //! ``` use std::fmt::Write; diff --git a/exp/pecos-neo/src/noise/patterns.rs b/exp/pecos-neo/src/noise/patterns.rs index 4d71391e3..32a37cb08 100644 --- a/exp/pecos-neo/src/noise/patterns.rs +++ b/exp/pecos-neo/src/noise/patterns.rs @@ -23,13 +23,13 @@ //! | [`depolarizing_only`] | Simple Pauli errors after gates | //! | [`depolarizing_with_measurement`] | Gate + measurement errors | //! | [`measurement_only`] | Just readout errors | -//! | [`dephasing_only`] | Z errors only (T2-like) | -//! | [`with_leakage`] | Leakage to non-computational states | +//! | `dephasing_only` (requires `composite-noise`) | Z errors only (T2-like) | +//! | `with_leakage` (requires `composite-noise`) | Leakage to non-computational states | //! | [`chain_correlated`] | Spatially correlated errors (1D) | -//! | [`chain_measurement_crosstalk`] | Measurement affects neighbors (1D) | -//! | [`grid_measurement_crosstalk`] | Measurement affects neighbors (2D) | -//! | [`realistic_device_noise`] | Full device model with all parameters | -//! | [`surface_code_noise`] | Optimized for surface code simulations | +//! | `chain_measurement_crosstalk` (requires `composite-noise`) | Measurement affects neighbors (1D) | +//! | `grid_measurement_crosstalk` (requires `composite-noise`) | Measurement affects neighbors (2D) | +//! | `realistic_device_noise` (requires `composite-noise`) | Full device model with all parameters | +//! | `surface_code_noise` (requires `composite-noise`) | Optimized for surface code simulations | //! //! # Examples //! @@ -54,22 +54,6 @@ //! let model = measurement_only(0.01, 0.05); // 1% and 5% //! ``` //! -//! ## Device Noise -//! -//! ``` -//! use pecos_neo::noise::prelude::*; -//! -//! let model = realistic_device_noise( -//! &DeviceNoiseParams::new() -//! .with_p1(0.001) // 0.1% single-qubit error -//! .with_p2(0.01) // 1% two-qubit error -//! .with_measurement_error(0.02) -//! .with_prep_error(0.001) -//! .with_t1(0.0001) // T1 decay rate -//! .with_t2(0.0005) // T2 dephasing rate -//! ); -//! ``` -//! //! ## Spatial Noise //! //! ``` @@ -78,14 +62,18 @@ //! // Errors that spread between qubits //! let model = chain_correlated(0.01, 0.5); // 50% correlation //! -//! // Measurement crosstalk on a grid -//! let model = grid_measurement_crosstalk(5, 0.01); // 5 columns //! ``` +//! +//! `DeviceNoiseParams`, realistic-device and surface-code models, leakage, +//! dephasing, and measurement-crosstalk patterns require the `composite-noise` +//! Cargo feature. use super::CorrelatedNoiseChannel; use super::builder::NoiseModelBuilder; use super::composer::ComposableNoiseModel; +#[cfg(feature = "composite-noise")] use super::composite::prelude::*; +#[cfg(feature = "composite-noise")] use super::topology::{chain_neighbors, grid_neighbors}; // ============================================================================ @@ -176,6 +164,7 @@ pub fn measurement_only(p01: f64, p10: f64) -> ComposableNoiseModel { /// let model = dephasing_only(0.001, 0.01); /// ``` #[must_use] +#[cfg(feature = "composite-noise")] pub fn dephasing_only(p1: f64, p2: f64) -> ComposableNoiseModel { let sq_noise = prob(p1, inject_z()); // For two-qubit dephasing: ZI, IZ, or ZZ @@ -211,6 +200,7 @@ pub fn dephasing_only(p1: f64, p2: f64) -> ComposableNoiseModel { /// let model = with_leakage(0.001, 0.01, 0.1, 0.5); /// ``` #[must_use] +#[cfg(feature = "composite-noise")] pub fn with_leakage( p1: f64, p2: f64, @@ -294,6 +284,7 @@ pub fn chain_correlated(base_probability: f64, correlation_factor: f64) -> Compo /// let model = chain_measurement_crosstalk(0.01); /// ``` #[must_use] +#[cfg(feature = "composite-noise")] pub fn chain_measurement_crosstalk(crosstalk_probability: f64) -> ComposableNoiseModel { let crosstalk = CompositeCrosstalkChannel::new("chain_crosstalk", prob(crosstalk_probability, pauli())) @@ -318,6 +309,7 @@ pub fn chain_measurement_crosstalk(crosstalk_probability: f64) -> ComposableNois /// let model = grid_measurement_crosstalk(5, 0.01); /// ``` #[must_use] +#[cfg(feature = "composite-noise")] pub fn grid_measurement_crosstalk(cols: usize, crosstalk_probability: f64) -> ComposableNoiseModel { let crosstalk = CompositeCrosstalkChannel::new("grid_crosstalk", prob(crosstalk_probability, pauli())) @@ -332,6 +324,7 @@ pub fn grid_measurement_crosstalk(cols: usize, crosstalk_probability: f64) -> Co // ============================================================================ /// Parameters for realistic device noise. +#[cfg(feature = "composite-noise")] #[derive(Debug, Clone)] pub struct DeviceNoiseParams { /// Single-qubit gate error probability. @@ -354,6 +347,7 @@ pub struct DeviceNoiseParams { pub t2_rate: f64, } +#[cfg(feature = "composite-noise")] impl Default for DeviceNoiseParams { fn default() -> Self { Self { @@ -370,6 +364,7 @@ impl Default for DeviceNoiseParams { } } +#[cfg(feature = "composite-noise")] impl DeviceNoiseParams { /// Create new device noise parameters. #[must_use] @@ -464,7 +459,24 @@ impl DeviceNoiseParams { /// .with_t2(0.0005) /// ); /// ``` +/// +/// ## Device Parameters +/// +/// ``` +/// use pecos_neo::noise::prelude::*; +/// +/// let model = realistic_device_noise( +/// &DeviceNoiseParams::new() +/// .with_p1(0.001) // 0.1% single-qubit error +/// .with_p2(0.01) // 1% two-qubit error +/// .with_measurement_error(0.02) +/// .with_prep_error(0.001) +/// .with_t1(0.0001) // T1 decay rate +/// .with_t2(0.0005) // T2 dephasing rate +/// ); +/// ``` #[must_use] +#[cfg(feature = "composite-noise")] pub fn realistic_device_noise(params: &DeviceNoiseParams) -> ComposableNoiseModel { let mut builder = NoiseModelBuilder::new(); @@ -542,6 +554,7 @@ pub fn realistic_device_noise(params: &DeviceNoiseParams) -> ComposableNoiseMode /// let model = surface_code_noise(0.001, false); /// ``` #[must_use] +#[cfg(feature = "composite-noise")] pub fn surface_code_noise(physical_error_rate: f64, with_crosstalk: bool) -> ComposableNoiseModel { // For surface codes, 2Q errors are typically ~10x worse than 1Q let p1 = physical_error_rate; diff --git a/exp/pecos-neo/src/noise/prelude.rs b/exp/pecos-neo/src/noise/prelude.rs index 2b0c00ffd..91c87d0ee 100644 --- a/exp/pecos-neo/src/noise/prelude.rs +++ b/exp/pecos-neo/src/noise/prelude.rs @@ -13,7 +13,8 @@ //! Unified prelude for the noise system. //! //! Re-exports everything needed to build noise models: pre-built patterns, -//! builder API, composite primitives, topology helpers, and validation. +//! builder API, topology helpers, and validation. Composite primitives require +//! the `composite-noise` Cargo feature. //! //! For a full guide with examples, see `docs/experimental/composable-noise.md`. @@ -45,6 +46,7 @@ pub use super::TwoQubitPauliWeights; // --- Composite System (Composition) --- +#[cfg(feature = "composite-noise")] pub use super::composite::prelude::*; // --- Core Traits and Types --- @@ -61,11 +63,14 @@ pub use super::topology::{ // --- Convenience Patterns (Pre-built Configurations) --- +#[cfg(feature = "composite-noise")] pub use super::patterns::{ - DeviceNoiseParams, chain_correlated, chain_measurement_crosstalk, dephasing_only, - depolarizing_only, depolarizing_with_measurement, grid_measurement_crosstalk, measurement_only, + DeviceNoiseParams, chain_measurement_crosstalk, dephasing_only, grid_measurement_crosstalk, realistic_device_noise, surface_code_noise, with_leakage, }; +pub use super::patterns::{ + chain_correlated, depolarizing_only, depolarizing_with_measurement, measurement_only, +}; // --- Validation --- diff --git a/exp/pecos-neo/src/noise/topology.rs b/exp/pecos-neo/src/noise/topology.rs index 1eeef477c..54f837784 100644 --- a/exp/pecos-neo/src/noise/topology.rs +++ b/exp/pecos-neo/src/noise/topology.rs @@ -26,21 +26,18 @@ //! //! # Neighbor Functions //! -//! Use these with `CompositeCrosstalkChannel::local()` to define which qubits -//! are affected by crosstalk: +//! Neighbor functions can be used directly, or with +//! `CompositeCrosstalkChannel::local()` when `composite-noise` is enabled: //! //! ``` //! use pecos_neo::noise::prelude::*; +//! use pecos_core::QubitId; //! -//! // 1D chain: qubit i has neighbors i-1 and i+1 -//! let crosstalk = CompositeCrosstalkChannel::new("chain", prob(0.01, pauli())) -//! .responds_to_measurement() -//! .local(chain_neighbors); -//! -//! // 2D grid (5 columns): up/down/left/right neighbors -//! let crosstalk = CompositeCrosstalkChannel::new("grid", prob(0.01, pauli())) -//! .responds_to_measurement() -//! .local(grid_neighbors(5)); +//! let neighbors = chain_neighbors(&[QubitId(2)]); +//! assert!(neighbors.contains(&QubitId(1))); +//! assert!(neighbors.contains(&QubitId(3))); +//! let grid = grid_neighbors(5); +//! assert!(grid(&[QubitId(6)]).contains(&QubitId(1))); //! ``` //! //! ## Grid Topology diff --git a/python/quantum-pecos/tests/docs/rust_crate/Cargo.toml b/python/quantum-pecos/tests/docs/rust_crate/Cargo.toml index 231a885f2..8d248f97f 100644 --- a/python/quantum-pecos/tests/docs/rust_crate/Cargo.toml +++ b/python/quantum-pecos/tests/docs/rust_crate/Cargo.toml @@ -20,7 +20,7 @@ pecos-foreign = { path = "../../../../../crates/pecos-foreign" } pecos-random = { path = "../../../../../crates/pecos-random" } pecos-qasm = { path = "../../../../../crates/pecos-qasm" } pecos-programs = { path = "../../../../../crates/pecos-programs" } -pecos-neo = { path = "../../../../../exp/pecos-neo" } +pecos-neo = { path = "../../../../../exp/pecos-neo", features = ["composite-noise"] } pecos-stab-tn = { path = "../../../../../exp/pecos-stab-tn" } # Common external crates used in documentation examples serde_json = "1.0"