Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
52 changes: 0 additions & 52 deletions crates/ppvm-pauli-word/src/loss/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,58 +230,6 @@ impl<A: PauliStorage, S: BuildHasher + Clone + Default + HashFinalize> PauliWord
}
}

#[inline(always)]
fn get_multiple<const Q: usize>(&self, indices: [usize; Q]) -> Self {
let mut result = Self::new(Q);
for (i, &idx) in indices.iter().enumerate() {
result.set(i, self.get(idx));
}
result
}

#[inline(always)]
fn set_multiple<const Q: usize, B: PauliStorage>(
&mut self,
indices: [usize; Q],
values: &Self,
) {
if values.nqubits != Q {
panic!("Values must have the same number of qubits as indices");
}
for (i, &idx) in indices.iter().enumerate() {
self.xbits.set(idx, values.xbits[i]);
self.zbits.set(idx, values.zbits[i]);
self.lbits.set(idx, values.lbits[i]);
}
self.rehash();
}

#[inline(always)]
fn get_slice(&self, slice: std::ops::Range<usize>) -> Self {
if slice.end > self.nqubits {
panic!("Slice out of bounds");
}
let n_qubits = slice.len();
let mut xbits = BitArray::ZERO;
let mut zbits = BitArray::ZERO;
let mut lbits = BitArray::ZERO;
for (i, idx) in slice.into_iter().enumerate() {
xbits.set(i, self.xbits[idx]);
zbits.set(i, self.zbits[idx]);
lbits.set(i, self.lbits[idx]);
}
let mut ret = Self {
xbits,
zbits,
lbits,
nqubits: n_qubits,
hash_cache: 0,
_phantom: std::marker::PhantomData,
};
ret.rehash();
ret
}

#[inline(always)]
fn is(&self, index: usize, pauli: Pauli) -> bool {
if index >= self.nqubits {
Expand Down
48 changes: 0 additions & 48 deletions crates/ppvm-pauli-word/src/word/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,54 +212,6 @@ impl<A: PauliStorage, S: BuildHasher + Clone + Default + HashFinalize, const REH
}
}

#[inline(always)]
fn get_multiple<const Q: usize>(&self, indices: [usize; Q]) -> Self {
let mut result = Self::new(Q);
for (i, &idx) in indices.iter().enumerate() {
result.set(i, self.get(idx));
}
result
}

#[inline(always)]
fn set_multiple<const Q: usize, B: PauliStorage>(
&mut self,
indices: [usize; Q],
values: &Self,
) {
if values.nqubits != Q {
panic!("Values must have the same number of qubits as indices");
}
for (i, &idx) in indices.iter().enumerate() {
self.xbits.set(idx, values.xbits[i]);
self.zbits.set(idx, values.zbits[i]);
}
self.rehash();
}

#[inline(always)]
fn get_slice(&self, slice: std::ops::Range<usize>) -> Self {
if slice.end > self.nqubits {
panic!("Slice out of bounds");
}
let n_qubits = slice.len();
let mut xbits = BitArray::ZERO;
let mut zbits = BitArray::ZERO;
for (i, idx) in slice.into_iter().enumerate() {
xbits.set(i, self.xbits[idx]);
zbits.set(i, self.zbits[idx]);
}
let mut ret = Self {
xbits,
zbits,
nqubits: n_qubits,
hash_cache: 0,
_phantom: std::marker::PhantomData,
};
ret.rehash();
ret
}

