Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
24 changes: 22 additions & 2 deletions crates/ppvm-python-native/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use ppvm_runtime::prelude::*;
use ppvm_runtime::strategy::{
CoefficientThreshold, CombinedStrategy, MaxLossWeight, MaxPauliWeight,
};
use ppvm_runtime::sum::PreserveConfig;
use pyo3::prelude::*;

macro_rules! create_interface_loss_methods {
Expand Down Expand Up @@ -59,21 +60,40 @@ macro_rules! create_interface {
#[pymethods]
impl $name {
#[new]
#[pyo3(signature = (n_qubits, min_abs_coeff = 1e-10, max_pauli_weight = usize::MAX, max_loss_weight = usize::MAX, terms = Vec::<String>::new(), coefficients = Vec::<f64>::new()))]
#[pyo3(signature = (n_qubits, min_abs_coeff = 1e-10, max_pauli_weight = usize::MAX, max_loss_weight = usize::MAX, terms = Vec::<String>::new(), coefficients = Vec::<f64>::new(), preserve = Vec::<String>::new(), preserve_threshold = 0.0_f64, preserve_weight_lambda = 0.0_f64))]
#[allow(clippy::too_many_arguments)]
pub fn new(
n_qubits: usize,
min_abs_coeff: f64,
max_pauli_weight: usize,
max_loss_weight: usize,
terms: Vec<String>,
coefficients: Vec<f64>
coefficients: Vec<f64>,
preserve: Vec<String>,
preserve_threshold: f64,
preserve_weight_lambda: f64,
) -> Self {
let _ = max_loss_weight; // unused in non-loss variants
let strategy = create_strategy!($loss, min_abs_coeff, max_pauli_weight, max_loss_weight);
// Build the optional preserve-aware truncation policy. When
// `preserve` is empty the configured `strategy` is used as
// before; when non-empty the listed Pauli strings are kept
// regardless of coefficient and the rest are dropped below
// `preserve_threshold * exp(preserve_weight_lambda * weight)`.
let preserve_config = if preserve.is_empty() {
None
} else {
Some(PreserveConfig::from_strings(
preserve.into_iter(),
preserve_threshold,
preserve_weight_lambda,
))
};
let mut ps = PauliSum::<$type>::builder()
.n_qubits(n_qubits)
.strategy(strategy)
.capacity(n_qubits)
.maybe_preserve(preserve_config)
.build();

assert_eq!(
Expand Down
4 changes: 2 additions & 2 deletions crates/ppvm-runtime/src/map/dashmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,9 @@ where
H: BuildHasher + Clone + Default + Sync + Send,
W: PauliWordTrait,
{
fn retain<F>(&mut self, f: F)
fn retain<F>(&mut self, mut f: F)
where
F: Fn(&W, &C) -> bool + Sync + Send,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure we don't need this? We may just be missing some tests on DashMap. It can't be too bad if it compiles, however.

F: FnMut(&W, &C) -> bool,
{
Self::retain(self, |k, v| f(k, v));
}
Expand Down
8 changes: 4 additions & 4 deletions crates/ppvm-runtime/src/map/hashmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,9 @@ macro_rules! impl_acmap_retain {
H: BuildHasher + Clone + Default,
W: PauliWordTrait,
{
fn retain<F>(&mut self, f: F)
fn retain<F>(&mut self, mut f: F)
where
F: Fn(&W, &V) -> bool + Sync + Send,
F: FnMut(&W, &V) -> bool,
{
Self::retain(self, |k, v| f(k, v));
}
Expand Down Expand Up @@ -351,9 +351,9 @@ mod ahash_impl {
H: BuildHasher + Clone + Default,
W: PauliWordTrait,
{
fn retain<F>(&mut self, f: F)
fn retain<F>(&mut self, mut f: F)
where
F: Fn(&W, &V) -> bool + Sync + Send,
F: FnMut(&W, &V) -> bool,
{
HashMap::retain(self, |k, v| f(k, v));
}
Expand Down
44 changes: 41 additions & 3 deletions crates/ppvm-runtime/src/sum/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

use crate::config::Config;
use crate::sum::preserve::PreserveConfig;
use crate::traits::*;

/// A sparse formal sum `Σ cᵢ Pᵢ` of Pauli strings.
Expand Down Expand Up @@ -42,6 +43,12 @@ pub struct PauliSum<T: Config> {
n_qubits: usize,
capacity: usize,
strategy: T::Strategy,
/// Optional observable-aware truncation policy. When `Some(...)`,
/// [`PauliSum::truncate`] uses this instead of `strategy`: the
/// chosen Pauli strings are never dropped, and the rest are
/// truncated with a weight-biased threshold. See
/// [`PreserveConfig`].
preserve: Option<PreserveConfig<T::PauliWordType>>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of the overall implementation here, tbh. Setting this is a flag that completely alters truncation behavior. It also breaks composition with other truncation strategies. You can't use this together with CoefficientThreshold, which you now work around by making the preservation truncation basically a combination of that strategy and the coefficient cutoff (I think, I'm not sure I fully understand the exponential there).

}

#[bon::bon]
Expand All @@ -51,6 +58,8 @@ impl<T: Config> PauliSum<T> {
/// One can optionally set
/// - the strategy for truncation, initialization etc.
/// - the capacity of the internal maps, default is strategy.capacity(n_qubits)
/// - a [`PreserveConfig`] for observable-aware truncation; when set
/// it supersedes `strategy` inside [`truncate`](Self::truncate).
#[builder]
pub fn new(
/// number of qubits
Expand All @@ -61,6 +70,8 @@ impl<T: Config> PauliSum<T> {
/// capacity of the internal maps, default is strategy.capacity(n_qubits)
#[builder(default = strategy.capacity(n_qubits))]
capacity: usize,
/// optional observable-aware truncation policy
preserve: Option<PreserveConfig<T::PauliWordType>>,
) -> Self {
Self {
map: (
Expand All @@ -71,6 +82,7 @@ impl<T: Config> PauliSum<T> {
n_qubits,
capacity,
strategy,
preserve,
}
}
}
Expand Down Expand Up @@ -240,10 +252,36 @@ impl<T: Config> PauliSum<T> {
}

/// Apply the configured truncation [`Strategy`](crate::traits::Strategy)
/// to the primary map, dropping entries that fall outside its policy.
/// — or, if a [`PreserveConfig`] was supplied at construction, use
/// the preserve-aware policy instead — to the primary map, dropping
/// entries that fall outside the active policy.
pub fn truncate(&mut self) {
let strategy = self.strategy;
strategy.truncate(self.data_mut());
if self.preserve.is_some() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above. This is odd and doesn't compose with other truncation strategies anymore.

// Capture the keep-set directly as `HashSet<W>` so per-key
// lookup is O(1) with no string conversion. `W: Send + Sync`
// is part of the `PauliWordTrait` bundle, so the closure
// satisfies `ACMap::retain`'s `Sync + Send` requirement.
let preserve = self.preserve.as_ref().unwrap();
let keep = preserve.keep.clone();
let base = preserve.base_threshold;
let lambda = preserve.weight_lambda;
let data = self.data_mut();
data.retain(|k, v| {
if keep.contains(k) {
return true;
}
let eff = base * (lambda * k.weight() as f64).exp();
!v.cutoff(eff)
});
} else {
let strategy = self.strategy;
strategy.truncate(self.data_mut());
}
}

/// Read-only access to the active [`PreserveConfig`], if any.
pub fn preserve(&self) -> Option<&PreserveConfig<T::PauliWordType>> {
self.preserve.as_ref()
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/ppvm-runtime/src/sum/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod data;
mod display;
mod noise;
mod ops;
mod preserve;
mod proj;
mod rot1;
mod rot2;
Expand All @@ -16,3 +17,4 @@ mod approx;

pub use data::PauliSum;
pub use ops::impl_op_mul_assign_coefficient;
pub use preserve::PreserveConfig;
Loading
Loading