use num_complex::Complex;
use crate::channel::kraus::KrausChannel;
use crate::error::{QuantumError, QuantumResult};
type Complex32 = Complex<f32>;
#[inline]
fn c(re: f32, im: f32) -> Complex32 {
Complex32::new(re, im)
}
const PROB_EPS: f32 = 1e-4;
pub fn pauli_channel(p_x: f32, p_y: f32, p_z: f32) -> QuantumResult<KrausChannel> {
if p_x < -PROB_EPS || p_y < -PROB_EPS || p_z < -PROB_EPS {
return Err(QuantumError::InvalidParameter {
name: "Pauli probabilities must be non-negative".into(),
});
}
let sum = p_x + p_y + p_z;
if sum > 1.0 + PROB_EPS {
return Err(QuantumError::InvalidParameter {
name: "Pauli probabilities must sum to at most 1".into(),
});
}
let p_i = (1.0 - sum).max(0.0);
let si = p_i.max(0.0).sqrt();
let sx = p_x.max(0.0).sqrt();
let sy = p_y.max(0.0).sqrt();
let sz = p_z.max(0.0).sqrt();
let k_i = vec![c(si, 0.0), c(0.0, 0.0), c(0.0, 0.0), c(si, 0.0)];
let k_x = vec![c(0.0, 0.0), c(sx, 0.0), c(sx, 0.0), c(0.0, 0.0)];
let k_y = vec![c(0.0, 0.0), c(0.0, -sy), c(0.0, sy), c(0.0, 0.0)];
let k_z = vec![c(sz, 0.0), c(0.0, 0.0), c(0.0, 0.0), c(-sz, 0.0)];
KrausChannel::new(vec![k_i, k_x, k_y, k_z], 2)
}
pub fn bit_flip_channel(p: f32) -> QuantumResult<KrausChannel> {
pauli_channel(p, 0.0, 0.0)
}
pub fn phase_flip_channel(p: f32) -> QuantumResult<KrausChannel> {
pauli_channel(0.0, 0.0, p)
}
pub fn bit_phase_flip_channel(p: f32) -> QuantumResult<KrausChannel> {
pauli_channel(0.0, p, 0.0)
}
pub fn pauli_twirl(channel: &KrausChannel) -> QuantumResult<KrausChannel> {
if channel.dim != 2 {
return Err(QuantumError::DimensionMismatch {
expected: 2,
got: channel.dim,
});
}
let (mut p_x, mut p_y, mut p_z) = (0.0f32, 0.0f32, 0.0f32);
let i_unit = c(0.0, 1.0);
for op in &channel.ops {
let k01 = op[1];
let k10 = op[2];
let k00 = op[0];
let k11 = op[3];
let c_x = (k01 + k10) * 0.5;
let c_y = i_unit * (k01 - k10) * 0.5;
let c_z = (k00 - k11) * 0.5;
p_x += c_x.norm_sqr();
p_y += c_y.norm_sqr();
p_z += c_z.norm_sqr();
}
pauli_channel(p_x, p_y, p_z)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::channel::noise::{amplitude_damping_channel, depolarizing_channel};
use crate::density::density::DensityMatrix;
use crate::gates::hadamard::gate_h;
use crate::statevec::apply_1q::apply_1q_inplace;
use crate::statevec::state::StateVector;
fn plus_density() -> DensityMatrix {
let mut sv =
StateVector::new_zero_state(1).expect("single-qubit zero state allocation cannot fail");
apply_1q_inplace(&mut sv, 0, &gate_h())
.expect("Hadamard on qubit 0 of a 1-qubit state vector cannot fail");
DensityMatrix::from_pure_state(&sv)
}
fn max_diff(a: &DensityMatrix, b: &DensityMatrix) -> f32 {
a.rho
.iter()
.zip(b.rho.iter())
.map(|(x, y)| (x - y).norm())
.fold(0.0, f32::max)
}
#[test]
fn pauli_channel_completeness_holds() {
assert!(pauli_channel(0.1, 0.2, 0.3).is_ok());
assert!(pauli_channel(0.0, 0.0, 0.0).is_ok()); assert!(pauli_channel(1.0, 0.0, 0.0).is_ok()); }
#[test]
fn pauli_channel_rejects_invalid_probabilities() {
assert!(pauli_channel(0.5, 0.5, 0.5).is_err()); assert!(pauli_channel(-0.1, 0.0, 0.0).is_err()); }
#[test]
fn bit_flip_channel_flips_zero_state() {
let p = 0.3f32;
let ch = bit_flip_channel(p)
.expect("bit-flip channel with probability 0.3 is within valid bounds");
let sv =
StateVector::new_zero_state(1).expect("single-qubit zero state allocation cannot fail");
let dm = DensityMatrix::from_pure_state(&sv);
let out = ch
.apply(&dm)
.expect("applying a Pauli channel to a valid density matrix cannot fail");
assert!(
(out.rho[0].re - (1.0 - p)).abs() < 1e-5,
"rho00={}",
out.rho[0].re
);
assert!((out.rho[3].re - p).abs() < 1e-5, "rho11={}", out.rho[3].re);
assert!(out.rho[1].norm() < 1e-6 && out.rho[2].norm() < 1e-6);
assert!((out.trace().re - 1.0).abs() < 1e-5);
}
#[test]
fn phase_flip_channel_kills_coherence_of_plus() {
let p = 0.25f32;
let ch = phase_flip_channel(p)
.expect("phase-flip channel with probability 0.25 is within valid bounds");
let out = ch
.apply(&plus_density())
.expect("applying a Pauli channel to a valid density matrix cannot fail");
let expected_off = (1.0 - 2.0 * p) / 2.0;
assert!(
(out.rho[1].re - expected_off).abs() < 1e-5,
"off={}",
out.rho[1].re
);
}
#[test]
fn pauli_channel_reproduces_depolarizing() {
let p = 0.4f32;
let pauli = pauli_channel(p / 4.0, p / 4.0, p / 4.0)
.expect("Pauli channel with equal probabilities summing to 0.3 is valid");
let depol = depolarizing_channel(p, 2)
.expect("depolarizing channel with probability 0.4 on a 2-dim space is valid");
let dm = plus_density();
let a = pauli
.apply(&dm)
.expect("applying a Pauli channel to a valid plus-state density matrix cannot fail");
let b = depol.apply(&dm).expect(
"applying a depolarizing channel to a valid plus-state density matrix cannot fail",
);
assert!(max_diff(&a, &b) < 1e-5, "max_diff={}", max_diff(&a, &b));
}
#[test]
fn pauli_twirl_is_identity_on_a_pauli_channel() {
let original = bit_flip_channel(0.2)
.expect("bit-flip channel with probability 0.2 is within valid bounds");
let twirled = pauli_twirl(&original)
.expect("twirling a valid single-qubit Pauli channel must succeed");
let dm = plus_density();
let a = original
.apply(&dm)
.expect("applying the original Pauli channel to a valid density matrix cannot fail");
let b = twirled
.apply(&dm)
.expect("applying the twirled Pauli channel to a valid density matrix cannot fail");
assert!(max_diff(&a, &b) < 1e-5, "max_diff={}", max_diff(&a, &b));
}
#[test]
fn pauli_twirl_of_amplitude_damping_matches_analytic_pauli_channel() {
let gamma = 0.3f32;
let ad_channel = amplitude_damping_channel(gamma)
.expect("amplitude-damping channel with γ=0.3 must construct successfully");
let twirled = pauli_twirl(&ad_channel)
.expect("Pauli-twirling a valid single-qubit amplitude-damping channel must succeed");
let s = (1.0 - gamma).sqrt();
let p_xy = gamma / 4.0;
let p_z = ((1.0 - s) / 2.0).powi(2);
let expected = pauli_channel(p_xy, p_xy, p_z)
.expect("analytically derived Pauli probabilities from amplitude damping are valid");
let dm = plus_density();
let a = twirled.apply(&dm).expect(
"twirled amplitude-damping channel applied to a valid density matrix cannot fail",
);
let b = expected
.apply(&dm)
.expect("analytic Pauli channel applied to a valid density matrix cannot fail");
assert!(max_diff(&a, &b) < 1e-5, "max_diff={}", max_diff(&a, &b));
assert!((a.trace().re - 1.0).abs() < 1e-5);
}
#[test]
fn pauli_twirl_rejects_multiqubit_channel() {
let id4: Vec<Complex32> = (0..16)
.map(|i| if i % 5 == 0 { c(1.0, 0.0) } else { c(0.0, 0.0) })
.collect();
let ch = KrausChannel::new(vec![id4], 4)
.expect("4×4 identity Kraus channel (one operator) is a valid CPTP map");
assert!(pauli_twirl(&ch).is_err());
}
}