paulistrings/lib.rs
1//! Classical simulation of quantum circuits by Pauli propagation.
2//!
3//! The library evolves operators in the Pauli basis under gates and noise
4//! channels, in either the forward or Heisenberg picture. It targets
5//! workloads where state-vector or tensor-network simulators are infeasible
6//! (10⁶–10⁸ terms) but the operator stays sparse in the Pauli basis.
7//!
8//! It is **not** a state-vector, tensor-network, stabilizer, or MPS
9//! simulator — those are explicit non-goals.
10//!
11//! # Design pillars
12//!
13//! In priority order:
14//!
15//! 1. **Correctness** of the Pauli algebra — symplectic encoding, exact
16//! phase tracking, sort-merge invariant restored after every layer.
17//! 2. **Performance** at 10⁶–10⁸ terms — structure-of-arrays storage,
18//! sort-merge propagation engine, Rayon-parallel scan and merge.
19//! 3. **Extensibility** for research — open [`Channel`] and
20//! [`TruncationPolicy`] traits with built-ins for Clifford gates,
21//! Pauli rotations, depolarizing / dephasing / amplitude-damping noise,
22//! coefficient and weight cutoffs, and `TopN` selection.
23//! 4. **GPU readiness** — `#[repr(C)]` `Pod` data types, fixed-fanout
24//! output buffers, shared-nothing parallelism that maps onto CUB
25//! primitives without restructuring.
26//!
27//! # Quick example
28//!
29//! Heisenberg-evolve the observable `Z₀ + 0.5·X₁` through an `H` gate on
30//! qubit 0:
31//!
32//! ```
33//! use paulistrings::{
34//! BuildAccumulator, Circuit, Direction, PauliString, Phase, TruncationPolicy,
35//! channel::Clifford1Q, propagate,
36//! };
37//! use num_complex::Complex64;
38//!
39//! let mut acc = BuildAccumulator::<1>::new(2);
40//! acc.add_term(PauliString::<1>::z(0), Phase::ONE, Complex64::new(1.0, 0.0));
41//! acc.add_term(PauliString::<1>::x(1), Phase::ONE, Complex64::new(0.5, 0.0));
42//! let observable = acc.finalize();
43//!
44//! let mut circuit = Circuit::<1>::new(2);
45//! circuit.push(Clifford1Q::h(0));
46//!
47//! struct KeepAll;
48//! impl<const W: usize> TruncationPolicy<W> for KeepAll {}
49//!
50//! let evolved = propagate(&circuit, observable, &KeepAll, Direction::Heisenberg);
51//!
52//! // H conjugates Z → X, so the Z₀ term becomes X₀; the X₁ term is untouched.
53//! assert_eq!(evolved.len(), 2);
54//! ```
55//!
56//! # Module map
57//!
58//! - [`PauliString`] — symplectic-encoded Pauli operator on up to `64·W`
59//! qubits, `Copy + Pod`, `Ord`-comparable.
60//! - [`PauliSum`] — weighted sum of Pauli strings in structure-of-arrays
61//! form. Sorted-and-deduplicated invariant maintained by every public
62//! operation.
63//! - [`BuildAccumulator`] — hashmap-based ingestion path. Used for
64//! constructing a [`PauliSum`] from unsorted inputs (Hamiltonian
65//! parsing, dict construction). Not used during propagation.
66//! - [`Phase`] — `i^k` factor (`k ∈ 0..=3`) returned by Pauli
67//! multiplication and folded into [`PauliSum`] coefficients at the
68//! boundary.
69//! - [`Circuit`] — ordered, heterogeneous list of channels.
70//! - [`Channel`] — extension trait shared by gates and noise. Built-ins
71//! in [`channel`]: [`channel::Clifford1Q`], [`channel::Clifford2Q`],
72//! [`channel::PauliRotation`], [`channel::Depolarizing`],
73//! [`channel::Dephasing`], [`channel::AmplitudeDamping`].
74//! - [`TruncationPolicy`] — composable per-term and per-layer term
75//! filters. Built-ins in [`truncation`]: [`truncation::CoefficientThreshold`],
76//! [`truncation::WeightCutoff`], [`truncation::TopN`], plus the
77//! [`truncation::And`] / [`truncation::Or`] combinators.
78//! - [`propagate`] / [`Direction`] — the propagation entry point.
79//! - [`engine`] — the sort-merge pipeline (scan → sort → merge). Most
80//! users will not call this directly; [`propagate`] is the front door.
81//! - [`examples`] — worked-example walkthroughs of full-scale simulations
82//! (currently: a 2D transverse-field Ising quench on 4×4 and 6×6
83//! lattices with embedded plot).
84//!
85//! # Choosing `W`
86//!
87//! [`PauliString`] is generic over a const `W: usize` — the number of
88//! 64-bit words used to store each of the `x` and `z` parts. So
89//! `PauliString<W>` covers up to `64·W` qubits, and the engine
90//! monomorphizes the entire pipeline at that `W`.
91//!
92//! For direct Rust use, pick the smallest `W` that fits your problem:
93//! one word per part is dramatically faster than two, and so on. The
94//! Python bindings monomorphize at the fixed set `W ∈ {1, 2, 4, 8, 16}`
95//! (≤ 64, 128, 256, 512, 1024 qubits) and dispatch on `num_qubits` at
96//! the boundary.
97
98#![warn(missing_docs)]
99#![cfg_attr(docsrs, feature(doc_auto_cfg))]
100#![allow(unused)]
101
102pub mod accumulator;
103pub mod channel;
104pub mod circuit;
105pub mod engine;
106pub mod examples;
107pub mod pauli_string;
108pub mod pauli_sum;
109pub mod phase;
110pub mod truncation;
111
112pub use accumulator::BuildAccumulator;
113pub use channel::{Channel, OutputBuffer};
114pub use circuit::Circuit;
115pub use engine::{propagate, Direction};
116pub use pauli_string::PauliString;
117pub use pauli_sum::PauliSum;
118pub use phase::Phase;
119pub use truncation::TruncationPolicy;