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