#[inline(always)]
fn is(&self, index: usize, pauli: Pauli) -> bool {
if index >= self.nqubits {
Expand Down
12 changes: 12 additions & 0 deletions crates/ppvm-tableau/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,9 @@ where
/// word) or [`Self::cz_block_pairs_cross_word`] (straddling two words), so
/// callers never need to reason about the `u64` packing. CZ is symmetric,
/// so the two bases may be passed in either order.
///
/// This is a whole-block primitive over the POD raw-word planes — see the
/// crate-level "Data-parallel / GPU-offloadable surface" section.
pub fn cz_block(&mut self, control_base: usize, target_base: usize, count: usize)
where
<<T::Storage as BitView>::Store as TryFrom<usize>>::Error: Debug,
Expand Down Expand Up @@ -1003,6 +1006,15 @@ where
+ Copy,
I: TableauIndex + Send + Sync,
{
/// Split every coefficient into a branch and non-branch term for a
/// single-qubit Pauli measurement/rotation, in one pass over the whole
/// coefficient array.
///
/// This is a whole-batch primitive over the coefficient array — see the
/// crate-level "Data-parallel / GPU-offloadable surface" section. The
/// `#[cfg(feature = "rayon")]` path computes each entry's branch/non-branch
/// coefficients in parallel; only the final accumulation into the
/// coefficient map is sequential.
pub(crate) fn branch_with_coefficients(
&mut self,
addr0: usize,
Expand Down
32 changes: 32 additions & 0 deletions crates/ppvm-tableau/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,38 @@
//! let r1 = LossyMeasure::measure(&mut tab, 1);
//! assert_eq!(r0, r1);
//! ```
//!
//! ## Data-parallel / GPU-offloadable surface
//!
//! A handful of primitives in this crate operate on whole contiguous arrays
//! at once, with no per-element host closures — the shape a thread pool or a
//! CUDA kernel would want:
//!
//! - Every tableau row ([`PhasedPauliWord`](ppvm_pauli_word::phase::PhasedPauliWord))
//! stores its Pauli word as fixed-size `bytemuck::Pod` integer bit-planes
//! (`xbits`/`zbits`, backed by `[u8; N]` or `[u64; N]`), reachable as
//! contiguous raw integer slices via `as_raw_slice`/`as_raw_mut_slice` —
//! plain-old-data, not a bit-addressed abstraction.
//! - The Clifford single-/two-qubit gates
//! (`crates/ppvm-tableau/src/gates/clifford.rs`) all run through one shared
//! implementation per gate that loops over rows operating directly on
//! those raw integer slices with a hoisted word-index/bit/mask, bypassing
//! `bitvec`'s per-bit bounds checks inside the loop. Every caller —
//! [`data::Tableau`], [`data::GeneralizedTableau`] (via the
//! `impl_generalized_tableau_clifford*` macros), and the fused batch path
//! below — funnels through that one implementation.
//! - [`CliffordBatch`](ppvm_traits::traits::CliffordBatch) methods (`x_many`,
//! `cz_many`, …) and [`data::GeneralizedTableau::cz_block`] /
//! `cz_block_pairs` / `cz_block_pairs_cross_word` apply a gate to a whole
//! contiguous block of qubits as bulk masked slice operations — the entry
//! points a thread pool or CUDA kernel would implement over the row
//! planes.
//! - `GeneralizedTableau::branch_with_coefficients` (crate-private) transforms
//! the whole coefficient array in one pass. Its `#[cfg(feature = "rayon")]`
//! path (`branch_coefficients_parallel`, gated by `RAYON_COEFF_THRESHOLD`)
//! computes the per-entry branch/non-branch coefficient math in parallel,
//! with no shared mutable state — the accumulation of those results into
//! the coefficient map afterwards remains sequential.

/// Core [`Tableau`](data::Tableau) and
/// [`GeneralizedTableau`](data::GeneralizedTableau) types.
Expand Down
34 changes: 0 additions & 34 deletions crates/ppvm-traits/src/map/dashmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,21 +78,6 @@ where
}
}

// impl<'a, S, V, State> ACMapIterMut<'a, S, V> for DashMap<PauliWord<S>, V, State>
// where
// S: PauliStorage + 'a,
// V: Coefficient + 'a,
// State: Clone + BuildHasher + 'a,
// {
// type Item = dashmap::mapref::multiple::RefMutMulti<'a, PauliWord<S>, V>;
// type IterMut =
// dashmap::iter::IterMut<'a, PauliWord<S>, V, State, DashMap<PauliWord<S>, V, State>>;

// fn iter_mut(&'a mut self) -> Self::IterMut {
// DashMap::iter_mut(self)
// }
// }

impl<'a, P, C, Hasher, W> Trace<'a, P> for DashMap<W, C, Hasher>
where
P: Sync + 'a,
Expand Down Expand Up @@ -123,11 +108,6 @@ where
.or_insert_with(|| entry.value().clone());
});

