1use nalgebra_sparse::{CooMatrix, CsrMatrix}; use quantrs2_circuit::prelude::*;
22use quantrs2_core::prelude::*;
23
24#[derive(Debug, Clone)]
26pub struct SparsePauli {
27 x_sparse: CsrMatrix<u8>,
29 z_sparse: CsrMatrix<u8>,
31 phase: u8,
33}
34
35impl SparsePauli {
36 #[must_use]
38 pub fn identity(num_qubits: usize) -> Self {
39 let x_sparse = CsrMatrix::zeros(1, num_qubits);
40 let z_sparse = CsrMatrix::zeros(1, num_qubits);
41 Self {
42 x_sparse,
43 z_sparse,
44 phase: 0,
45 }
46 }
47
48 pub fn single_qubit(
50 num_qubits: usize,
51 qubit: usize,
52 pauli: char,
53 ) -> Result<Self, QuantRS2Error> {
54 let mut x_values = vec![];
55 let mut x_indices = vec![];
56 let mut z_values = vec![];
57 let mut z_indices = vec![];
58
59 match pauli {
60 'X' => {
61 x_values.push(1u8);
62 x_indices.push(qubit);
63 }
64 'Y' => {
65 x_values.push(1u8);
66 x_indices.push(qubit);
67 z_values.push(1u8);
68 z_indices.push(qubit);
69 }
70 'Z' => {
71 z_values.push(1u8);
72 z_indices.push(qubit);
73 }
74 _ => {}
75 }
76
77 let x_sparse = if x_values.is_empty() {
78 CsrMatrix::zeros(1, num_qubits)
79 } else {
80 let coo = CooMatrix::try_from_triplets(
81 1,
82 num_qubits,
83 vec![0; x_values.len()],
84 x_indices,
85 x_values,
86 )
87 .map_err(|e| {
88 QuantRS2Error::InvalidInput(format!("Failed to create sparse X matrix: {e}"))
89 })?;
90 CsrMatrix::from(&coo)
91 };
92
93 let z_sparse = if z_values.is_empty() {
94 CsrMatrix::zeros(1, num_qubits)
95 } else {
96 let coo = CooMatrix::try_from_triplets(
97 1,
98 num_qubits,
99 vec![0; z_values.len()],
100 z_indices,
101 z_values,
102 )
103 .map_err(|e| {
104 QuantRS2Error::InvalidInput(format!("Failed to create sparse Z matrix: {e}"))
105 })?;
106 CsrMatrix::from(&coo)
107 };
108
109 Ok(Self {
110 x_sparse,
111 z_sparse,
112 phase: 0,
113 })
114 }
115
116 fn commutation_phase(&self, other: &Self) -> u8 {
118 let mut phase = 0u8;
119
120 for col in 0..self.x_sparse.ncols() {
122 let self_x = self
123 .x_sparse
124 .get_entry(0, col)
125 .map_or(0, nalgebra_sparse::SparseEntry::into_value);
126 let self_z = self
127 .z_sparse
128 .get_entry(0, col)
129 .map_or(0, nalgebra_sparse::SparseEntry::into_value);
130 let other_x = other
131 .x_sparse
132 .get_entry(0, col)
133 .map_or(0, nalgebra_sparse::SparseEntry::into_value);
134 let other_z = other
135 .z_sparse
136 .get_entry(0, col)
137 .map_or(0, nalgebra_sparse::SparseEntry::into_value);
138
139 if self_x > 0 && other_z > 0 && self_z == 0 {
141 phase = (phase + 2) % 4; }
143 if self_z > 0 && other_x > 0 && self_x == 0 {
144 phase = (phase + 2) % 4; }
146 }
147
148 phase
149 }
150}
151
152pub struct SparseStabilizerTableau {
154 num_qubits: usize,
155 stabilizers: Vec<SparsePauli>,
157 destabilizers: Vec<SparsePauli>,
159}
160
161impl SparseStabilizerTableau {
162 #[must_use]
164 pub fn new(num_qubits: usize) -> Self {
165 let mut stabilizers = Vec::with_capacity(num_qubits);
166 let mut destabilizers = Vec::with_capacity(num_qubits);
167
168 for i in 0..num_qubits {
169 stabilizers.push(
171 SparsePauli::single_qubit(num_qubits, i, 'Z')
172 .expect("Failed to create stabilizer Z operator"),
173 );
174 destabilizers.push(
176 SparsePauli::single_qubit(num_qubits, i, 'X')
177 .expect("Failed to create destabilizer X operator"),
178 );
179 }
180
181 Self {
182 num_qubits,
183 stabilizers,
184 destabilizers,
185 }
186 }
187
188 pub fn apply_h(&mut self, qubit: usize) -> Result<(), QuantRS2Error> {
190 if qubit >= self.num_qubits {
191 return Err(QuantRS2Error::InvalidQubitId(qubit as u32));
192 }
193
194 for i in 0..self.num_qubits {
196 let stab = &mut self.stabilizers[i];
198 let x_val = stab
199 .x_sparse
200 .get_entry(0, qubit)
201 .map_or(0, nalgebra_sparse::SparseEntry::into_value);
202 let z_val = stab
203 .z_sparse
204 .get_entry(0, qubit)
205 .map_or(0, nalgebra_sparse::SparseEntry::into_value);
206
207 if x_val > 0 && z_val > 0 {
209 stab.phase = (stab.phase + 2) % 4; }
211
212 let mut new_x_values = vec![];
214 let mut new_x_indices = vec![];
215 let mut new_z_values = vec![];
216 let mut new_z_indices = vec![];
217
218 for (_, col, val) in stab.x_sparse.triplet_iter() {
220 if col != qubit && *val > 0 {
221 new_x_values.push(1u8);
222 new_x_indices.push(col);
223 }
224 }
225 for (_, col, val) in stab.z_sparse.triplet_iter() {
226 if col != qubit && *val > 0 {
227 new_z_values.push(1u8);
228 new_z_indices.push(col);
229 }
230 }
231
232 if z_val > 0 {
234 new_x_values.push(1u8);
235 new_x_indices.push(qubit);
236 }
237 if x_val > 0 {
238 new_z_values.push(1u8);
239 new_z_indices.push(qubit);
240 }
241
242 stab.x_sparse = if new_x_values.is_empty() {
244 CsrMatrix::zeros(1, self.num_qubits)
245 } else {
246 let coo = CooMatrix::try_from_triplets(
247 1,
248 self.num_qubits,
249 vec![0; new_x_values.len()],
250 new_x_indices,
251 new_x_values,
252 )
253 .map_err(|e| {
254 QuantRS2Error::GateApplicationFailed(format!(
255 "Failed to rebuild sparse X matrix: {e}"
256 ))
257 })?;
258 CsrMatrix::from(&coo)
259 };
260
261 stab.z_sparse = if new_z_values.is_empty() {
262 CsrMatrix::zeros(1, self.num_qubits)
263 } else {
264 let coo = CooMatrix::try_from_triplets(
265 1,
266 self.num_qubits,
267 vec![0; new_z_values.len()],
268 new_z_indices,
269 new_z_values,
270 )
271 .map_err(|e| {
272 QuantRS2Error::GateApplicationFailed(format!(
273 "Failed to rebuild sparse Z matrix: {e}"
274 ))
275 })?;
276 CsrMatrix::from(&coo)
277 };
278
279 let destab = &mut self.destabilizers[i];
281 let dx_val = destab
282 .x_sparse
283 .get_entry(0, qubit)
284 .map_or(0, nalgebra_sparse::SparseEntry::into_value);
285 let dz_val = destab
286 .z_sparse
287 .get_entry(0, qubit)
288 .map_or(0, nalgebra_sparse::SparseEntry::into_value);
289
290 if dx_val > 0 && dz_val > 0 {
291 destab.phase = (destab.phase + 2) % 4;
292 }
293
294 let mut new_dx_values = vec![];
296 let mut new_dx_indices = vec![];
297 let mut new_dz_values = vec![];
298 let mut new_dz_indices = vec![];
299
300 for (_, col, val) in destab.x_sparse.triplet_iter() {
301 if col != qubit && *val > 0 {
302 new_dx_values.push(1u8);
303 new_dx_indices.push(col);
304 }
305 }
306 for (_, col, val) in destab.z_sparse.triplet_iter() {
307 if col != qubit && *val > 0 {
308 new_dz_values.push(1u8);
309 new_dz_indices.push(col);
310 }
311 }
312
313 if dz_val > 0 {
314 new_dx_values.push(1u8);
315 new_dx_indices.push(qubit);
316 }
317 if dx_val > 0 {
318 new_dz_values.push(1u8);
319 new_dz_indices.push(qubit);
320 }
321
322 destab.x_sparse = if new_dx_values.is_empty() {
323 CsrMatrix::zeros(1, self.num_qubits)
324 } else {
325 let coo = CooMatrix::try_from_triplets(
326 1,
327 self.num_qubits,
328 vec![0; new_dx_values.len()],
329 new_dx_indices,
330 new_dx_values,
331 )
332 .map_err(|e| {
333 QuantRS2Error::GateApplicationFailed(format!(
334 "Failed to rebuild destabilizer X matrix: {e}"
335 ))
336 })?;
337 CsrMatrix::from(&coo)
338 };
339
340 destab.z_sparse = if new_dz_values.is_empty() {
341 CsrMatrix::zeros(1, self.num_qubits)
342 } else {
343 let coo = CooMatrix::try_from_triplets(
344 1,
345 self.num_qubits,
346 vec![0; new_dz_values.len()],
347 new_dz_indices,
348 new_dz_values,
349 )
350 .map_err(|e| {
351 QuantRS2Error::GateApplicationFailed(format!(
352 "Failed to rebuild destabilizer Z matrix: {e}"
353 ))
354 })?;
355 CsrMatrix::from(&coo)
356 };
357 }
358
359 Ok(())
360 }
361
362 pub fn apply_cnot(&mut self, control: usize, target: usize) -> Result<(), QuantRS2Error> {
364 if control >= self.num_qubits || target >= self.num_qubits {
365 return Err(QuantRS2Error::InvalidQubitId(control.max(target) as u32));
366 }
367
368 for i in 0..self.num_qubits {
370 let stab = &mut self.stabilizers[i];
372 let control_x = stab
373 .x_sparse
374 .get_entry(0, control)
375 .map_or(0, nalgebra_sparse::SparseEntry::into_value);
376 let control_z = stab
377 .z_sparse
378 .get_entry(0, control)
379 .map_or(0, nalgebra_sparse::SparseEntry::into_value);
380 let target_x = stab
381 .x_sparse
382 .get_entry(0, target)
383 .map_or(0, nalgebra_sparse::SparseEntry::into_value);
384 let target_z = stab
385 .z_sparse
386 .get_entry(0, target)
387 .map_or(0, nalgebra_sparse::SparseEntry::into_value);
388
389 if control_x > 0 {
391 let mut new_x_values = vec![];
393 let mut new_x_indices = vec![];
394
395 for (_, col, val) in stab.x_sparse.triplet_iter() {
396 if col != target && *val > 0 {
397 new_x_values.push(1u8);
398 new_x_indices.push(col);
399 }
400 }
401
402 if target_x == 0 {
404 new_x_values.push(1u8);
405 new_x_indices.push(target);
406 }
407
408 stab.x_sparse = if new_x_values.is_empty() {
409 CsrMatrix::zeros(1, self.num_qubits)
410 } else {
411 let coo = CooMatrix::try_from_triplets(
412 1,
413 self.num_qubits,
414 vec![0; new_x_values.len()],
415 new_x_indices,
416 new_x_values,
417 )
418 .map_err(|e| {
419 QuantRS2Error::GateApplicationFailed(format!(
420 "Failed to update X matrix in CNOT: {e}"
421 ))
422 })?;
423 CsrMatrix::from(&coo)
424 };
425 }
426
427 if target_z > 0 {
429 let mut new_z_values = vec![];
430 let mut new_z_indices = vec![];
431
432 for (_, col, val) in stab.z_sparse.triplet_iter() {
433 if col != control && *val > 0 {
434 new_z_values.push(1u8);
435 new_z_indices.push(col);
436 }
437 }
438
439 if control_z == 0 {
441 new_z_values.push(1u8);
442 new_z_indices.push(control);
443 }
444
445 stab.z_sparse = if new_z_values.is_empty() {
446 CsrMatrix::zeros(1, self.num_qubits)
447 } else {
448 let coo = CooMatrix::try_from_triplets(
449 1,
450 self.num_qubits,
451 vec![0; new_z_values.len()],
452 new_z_indices,
453 new_z_values,
454 )
455 .map_err(|e| {
456 QuantRS2Error::GateApplicationFailed(format!(
457 "Failed to update Z matrix in CNOT target: {e}"
458 ))
459 })?;
460 CsrMatrix::from(&coo)
461 };
462 }
463 }
464
465 Ok(())
466 }
467
468 pub fn apply_s(&mut self, qubit: usize) -> Result<(), QuantRS2Error> {
470 if qubit >= self.num_qubits {
471 return Err(QuantRS2Error::InvalidQubitId(qubit as u32));
472 }
473
474 for i in 0..self.num_qubits {
476 let stab = &mut self.stabilizers[i];
477 let x_val = stab
478 .x_sparse
479 .get_entry(0, qubit)
480 .map_or(0, nalgebra_sparse::SparseEntry::into_value);
481 let z_val = stab
482 .z_sparse
483 .get_entry(0, qubit)
484 .map_or(0, nalgebra_sparse::SparseEntry::into_value);
485
486 if x_val > 0 && z_val == 0 {
488 let mut new_z_values = vec![];
490 let mut new_z_indices = vec![];
491
492 for (_, col, val) in stab.z_sparse.triplet_iter() {
493 if *val > 0 {
494 new_z_values.push(1u8);
495 new_z_indices.push(col);
496 }
497 }
498
499 new_z_values.push(1u8);
500 new_z_indices.push(qubit);
501
502 stab.z_sparse = if new_z_values.is_empty() {
503 CsrMatrix::zeros(1, self.num_qubits)
504 } else {
505 let coo = CooMatrix::try_from_triplets(
506 1,
507 self.num_qubits,
508 vec![0; new_z_values.len()],
509 new_z_indices,
510 new_z_values,
511 )
512 .map_err(|e| {
513 QuantRS2Error::GateApplicationFailed(format!(
514 "Failed to update Z matrix in S gate: {e}"
515 ))
516 })?;
517 CsrMatrix::from(&coo)
518 };
519
520 stab.phase = (stab.phase + 1) % 4; }
523 }
524
525 Ok(())
526 }
527
528 #[must_use]
530 pub fn get_stabilizers(&self) -> Vec<String> {
531 self.stabilizers
532 .iter()
533 .map(|stab| {
534 let mut s = String::new();
535 s.push(match stab.phase {
536 0 => '+',
537 1 => 'i',
538 2 => '-',
539 3 => '-',
540 _ => '+',
541 });
542
543 for j in 0..self.num_qubits {
544 let has_x = stab
545 .x_sparse
546 .get_entry(0, j)
547 .map_or(0, nalgebra_sparse::SparseEntry::into_value)
548 > 0;
549 let has_z = stab
550 .z_sparse
551 .get_entry(0, j)
552 .map_or(0, nalgebra_sparse::SparseEntry::into_value)
553 > 0;
554
555 s.push(match (has_x, has_z) {
556 (false, false) => 'I',
557 (true, false) => 'X',
558 (false, true) => 'Z',
559 (true, true) => 'Y',
560 });
561 }
562
563 s
564 })
565 .collect()
566 }
567
568 #[must_use]
570 pub fn get_sparsity_info(&self) -> (f64, f64) {
571 let total_entries = self.num_qubits * self.num_qubits;
572
573 let stab_nonzero: usize = self
574 .stabilizers
575 .iter()
576 .map(|s| s.x_sparse.nnz() + s.z_sparse.nnz())
577 .sum();
578
579 let destab_nonzero: usize = self
580 .destabilizers
581 .iter()
582 .map(|s| s.x_sparse.nnz() + s.z_sparse.nnz())
583 .sum();
584
585 let stab_sparsity = 1.0 - (stab_nonzero as f64 / total_entries as f64);
586 let destab_sparsity = 1.0 - (destab_nonzero as f64 / total_entries as f64);
587
588 (stab_sparsity, destab_sparsity)
589 }
590}
591
592pub struct SparseCliffordSimulator {
594 tableau: SparseStabilizerTableau,
595 measurement_record: Vec<(usize, bool)>,
596}
597
598impl SparseCliffordSimulator {
599 #[must_use]
601 pub fn new(num_qubits: usize) -> Self {
602 Self {
603 tableau: SparseStabilizerTableau::new(num_qubits),
604 measurement_record: Vec::new(),
605 }
606 }
607
608 pub fn apply_gate(&mut self, gate: CliffordGate) -> Result<(), QuantRS2Error> {
610 match gate {
611 CliffordGate::H(q) => self.tableau.apply_h(q),
612 CliffordGate::S(q) => self.tableau.apply_s(q),
613 CliffordGate::CNOT(c, t) => self.tableau.apply_cnot(c, t),
614 CliffordGate::X(q) | CliffordGate::Y(q) | CliffordGate::Z(q) => {
615 Ok(())
617 }
618 }
619 }
620
621 #[must_use]
623 pub fn get_stabilizers(&self) -> Vec<String> {
624 self.tableau.get_stabilizers()
625 }
626
627 #[must_use]
629 pub fn get_sparsity_info(&self) -> (f64, f64) {
630 self.tableau.get_sparsity_info()
631 }
632
633 #[must_use]
635 pub const fn num_qubits(&self) -> usize {
636 self.tableau.num_qubits
637 }
638}
639
640#[derive(Debug, Clone, Copy)]
642pub enum CliffordGate {
643 H(usize),
644 S(usize),
645 X(usize),
646 Y(usize),
647 Z(usize),
648 CNOT(usize, usize),
649}
650
651#[cfg(test)]
652mod tests {
653 use super::*;
654
655 #[test]
656 fn test_sparse_init() {
657 let sim = SparseCliffordSimulator::new(100);
658 let (stab_sparsity, destab_sparsity) = sim.get_sparsity_info();
659
660 assert!(stab_sparsity > 0.98);
662 assert!(destab_sparsity > 0.98);
663 }
664
665 #[test]
666 fn test_sparse_hadamard() {
667 let mut sim = SparseCliffordSimulator::new(5);
668 sim.apply_gate(CliffordGate::H(0))
669 .expect("Failed to apply Hadamard gate");
670
671 let stabs = sim.get_stabilizers();
672 assert_eq!(stabs[0], "+XIIII");
673 }
674
675 #[test]
676 fn test_sparse_cnot_chain() {
677 let mut sim = SparseCliffordSimulator::new(10);
678
679 for i in 0..9 {
681 sim.apply_gate(CliffordGate::CNOT(i, i + 1))
682 .expect("Failed to apply CNOT gate");
683 }
684
685 let (stab_sparsity, _) = sim.get_sparsity_info();
686 assert!(stab_sparsity > 0.8);
688 }
689}