1use nalgebra_sparse::{CooMatrix, CsrMatrix};
8use quantrs2_circuit::prelude::*;
9use quantrs2_core::prelude::*;
10
11#[derive(Debug, Clone)]
13pub struct SparsePauli {
14 x_sparse: CsrMatrix<u8>,
16 z_sparse: CsrMatrix<u8>,
18 phase: u8,
20}
21
22impl SparsePauli {
23 pub fn identity(num_qubits: usize) -> Self {
25 let x_sparse = CsrMatrix::zeros(1, num_qubits);
26 let z_sparse = CsrMatrix::zeros(1, num_qubits);
27 Self {
28 x_sparse,
29 z_sparse,
30 phase: 0,
31 }
32 }
33
34 pub fn single_qubit(num_qubits: usize, qubit: usize, pauli: char) -> Self {
36 let mut x_values = vec![];
37 let mut x_indices = vec![];
38 let mut z_values = vec![];
39 let mut z_indices = vec![];
40
41 match pauli {
42 'X' => {
43 x_values.push(1u8);
44 x_indices.push(qubit);
45 }
46 'Y' => {
47 x_values.push(1u8);
48 x_indices.push(qubit);
49 z_values.push(1u8);
50 z_indices.push(qubit);
51 }
52 'Z' => {
53 z_values.push(1u8);
54 z_indices.push(qubit);
55 }
56 _ => {}
57 }
58
59 let x_sparse = if x_values.is_empty() {
60 CsrMatrix::zeros(1, num_qubits)
61 } else {
62 let coo = CooMatrix::try_from_triplets(
63 1,
64 num_qubits,
65 vec![0; x_values.len()],
66 x_indices,
67 x_values,
68 )
69 .unwrap();
70 CsrMatrix::from(&coo)
71 };
72
73 let z_sparse = if z_values.is_empty() {
74 CsrMatrix::zeros(1, num_qubits)
75 } else {
76 let coo = CooMatrix::try_from_triplets(
77 1,
78 num_qubits,
79 vec![0; z_values.len()],
80 z_indices,
81 z_values,
82 )
83 .unwrap();
84 CsrMatrix::from(&coo)
85 };
86
87 Self {
88 x_sparse,
89 z_sparse,
90 phase: 0,
91 }
92 }
93
94 fn commutation_phase(&self, other: &Self) -> u8 {
96 let mut phase = 0u8;
97
98 for col in 0..self.x_sparse.ncols() {
100 let self_x = self
101 .x_sparse
102 .get_entry(0, col)
103 .map_or(0, |v| v.into_value());
104 let self_z = self
105 .z_sparse
106 .get_entry(0, col)
107 .map_or(0, |v| v.into_value());
108 let other_x = other
109 .x_sparse
110 .get_entry(0, col)
111 .map_or(0, |v| v.into_value());
112 let other_z = other
113 .z_sparse
114 .get_entry(0, col)
115 .map_or(0, |v| v.into_value());
116
117 if self_x > 0 && other_z > 0 && self_z == 0 {
119 phase = (phase + 2) % 4; }
121 if self_z > 0 && other_x > 0 && self_x == 0 {
122 phase = (phase + 2) % 4; }
124 }
125
126 phase
127 }
128}
129
130pub struct SparseStabilizerTableau {
132 num_qubits: usize,
133 stabilizers: Vec<SparsePauli>,
135 destabilizers: Vec<SparsePauli>,
137}
138
139impl SparseStabilizerTableau {
140 pub fn new(num_qubits: usize) -> Self {
142 let mut stabilizers = Vec::with_capacity(num_qubits);
143 let mut destabilizers = Vec::with_capacity(num_qubits);
144
145 for i in 0..num_qubits {
146 stabilizers.push(SparsePauli::single_qubit(num_qubits, i, 'Z'));
148 destabilizers.push(SparsePauli::single_qubit(num_qubits, i, 'X'));
150 }
151
152 Self {
153 num_qubits,
154 stabilizers,
155 destabilizers,
156 }
157 }
158
159 pub fn apply_h(&mut self, qubit: usize) -> Result<(), QuantRS2Error> {
161 if qubit >= self.num_qubits {
162 return Err(QuantRS2Error::InvalidQubitId(qubit as u32));
163 }
164
165 for i in 0..self.num_qubits {
167 let stab = &mut self.stabilizers[i];
169 let x_val = stab
170 .x_sparse
171 .get_entry(0, qubit)
172 .map_or(0, |v| v.into_value());
173 let z_val = stab
174 .z_sparse
175 .get_entry(0, qubit)
176 .map_or(0, |v| v.into_value());
177
178 if x_val > 0 && z_val > 0 {
180 stab.phase = (stab.phase + 2) % 4; }
182
183 let mut new_x_values = vec![];
185 let mut new_x_indices = vec![];
186 let mut new_z_values = vec![];
187 let mut new_z_indices = vec![];
188
189 for (_, col, val) in stab.x_sparse.triplet_iter() {
191 if col != qubit && *val > 0 {
192 new_x_values.push(1u8);
193 new_x_indices.push(col);
194 }
195 }
196 for (_, col, val) in stab.z_sparse.triplet_iter() {
197 if col != qubit && *val > 0 {
198 new_z_values.push(1u8);
199 new_z_indices.push(col);
200 }
201 }
202
203 if z_val > 0 {
205 new_x_values.push(1u8);
206 new_x_indices.push(qubit);
207 }
208 if x_val > 0 {
209 new_z_values.push(1u8);
210 new_z_indices.push(qubit);
211 }
212
213 stab.x_sparse = if new_x_values.is_empty() {
215 CsrMatrix::zeros(1, self.num_qubits)
216 } else {
217 let coo = CooMatrix::try_from_triplets(
218 1,
219 self.num_qubits,
220 vec![0; new_x_values.len()],
221 new_x_indices,
222 new_x_values,
223 )
224 .unwrap();
225 CsrMatrix::from(&coo)
226 };
227
228 stab.z_sparse = if new_z_values.is_empty() {
229 CsrMatrix::zeros(1, self.num_qubits)
230 } else {
231 let coo = CooMatrix::try_from_triplets(
232 1,
233 self.num_qubits,
234 vec![0; new_z_values.len()],
235 new_z_indices,
236 new_z_values,
237 )
238 .unwrap();
239 CsrMatrix::from(&coo)
240 };
241
242 let destab = &mut self.destabilizers[i];
244 let dx_val = destab
245 .x_sparse
246 .get_entry(0, qubit)
247 .map_or(0, |v| v.into_value());
248 let dz_val = destab
249 .z_sparse
250 .get_entry(0, qubit)
251 .map_or(0, |v| v.into_value());
252
253 if dx_val > 0 && dz_val > 0 {
254 destab.phase = (destab.phase + 2) % 4;
255 }
256
257 let mut new_dx_values = vec![];
259 let mut new_dx_indices = vec![];
260 let mut new_dz_values = vec![];
261 let mut new_dz_indices = vec![];
262
263 for (_, col, val) in destab.x_sparse.triplet_iter() {
264 if col != qubit && *val > 0 {
265 new_dx_values.push(1u8);
266 new_dx_indices.push(col);
267 }
268 }
269 for (_, col, val) in destab.z_sparse.triplet_iter() {
270 if col != qubit && *val > 0 {
271 new_dz_values.push(1u8);
272 new_dz_indices.push(col);
273 }
274 }
275
276 if dz_val > 0 {
277 new_dx_values.push(1u8);
278 new_dx_indices.push(qubit);
279 }
280 if dx_val > 0 {
281 new_dz_values.push(1u8);
282 new_dz_indices.push(qubit);
283 }
284
285 destab.x_sparse = if new_dx_values.is_empty() {
286 CsrMatrix::zeros(1, self.num_qubits)
287 } else {
288 let coo = CooMatrix::try_from_triplets(
289 1,
290 self.num_qubits,
291 vec![0; new_dx_values.len()],
292 new_dx_indices,
293 new_dx_values,
294 )
295 .unwrap();
296 CsrMatrix::from(&coo)
297 };
298
299 destab.z_sparse = if new_dz_values.is_empty() {
300 CsrMatrix::zeros(1, self.num_qubits)
301 } else {
302 let coo = CooMatrix::try_from_triplets(
303 1,
304 self.num_qubits,
305 vec![0; new_dz_values.len()],
306 new_dz_indices,
307 new_dz_values,
308 )
309 .unwrap();
310 CsrMatrix::from(&coo)
311 };
312 }
313
314 Ok(())
315 }
316
317 pub fn apply_cnot(&mut self, control: usize, target: usize) -> Result<(), QuantRS2Error> {
319 if control >= self.num_qubits || target >= self.num_qubits {
320 return Err(QuantRS2Error::InvalidQubitId(control.max(target) as u32));
321 }
322
323 for i in 0..self.num_qubits {
325 let stab = &mut self.stabilizers[i];
327 let control_x = stab
328 .x_sparse
329 .get_entry(0, control)
330 .map_or(0, |v| v.into_value());
331 let control_z = stab
332 .z_sparse
333 .get_entry(0, control)
334 .map_or(0, |v| v.into_value());
335 let target_x = stab
336 .x_sparse
337 .get_entry(0, target)
338 .map_or(0, |v| v.into_value());
339 let target_z = stab
340 .z_sparse
341 .get_entry(0, target)
342 .map_or(0, |v| v.into_value());
343
344 if control_x > 0 {
346 let mut new_x_values = vec![];
348 let mut new_x_indices = vec![];
349
350 for (_, col, val) in stab.x_sparse.triplet_iter() {
351 if col != target && *val > 0 {
352 new_x_values.push(1u8);
353 new_x_indices.push(col);
354 }
355 }
356
357 if target_x == 0 {
359 new_x_values.push(1u8);
360 new_x_indices.push(target);
361 }
362
363 stab.x_sparse = if new_x_values.is_empty() {
364 CsrMatrix::zeros(1, self.num_qubits)
365 } else {
366 let coo = CooMatrix::try_from_triplets(
367 1,
368 self.num_qubits,
369 vec![0; new_x_values.len()],
370 new_x_indices,
371 new_x_values,
372 )
373 .unwrap();
374 CsrMatrix::from(&coo)
375 };
376 }
377
378 if target_z > 0 {
380 let mut new_z_values = vec![];
381 let mut new_z_indices = vec![];
382
383 for (_, col, val) in stab.z_sparse.triplet_iter() {
384 if col != control && *val > 0 {
385 new_z_values.push(1u8);
386 new_z_indices.push(col);
387 }
388 }
389
390 if control_z == 0 {
392 new_z_values.push(1u8);
393 new_z_indices.push(control);
394 }
395
396 stab.z_sparse = if new_z_values.is_empty() {
397 CsrMatrix::zeros(1, self.num_qubits)
398 } else {
399 let coo = CooMatrix::try_from_triplets(
400 1,
401 self.num_qubits,
402 vec![0; new_z_values.len()],
403 new_z_indices,
404 new_z_values,
405 )
406 .unwrap();
407 CsrMatrix::from(&coo)
408 };
409 }
410 }
411
412 Ok(())
413 }
414
415 pub fn apply_s(&mut self, qubit: usize) -> Result<(), QuantRS2Error> {
417 if qubit >= self.num_qubits {
418 return Err(QuantRS2Error::InvalidQubitId(qubit as u32));
419 }
420
421 for i in 0..self.num_qubits {
423 let stab = &mut self.stabilizers[i];
424 let x_val = stab
425 .x_sparse
426 .get_entry(0, qubit)
427 .map_or(0, |v| v.into_value());
428 let z_val = stab
429 .z_sparse
430 .get_entry(0, qubit)
431 .map_or(0, |v| v.into_value());
432
433 if x_val > 0 && z_val == 0 {
435 let mut new_z_values = vec![];
437 let mut new_z_indices = vec![];
438
439 for (_, col, val) in stab.z_sparse.triplet_iter() {
440 if *val > 0 {
441 new_z_values.push(1u8);
442 new_z_indices.push(col);
443 }
444 }
445
446 new_z_values.push(1u8);
447 new_z_indices.push(qubit);
448
449 stab.z_sparse = if new_z_values.is_empty() {
450 CsrMatrix::zeros(1, self.num_qubits)
451 } else {
452 let coo = CooMatrix::try_from_triplets(
453 1,
454 self.num_qubits,
455 vec![0; new_z_values.len()],
456 new_z_indices,
457 new_z_values,
458 )
459 .unwrap();
460 CsrMatrix::from(&coo)
461 };
462
463 stab.phase = (stab.phase + 1) % 4; }
466 }
467
468 Ok(())
469 }
470
471 pub fn get_stabilizers(&self) -> Vec<String> {
473 self.stabilizers
474 .iter()
475 .map(|stab| {
476 let mut s = String::new();
477 s.push(match stab.phase {
478 0 => '+',
479 1 => 'i',
480 2 => '-',
481 3 => '-',
482 _ => '+',
483 });
484
485 for j in 0..self.num_qubits {
486 let has_x = stab.x_sparse.get_entry(0, j).map_or(0, |v| v.into_value()) > 0;
487 let has_z = stab.z_sparse.get_entry(0, j).map_or(0, |v| v.into_value()) > 0;
488
489 s.push(match (has_x, has_z) {
490 (false, false) => 'I',
491 (true, false) => 'X',
492 (false, true) => 'Z',
493 (true, true) => 'Y',
494 });
495 }
496
497 s
498 })
499 .collect()
500 }
501
502 pub fn get_sparsity_info(&self) -> (f64, f64) {
504 let total_entries = self.num_qubits * self.num_qubits;
505
506 let stab_nonzero: usize = self
507 .stabilizers
508 .iter()
509 .map(|s| s.x_sparse.nnz() + s.z_sparse.nnz())
510 .sum();
511
512 let destab_nonzero: usize = self
513 .destabilizers
514 .iter()
515 .map(|s| s.x_sparse.nnz() + s.z_sparse.nnz())
516 .sum();
517
518 let stab_sparsity = 1.0 - (stab_nonzero as f64 / total_entries as f64);
519 let destab_sparsity = 1.0 - (destab_nonzero as f64 / total_entries as f64);
520
521 (stab_sparsity, destab_sparsity)
522 }
523}
524
525pub struct SparseCliffordSimulator {
527 tableau: SparseStabilizerTableau,
528 measurement_record: Vec<(usize, bool)>,
529}
530
531impl SparseCliffordSimulator {
532 pub fn new(num_qubits: usize) -> Self {
534 Self {
535 tableau: SparseStabilizerTableau::new(num_qubits),
536 measurement_record: Vec::new(),
537 }
538 }
539
540 pub fn apply_gate(&mut self, gate: CliffordGate) -> Result<(), QuantRS2Error> {
542 match gate {
543 CliffordGate::H(q) => self.tableau.apply_h(q),
544 CliffordGate::S(q) => self.tableau.apply_s(q),
545 CliffordGate::CNOT(c, t) => self.tableau.apply_cnot(c, t),
546 CliffordGate::X(q) | CliffordGate::Y(q) | CliffordGate::Z(q) => {
547 Ok(())
549 }
550 }
551 }
552
553 pub fn get_stabilizers(&self) -> Vec<String> {
555 self.tableau.get_stabilizers()
556 }
557
558 pub fn get_sparsity_info(&self) -> (f64, f64) {
560 self.tableau.get_sparsity_info()
561 }
562
563 pub fn num_qubits(&self) -> usize {
565 self.tableau.num_qubits
566 }
567}
568
569#[derive(Debug, Clone, Copy)]
571pub enum CliffordGate {
572 H(usize),
573 S(usize),
574 X(usize),
575 Y(usize),
576 Z(usize),
577 CNOT(usize, usize),
578}
579
580#[cfg(test)]
581mod tests {
582 use super::*;
583
584 #[test]
585 fn test_sparse_init() {
586 let sim = SparseCliffordSimulator::new(100);
587 let (stab_sparsity, destab_sparsity) = sim.get_sparsity_info();
588
589 assert!(stab_sparsity > 0.98);
591 assert!(destab_sparsity > 0.98);
592 }
593
594 #[test]
595 fn test_sparse_hadamard() {
596 let mut sim = SparseCliffordSimulator::new(5);
597 sim.apply_gate(CliffordGate::H(0)).unwrap();
598
599 let stabs = sim.get_stabilizers();
600 assert_eq!(stabs[0], "+XIIII");
601 }
602
603 #[test]
604 fn test_sparse_cnot_chain() {
605 let mut sim = SparseCliffordSimulator::new(10);
606
607 for i in 0..9 {
609 sim.apply_gate(CliffordGate::CNOT(i, i + 1)).unwrap();
610 }
611
612 let (stab_sparsity, _) = sim.get_sparsity_info();
613 assert!(stab_sparsity > 0.8);
615 }
616}