use super::{Backend, drive};
use crate::circuit::Circuit;
use crate::complex::Complex64;
use crate::density::DensityMatrix;
use crate::gate::{Gate1, Gate2};
use crate::noise::NoiseModel;
use crate::rng::Rng;
pub struct DensityMatrixBackend {
rho: DensityMatrix,
rng: Rng,
noise: NoiseModel,
}
#[derive(Clone, Debug)]
pub struct DensityMatrixExecution {
rho: DensityMatrix,
classical: Vec<bool>,
}
impl DensityMatrixExecution {
#[must_use]
pub fn density_matrix(&self) -> &DensityMatrix {
&self.rho
}
#[must_use]
pub fn classical(&self) -> &[bool] {
&self.classical
}
#[must_use]
pub fn into_density_matrix(self) -> DensityMatrix {
self.rho
}
}
impl DensityMatrixBackend {
pub fn run(circuit: &Circuit) -> crate::Result<DensityMatrixExecution> {
Self::run_with_noise_seeded(circuit, &NoiseModel::ideal(), 0)
}
pub fn run_with_noise(
circuit: &Circuit,
noise: &NoiseModel,
) -> crate::Result<DensityMatrixExecution> {
Self::run_with_noise_seeded(circuit, noise, 0)
}
pub fn run_with_noise_seeded(
circuit: &Circuit,
noise: &NoiseModel,
seed: u64,
) -> crate::Result<DensityMatrixExecution> {
circuit.validate()?;
let mut backend = Self {
rho: DensityMatrix::zero(circuit.num_qubits()),
rng: Rng::seed_from_u64(seed),
noise: noise.clone(),
};
let classical = drive(&mut backend, circuit.ops(), circuit.num_classical());
Ok(DensityMatrixExecution {
rho: backend.rho,
classical,
})
}
}
impl Backend for DensityMatrixBackend {
fn apply_1q(&mut self, gate: &Gate1, target: usize) {
let u: Vec<Complex64> = gate.m.to_vec();
self.rho.apply_unitary(&u, &[target]);
if let Some(ch) = &self.noise.after_1q.clone() {
self.rho.apply_channel(&ch.kraus_ops(), target);
}
}
fn apply_2q(&mut self, gate: &Gate2, a: usize, b: usize) {
let u: Vec<Complex64> = gate.m.to_vec();
self.rho.apply_unitary(&u, &[b, a]);
if let Some(ch) = &self.noise.after_2q.clone() {
let ops = ch.kraus_ops();
self.rho.apply_channel(&ops, a);
self.rho.apply_channel(&ops, b);
}
}
fn apply_controlled(&mut self, controls: &[usize], gate: &Gate1, target: usize) {
let n = self.rho.num_qubits();
let u = controlled_unitary(controls, gate, target, n);
let all_qubits: Vec<usize> = (0..n).collect();
self.rho.apply_unitary(&u, &all_qubits);
if let Some(ch) = &self.noise.after_1q.clone() {
let ops = ch.kraus_ops();
self.rho.apply_channel(&ops, target);
for &c in controls {
self.rho.apply_channel(&ops, c);
}
}
}
fn measure(&mut self, qubit: usize) -> bool {
let p1 = measure_probability(&self.rho, qubit);
let raw_outcome = self.rng.next_f64() < p1;
collapse(&mut self.rho, qubit, raw_outcome);
self.rho.renormalize();
if self.noise.readout_error > 0.0 && self.rng.next_f64() < self.noise.readout_error {
!raw_outcome
} else {
raw_outcome
}
}
}
fn measure_probability(rho: &DensityMatrix, qubit: usize) -> f64 {
debug_assert!(
qubit < rho.num_qubits(),
"measure qubit {qubit} out of range"
);
let bit = 1usize << qubit;
let dim = rho.dim();
(0..dim)
.filter(|&j| j & bit != 0)
.map(|j| rho.probability(j))
.sum()
}
fn collapse(rho: &mut DensityMatrix, qubit: usize, outcome: bool) {
debug_assert!(
qubit < rho.num_qubits(),
"collapse qubit {qubit} out of range"
);
let bit = 1usize << qubit;
let dim = rho.dim();
let data = rho.data_mut();
for i in 0..dim {
let i_matches = ((i & bit) != 0) == outcome;
for j in 0..dim {
let j_matches = ((j & bit) != 0) == outcome;
if !i_matches || !j_matches {
data[i * dim + j] = Complex64::ZERO;
}
}
}
}
fn controlled_unitary(controls: &[usize], gate: &Gate1, target: usize, n: usize) -> Vec<Complex64> {
debug_assert!(
target < n,
"target {target} out of range for {n}-qubit register"
);
debug_assert!(
controls.iter().all(|&c| c < n),
"control qubit out of range"
);
debug_assert!(!controls.contains(&target), "target appears in controls");
let dim = 1usize << n;
let mut u = vec![Complex64::ZERO; dim * dim];
let control_mask: usize = controls.iter().fold(0, |acc, &c| acc | (1 << c));
let m = &gate.m;
for col in 0..dim {
if col & control_mask == control_mask {
let col_t = (col >> target) & 1; let partner = col ^ (1 << target); if col_t == 0 {
u[col * dim + col] = m[0]; u[partner * dim + col] = m[2]; } else {
u[partner * dim + col] = m[1]; u[col * dim + col] = m[3]; }
} else {
u[col * dim + col] = Complex64::ONE;
}
}
u
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gate::{Gate1, Gate2};
use crate::{Circuit, StateVectorBackend};
fn bell_circuit() -> Circuit {
let mut c = Circuit::new(2);
c.h(0).cnot(0, 1);
c
}
#[test]
fn ideal_matches_statevector_probabilities() {
let c = bell_circuit();
let sv = StateVectorBackend::run(&c).unwrap();
let dm = DensityMatrixBackend::run(&c).unwrap();
let rho = dm.density_matrix();
for j in 0..4 {
let sv_prob = sv.state().probability(j);
let dm_prob = rho.probability(j);
assert!(
(sv_prob - dm_prob).abs() < 1e-12,
"basis {j}: sv={sv_prob} dm={dm_prob}"
);
}
}
#[test]
fn ideal_state_is_pure() {
let c = bell_circuit();
let exec = DensityMatrixBackend::run(&c).unwrap();
assert!((exec.density_matrix().purity() - 1.0).abs() < 1e-10);
}
#[test]
fn depolarizing_reduces_purity() {
let c = bell_circuit();
let noise = NoiseModel::uniform_depolarizing(0.05);
let exec = DensityMatrixBackend::run_with_noise(&c, &noise).unwrap();
assert!(exec.density_matrix().purity() < 1.0);
}
#[test]
fn trace_preserved_under_noise() {
let c = bell_circuit();
let noise = NoiseModel::uniform_depolarizing(0.1);
let exec = DensityMatrixBackend::run_with_noise(&c, &noise).unwrap();
assert!((exec.density_matrix().trace() - 1.0).abs() < 1e-10);
}
#[test]
fn amplitude_damping_decays_excited_state() {
let mut c = Circuit::new(1);
c.x(0);
let noise = NoiseModel::amplitude_damping(0.99);
let exec = DensityMatrixBackend::run_with_noise(&c, &noise).unwrap();
let rho = exec.density_matrix();
assert!(rho.probability(0) > 0.9, "P(0)={}", rho.probability(0));
}
#[test]
fn dephasing_kills_coherences() {
let mut c = Circuit::new(1);
c.h(0);
let noise = NoiseModel::dephasing(0.5);
let exec = DensityMatrixBackend::run_with_noise(&c, &noise).unwrap();
let rho = exec.density_matrix();
assert!(rho.get(0, 1).norm() < 1e-12, "off-diag: {}", rho.get(0, 1));
assert!(rho.get(1, 0).norm() < 1e-12, "off-diag: {}", rho.get(1, 0));
}
#[test]
fn measurement_collapses_state() {
let mut c = Circuit::with_classical(1, 1);
c.h(0).measure(0, 0);
let exec = DensityMatrixBackend::run(&c).unwrap();
assert!((exec.density_matrix().purity() - 1.0).abs() < 1e-10);
}
#[test]
fn ideal_cnot_matches_statevector_all_n() {
for n in 2..=4 {
let mut c = Circuit::new(n);
c.h(0);
for q in 0..n - 1 {
c.cnot(q, q + 1);
}
let sv = StateVectorBackend::run(&c).unwrap();
let dm = DensityMatrixBackend::run(&c).unwrap();
let dim = 1 << n;
for j in 0..dim {
let a = sv.state().probability(j);
let b = dm.density_matrix().probability(j);
assert!((a - b).abs() < 1e-10, "n={n} basis {j}: sv={a} dm={b}");
}
}
}
#[test]
fn expectation_z_of_zero_state_is_one() {
let rho = DensityMatrix::zero(1);
let z = rho.expectation_pauli(0, 'Z');
assert!((z - 1.0).abs() < 1e-12);
}
#[test]
fn expectation_x_of_plus_state_is_one() {
let mut c = Circuit::new(1);
c.h(0);
let exec = DensityMatrixBackend::run(&c).unwrap();
let x_exp = exec.density_matrix().expectation_pauli(0, 'X');
assert!((x_exp - 1.0).abs() < 1e-10, "<X> = {x_exp}");
}
#[test]
fn controlled_unitary_cnot_matches_gate2_cnot() {
let n = 2;
let u = controlled_unitary(&[1], &Gate1::x(), 0, n);
let cnot = Gate2::cnot();
for (a, b) in u.iter().zip(cnot.m.iter()) {
assert!((*a - *b).norm() < 1e-12, "a={a} b={b}");
}
}
}