aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md28
-rw-r--r--libpsi-core/src/core/mod.rs2
-rw-r--r--libpsi-core/src/core/noise.rs560
-rw-r--r--libpsi-core/src/lib.rs1
-rw-r--r--tester/src/main.rs8
-rw-r--r--tester/src/noise.rs172
6 files changed, 771 insertions, 0 deletions
diff --git a/README.md b/README.md
index 4605e24..c5707c8 100644
--- a/README.md
+++ b/README.md
@@ -61,6 +61,34 @@ Automatic detection and use of platform-specific SIMD instructions:
- Multi-pass fusion until convergence
- Execution layer grouping for parallelism
+### Noise Channels (Density Matrix)
+
+Realistic quantum noise simulation using Kraus operators:
+
+| Channel | Description |
+|---------|-------------|
+| `depolarising(p)` | Random Pauli error with probability $p$ |
+| `amplitude_damping(γ)` | Energy decay ($T_1$ relaxation) |
+| `phase_damping(γ)` | Phase decoherence ($T_2$ dephasing) |
+| `bit_flip(p)` | $X$ error with probability $p$ |
+| `phase_flip(p)` | $Z$ error with probability $p$ |
+| `bit_phase_flip(p)` | $Y$ error with probability $p$ |
+
+```rust
+use libpsi_core::{DensityMatrix, NoiseChannel};
+
+// Create density matrix from circuit state
+let dm = DensityMatrix::from_state_vector(&state_vec);
+
+// Apply noise
+let noise = NoiseChannel::depolarising(0.05);
+dm.apply_noise_channel(&noise, 0); // Apply to qubit 0
+
+// Check properties
+println!("Purity: {}", dm.purity()); // 1.0 = pure, <1.0 = mixed
+println!("Fidelity: {}", dm.fidelity_with_pure_state(&ideal_state));
+```
+
## Project Structure
- **`libpsi-core`**: Core quantum simulation library
diff --git a/libpsi-core/src/core/mod.rs b/libpsi-core/src/core/mod.rs
index 343e319..c38cf38 100644
--- a/libpsi-core/src/core/mod.rs
+++ b/libpsi-core/src/core/mod.rs
@@ -3,6 +3,7 @@ pub mod classical_components;
pub mod custom_gate;
pub mod gates;
pub mod kernel;
+pub mod noise;
pub mod quantum_components;
pub mod runtime;
@@ -11,5 +12,6 @@ pub use classical_components::*;
pub use custom_gate::*;
pub use gates::*;
pub use kernel::*;
+pub use noise::*;
pub use quantum_components::*;
pub use runtime::*;
diff --git a/libpsi-core/src/core/noise.rs b/libpsi-core/src/core/noise.rs
new file mode 100644
index 0000000..6836c00
--- /dev/null
+++ b/libpsi-core/src/core/noise.rs
@@ -0,0 +1,560 @@
+use crate::{complex, Complex, Matrix};
+
+#[derive(Clone, Debug)]
+pub struct KrausOperator {
+ pub matrix: Matrix<Complex<f64>>,
+ pub name: String,
+}
+
+impl KrausOperator {
+ pub fn new(name: &str, matrix: Matrix<Complex<f64>>) -> Self {
+ Self {
+ matrix,
+ name: name.to_string(),
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct NoiseChannel {
+ pub name: String,
+ pub operators: Vec<KrausOperator>,
+ pub num_qubits: usize,
+}
+
+impl NoiseChannel {
+ pub fn new(name: &str, operators: Vec<KrausOperator>, num_qubits: usize) -> Self {
+ Self {
+ name: name.to_string(),
+ operators,
+ num_qubits,
+ }
+ }
+
+ pub fn depolarising(p: f64) -> Self {
+ let sqrt_1_p = (1.0 - p).sqrt();
+ let sqrt_p3 = (p / 3.0).sqrt();
+
+ let k0 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(sqrt_1_p, 0.0),
+ complex!(0.0, 0.0),
+ complex!(0.0, 0.0),
+ complex!(sqrt_1_p, 0.0),
+ ],
+ );
+
+ let k1 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(0.0, 0.0),
+ complex!(sqrt_p3, 0.0),
+ complex!(sqrt_p3, 0.0),
+ complex!(0.0, 0.0),
+ ],
+ );
+
+ let k2 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(0.0, 0.0),
+ complex!(0.0, -sqrt_p3),
+ complex!(0.0, sqrt_p3),
+ complex!(0.0, 0.0),
+ ],
+ );
+
+ let k3 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(sqrt_p3, 0.0),
+ complex!(0.0, 0.0),
+ complex!(0.0, 0.0),
+ complex!(-sqrt_p3, 0.0),
+ ],
+ );
+
+ Self::new(
+ "Depolarising",
+ vec![
+ KrausOperator::new("K0", k0),
+ KrausOperator::new("K1(X)", k1),
+ KrausOperator::new("K2(Y)", k2),
+ KrausOperator::new("K3(Z)", k3),
+ ],
+ 1,
+ )
+ }
+
+ pub fn amplitude_damping(gamma: f64) -> Self {
+ let sqrt_gamma = gamma.sqrt();
+ let sqrt_1_gamma = (1.0 - gamma).sqrt();
+
+ let k0 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(1.0, 0.0),
+ complex!(0.0, 0.0),
+ complex!(0.0, 0.0),
+ complex!(sqrt_1_gamma, 0.0),
+ ],
+ );
+
+ let k1 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(0.0, 0.0),
+ complex!(sqrt_gamma, 0.0),
+ complex!(0.0, 0.0),
+ complex!(0.0, 0.0),
+ ],
+ );
+
+ Self::new(
+ "AmplitudeDamping",
+ vec![
+ KrausOperator::new("K0", k0),
+ KrausOperator::new("K1", k1),
+ ],
+ 1,
+ )
+ }
+
+ pub fn phase_damping(gamma: f64) -> Self {
+ let sqrt_gamma = gamma.sqrt();
+ let sqrt_1_gamma = (1.0 - gamma).sqrt();
+
+ let k0 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(1.0, 0.0),
+ complex!(0.0, 0.0),
+ complex!(0.0, 0.0),
+ complex!(sqrt_1_gamma, 0.0),
+ ],
+ );
+
+ let k1 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(0.0, 0.0),
+ complex!(0.0, 0.0),
+ complex!(0.0, 0.0),
+ complex!(sqrt_gamma, 0.0),
+ ],
+ );
+
+ Self::new(
+ "PhaseDamping",
+ vec![
+ KrausOperator::new("K0", k0),
+ KrausOperator::new("K1", k1),
+ ],
+ 1,
+ )
+ }
+
+ pub fn bit_flip(p: f64) -> Self {
+ let sqrt_1_p = (1.0 - p).sqrt();
+ let sqrt_p = p.sqrt();
+
+ let k0 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(sqrt_1_p, 0.0),
+ complex!(0.0, 0.0),
+ complex!(0.0, 0.0),
+ complex!(sqrt_1_p, 0.0),
+ ],
+ );
+
+ let k1 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(0.0, 0.0),
+ complex!(sqrt_p, 0.0),
+ complex!(sqrt_p, 0.0),
+ complex!(0.0, 0.0),
+ ],
+ );
+
+ Self::new(
+ "BitFlip",
+ vec![
+ KrausOperator::new("K0(I)", k0),
+ KrausOperator::new("K1(X)", k1),
+ ],
+ 1,
+ )
+ }
+
+ pub fn phase_flip(p: f64) -> Self {
+ let sqrt_1_p = (1.0 - p).sqrt();
+ let sqrt_p = p.sqrt();
+
+ let k0 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(sqrt_1_p, 0.0),
+ complex!(0.0, 0.0),
+ complex!(0.0, 0.0),
+ complex!(sqrt_1_p, 0.0),
+ ],
+ );
+
+ let k1 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(sqrt_p, 0.0),
+ complex!(0.0, 0.0),
+ complex!(0.0, 0.0),
+ complex!(-sqrt_p, 0.0),
+ ],
+ );
+
+ Self::new(
+ "PhaseFlip",
+ vec![
+ KrausOperator::new("K0(I)", k0),
+ KrausOperator::new("K1(Z)", k1),
+ ],
+ 1,
+ )
+ }
+
+ pub fn bit_phase_flip(p: f64) -> Self {
+ let sqrt_1_p = (1.0 - p).sqrt();
+ let sqrt_p = p.sqrt();
+
+ let k0 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(sqrt_1_p, 0.0),
+ complex!(0.0, 0.0),
+ complex!(0.0, 0.0),
+ complex!(sqrt_1_p, 0.0),
+ ],
+ );
+
+ let k1 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(0.0, 0.0),
+ complex!(0.0, -sqrt_p),
+ complex!(0.0, sqrt_p),
+ complex!(0.0, 0.0),
+ ],
+ );
+
+ Self::new(
+ "BitPhaseFlip",
+ vec![
+ KrausOperator::new("K0(I)", k0),
+ KrausOperator::new("K1(Y)", k1),
+ ],
+ 1,
+ )
+ }
+
+ pub fn generalised_amplitude_damping(p: f64, gamma: f64) -> Self {
+ let sqrt_p = p.sqrt();
+ let sqrt_1_p = (1.0 - p).sqrt();
+ let sqrt_gamma = gamma.sqrt();
+ let sqrt_1_gamma = (1.0 - gamma).sqrt();
+
+ let k0 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(sqrt_p, 0.0),
+ complex!(0.0, 0.0),
+ complex!(0.0, 0.0),
+ complex!(sqrt_p * sqrt_1_gamma, 0.0),
+ ],
+ );
+
+ let k1 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(0.0, 0.0),
+ complex!(sqrt_p * sqrt_gamma, 0.0),
+ complex!(0.0, 0.0),
+ complex!(0.0, 0.0),
+ ],
+ );
+
+ let k2 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(sqrt_1_p * sqrt_1_gamma, 0.0),
+ complex!(0.0, 0.0),
+ complex!(0.0, 0.0),
+ complex!(sqrt_1_p, 0.0),
+ ],
+ );
+
+ let k3 = Matrix::new(
+ 2,
+ 2,
+ vec![
+ complex!(0.0, 0.0),
+ complex!(0.0, 0.0),
+ complex!(sqrt_1_p * sqrt_gamma, 0.0),
+ complex!(0.0, 0.0),
+ ],
+ );
+
+ Self::new(
+ "GeneralisedAmplitudeDamping",
+ vec![
+ KrausOperator::new("K0", k0),
+ KrausOperator::new("K1", k1),
+ KrausOperator::new("K2", k2),
+ KrausOperator::new("K3", k3),
+ ],
+ 1,
+ )
+ }
+}
+
+#[derive(Clone)]
+pub struct DensityMatrix {
+ pub data: Vec<Complex<f64>>,
+ pub dim: usize,
+ pub num_qubits: usize,
+}
+
+impl DensityMatrix {
+ pub fn new(num_qubits: usize) -> Self {
+ let dim = 1 << num_qubits;
+ let mut data = vec![complex!(0.0, 0.0); dim * dim];
+ data[0] = complex!(1.0, 0.0);
+ Self {
+ data,
+ dim,
+ num_qubits,
+ }
+ }
+
+ pub fn from_state_vector(state: &[Complex<f64>]) -> Self {
+ let dim = state.len();
+ let num_qubits = (dim as f64).log2() as usize;
+ let mut data = vec![complex!(0.0, 0.0); dim * dim];
+
+ for i in 0..dim {
+ for j in 0..dim {
+ data[i * dim + j] = state[i] * state[j].get_conjugate();
+ }
+ }
+
+ Self {
+ data,
+ dim,
+ num_qubits,
+ }
+ }
+
+ pub fn get(&self, row: usize, col: usize) -> Complex<f64> {
+ self.data[row * self.dim + col]
+ }
+
+ pub fn set(&mut self, row: usize, col: usize, value: Complex<f64>) {
+ self.data[row * self.dim + col] = value;
+ }
+
+ pub fn trace(&self) -> Complex<f64> {
+ let mut sum = complex!(0.0, 0.0);
+ for i in 0..self.dim {
+ sum = sum + self.get(i, i);
+ }
+ sum
+ }
+
+ pub fn purity(&self) -> f64 {
+ let mut sum = complex!(0.0, 0.0);
+ for i in 0..self.dim {
+ for j in 0..self.dim {
+ let rho_ij = self.get(i, j);
+ let rho_ji = self.get(j, i);
+ sum = sum + rho_ij * rho_ji;
+ }
+ }
+ sum.real
+ }
+
+ pub fn is_pure(&self, tolerance: f64) -> bool {
+ (self.purity() - 1.0).abs() < tolerance
+ }
+
+ pub fn probabilities(&self) -> Vec<f64> {
+ (0..self.dim).map(|i| self.get(i, i).real).collect()
+ }
+
+ pub fn apply_unitary(&mut self, gate: &Matrix<Complex<f64>>, targets: &[usize]) {
+ let g = targets.len();
+ let gate_dim = 1 << g;
+
+ let target_bits: Vec<usize> = targets
+ .iter()
+ .map(|&t| self.num_qubits - 1 - t)
+ .collect();
+
+ let mut non_target_mask: usize = (1 << self.num_qubits) - 1;
+ for &pos in &target_bits {
+ non_target_mask &= !(1 << pos);
+ }
+
+ let mut new_data = vec![complex!(0.0, 0.0); self.dim * self.dim];
+
+ for i in 0..self.dim {
+ for j in 0..self.dim {
+ let mut sum = complex!(0.0, 0.0);
+
+ for k in 0..gate_dim {
+ for l in 0..gate_dim {
+ let mut src_i = i & non_target_mask;
+ let mut src_j = j & non_target_mask;
+
+ for (idx, &pos) in target_bits.iter().enumerate() {
+ if (k >> (g - 1 - idx)) & 1 == 1 {
+ src_i |= 1 << pos;
+ }
+ if (l >> (g - 1 - idx)) & 1 == 1 {
+ src_j |= 1 << pos;
+ }
+ }
+
+ let mut tgt_i = 0usize;
+ let mut tgt_j = 0usize;
+ for (idx, &pos) in target_bits.iter().enumerate() {
+ if (i >> pos) & 1 == 1 {
+ tgt_i |= 1 << (g - 1 - idx);
+ }
+ if (j >> pos) & 1 == 1 {
+ tgt_j |= 1 << (g - 1 - idx);
+ }
+ }
+
+ let u_ik = gate.data[tgt_i * gate_dim + k];
+ let u_jl_dag = gate.data[tgt_j * gate_dim + l].get_conjugate();
+ let rho_kl = self.get(src_i, src_j);
+
+ sum = sum + u_ik * rho_kl * u_jl_dag;
+ }
+ }
+
+ new_data[i * self.dim + j] = sum;
+ }
+ }
+
+ self.data = new_data;
+ }
+
+ pub fn apply_noise_channel(&mut self, channel: &NoiseChannel, target: usize) {
+ if channel.num_qubits != 1 {
+ panic!("Only single-qubit noise channels are currently supported");
+ }
+
+ let target_bit = self.num_qubits - 1 - target;
+ let mut new_data = vec![complex!(0.0, 0.0); self.dim * self.dim];
+
+ for kraus in &channel.operators {
+ let k = &kraus.matrix;
+
+ for i in 0..self.dim {
+ for j in 0..self.dim {
+ let i_target = (i >> target_bit) & 1;
+ let j_target = (j >> target_bit) & 1;
+
+ for ki in 0..2 {
+ for kj in 0..2 {
+ let src_i = (i & !(1 << target_bit)) | (ki << target_bit);
+ let src_j = (j & !(1 << target_bit)) | (kj << target_bit);
+
+ let k_elem = k.data[i_target * 2 + ki];
+ let k_dag_elem = k.data[j_target * 2 + kj].get_conjugate();
+ let rho_elem = self.get(src_i, src_j);
+
+ new_data[i * self.dim + j] =
+ new_data[i * self.dim + j] + k_elem * rho_elem * k_dag_elem;
+ }
+ }
+ }
+ }
+ }
+
+ self.data = new_data;
+ }
+
+ pub fn measure_probability(&self, qubit: usize, outcome: usize) -> f64 {
+ let target_bit = self.num_qubits - 1 - qubit;
+ let mut prob = 0.0;
+
+ for i in 0..self.dim {
+ if (i >> target_bit) & 1 == outcome {
+ prob += self.get(i, i).real;
+ }
+ }
+
+ prob
+ }
+
+ pub fn fidelity_with_pure_state(&self, state: &[Complex<f64>]) -> f64 {
+ let mut sum = complex!(0.0, 0.0);
+
+ for i in 0..self.dim {
+ for j in 0..self.dim {
+ sum = sum + state[i].get_conjugate() * self.get(i, j) * state[j];
+ }
+ }
+
+ sum.real
+ }
+}
+
+impl std::fmt::Display for DensityMatrix {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ writeln!(f, "DensityMatrix ({} qubits, {}×{}):", self.num_qubits, self.dim, self.dim)?;
+ writeln!(f, " Trace: {:.6}", self.trace().real)?;
+ writeln!(f, " Purity: {:.6}", self.purity())?;
+ writeln!(f, " Pure: {}", self.is_pure(1e-10))?;
+ writeln!(f, " Probabilities: {:?}", self.probabilities())?;
+ Ok(())
+ }
+}
+
+impl std::fmt::Debug for DensityMatrix {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ writeln!(f, "DensityMatrix {}×{}:", self.dim, self.dim)?;
+ for i in 0..self.dim {
+ write!(f, " [")?;
+ for j in 0..self.dim {
+ let val = self.get(i, j);
+ if j > 0 {
+ write!(f, ", ")?;
+ }
+ write!(f, "{:.4}+{:.4}i", val.real, val.imaginary)?;
+ }
+ writeln!(f, "]")?;
+ }
+ Ok(())
+ }
+}
+
diff --git a/libpsi-core/src/lib.rs b/libpsi-core/src/lib.rs
index 96c77a0..5876918 100644
--- a/libpsi-core/src/lib.rs
+++ b/libpsi-core/src/lib.rs
@@ -13,5 +13,6 @@ pub use core::classical_components::*;
pub use core::custom_gate::*;
pub use core::gates;
pub use core::kernel::*;
+pub use core::noise::*;
pub use core::quantum_components::*;
pub use core::runtime::*;
diff --git a/tester/src/main.rs b/tester/src/main.rs
index 854472c..8a9a41c 100644
--- a/tester/src/main.rs
+++ b/tester/src/main.rs
@@ -3,6 +3,7 @@ mod clifford;
mod common;
mod custom_gates;
mod kernels;
+mod noise;
mod non_clifford;
mod simd;
@@ -25,6 +26,7 @@ fn print_usage() {
println!(" custom Run custom gate tests only");
println!(" kernels Run kernel batching tests only");
println!(" simd Run SIMD acceleration tests only");
+ println!(" noise Run noise channel tests only");
println!(" bench Run benchmark tests only");
println!(" help Show this help message");
println!();
@@ -34,6 +36,7 @@ fn print_usage() {
println!(" tester non-clifford # Run only rotation/parametric gate tests");
println!(" tester kernels # Run only kernel batching tests");
println!(" tester simd # Run only SIMD tests");
+ println!(" tester noise # Run only noise channel tests");
println!(" tester custom bench # Run custom gates and benchmarks");
}
@@ -58,6 +61,7 @@ fn main() {
let run_custom = run_all || args.iter().any(|a| a == "custom");
let run_kernels = run_all || args.iter().any(|a| a == "kernels");
let run_simd = run_all || args.iter().any(|a| a == "simd");
+ let run_noise = run_all || args.iter().any(|a| a == "noise");
let run_bench = run_all || args.iter().any(|a| a == "bench");
if run_clifford {
@@ -80,6 +84,10 @@ fn main() {
simd::run_all(&mut results);
}
+ if run_noise {
+ noise::run_all(&mut results);
+ }
+
if run_bench {
benchmarks::run_all(&mut results);
}
diff --git a/tester/src/noise.rs b/tester/src/noise.rs
new file mode 100644
index 0000000..54edfe0
--- /dev/null
+++ b/tester/src/noise.rs
@@ -0,0 +1,172 @@
+use crate::common::{print_section, BenchmarkResult};
+use libpsi_core::{
+ complex, DensityMatrix, NoiseChannel, QuantumCircuit, Runtime, Vector,
+};
+use std::time::Instant;
+
+pub fn run_all(results: &mut Vec<BenchmarkResult>) {
+ println!("═══════════════════════════════════════════════════════════════");
+ println!(" NOISE CHANNEL TESTS");
+ println!("═══════════════════════════════════════════════════════════════\n");
+
+ test_density_matrix_basics(results);
+ test_noise_channels(results);
+ test_noisy_circuit(results);
+}
+
+pub fn test_density_matrix_basics(results: &mut Vec<BenchmarkResult>) {
+ print_section("Density Matrix Basics");
+
+ let dm = DensityMatrix::new(2);
+ println!("Initial |00⟩ state:");
+ println!("{}", dm);
+
+ let mut circuit = QuantumCircuit::new(2);
+ circuit.h(0).cnot(0, 1);
+ circuit.compute_with(Runtime::BasicRT);
+ let state = circuit.state();
+
+ let state_vec: Vec<_> = (0..state.size())
+ .map(|i| state.get(i))
+ .collect();
+
+ let dm_bell = DensityMatrix::from_state_vector(&state_vec);
+ println!("Bell state |Φ+⟩:");
+ println!("{}", dm_bell);
+ println!("Full matrix:");
+ println!("{:?}", dm_bell);
+
+ let is_pure = dm_bell.is_pure(1e-10);
+ println!("Purity check: {}\n", if is_pure { "✓ Pure" } else { "✗ Mixed" });
+
+ results.push(BenchmarkResult {
+ name: "DM: Bell state".to_string(),
+ basic_time: std::time::Duration::from_micros(0),
+ mt_time: std::time::Duration::from_micros(0),
+ results_match: is_pure,
+ });
+}
+
+pub fn test_noise_channels(results: &mut Vec<BenchmarkResult>) {
+ print_section("Noise Channel Effects");
+
+ let channels: Vec<(&str, NoiseChannel)> = vec![
+ ("Depolarising (p=0.1)", NoiseChannel::depolarising(0.1)),
+ ("Amplitude Damping (γ=0.2)", NoiseChannel::amplitude_damping(0.2)),
+ ("Phase Damping (γ=0.2)", NoiseChannel::phase_damping(0.2)),
+ ("Bit Flip (p=0.1)", NoiseChannel::bit_flip(0.1)),
+ ("Phase Flip (p=0.1)", NoiseChannel::phase_flip(0.1)),
+ ("Bit-Phase Flip (p=0.1)", NoiseChannel::bit_phase_flip(0.1)),
+ ];
+
+ let plus_state = vec![
+ complex!(1.0 / 2.0_f64.sqrt(), 0.0),
+ complex!(1.0 / 2.0_f64.sqrt(), 0.0),
+ ];
+
+ println!("Starting with |+⟩ state: (|0⟩ + |1⟩)/√2\n");
+
+ for (name, channel) in channels {
+ let mut dm = DensityMatrix::from_state_vector(&plus_state);
+ let initial_purity = dm.purity();
+
+ let start = Instant::now();
+ dm.apply_noise_channel(&channel, 0);
+ let elapsed = start.elapsed();
+
+ let final_purity = dm.purity();
+ let fidelity = dm.fidelity_with_pure_state(&plus_state);
+
+ println!("{:30}", name);
+ println!(" Purity: {:.4} → {:.4}", initial_purity, final_purity);
+ println!(" Fidelity with |+⟩: {:.4}", fidelity);
+ println!(" Probabilities: {:?}", dm.probabilities());
+ println!(" Time: {:.2}μs\n", elapsed.as_secs_f64() * 1_000_000.0);
+
+ let purity_decreased = final_purity <= initial_purity + 1e-10;
+
+ results.push(BenchmarkResult {
+ name: format!("Noise: {}", name),
+ basic_time: elapsed,
+ mt_time: elapsed,
+ results_match: purity_decreased,
+ });
+ }
+}
+
+pub fn test_noisy_circuit(results: &mut Vec<BenchmarkResult>) {
+ print_section("Noisy Circuit Simulation");
+
+ let mut circuit = QuantumCircuit::new(2);
+ circuit.h(0).cnot(0, 1);
+ circuit.compute_with(Runtime::BasicRT);
+ let state = circuit.state();
+ let state_vec: Vec<_> = (0..state.size()).map(|i| state.get(i)).collect();
+
+ let mut dm = DensityMatrix::from_state_vector(&state_vec);
+ println!("Bell state before noise:");
+ println!("{}", dm);
+
+ let depol = NoiseChannel::depolarising(0.05);
+
+ let start = Instant::now();
+ dm.apply_noise_channel(&depol, 0);
+ dm.apply_noise_channel(&depol, 1);
+ let elapsed = start.elapsed();
+
+ println!("Bell state after 5% depolarising on both qubits:");
+ println!("{}", dm);
+
+ let fidelity = dm.fidelity_with_pure_state(&state_vec);
+ println!("Fidelity with ideal Bell state: {:.4}", fidelity);
+ println!("Time: {:.2}μs\n", elapsed.as_secs_f64() * 1_000_000.0);
+
+ let mut dm2 = DensityMatrix::from_state_vector(&state_vec);
+ let amp_damp = NoiseChannel::amplitude_damping(0.1);
+
+ dm2.apply_noise_channel(&amp_damp, 0);
+ dm2.apply_noise_channel(&amp_damp, 1);
+
+ println!("Bell state after 10% amplitude damping on both qubits:");
+ println!("{}", dm2);
+ println!("Probabilities show decay towards |00⟩: {:?}", dm2.probabilities());
+
+ results.push(BenchmarkResult {
+ name: "Noisy Bell circuit".to_string(),
+ basic_time: elapsed,
+ mt_time: elapsed,
+ results_match: fidelity > 0.8 && fidelity < 1.0,
+ });
+
+ println!();
+ print_section("T1/T2 Relaxation Simulation");
+
+ let one_state = vec![complex!(0.0, 0.0), complex!(1.0, 0.0)];
+ let mut dm_t1 = DensityMatrix::from_state_vector(&one_state);
+
+ println!("Simulating T1 decay of |1⟩ state:");
+ println!(" Initial: P(0)={:.4}, P(1)={:.4}", dm_t1.probabilities()[0], dm_t1.probabilities()[1]);
+
+ let t1_channel = NoiseChannel::amplitude_damping(0.3);
+ for step in 1..=5 {
+ dm_t1.apply_noise_channel(&t1_channel, 0);
+ println!(
+ " Step {}: P(0)={:.4}, P(1)={:.4}, Purity={:.4}",
+ step,
+ dm_t1.probabilities()[0],
+ dm_t1.probabilities()[1],
+ dm_t1.purity()
+ );
+ }
+
+ let decayed = dm_t1.probabilities()[0] > 0.8;
+ println!(" Decay complete: {}\n", if decayed { "✓" } else { "✗" });
+
+ results.push(BenchmarkResult {
+ name: "T1 decay simulation".to_string(),
+ basic_time: std::time::Duration::from_micros(0),
+ mt_time: std::time::Duration::from_micros(0),
+ results_match: decayed,
+ });
+}
+