use std::collections::HashMap;
use num_complex::Complex;
use crate::error::{QuantumError, QuantumResult};
use crate::statevec::state::StateVector;
type Complex32 = Complex<f32>;
const PRUNE_EPS: f32 = 1e-12;
#[derive(Debug, Clone)]
pub struct SparseStateVector {
amps: HashMap<usize, Complex32>,
pub n_qubits: usize,
}
impl SparseStateVector {
pub fn new_zero_state(n_qubits: usize) -> QuantumResult<Self> {
if n_qubits == 0 || n_qubits > 30 {
return Err(QuantumError::InvalidQubitCount { n: n_qubits });
}
let mut amps = HashMap::new();
amps.insert(0usize, Complex32::new(1.0, 0.0));
Ok(Self { amps, n_qubits })
}
#[must_use]
pub fn from_dense(sv: &StateVector) -> Self {
let mut amps = HashMap::new();
for (i, a) in sv.amps.iter().enumerate() {
if a.norm_sqr() > PRUNE_EPS * PRUNE_EPS {
amps.insert(i, *a);
}
}
Self {
amps,
n_qubits: sv.n_qubits,
}
}
pub fn to_dense(&self) -> QuantumResult<StateVector> {
let dim = 1usize << self.n_qubits;
let mut dense = vec![Complex32::new(0.0, 0.0); dim];
for (&i, &a) in &self.amps {
if i < dim {
dense[i] = a;
}
}
Ok(StateVector {
amps: dense,
n_qubits: self.n_qubits,
})
}
#[must_use]
pub fn occupancy(&self) -> usize {
self.amps.len()
}
#[must_use]
pub fn norm_sq(&self) -> f32 {
self.amps.values().map(|a| a.norm_sqr()).sum()
}
#[must_use]
pub fn amp(&self, idx: usize) -> Complex32 {
self.amps
.get(&idx)
.copied()
.unwrap_or_else(|| Complex32::new(0.0, 0.0))
}
fn prune(&mut self) {
self.amps
.retain(|_, a| a.norm_sqr() > PRUNE_EPS * PRUNE_EPS);
}
pub fn apply_1q(&mut self, qubit: usize, gate: &[[Complex32; 2]; 2]) -> QuantumResult<()> {
if qubit >= self.n_qubits {
return Err(QuantumError::QubitIndexOutOfRange {
index: qubit,
n_qubits: self.n_qubits,
});
}
let mask = 1usize << qubit;
let mut low_keys: Vec<usize> = Vec::new();
let mut seen: std::collections::HashSet<usize> = std::collections::HashSet::new();
for &k in self.amps.keys() {
let low = k & !mask;
if seen.insert(low) {
low_keys.push(low);
}
}
let mut new_amps = self.amps.clone();
for low in low_keys {
let high = low | mask;
let x0 = self.amp(low);
let x1 = self.amp(high);
let y0 = gate[0][0] * x0 + gate[0][1] * x1;
let y1 = gate[1][0] * x0 + gate[1][1] * x1;
write_or_remove(&mut new_amps, low, y0);
write_or_remove(&mut new_amps, high, y1);
}
self.amps = new_amps;
self.prune();
Ok(())
}
pub fn apply_cnot(&mut self, ctrl: usize, tgt: usize) -> QuantumResult<()> {
if ctrl >= self.n_qubits {
return Err(QuantumError::QubitIndexOutOfRange {
index: ctrl,
n_qubits: self.n_qubits,
});
}
if tgt >= self.n_qubits {
return Err(QuantumError::QubitIndexOutOfRange {
index: tgt,
n_qubits: self.n_qubits,
});
}
if ctrl == tgt {
return Err(QuantumError::InvalidParameter {
name: "ctrl and tgt must differ".into(),
});
}
let cmask = 1usize << ctrl;
let tmask = 1usize << tgt;
let mut new_amps = HashMap::with_capacity(self.amps.len());
for (&k, &a) in &self.amps {
let nk = if k & cmask != 0 { k ^ tmask } else { k };
new_amps.insert(nk, a);
}
self.amps = new_amps;
Ok(())
}
pub fn apply_ccx(&mut self, c0: usize, c1: usize, tgt: usize) -> QuantumResult<()> {
for &q in &[c0, c1, tgt] {
if q >= self.n_qubits {
return Err(QuantumError::QubitIndexOutOfRange {
index: q,
n_qubits: self.n_qubits,
});
}
}
if c0 == c1 || c0 == tgt || c1 == tgt {
return Err(QuantumError::InvalidParameter {
name: "controls and target must be distinct".into(),
});
}
let m0 = 1usize << c0;
let m1 = 1usize << c1;
let tmask = 1usize << tgt;
let mut new_amps = HashMap::with_capacity(self.amps.len());
for (&k, &a) in &self.amps {
let nk = if (k & m0 != 0) && (k & m1 != 0) {
k ^ tmask
} else {
k
};
new_amps.insert(nk, a);
}
self.amps = new_amps;
Ok(())
}
pub fn normalize(&mut self) {
let norm = self.norm_sq().sqrt();
if norm > 1e-20 {
let inv = 1.0 / norm;
for a in self.amps.values_mut() {
*a *= inv;
}
}
}
}
fn write_or_remove(map: &mut HashMap<usize, Complex32>, key: usize, value: Complex32) {
if value.norm_sqr() > PRUNE_EPS * PRUNE_EPS {
map.insert(key, value);
} else {
map.remove(&key);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gates::controlled::apply_cnot as dense_cnot;
use crate::gates::hadamard::gate_h;
use crate::gates::pauli::gate_x;
use crate::statevec::apply_1q::apply_1q_inplace;
#[test]
fn zero_state_occupancy_is_one() {
let s = SparseStateVector::new_zero_state(5).expect("valid");
assert_eq!(s.occupancy(), 1);
assert!((s.norm_sq() - 1.0).abs() < 1e-6);
assert!((s.amp(0).re - 1.0).abs() < 1e-6);
}
#[test]
fn permutation_circuit_stays_sparse() {
let mut s = SparseStateVector::new_zero_state(8).expect("valid");
s.apply_1q(0, &gate_x()).expect("x0");
s.apply_1q(3, &gate_x()).expect("x3");
s.apply_cnot(0, 1).expect("cnot");
s.apply_ccx(0, 3, 5).expect("ccx");
assert_eq!(s.occupancy(), 1, "permutation must keep occupancy 1");
assert!((s.norm_sq() - 1.0).abs() < 1e-6);
}
#[test]
fn hadamard_doubles_occupancy() {
let mut s = SparseStateVector::new_zero_state(3).expect("valid");
s.apply_1q(0, &gate_h()).expect("h");
assert_eq!(s.occupancy(), 2);
let inv_sqrt2 = std::f32::consts::FRAC_1_SQRT_2;
assert!((s.amp(0).re - inv_sqrt2).abs() < 1e-5);
assert!((s.amp(1).re - inv_sqrt2).abs() < 1e-5);
}
#[test]
fn bell_state_matches_dense() {
let mut s = SparseStateVector::new_zero_state(2).expect("valid");
s.apply_1q(0, &gate_h()).expect("h");
s.apply_cnot(0, 1).expect("cnot");
let mut dv = StateVector::new_zero_state(2).expect("valid");
apply_1q_inplace(&mut dv, 0, &gate_h()).expect("h");
dense_cnot(&mut dv, 0, 1).expect("cnot");
let recon = s.to_dense().expect("dense");
for i in 0..4 {
assert!(
(recon.amps[i] - dv.amps[i]).norm() < 1e-5,
"idx {i}: sparse {:?} dense {:?}",
recon.amps[i],
dv.amps[i]
);
}
}
#[test]
fn roundtrip_dense_sparse_dense() {
let mut dv = StateVector::new_zero_state(3).expect("valid");
apply_1q_inplace(&mut dv, 0, &gate_h()).expect("h0");
apply_1q_inplace(&mut dv, 2, &gate_h()).expect("h2");
let sparse = SparseStateVector::from_dense(&dv);
let back = sparse.to_dense().expect("dense");
for (a, b) in back.amps.iter().zip(dv.amps.iter()) {
assert!((a - b).norm() < 1e-6);
}
}
#[test]
fn out_of_range_rejected() {
let mut s = SparseStateVector::new_zero_state(2).expect("valid");
assert!(s.apply_1q(5, &gate_x()).is_err());
assert!(s.apply_cnot(0, 0).is_err());
}
}