Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/target
/debug
.DS_Store
.vscode/
*__pycache__*
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion crates/ppvm-python-native/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ bnum = "0.13.0"
num = "0.4.3"
paste = "1.0.15"
ppvm-runtime = { version = "0.1.0", path = "../ppvm-runtime" }
ppvm-stim = { version = "0.1.0", path = "../ppvm-stim" }
ppvm-stim = { version = "0.1.0", path = "../ppvm-stim", features = ["rayon"] }
ppvm-tableau = { version = "0.1.0", path = "../ppvm-tableau" }
pyo3 = { version = "0.27.0", features = ["multiple-pymethods"] }
39 changes: 23 additions & 16 deletions crates/ppvm-python-native/src/interface_tableau.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,46 +255,53 @@ macro_rules! create_interface {
prog: &crate::stim_program::PyStimProgram,
) -> pyo3::PyResult<Vec<u8>> {
let mut results = Vec::with_capacity(prog.measurement_count());
ppvm_stim::execute_prepared(&prog.instructions, &mut self.inner, &mut results);
ppvm_stim::execute_validated(&prog.instructions, &mut self.inner, &mut results);
Ok(results
.into_iter()
.map(crate::interface_tableau::measurement_to_u8)
.collect())
}

/// Multi-shot sampling: builds a fresh tableau per shot.
///
/// Shots run in parallel on rayon's global thread pool (GIL
/// released), falling back to serial for small batches. Shot `i`
/// is seeded with `seed.wrapping_add(i)` when `seed` is given
/// (wrapping mod 2⁶⁴), so results are reproducible and
/// independent of the thread count; set the `RAYON_NUM_THREADS`
/// environment variable to control the pool size.
#[staticmethod]
#[pyo3(signature = (prog, n_qubits, min_abs_coeff = 1e-10, num_shots = 1, seed = None))]
pub fn sample(
py: Python<'_>,
prog: &crate::stim_program::PyStimProgram,
n_qubits: usize,
min_abs_coeff: f64,
num_shots: usize,
seed: Option<u64>,
) -> pyo3::PyResult<Vec<Vec<u8>>> {
let mut next_seed = seed;
let count = prog.measurement_count();
Ok((0..num_shots)
.map(|_| {
let s = next_seed;
if let Some(ref mut v) = next_seed {
*v = v.wrapping_add(1);
}
let mut tab = match s {
// `prog` was already validated at `StimProgram.parse()` time;
// use the validated path to skip redundant re-validation.
let raw = py.detach(|| {
Comment thread
Roger-luo marked this conversation as resolved.
Comment thread
Roger-luo marked this conversation as resolved.
ppvm_stim::sample_validated(
&prog.0.instructions,
prog.0.measurement_count(),
num_shots,
|i| match seed {
Some(s) => GeneralizedTableau::<$type, $indexType>::new_with_seed(
n_qubits,
min_abs_coeff,
s,
s.wrapping_add(i as u64),
),
None => GeneralizedTableau::<$type, $indexType>::new(
n_qubits,
min_abs_coeff,
),
};
let mut shot = Vec::with_capacity(count);
ppvm_stim::execute_prepared(&prog.instructions, &mut tab, &mut shot);
shot
})
},
)
});
Ok(raw
.into_iter()
.map(|shot| {
shot.into_iter()
.map(crate::interface_tableau::measurement_to_u8)
Expand Down
8 changes: 4 additions & 4 deletions crates/ppvm-python-native/src/stim_program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ use pyo3::exceptions::{PyIOError, PyValueError};
use pyo3::prelude::*;
use std::ops::Deref;

use ppvm_stim::{ExtendedProgram, parse_extended, prepare};
use ppvm_stim::{ExtendedProgram, parse_extended, validate};

/// Python-facing wrapper around a prepared extended Stim program.
/// Python-facing wrapper around a validated extended Stim program.
#[pyclass(name = "StimProgram", module = "ppvm_python_native")]
pub struct PyStimProgram(pub ExtendedProgram);

#[pymethods]
impl PyStimProgram {
/// Parse and prepare a Stim circuit string.
/// Parse and validate a Stim circuit string.
#[staticmethod]
pub fn parse(src: &str) -> PyResult<Self> {
let program = parse_extended(src).map_err(stim_to_pyerr)?;
prepare(&program).map_err(stim_to_pyerr_exec)?;
validate(&program).map_err(stim_to_pyerr_exec)?;
Ok(Self(program))
}

Expand Down
6 changes: 6 additions & 0 deletions crates/ppvm-stim/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ name = "ppvm-stim"
version = "0.1.0"
edition = "2024"

[features]
default = []
# Parallelise multi-shot sampling across the global rayon thread pool.
rayon = ["dep:rayon"]

[dependencies]
ppvm-runtime = { version = "0.1.0", path = "../ppvm-runtime" }
ppvm-tableau = { version = "0.1.0", path = "../ppvm-tableau" }
Expand All @@ -11,6 +16,7 @@ itertools = "0.14"
num = "0.4.3"
thiserror = "2"
stim-parser = { version = "0.1.0", path = "../stim-parser" }
rayon = { version = "1", optional = true }

[dev-dependencies]
# Used by benchmarks.
Expand Down
Loading