use crate::data::GeneralizedTableau;
use crate::prelude::*;
use bitvec::view::BitView;
use num::PrimInt;
use num::complex::{Complex, Complex64, ComplexFloat};
use num::traits::{One, ToPrimitive, Zero};
use ppvm_pauli_word::pattern::PauliPattern;
use ppvm_traits::FoldHashMap as HashMap;
use std::fmt::Debug;
impl<T, I, C> GeneralizedTableau<T, I, C>
where
T: Config,
<<T as Config>::Storage as BitView>::Store: PrimInt,
C: SparseVector<Complex<T::Coeff>, I> + Debug,
T::Coeff:
One + Zero + Clone + num::Num + ToPrimitive + Debug + std::ops::Mul<f64> + PartialOrd<f64>,
Complex<T::Coeff>: std::ops::Mul<Output = Complex<T::Coeff>>
+ From<Complex64>
+ std::ops::MulAssign
+ std::ops::AddAssign
+ One
+ ComplexFloat
+ Copy,
I: TableauIndex + Debug,
{
pub fn expectation<W: PauliWordTrait>(&self, word: &W) -> f64 {
let (phase, stab_anticomm, destab_anticomm) = self.compute_decomposition_word(word);
if stab_anticomm == I::zero() {
let entries: Vec<(Complex<T::Coeff>, I)> = self.coefficients.iter().copied().collect();
Self::compute_overlap_case_b(&entries, phase, destab_anticomm)
} else {
let coeff_map: HashMap<I, Complex<T::Coeff>> =
self.coefficients.iter().map(|&(c, i)| (i, c)).collect();
let odd_phase_mask = self.odd_phase_destabilizer_mask();
Self::compute_overlap_case_a(
&coeff_map,
phase,
destab_anticomm,
stab_anticomm,
odd_phase_mask,
)
}
}
pub fn trace(&self, pattern: &PauliPattern) -> f64 {
let mut sum = 0.0f64;
for word in pattern.enumerate_matches::<T::Storage>(self.n_qubits()) {
sum += self.expectation(&word);
}
sum
}
}
#[cfg(test)]
mod tests {
use super::*;
use ppvm_pauli_sum::config::fxhash::ByteF64;
type TestTableau = GeneralizedTableau<ByteF64<1>>;
fn word(s: &str) -> PauliWord<u64> {
s.into()
}
fn assert_close(actual: f64, expected: f64, tol: f64) {
assert!(
(actual - expected).abs() < tol,
"expected {expected}, got {actual} (|Δ| = {})",
(actual - expected).abs()
);
}
#[test]
fn expectation_z_on_zero_state_is_one() {
let tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
assert_close(tab.expectation(&word("Z")), 1.0, 1e-12);
}
#[test]
fn expectation_x_on_zero_state_is_zero() {
let tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
assert_close(tab.expectation(&word("X")), 0.0, 1e-12);
}
#[test]
fn expectation_identity_on_zero_state_is_one() {
let tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
assert_close(tab.expectation(&word("I")), 1.0, 1e-12);
}
#[test]
fn expectation_x_on_plus_state_is_one() {
let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
tab.h(0);
assert_close(tab.expectation(&word("X")), 1.0, 1e-12);
assert_close(tab.expectation(&word("Z")), 0.0, 1e-12);
}
fn bell() -> TestTableau {
let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
tab.h(0);
tab.cnot(0, 1);
tab
}
#[test]
fn bell_state_pauli_expectations() {
let tab = bell();
assert_close(tab.expectation(&word("II")), 1.0, 1e-12);
assert_close(tab.expectation(&word("ZZ")), 1.0, 1e-12);
assert_close(tab.expectation(&word("XX")), 1.0, 1e-12);
assert_close(tab.expectation(&word("YY")), -1.0, 1e-12);
assert_close(tab.expectation(&word("IZ")), 0.0, 1e-12);
assert_close(tab.expectation(&word("ZI")), 0.0, 1e-12);
assert_close(tab.expectation(&word("XZ")), 0.0, 1e-12);
assert_close(tab.expectation(&word("YX")), 0.0, 1e-12);
}
#[test]
fn ghz_state_expectations() {
let mut tab: TestTableau = GeneralizedTableau::new(3, 1e-12);
tab.h(0);
tab.cnot(0, 1);
tab.cnot(1, 2);
assert_close(tab.expectation(&word("III")), 1.0, 1e-12); assert_close(tab.expectation(&word("ZZZ")), 0.0, 1e-12); assert_close(tab.expectation(&word("ZIZ")), 1.0, 1e-12); assert_close(tab.expectation(&word("ZZI")), 1.0, 1e-12); assert_close(tab.expectation(&word("IZI")), 0.0, 1e-12); assert_close(tab.expectation(&word("XXX")), 1.0, 1e-12);
assert_close(tab.expectation(&word("YYY")), 0.0, 1e-12);
}
#[test]
fn ry_rotation_z_expectation_is_cos_theta() {
for theta in [0.0, 0.3, 1.0, std::f64::consts::PI / 2.0] {
let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
tab.ry(0, theta);
assert_close(tab.expectation(&word("Z")), theta.cos(), 1e-12);
assert_close(tab.expectation(&word("X")), theta.sin(), 1e-12);
}
}
#[test]
fn t_plus_x_expectation_is_cos_pi_over_4() {
let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
tab.h(0);
tab.t(0);
assert_close(
tab.expectation(&word("X")),
std::f64::consts::FRAC_1_SQRT_2,
1e-12,
);
}
#[test]
fn t_plus_y_expectation_is_sin_pi_over_4() {
let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
tab.h(0);
tab.t(0);
assert_close(
tab.expectation(&word("Y")),
std::f64::consts::FRAC_1_SQRT_2,
1e-12,
);
}
#[test]
fn trace_of_z_or_identity_pattern_on_bell_is_two() {
let tab = bell();
let pat = PauliPattern::parse("Z?{2}").expect("parse Z?{2}");
assert_close(tab.trace(&pat), 2.0, 1e-12);
}
#[test]
fn trace_of_y_or_identity_pattern_on_bell_is_zero() {
let tab = bell();
let pat = PauliPattern::parse("Y?{2}").expect("parse Y?{2}");
assert_close(tab.trace(&pat), 0.0, 1e-12);
}
#[test]
fn trace_of_positional_pattern_on_bell_matches_single_pauli() {
let tab = bell();
let pat = PauliPattern::parse("Z0Z1").expect("parse Z0Z1");
assert_close(tab.trace(&pat), 1.0, 1e-12);
}
#[test]
fn forward_shots_match_backward_pauli_sum_under_depolarizing_noise() {
use ppvm_pauli_sum::config::indexmap::ByteFxHashF64;
let p = 0.05_f64;
let n_shots: u64 = 4000;
let n_qubits = 2;
let mut sum = 0.0_f64;
for shot in 0..n_shots {
let mut tab: TestTableau = GeneralizedTableau::new_with_seed(n_qubits, 1e-12, shot);
tab.h(0);
tab.depolarize1(0, p);
tab.cnot(0, 1);
tab.depolarize1(0, p);
tab.depolarize1(1, p);
sum += tab.expectation(&word("ZZ"));
}
let avg = sum / (n_shots as f64);
let mut ps: PauliSum<ByteFxHashF64<1>> = PauliSum::builder().n_qubits(n_qubits).build();
ps += ("ZZ", 1.0);
ps.depolarize1(1, p);
ps.depolarize1(0, p);
ps.cnot(0, 1);
ps.depolarize1(0, p);
ps.h(0);
let z_or_i = PauliPattern::parse("Z?{2}").expect("parse Z?{2}");
let exact = ps.trace(&z_or_i);
let tol = 5.0 / (n_shots as f64).sqrt();
assert!(
(avg - exact).abs() < tol,
"tableau avg {avg} vs PauliSum exact {exact}, |Δ|={} (tol {tol})",
(avg - exact).abs()
);
}
}