use crate::{
backend::backend::{Gate1, Gate2, QuantumBackend},
error::Result,
};
use async_trait::async_trait;
use rand::rngs::StdRng;
use rand::{Rng, RngCore, SeedableRng, rng};
use std::collections::HashMap;
#[derive(Copy, Clone, Debug)]
enum SinglePauli {
I,
X,
Y,
Z,
}
impl SinglePauli {
#[inline]
async fn apply<B: QuantumBackend>(self, backend: &mut B, qubit: usize) -> Result<()> {
match self {
SinglePauli::I => Ok(()),
SinglePauli::X => backend.apply_single_gate(qubit, Gate1::X).await,
SinglePauli::Y => backend.apply_single_gate(qubit, Gate1::Y).await,
SinglePauli::Z => backend.apply_single_gate(qubit, Gate1::Z).await,
}
}
}
#[inline]
fn decode_two_bits(code: u8) -> SinglePauli {
match code & 0b11 {
0 => SinglePauli::I,
1 => SinglePauli::X,
2 => SinglePauli::Y,
_ => SinglePauli::Z,
}
}
pub struct NoisyBackend<B: QuantumBackend> {
inner: B,
depolarizing_rate: f64,
measurement_error: f64,
measured_bits: HashMap<usize, u8>,
rng: StdRng,
}
impl<B: QuantumBackend> NoisyBackend<B> {
fn clamp01(x: f64) -> f64 {
if x.is_finite() {
x.clamp(0.0, 1.0)
} else {
0.0
}
}
pub fn new(inner: B, depolarizing_rate: f64, measurement_error: f64) -> Self {
let mut thread_rng = rng();
let seed = thread_rng.next_u64();
Self::new_with_seed(inner, depolarizing_rate, measurement_error, seed)
}
pub fn new_with_seed(
inner: B,
depolarizing_rate: f64,
measurement_error: f64,
seed: u64,
) -> Self {
Self {
inner,
depolarizing_rate: Self::clamp01(depolarizing_rate),
measurement_error: Self::clamp01(measurement_error),
measured_bits: HashMap::new(),
rng: StdRng::seed_from_u64(seed),
}
}
#[inline]
async fn apply_noise_1q(&mut self, qubit: usize) -> Result<()> {
if self.rng.random::<f64>() < self.depolarizing_rate {
match self.rng.random_range(0u8..3) {
0 => self.inner.apply_single_gate(qubit, Gate1::X).await?,
1 => self.inner.apply_single_gate(qubit, Gate1::Y).await?,
_ => self.inner.apply_single_gate(qubit, Gate1::Z).await?,
}
}
Ok(())
}
#[inline]
async fn apply_noise_2q(&mut self, q1: usize, q2: usize) -> Result<()> {
let p2 = (2.0 * self.depolarizing_rate).clamp(0.0, 1.0);
if self.rng.random::<f64>() < p2 {
let code = self.rng.random_range(1u8..=15u8);
let p_left = decode_two_bits(code >> 2);
let p_right = decode_two_bits(code);
p_left.apply(&mut self.inner, q1).await?;
p_right.apply(&mut self.inner, q2).await?;
}
Ok(())
}
}
#[async_trait]
impl<B: QuantumBackend> QuantumBackend for NoisyBackend<B> {
async fn apply_single_gate(&mut self, qubit: usize, gate: Gate1) -> Result<()> {
self.measured_bits.remove(&qubit);
self.inner.apply_single_gate(qubit, gate).await?;
self.apply_noise_1q(qubit).await
}
async fn apply_two_gate(&mut self, q1: usize, q2: usize, gate: Gate2) -> Result<()> {
self.measured_bits.remove(&q1);
self.measured_bits.remove(&q2);
self.inner.apply_two_gate(q1, q2, gate).await?;
self.apply_noise_2q(q1, q2).await
}
async fn measure(&mut self, qubit: usize) -> Result<u8> {
if let Some(&bit) = self.measured_bits.get(&qubit) {
return Ok(bit);
}
let raw_result = self.inner.measure(qubit).await?;
let final_result = if self.rng.random::<f64>() < self.measurement_error {
1 - raw_result
} else {
raw_result
};
self.measured_bits.insert(qubit, final_result);
Ok(final_result)
}
async fn create_entanglement(&mut self, q1: usize, q2: usize) -> Result<()> {
self.measured_bits.remove(&q1);
self.measured_bits.remove(&q2);
self.inner.create_entanglement(q1, q2).await?;
self.apply_noise_2q(q1, q2).await
}
fn qubit_count(&self) -> usize {
self.inner.qubit_count()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::backend::state_vector::StateVectorBackend;
#[tokio::test]
async fn test_measurement_caching() {
let inner = StateVectorBackend::new(1);
let mut noisy = NoisyBackend::new_with_seed(inner, 0.0, 0.5, 42);
let first = noisy.measure(0).await.unwrap();
let second = noisy.measure(0).await.unwrap();
assert_eq!(
first, second,
"Repeated measurements should return same value"
);
}
#[tokio::test]
async fn test_cache_invalidation() {
let inner = StateVectorBackend::new(1);
let mut noisy = NoisyBackend::new_with_seed(inner, 0.0, 0.0, 42);
let first = noisy.measure(0).await.unwrap();
noisy.apply_single_gate(0, Gate1::X).await.unwrap();
let second = noisy.measure(0).await.unwrap();
assert_ne!(first, second, "Gate should invalidate cached measurement");
}
#[tokio::test]
async fn test_noise_rates_clamped() {
let inner = StateVectorBackend::new(1);
let noisy = NoisyBackend::new(inner, -0.5, 1.5);
assert_eq!(noisy.depolarizing_rate, 0.0);
assert_eq!(noisy.measurement_error, 1.0);
}
#[tokio::test]
async fn test_deterministic_with_seed() {
let inner1 = StateVectorBackend::new(1);
let mut noisy1 = NoisyBackend::new_with_seed(inner1, 0.0, 1.0, 12345);
let inner2 = StateVectorBackend::new(1);
let mut noisy2 = NoisyBackend::new_with_seed(inner2, 0.0, 1.0, 12345);
let m1 = noisy1.measure(0).await.unwrap();
let m2 = noisy2.measure(0).await.unwrap();
assert_eq!(
m1, m2,
"Same seed should give deterministic measurement errors"
);
assert_eq!(m1, 1, "100% measurement error should flip |0⟩ to 1");
}
}