// // FIXME: clone is not very efficient when T::Coeff is an expression
// self.par_extend(
// dest.par_iter()
// .map(|entry| (entry.key().clone(), entry.value().clone())),
// );
dest.clear();
}
}
Expand All @@ -139,20 +119,6 @@ where
H: Default + Clone + BuildHasher + Send + Sync,
W: PauliWordTrait + Send + Sync,
{
fn map_insert<F>(&mut self, dest: &mut Self, f: F)
where
F: Fn(&W, &mut C) -> Option<(W, C)> + Sync + Send,
{
self.par_iter_mut().for_each(|mut entry| {
let (k, v) = entry.pair_mut();
if let Some((new_k, new_v)) = f(k, v) {
dest.entry(new_k)
.and_modify(|v| *v += new_v.clone())
.or_insert(new_v);
}
})
}

fn map_insert_vec<F>(&mut self, dest: &mut Vec<(W, C)>, f: F)
where
F: Fn(&W, &mut C) -> Option<(W, C)> + Sync + Send,
Expand Down
27 changes: 0 additions & 27 deletions crates/ppvm-traits/src/map/hashmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,20 +88,6 @@ macro_rules! impl_acmap_iter {
Self::iter(self)
}
}

// impl<'a, S, V, State> crate::traits::ACMapIterMut<'a, S, V>
// for $name<PauliWord<S>, V, State>
// where
// S: PauliStorage + 'a,
// V: Coefficient + 'a,
// State: Clone + BuildHasher + 'a,
// {
// type Item = (&'a PauliWord<S>, &'a mut V);
// type IterMut = std::collections::hash_map::IterMut<'a, PauliWord<S>, V>;
// fn iter_mut(&'a mut self) -> Self::IterMut {
// Self::iter_mut(self)
// }
// }
};
}

