use hashbrown::HashMap;
use num_complex::Complex64;
use rustc_hash::FxBuildHasher;
use super::*;
use crate::channel::prepared::Prepared;
use crate::channel::{Channel, OutputBuffer, Prepare, PtmChannel};
use crate::pauli_string::PauliString;
use crate::pauli_sum::PauliSum;
use crate::truncation::TruncationPolicy;
pub fn naive_apply_layer<const W: usize>(
input: &PauliSum<W>,
ch: &dyn Channel<W>,
policy: &dyn TruncationPolicy<W>,
adjoint: bool,
) -> PauliSum<W> {
let mut out = OutputBuffer::<W>::new();
let mut acc: HashMap<([u64; W], [u64; W]), Complex64, FxBuildHasher> =
HashMap::with_capacity_and_hasher(input.len(), FxBuildHasher);
for (x, z, c) in input.iter() {
out.clear();
if adjoint {
ch.apply_adjoint(x, z, c, &mut out);
} else {
ch.apply(x, z, c, &mut out);
}
for (out_x, out_z, out_c) in out.terms() {
*acc.entry((out_x, out_z)).or_insert(ZERO) += out_c;
}
}
let mut kept: Vec<([u64; W], [u64; W], Complex64)> = acc
.into_iter()
.filter(|((x, z), v)| *v != ZERO && policy.keep_term(x, z, *v))
.map(|((x, z), v)| (x, z, v))
.collect();
kept.sort_unstable_by_key(|&(x, z, _)| (x, z));
let xs: Vec<[u64; W]> = kept.iter().map(|t| t.0).collect();
let zs: Vec<[u64; W]> = kept.iter().map(|t| t.1).collect();
let cs: Vec<Complex64> = kept.iter().map(|t| t.2).collect();
PauliSum::from_key_sorted(&xs, &zs, &cs, input.hash().clone(), input.num_qubits())
}
pub fn prepare<const W: usize, C: Channel<W> + ?Sized>(
channel: &C,
hash: &crate::Gf2Hash<W>,
adjoint: bool,
) -> Option<Prepared<W>> {
channel.prepare(hash, adjoint)
}
pub fn is_key_preserving<const W: usize>(
channel: &dyn Channel<W>,
hash: &crate::Gf2Hash<W>,
) -> bool {
matches!(channel.prepare(hash, false), Some(Prepared::Local(ptm)) if ptm.is_key_preserving())
}
pub fn key_delta_masks<const W: usize>(
channel: &dyn Channel<W>,
hash: &crate::Gf2Hash<W>,
) -> Vec<([u64; W], [u64; W])> {
channel.prepare(hash, false).expect("a built-in always prepares").key_delta_masks()
}
pub fn outputs<const W: usize, C: Channel<W> + ?Sized>(
ch: &C,
adjoint: bool,
p: PauliString<W>,
coeff: Complex64,
) -> Vec<([u64; W], [u64; W], Complex64)> {
let mut v = raw_outputs(ch, adjoint, p, coeff)
.into_iter()
.map(|(q, c)| (q.x, q.z, c))
.collect::<Vec<_>>();
v.sort_by(|a, b| (a.0, a.1).cmp(&(b.0, b.1)));
v.retain(|t| t.2.norm() > 1e-15);
v
}
pub fn raw_outputs<const W: usize, C: Channel<W> + ?Sized>(
ch: &C,
adjoint: bool,
p: PauliString<W>,
coeff: Complex64,
) -> Vec<(PauliString<W>, Complex64)> {
let mut out = OutputBuffer::<W>::new();
if adjoint {
ch.apply_adjoint(&p.x, &p.z, coeff, &mut out);
} else {
ch.apply(&p.x, &p.z, coeff, &mut out);
}
out.terms().map(|(x, z, c)| (PauliString::<W> { x, z }, c)).collect()
}
pub fn differential_channels_w1() -> Vec<(&'static str, Box<dyn Channel<1>>)> {
use crate::channel::rotation::PauliRotation;
vec![
("identity", Box::new(IdentityChannel::new())),
("h", Box::new(PtmChannel::hadamard(3))),
("s", Box::new(PtmChannel::phase_gate(3))),
("x", Box::new(PtmChannel::pauli_x(3))),
("y", Box::new(PtmChannel::pauli_y(3))),
("z", Box::new(PtmChannel::pauli_z(3))),
("cnot", Box::new(PtmChannel::cnot(1, 5))),
("cz", Box::new(PtmChannel::cz(1, 5))),
("swap", Box::new(PtmChannel::swap(1, 5))),
("depolarizing", Box::new(PtmChannel::depolarizing(2, 0.07))),
("dephasing", Box::new(PtmChannel::dephasing(2, 0.07))),
("amp_damping", Box::new(PtmChannel::amplitude_damping(2, 0.3))),
("rot_z", Box::new(PauliRotation::new(PauliString::<1>::z(2), 0.41))),
(
"rot_zz",
Box::new(PauliRotation::new(
{
let mut g = PauliString::<1>::z(1);
g.mul_assign(&PauliString::<1>::z(6));
g
},
0.41,
)),
),
(
"t_gate",
Box::new(PtmChannel::unitary_1q(
2,
[
[Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)],
[
Complex64::new(0.0, 0.0),
Complex64::from_polar(1.0, std::f64::consts::FRAC_PI_4),
],
],
)),
),
(
"general_2q",
Box::new(PtmChannel::unitary_2q(1, 5, sqrt_swap_matrix())),
),
(
"haar_su4",
Box::new(PtmChannel::unitary_2q(1, 5, haar_su4_matrix())),
),
(
"rot_wide",
Box::new(PauliRotation::new(
{
let mut g = PauliString::<1>::z(0);
for q in [2, 4, 6] {
g.mul_assign(&PauliString::<1>::x(q));
}
g
},
0.41,
)),
),
]
}
pub fn differential_channels_w2() -> Vec<(&'static str, Box<dyn Channel<2>>)> {
use crate::channel::rotation::PauliRotation;
vec![
("h@70", Box::new(PtmChannel::hadamard(70))),
("s@64", Box::new(PtmChannel::phase_gate(64))),
("cnot@60,70", Box::new(PtmChannel::cnot(60, 70))),
("swap@0,127", Box::new(PtmChannel::swap(0, 127))),
("amp_damping@70", Box::new(PtmChannel::amplitude_damping(70, 0.25))),
("rot_y@70", Box::new(PauliRotation::new(PauliString::<2>::y(70), 0.33))),
(
"rot_zz_cross_word",
Box::new(PauliRotation::new(
{
let mut g = PauliString::<2>::z(9);
g.mul_assign(&PauliString::<2>::z(70));
g
},
0.33,
)),
),
("haar_su4_cross_word", Box::new(PtmChannel::unitary_2q(60, 70, haar_su4_matrix()))),
]
}
#[derive(Clone, Copy, Debug, Default)]
pub struct IdentityChannel;
impl IdentityChannel {
pub const fn new() -> Self {
Self
}
}
impl<const W: usize> Channel<W> for IdentityChannel {
fn support(&self) -> [u64; W] {
[0; W]
}
fn apply(&self, x: &[u64; W], z: &[u64; W], c: Complex64, out: &mut OutputBuffer<W>) {
out.push(*x, *z, c);
}
fn apply_adjoint(&self, x: &[u64; W], z: &[u64; W], c: Complex64, out: &mut OutputBuffer<W>) {
self.apply(x, z, c, out);
}
}
pub struct ShiftX;
impl<const W: usize> Channel<W> for ShiftX {
fn apply_adjoint(
&self,
input_x: &[u64; W],
input_z: &[u64; W],
coeff: Complex64,
out: &mut crate::channel::OutputBuffer<W>,
) {
self.apply(input_x, input_z, coeff, out);
}
fn support(&self) -> [u64; W] {
crate::channel::support_mask(&[0])
}
fn apply(&self, x: &[u64; W], z: &[u64; W], c: Complex64, out: &mut OutputBuffer<W>) {
let mut kx = *x;
kx[0] ^= 1;
out.push(kx, *z, c);
}
}
pub fn cancellation_channel() -> PtmChannel {
PtmChannel::amplitude_damping(0, 0.5)
}