pub(crate) mod backend;
pub(crate) mod bucketed;
mod coset;
#[cfg(feature = "cuda")]
mod cuda_context;
pub(crate) mod driver;
#[cfg(feature = "cuda")]
pub mod gpu;
pub(crate) mod knob;
mod merge;
pub(crate) mod partitioned;
pub(crate) mod plan;
#[cfg(feature = "phase-timing")]
pub(crate) mod stats;
pub(crate) mod timing;
pub(crate) mod trace;
pub(crate) mod transport;
use crate::circuit::Circuit;
use crate::pauli_sum::hash::PartitionRows;
use crate::pauli_sum::storage::{DEFAULT_MIN_BUCKETS, DEFAULT_TARGET_BUCKET_LEN};
use crate::pauli_sum::PauliSum;
use crate::truncation::TruncationPolicy;
use backend::HostPartition;
use bucketed::LayerScratch;
use driver::{run_layers, PartitionContext, PartitionWork};
use trace::assemble;
use transport::SoloTransport;
pub(crate) const LOG_TARGET: &str = "paulistrings::propagate";
pub(crate) fn progress_scope(
rank: Option<usize>,
partitions: usize,
placement: Option<&str>,
) -> String {
let mut scope = match (rank, partitions) {
(Some(rank), _) => format!("partition {rank}/{partitions}"),
(None, 1) => "1 partition".to_string(),
(None, _) => format!("{partitions} partitions"),
};
if let Some(placement) = placement {
scope.push_str(&format!(" [{placement}]"));
}
scope
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Direction {
Forward,
Heisenberg,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PropagateOptions {
pub target_bucket_len: usize,
pub min_buckets: usize,
}
const SMALLEST_MIN_BUCKETS: usize = 16;
impl PropagateOptions {
pub fn validate(&self) -> Result<(), String> {
if self.target_bucket_len == 0 {
return Err("target_bucket_len must be at least 1, got 0".to_string());
}
if self.min_buckets < SMALLEST_MIN_BUCKETS {
return Err(format!(
"min_buckets must be at least {SMALLEST_MIN_BUCKETS}, got {}",
self.min_buckets
));
}
Ok(())
}
pub(crate) fn assert_valid(&self) {
if let Err(message) = self.validate() {
panic!("{message}");
}
}
}
impl Default for PropagateOptions {
fn default() -> Self {
Self { target_bucket_len: DEFAULT_TARGET_BUCKET_LEN, min_buckets: DEFAULT_MIN_BUCKETS }
}
}
pub fn propagate<const W: usize, T>(
circuit: &Circuit<W>,
sum: PauliSum<W>,
policy: &T,
direction: Direction,
) -> PauliSum<W>
where
T: TruncationPolicy<W> + ?Sized,
{
let mut scratch = LayerScratch::<W>::new();
propagate_with(circuit, sum, policy, direction, &mut scratch, PropagateOptions::default())
}
pub fn propagate_with<const W: usize, T>(
circuit: &Circuit<W>,
sum: PauliSum<W>,
policy: &T,
direction: Direction,
scratch: &mut LayerScratch<W>,
options: PropagateOptions,
) -> PauliSum<W>
where
T: TruncationPolicy<W> + ?Sized,
{
options.assert_valid();
let num_channels = circuit.channels.len();
let terms_in = sum.len();
let started = std::time::Instant::now();
log::info!(
target: LOG_TARGET,
"propagate: {terms_in} terms through {num_channels} channels ({direction:?}), {}",
progress_scope(None, 1, None),
);
let tracing = scratch.trace.is_some();
let rows = PartitionRows::none(sum.num_qubits());
let mut work = PartitionWork {
local: HostPartition::with_layer_scratch(sum, std::mem::take(scratch)),
rows: Vec::with_capacity(if tracing { num_channels } else { 0 }),
};
let context = PartitionContext { rows: &rows, rank: 0, size: 1, tracing };
run_layers(circuit, policy, direction, options, context, &mut work, &SoloTransport);
let (sum, layer_scratch) = work.local.into_parts();
*scratch = layer_scratch;
if let Some(trace) = scratch.trace.as_mut() {
assemble(trace, vec![work.rows], 1);
}
log::info!(
target: LOG_TARGET,
"propagate: {num_channels} layers applied, {terms_in} -> {} terms, {:.3} s, {}",
sum.len(),
started.elapsed().as_secs_f64(),
progress_scope(None, 1, None),
);
sum
}
#[cfg(test)]
mod tests;