Expand Down Expand Up @@ -154,19 +140,6 @@ macro_rules! impl_acmap_insert {
Hasher: Default + Clone + BuildHasher + 'a,
W: PauliWordTrait + 'a,
{
fn map_insert<F>(&mut self, dest: &mut Self, f: F)
where
F: Fn(&W, &mut C) -> Option<(W, C)> + Sync + Send,
{
for (k, v) in self.iter_mut() {
if let Some((new_k, new_v)) = f(k, v) {
dest.entry(new_k)
.and_modify(|val| *val += new_v.clone())
.or_insert(new_v);
}
}
}

fn map_insert_vec<F>(&mut self, dest: &mut Vec<(W, C)>, f: F)
where
F: Fn(&W, &mut C) -> Option<(W, C)> + Sync + Send,
Expand Down
50 changes: 37 additions & 13 deletions crates/ppvm-traits/src/traits/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ pub trait ACMapMulAssign<V: Coefficient, H: BuildHasher + Clone + Default> {
fn mul_assign(&mut self, value: V);
}

/// Combined in-place modify + insert pattern used to express branching
/// gates (where one input entry can produce several output entries).
/// In-place modify + insert pattern used to express branching gates
/// (where one input entry can produce zero, one, or several output
/// entries).
pub trait ACMapInsert<
S: PauliStorage,
V: Coefficient,
Expand All @@ -69,21 +70,17 @@ pub trait ACMapInsert<
>
{
/// Modify each existing entry in place; if `f` returns `Some((k', v'))`,
/// also insert that new entry into `dest`.
fn map_insert<F>(&mut self, dest: &mut Self, f: F)
where
F: Fn(&W, &mut V) -> Option<(W, V)> + Sync + Send;

/// Like [`map_insert`](Self::map_insert) but appends produced entries
/// into a plain `Vec` (a push per entry, no hashing) instead of a map.
/// The caller merges the buffer into the destination map afterwards,
/// avoiding a second hashmap probe per produced entry.
/// append that new entry into `dest`, a plain `Vec` (a push per entry,
/// no hashing). This is the primary hot-path entry point: the caller
/// merges the buffer into the destination map afterwards, avoiding a
/// second hashmap probe per produced entry.
fn map_insert_vec<F>(&mut self, dest: &mut Vec<(W, V)>, f: F)
where
F: Fn(&W, &mut V) -> Option<(W, V)> + Sync + Send;

Comment on lines 62 to 80
/// Same as [`map_insert`](Self::map_insert) but `f` may return a
/// `Vec` of new entries to be inserted into `dest`.
/// Like [`map_insert_vec`](Self::map_insert_vec) but `f` may return a
/// `Vec` of new entries per existing entry, inserted directly into the
/// destination map `dest`.
fn map_insert_multiple<F>(&mut self, dest: &mut Self, f: F)
where
F: Fn(&W, &mut V) -> Option<Vec<(W, V)>> + Sync + Send;
Expand Down Expand Up @@ -149,6 +146,33 @@ pub trait ACMapRetain<
///
/// You don't normally implement `ACMap` directly: the blanket impl below
/// covers any type that implements all the constituent traits.
///
/// # Map-backend implementor's guide
///
/// A new backing map (a GPU-resident map, a sharded map, anything beyond
/// the existing `HashMap` / `IndexMap` / `AHashMap` / `DashMap` backends)
/// must implement exactly these traits — nothing more:
Comment on lines +152 to +154
///
/// * [`ACMapBase`] — construction, length, clear.
/// * [`ACMapIter`] — borrowing iteration over `(key, value)`.
/// * [`ACMapAddAssign`] — `+=` insert-or-accumulate.
/// * [`ACMapMulAssign`] — scalar `*=`.
/// * [`ACMapInsert`] — just `map_insert_vec` and `map_insert_multiple`
/// (there is no third variant to implement).
/// * [`ACMapContains`] — membership queries.
/// * [`ACMapScale`] — in-place per-entry transform.
/// * [`ACMapRetain`] — predicate-based entry removal.
/// * [`ACMapConsume`] — drain-and-accumulate merge of two maps.
///
/// Of these, `map_insert_vec`, `map_insert_multiple`, `scale`,
/// `mul_assign`, `retain`, and `consume` are whole-collection batch entry
/// points: a concurrent or GPU backend can dispatch each of them as one
/// parallel pass over every entry. `add_assign` is the exception — it
/// operates on a single key at a time, so a thread-pool backend typically
/// only parallelizes its batch sibling, `map_add_assign`. See the
/// `DashMap` impl in `crates/ppvm-traits/src/map/dashmap.rs` for a working
/// concurrent precedent: it implements every batch method with rayon
/// `par_iter`.
pub trait ACMap<
S: PauliStorage,
V: Coefficient,
Expand Down
3 changes: 1 addition & 2 deletions crates/ppvm-traits/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ mod hash;
mod map;
mod measure;
mod noise;
mod ptm;
mod reset;
mod storage;
mod strategy;
Expand All @@ -26,7 +25,7 @@ pub use map::{
pub use measure::{LossyMeasure, Measure};
pub use noise::{
AmplitudeDamping, AsymmetricLossChannel, CorrelatedLossChannel, Depolarizing, Depolarizing2,
LossChannel, PauliError, PauliErrorAll, ResetLossChannel, TwoQubitPauliError,
LossChannel, PauliError, ResetLossChannel, TwoQubitPauliError,
};
pub use reset::Reset;
pub use storage::PauliStorage;
Expand Down
8 changes: 0 additions & 8 deletions crates/ppvm-traits/src/traits/noise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,6 @@ pub trait PauliError<T: Config> {
}
}

/// Apply the same single-qubit Pauli error channel uniformly to every
/// qubit in the system.
pub trait PauliErrorAll<T: Config> {
/// Apply `Pauli error` with probabilities `p = [p_x, p_y, p_z]` to
/// every qubit.
fn pauli_error_all(&mut self, p: [T::Coeff; 3]);
}

/// Two-qubit Pauli error channel.
pub trait TwoQubitPauliError<T: Config> {
Comment on lines 59 to 63
/// Apply a two-qubit Pauli-error channel to one pair. Probabilities are given in the order:
Expand Down
Loading
Loading