Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 92 additions & 0 deletions crates/ppvm-runtime/src/traits/clifford.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,98 @@ impl<T: PauliWordTrait> Clifford for T {
}
}

/// Batched Clifford gates: apply the same gate to many qubits in one call.
///
/// Default implementations loop over the corresponding single-qubit
/// (or single-pair) method on [`Clifford`]. Types like the stabilizer
/// `Tableau` override these methods with a fused inner-loop or bitmask
/// implementation. Types that don't need specialization can implement
/// this trait with an empty `impl` to use the defaults.
pub trait CliffordBatch: Clifford {
/// Apply Pauli `X` to every qubit in `indices`.
fn x_batch(&mut self, indices: &[usize]) {
for &q in indices {
self.x(q);
}
}
/// Apply Pauli `Y` to every qubit in `indices`.
fn y_batch(&mut self, indices: &[usize]) {
for &q in indices {
self.y(q);
}
}
/// Apply Pauli `Z` to every qubit in `indices`.
fn z_batch(&mut self, indices: &[usize]) {
for &q in indices {
self.z(q);
}
}
/// Apply Hadamard `H` to every qubit in `indices`.
fn h_batch(&mut self, indices: &[usize]) {
for &q in indices {
self.h(q);
}
}
/// Apply phase gate `S` to every qubit in `indices`.
fn s_batch(&mut self, indices: &[usize]) {
for &q in indices {
self.s(q);
}
}
/// Apply `CNOT` to every `(control, target)` pair.
fn cnot_batch(&mut self, pairs: &[(usize, usize)]) {
for &(c, t) in pairs {
self.cnot(c, t);
}
}
/// Apply `CZ` to every `(control, target)` pair.
fn cz_batch(&mut self, pairs: &[(usize, usize)]) {
for &(c, t) in pairs {
self.cz(c, t);
}
}
}

/// Batched form of [`CliffordExtensions`].
pub trait CliffordExtensionsBatch: CliffordExtensions + CliffordBatch {
/// Apply `S†` to every qubit in `indices`.
fn s_adj_batch(&mut self, indices: &[usize]) {
for &q in indices {
self.s_adj(q);
}
}
/// Apply `√X` to every qubit in `indices`.
fn sqrt_x_batch(&mut self, indices: &[usize]) {
for &q in indices {
self.sqrt_x(q);
}
}
/// Apply `(√X)†` to every qubit in `indices`.
fn sqrt_x_adj_batch(&mut self, indices: &[usize]) {
for &q in indices {
self.sqrt_x_adj(q);
}
}
/// Apply `√Y` to every qubit in `indices`.
fn sqrt_y_batch(&mut self, indices: &[usize]) {
for &q in indices {
self.sqrt_y(q);
}
}
/// Apply `(√Y)†` to every qubit in `indices`.
fn sqrt_y_adj_batch(&mut self, indices: &[usize]) {
for &q in indices {
self.sqrt_y_adj(q);
}
}
/// Apply `CY` to every `(control, target)` pair.
fn cy_batch(&mut self, pairs: &[(usize, usize)]) {
for &(c, t) in pairs {
self.cy(c, t);
}
}
}

impl<T: PauliWordTrait> CliffordExtensions for T {
// | Gate | X | Y | Z |
// |:----------:|:---:|:---:|:---:|
Expand Down
2 changes: 1 addition & 1 deletion crates/ppvm-runtime/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ mod trace;
mod word_trait;

pub use branch::{CRx, Projection, RotationOne, RotationTwo, TGate, U3Gate};
pub use clifford::{Clifford, CliffordExtensions};
pub use clifford::{Clifford, CliffordBatch, CliffordExtensions, CliffordExtensionsBatch};
pub use coefficient::{Coefficient, ComplexCoefficient};
pub use map::{
ACMap, ACMapAddAssign, ACMapBase, ACMapConsume, ACMapContains, ACMapInsert, ACMapIter,
Expand Down
1 change: 1 addition & 0 deletions crates/ppvm-stim/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ stim-parser = { version = "0.1.0", path = "../stim-parser" }
[dev-dependencies]
# Used by benchmarks.
criterion = "0.7.0"
bnum = { version = "0.13", features = ["numtraits"] }

[[bench]]
name = "tableau-msd-stim"
Expand Down
46 changes: 46 additions & 0 deletions crates/ppvm-stim/examples/surface_d30.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2026 The PPVM Authors
// SPDX-License-Identifier: Apache-2.0

//! Run the distance-30 surface-code memory circuit in `surface_d30.stim`
//! against a [`GeneralizedTableau`].
//!
//! The circuit is pure Clifford (resets, H, CX, measurements, plus
//! depolarizing / X noise), so no non-Clifford branching occurs and a plain
//! `usize` branch index suffices. Its 1889 qubits need `ceil(1889 / 8) = 237`
//! bytes per Pauli word, hence the `<237>` in the config.

use std::time::Instant;

use bnum::types::U2048;
use ppvm_runtime::config::indexmap::ByteFxHashF64;
use ppvm_stim::{parse_extended, sample};
use ppvm_tableau::prelude::*;

type Tab = GeneralizedTableau<ByteFxHashF64<237>, U2048>;

const N_QUBITS: usize = 1889;
const SHOTS: usize = 12;

fn main() -> Result<(), ppvm_stim::Error> {
// The .stim file sits next to this example; embed it at compile time.
let prog = parse_extended(include_str!("surface_d30.stim"))?;
println!(
"parsed {} measurement records over {N_QUBITS} qubits",
prog.measurement_count()
);

// Parse once, build a fresh tableau per shot via the factory closure.
let start = Instant::now();
let shots = sample(&prog, SHOTS, || Tab::new(N_QUBITS, 1e-10))?;
let elapsed = start.elapsed();

for (i, shot) in shots.iter().enumerate() {
let ones = shot.iter().filter(|&&b| b == Some(true)).count();
println!("shot {i}: {ones} ones / {} measurements", shot.len());
}
println!(
"{SHOTS} shots in {elapsed:.2?} ({:.2?}/shot)",
elapsed / SHOTS as u32
);
Ok(())
}
Loading
Loading