oxicuda_quantum/statevec/
sparse.rs1use std::collections::HashMap;
19
20use num_complex::Complex;
21
22use crate::error::{QuantumError, QuantumResult};
23use crate::statevec::state::StateVector;
24
25type Complex32 = Complex<f32>;
26
27const PRUNE_EPS: f32 = 1e-12;
29
30#[derive(Debug, Clone)]
32pub struct SparseStateVector {
33 amps: HashMap<usize, Complex32>,
35 pub n_qubits: usize,
37}
38
39impl SparseStateVector {
40 pub fn new_zero_state(n_qubits: usize) -> QuantumResult<Self> {
46 if n_qubits == 0 || n_qubits > 30 {
47 return Err(QuantumError::InvalidQubitCount { n: n_qubits });
48 }
49 let mut amps = HashMap::new();
50 amps.insert(0usize, Complex32::new(1.0, 0.0));
51 Ok(Self { amps, n_qubits })
52 }
53
54 #[must_use]
56 pub fn from_dense(sv: &StateVector) -> Self {
57 let mut amps = HashMap::new();
58 for (i, a) in sv.amps.iter().enumerate() {
59 if a.norm_sqr() > PRUNE_EPS * PRUNE_EPS {
60 amps.insert(i, *a);
61 }
62 }
63 Self {
64 amps,
65 n_qubits: sv.n_qubits,
66 }
67 }
68
69 pub fn to_dense(&self) -> QuantumResult<StateVector> {
74 let dim = 1usize << self.n_qubits;
75 let mut dense = vec![Complex32::new(0.0, 0.0); dim];
76 for (&i, &a) in &self.amps {
77 if i < dim {
78 dense[i] = a;
79 }
80 }
81 Ok(StateVector {
82 amps: dense,
83 n_qubits: self.n_qubits,
84 })
85 }
86
87 #[must_use]
89 pub fn occupancy(&self) -> usize {
90 self.amps.len()
91 }
92
93 #[must_use]
95 pub fn norm_sq(&self) -> f32 {
96 self.amps.values().map(|a| a.norm_sqr()).sum()
97 }
98
99 #[must_use]
101 pub fn amp(&self, idx: usize) -> Complex32 {
102 self.amps
103 .get(&idx)
104 .copied()
105 .unwrap_or_else(|| Complex32::new(0.0, 0.0))
106 }
107
108 fn prune(&mut self) {
110 self.amps
111 .retain(|_, a| a.norm_sqr() > PRUNE_EPS * PRUNE_EPS);
112 }
113
114 pub fn apply_1q(&mut self, qubit: usize, gate: &[[Complex32; 2]; 2]) -> QuantumResult<()> {
123 if qubit >= self.n_qubits {
124 return Err(QuantumError::QubitIndexOutOfRange {
125 index: qubit,
126 n_qubits: self.n_qubits,
127 });
128 }
129 let mask = 1usize << qubit;
130
131 let mut low_keys: Vec<usize> = Vec::new();
134 let mut seen: std::collections::HashSet<usize> = std::collections::HashSet::new();
135 for &k in self.amps.keys() {
136 let low = k & !mask;
137 if seen.insert(low) {
138 low_keys.push(low);
139 }
140 }
141
142 let mut new_amps = self.amps.clone();
143 for low in low_keys {
144 let high = low | mask;
145 let x0 = self.amp(low);
146 let x1 = self.amp(high);
147 let y0 = gate[0][0] * x0 + gate[0][1] * x1;
148 let y1 = gate[1][0] * x0 + gate[1][1] * x1;
149 write_or_remove(&mut new_amps, low, y0);
150 write_or_remove(&mut new_amps, high, y1);
151 }
152 self.amps = new_amps;
153 self.prune();
154 Ok(())
155 }
156
157 pub fn apply_cnot(&mut self, ctrl: usize, tgt: usize) -> QuantumResult<()> {
166 if ctrl >= self.n_qubits {
167 return Err(QuantumError::QubitIndexOutOfRange {
168 index: ctrl,
169 n_qubits: self.n_qubits,
170 });
171 }
172 if tgt >= self.n_qubits {
173 return Err(QuantumError::QubitIndexOutOfRange {
174 index: tgt,
175 n_qubits: self.n_qubits,
176 });
177 }
178 if ctrl == tgt {
179 return Err(QuantumError::InvalidParameter {
180 name: "ctrl and tgt must differ".into(),
181 });
182 }
183 let cmask = 1usize << ctrl;
184 let tmask = 1usize << tgt;
185 let mut new_amps = HashMap::with_capacity(self.amps.len());
186 for (&k, &a) in &self.amps {
187 let nk = if k & cmask != 0 { k ^ tmask } else { k };
188 new_amps.insert(nk, a);
189 }
190 self.amps = new_amps;
191 Ok(())
192 }
193
194 pub fn apply_ccx(&mut self, c0: usize, c1: usize, tgt: usize) -> QuantumResult<()> {
199 for &q in &[c0, c1, tgt] {
200 if q >= self.n_qubits {
201 return Err(QuantumError::QubitIndexOutOfRange {
202 index: q,
203 n_qubits: self.n_qubits,
204 });
205 }
206 }
207 if c0 == c1 || c0 == tgt || c1 == tgt {
208 return Err(QuantumError::InvalidParameter {
209 name: "controls and target must be distinct".into(),
210 });
211 }
212 let m0 = 1usize << c0;
213 let m1 = 1usize << c1;
214 let tmask = 1usize << tgt;
215 let mut new_amps = HashMap::with_capacity(self.amps.len());
216 for (&k, &a) in &self.amps {
217 let nk = if (k & m0 != 0) && (k & m1 != 0) {
218 k ^ tmask
219 } else {
220 k
221 };
222 new_amps.insert(nk, a);
223 }
224 self.amps = new_amps;
225 Ok(())
226 }
227
228 pub fn normalize(&mut self) {
230 let norm = self.norm_sq().sqrt();
231 if norm > 1e-20 {
232 let inv = 1.0 / norm;
233 for a in self.amps.values_mut() {
234 *a *= inv;
235 }
236 }
237 }
238}
239
240fn write_or_remove(map: &mut HashMap<usize, Complex32>, key: usize, value: Complex32) {
242 if value.norm_sqr() > PRUNE_EPS * PRUNE_EPS {
243 map.insert(key, value);
244 } else {
245 map.remove(&key);
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252 use crate::gates::controlled::apply_cnot as dense_cnot;
253 use crate::gates::hadamard::gate_h;
254 use crate::gates::pauli::gate_x;
255 use crate::statevec::apply_1q::apply_1q_inplace;
256
257 #[test]
258 fn zero_state_occupancy_is_one() {
259 let s = SparseStateVector::new_zero_state(5).expect("valid");
260 assert_eq!(s.occupancy(), 1);
261 assert!((s.norm_sq() - 1.0).abs() < 1e-6);
262 assert!((s.amp(0).re - 1.0).abs() < 1e-6);
263 }
264
265 #[test]
266 fn permutation_circuit_stays_sparse() {
267 let mut s = SparseStateVector::new_zero_state(8).expect("valid");
269 s.apply_1q(0, &gate_x()).expect("x0");
270 s.apply_1q(3, &gate_x()).expect("x3");
271 s.apply_cnot(0, 1).expect("cnot");
272 s.apply_ccx(0, 3, 5).expect("ccx");
273 assert_eq!(s.occupancy(), 1, "permutation must keep occupancy 1");
274 assert!((s.norm_sq() - 1.0).abs() < 1e-6);
275 }
276
277 #[test]
278 fn hadamard_doubles_occupancy() {
279 let mut s = SparseStateVector::new_zero_state(3).expect("valid");
280 s.apply_1q(0, &gate_h()).expect("h");
281 assert_eq!(s.occupancy(), 2);
282 let inv_sqrt2 = std::f32::consts::FRAC_1_SQRT_2;
283 assert!((s.amp(0).re - inv_sqrt2).abs() < 1e-5);
284 assert!((s.amp(1).re - inv_sqrt2).abs() < 1e-5);
285 }
286
287 #[test]
288 fn bell_state_matches_dense() {
289 let mut s = SparseStateVector::new_zero_state(2).expect("valid");
291 s.apply_1q(0, &gate_h()).expect("h");
292 s.apply_cnot(0, 1).expect("cnot");
293
294 let mut dv = StateVector::new_zero_state(2).expect("valid");
295 apply_1q_inplace(&mut dv, 0, &gate_h()).expect("h");
296 dense_cnot(&mut dv, 0, 1).expect("cnot");
297
298 let recon = s.to_dense().expect("dense");
299 for i in 0..4 {
300 assert!(
301 (recon.amps[i] - dv.amps[i]).norm() < 1e-5,
302 "idx {i}: sparse {:?} dense {:?}",
303 recon.amps[i],
304 dv.amps[i]
305 );
306 }
307 }
308
309 #[test]
310 fn roundtrip_dense_sparse_dense() {
311 let mut dv = StateVector::new_zero_state(3).expect("valid");
312 apply_1q_inplace(&mut dv, 0, &gate_h()).expect("h0");
313 apply_1q_inplace(&mut dv, 2, &gate_h()).expect("h2");
314 let sparse = SparseStateVector::from_dense(&dv);
315 let back = sparse.to_dense().expect("dense");
316 for (a, b) in back.amps.iter().zip(dv.amps.iter()) {
317 assert!((a - b).norm() < 1e-6);
318 }
319 }
320
321 #[test]
322 fn out_of_range_rejected() {
323 let mut s = SparseStateVector::new_zero_state(2).expect("valid");
324 assert!(s.apply_1q(5, &gate_x()).is_err());
325 assert!(s.apply_cnot(0, 0).is_err());
326 }
327}