use num::Zero;
use rand::{Rng, RngExt};
use ppvm_traits::traits::{Clifford, Coefficient};
#[inline]
fn is_probability<C: PartialOrd<f64>>(p: &C) -> bool {
if *p < 0.0 {
return false;
}
*p <= 1.0
}
pub trait TableauLike: Clifford {
type Coeff: Coefficient + PartialOrd<f64>;
type Rng: Rng + RngExt;
fn rng_mut(&mut self) -> &mut Self::Rng;
#[inline]
fn is_qubit_lost(&self, _addr: usize) -> bool {
false
}
#[inline]
fn depolarize_impl(&mut self, addr0: usize, p: Self::Coeff) {
debug_assert!(is_probability(&p));
let r = self.rng_mut().random::<f64>();
if p <= r {
return;
}
if p > r * 3.0 {
self.x(addr0);
} else if p > r * 1.5 {
self.y(addr0);
} else {
self.z(addr0);
}
}
#[inline]
fn pauli_error_impl(&mut self, addr0: usize, p: [Self::Coeff; 3]) {
debug_assert!(p.iter().all(is_probability));
let r = self.rng_mut().random::<f64>();
let mut cumulative = Self::Coeff::zero();
for (i, p_) in p.iter().enumerate() {
cumulative += p_.clone();
if cumulative > r {
match i {
0 => self.x(addr0),
1 => self.y(addr0),
_ => self.z(addr0),
}
return;
}
}
}
#[inline]
fn two_qubit_pauli_error_impl(&mut self, addr0: usize, addr1: usize, p: [Self::Coeff; 15]) {
if self.is_qubit_lost(addr0) || self.is_qubit_lost(addr1) {
return;
}
debug_assert!(p.iter().all(is_probability));
let r = self.rng_mut().random::<f64>();
let sum = Self::Coeff::zero();
let idx = p
.iter()
.scan(sum, |acc, p_| {
*acc += p_.clone();
Some(acc.clone())
})
.position(|cum_prob| cum_prob > r);
if let Some(i) = idx {
#[rustfmt::skip]
const PAULI_PAIRS: [(u8, u8); 16] = [
(0,0),(0,1),(0,2),(0,3),
(1,0),(1,1),(1,2),(1,3),
(2,0),(2,1),(2,2),(2,3),
(3,0),(3,1),(3,2),(3,3),
];
let cartesian_index = PAULI_PAIRS[i + 1];
match cartesian_index.0 {
0 => {}
1 => self.x(addr0),
2 => self.y(addr0),
_ => self.z(addr0),
}
match cartesian_index.1 {
0 => {}
1 => self.x(addr1),
2 => self.y(addr1),
_ => self.z(addr1),
}
}
}
#[inline]
fn depolarize2_impl(&mut self, addr0: usize, addr1: usize, p: Self::Coeff) {
if self.is_qubit_lost(addr0) || self.is_qubit_lost(addr1) {
return;
}
debug_assert!(is_probability(&p));
let p_arr: [Self::Coeff; 15] = core::array::from_fn(|_| p.clone() * (1.0 / 15.0));
self.two_qubit_pauli_error_impl(addr0, addr1, p_arr);
}
}