1use quantrs2_circuit::prelude::*;
8use quantrs2_core::prelude::*;
9use scirs2_sparse::coo::CooMatrix;
10use scirs2_sparse::csr::CsrMatrix;
11
12fn build_csr_1row(
14 num_cols: usize,
15 col_indices: Vec<usize>,
16 values: Vec<u8>,
17) -> Result<CsrMatrix<u8>, QuantRS2Error> {
18 if values.is_empty() {
19 return Ok(CsrMatrix::empty((1, num_cols)));
20 }
21 let row_indices = vec![0usize; values.len()];
22 CooMatrix::new(values, row_indices, col_indices, (1, num_cols))
23 .and_then(|coo| {
24 let (rows, cols, vals) = coo.get_triplets();
25 CsrMatrix::from_triplets(1, num_cols, rows, cols, vals)
26 })
27 .map_err(|e| QuantRS2Error::InvalidInput(format!("Failed to build sparse matrix: {e}")))
28}
29
30fn iter_row0(mat: &CsrMatrix<u8>) -> impl Iterator<Item = (usize, u8)> + '_ {
32 let start = mat.indptr[0];
33 let end = mat.indptr[1];
34 (start..end).map(|j| (mat.indices[j], mat.data[j]))
35}
36
37#[derive(Clone)]
39pub struct SparsePauli {
40 x_sparse: CsrMatrix<u8>,
42 z_sparse: CsrMatrix<u8>,
44 phase: u8,
46}
47
48impl SparsePauli {
49 #[must_use]
51 pub fn identity(num_qubits: usize) -> Self {
52 let x_sparse = CsrMatrix::empty((1, num_qubits));
53 let z_sparse = CsrMatrix::empty((1, num_qubits));
54 Self {
55 x_sparse,
56 z_sparse,
57 phase: 0,
58 }
59 }
60
61 pub fn single_qubit(
63 num_qubits: usize,
64 qubit: usize,
65 pauli: char,
66 ) -> Result<Self, QuantRS2Error> {
67 let mut x_values = vec![];
68 let mut x_indices = vec![];
69 let mut z_values = vec![];
70 let mut z_indices = vec![];
71
72 match pauli {
73 'X' => {
74 x_values.push(1u8);
75 x_indices.push(qubit);
76 }
77 'Y' => {
78 x_values.push(1u8);
79 x_indices.push(qubit);
80 z_values.push(1u8);
81 z_indices.push(qubit);
82 }
83 'Z' => {
84 z_values.push(1u8);
85 z_indices.push(qubit);
86 }
87 _ => {}
88 }
89
90 let x_sparse = build_csr_1row(num_qubits, x_indices, x_values)?;
91 let z_sparse = build_csr_1row(num_qubits, z_indices, z_values)?;
92
93 Ok(Self {
94 x_sparse,
95 z_sparse,
96 phase: 0,
97 })
98 }
99
100 fn commutation_phase(&self, other: &Self) -> u8 {
102 let mut phase = 0u8;
103
104 for col in 0..self.x_sparse.cols() {
106 let self_x = self.x_sparse.get(0, col);
107 let self_z = self.z_sparse.get(0, col);
108 let other_x = other.x_sparse.get(0, col);
109 let other_z = other.z_sparse.get(0, col);
110
111 if self_x > 0 && other_z > 0 && self_z == 0 {
113 phase = (phase + 2) % 4; }
115 if self_z > 0 && other_x > 0 && self_x == 0 {
116 phase = (phase + 2) % 4; }
118 }
119
120 phase
121 }
122}
123
124pub struct SparseStabilizerTableau {
126 num_qubits: usize,
127 stabilizers: Vec<SparsePauli>,
129 destabilizers: Vec<SparsePauli>,
131}
132
133impl SparseStabilizerTableau {
134 #[must_use]
136 pub fn new(num_qubits: usize) -> Self {
137 let mut stabilizers = Vec::with_capacity(num_qubits);
138 let mut destabilizers = Vec::with_capacity(num_qubits);
139
140 for i in 0..num_qubits {
141 stabilizers.push(
143 SparsePauli::single_qubit(num_qubits, i, 'Z')
144 .unwrap_or_else(|_| SparsePauli::identity(num_qubits)),
145 );
146 destabilizers.push(
148 SparsePauli::single_qubit(num_qubits, i, 'X')
149 .unwrap_or_else(|_| SparsePauli::identity(num_qubits)),
150 );
151 }
152
153 Self {
154 num_qubits,
155 stabilizers,
156 destabilizers,
157 }
158 }
159
160 pub fn apply_h(&mut self, qubit: usize) -> Result<(), QuantRS2Error> {
162 if qubit >= self.num_qubits {
163 return Err(QuantRS2Error::InvalidQubitId(qubit as u32));
164 }
165
166 for i in 0..self.num_qubits {
168 {
170 let stab = &mut self.stabilizers[i];
171 let x_val = stab.x_sparse.get(0, qubit);
172 let z_val = stab.z_sparse.get(0, qubit);
173
174 if x_val > 0 && z_val > 0 {
176 stab.phase = (stab.phase + 2) % 4; }
178
179 let mut new_x_values = vec![];
181 let mut new_x_indices = vec![];
182 let mut new_z_values = vec![];
183 let mut new_z_indices = vec![];
184
185 for (col, val) in iter_row0(&stab.x_sparse) {
187 if col != qubit && val > 0 {
188 new_x_values.push(1u8);
189 new_x_indices.push(col);
190 }
191 }
192 for (col, val) in iter_row0(&stab.z_sparse) {
193 if col != qubit && val > 0 {
194 new_z_values.push(1u8);
195 new_z_indices.push(col);
196 }
197 }
198
199 if z_val > 0 {
201 new_x_values.push(1u8);
202 new_x_indices.push(qubit);
203 }
204 if x_val > 0 {
205 new_z_values.push(1u8);
206 new_z_indices.push(qubit);
207 }
208
209 stab.x_sparse = build_csr_1row(self.num_qubits, new_x_indices, new_x_values)
210 .map_err(|e| {
211 QuantRS2Error::GateApplicationFailed(format!(
212 "Failed to rebuild sparse X matrix: {e}"
213 ))
214 })?;
215 stab.z_sparse = build_csr_1row(self.num_qubits, new_z_indices, new_z_values)
216 .map_err(|e| {
217 QuantRS2Error::GateApplicationFailed(format!(
218 "Failed to rebuild sparse Z matrix: {e}"
219 ))
220 })?;
221 }
222
223 {
225 let destab = &mut self.destabilizers[i];
226 let dx_val = destab.x_sparse.get(0, qubit);
227 let dz_val = destab.z_sparse.get(0, qubit);
228
229 if dx_val > 0 && dz_val > 0 {
230 destab.phase = (destab.phase + 2) % 4;
231 }
232
233 let mut new_dx_values = vec![];
234 let mut new_dx_indices = vec![];
235 let mut new_dz_values = vec![];
236 let mut new_dz_indices = vec![];
237
238 for (col, val) in iter_row0(&destab.x_sparse) {
239 if col != qubit && val > 0 {
240 new_dx_values.push(1u8);
241 new_dx_indices.push(col);
242 }
243 }
244 for (col, val) in iter_row0(&destab.z_sparse) {
245 if col != qubit && val > 0 {
246 new_dz_values.push(1u8);
247 new_dz_indices.push(col);
248 }
249 }
250
251 if dz_val > 0 {
252 new_dx_values.push(1u8);
253 new_dx_indices.push(qubit);
254 }
255 if dx_val > 0 {
256 new_dz_values.push(1u8);
257 new_dz_indices.push(qubit);
258 }
259
260 destab.x_sparse = build_csr_1row(self.num_qubits, new_dx_indices, new_dx_values)
261 .map_err(|e| {
262 QuantRS2Error::GateApplicationFailed(format!(
263 "Failed to rebuild destabilizer X matrix: {e}"
264 ))
265 })?;
266 destab.z_sparse = build_csr_1row(self.num_qubits, new_dz_indices, new_dz_values)
267 .map_err(|e| {
268 QuantRS2Error::GateApplicationFailed(format!(
269 "Failed to rebuild destabilizer Z matrix: {e}"
270 ))
271 })?;
272 }
273 }
274
275 Ok(())
276 }
277
278 pub fn apply_cnot(&mut self, control: usize, target: usize) -> Result<(), QuantRS2Error> {
280 if control >= self.num_qubits || target >= self.num_qubits {
281 return Err(QuantRS2Error::InvalidQubitId(control.max(target) as u32));
282 }
283
284 for i in 0..self.num_qubits {
286 {
288 let stab = &mut self.stabilizers[i];
289 let control_x = stab.x_sparse.get(0, control);
290 let control_z = stab.z_sparse.get(0, control);
291 let target_x = stab.x_sparse.get(0, target);
292 let target_z = stab.z_sparse.get(0, target);
293
294 if control_x > 0 {
296 let mut new_x_values = vec![];
297 let mut new_x_indices = vec![];
298
299 for (col, val) in iter_row0(&stab.x_sparse) {
300 if col != target && val > 0 {
301 new_x_values.push(1u8);
302 new_x_indices.push(col);
303 }
304 }
305
306 if target_x == 0 {
308 new_x_values.push(1u8);
309 new_x_indices.push(target);
310 }
311
312 stab.x_sparse = build_csr_1row(self.num_qubits, new_x_indices, new_x_values)
313 .map_err(|e| {
314 QuantRS2Error::GateApplicationFailed(format!(
315 "Failed to update X matrix in CNOT: {e}"
316 ))
317 })?;
318 }
319
320 if target_z > 0 {
322 let mut new_z_values = vec![];
323 let mut new_z_indices = vec![];
324
325 for (col, val) in iter_row0(&stab.z_sparse) {
326 if col != control && val > 0 {
327 new_z_values.push(1u8);
328 new_z_indices.push(col);
329 }
330 }
331
332 if control_z == 0 {
334 new_z_values.push(1u8);
335 new_z_indices.push(control);
336 }
337
338 stab.z_sparse = build_csr_1row(self.num_qubits, new_z_indices, new_z_values)
339 .map_err(|e| {
340 QuantRS2Error::GateApplicationFailed(format!(
341 "Failed to update Z matrix in CNOT target: {e}"
342 ))
343 })?;
344 }
345 }
346 }
347
348 Ok(())
349 }
350
351 pub fn apply_s(&mut self, qubit: usize) -> Result<(), QuantRS2Error> {
353 if qubit >= self.num_qubits {
354 return Err(QuantRS2Error::InvalidQubitId(qubit as u32));
355 }
356
357 for i in 0..self.num_qubits {
359 let stab = &mut self.stabilizers[i];
360 let x_val = stab.x_sparse.get(0, qubit);
361 let z_val = stab.z_sparse.get(0, qubit);
362
363 if x_val > 0 && z_val == 0 {
365 let mut new_z_values = vec![];
367 let mut new_z_indices = vec![];
368
369 for (col, val) in iter_row0(&stab.z_sparse) {
370 if val > 0 {
371 new_z_values.push(1u8);
372 new_z_indices.push(col);
373 }
374 }
375
376 new_z_values.push(1u8);
377 new_z_indices.push(qubit);
378
379 stab.z_sparse = build_csr_1row(self.num_qubits, new_z_indices, new_z_values)
380 .map_err(|e| {
381 QuantRS2Error::GateApplicationFailed(format!(
382 "Failed to update Z matrix in S gate: {e}"
383 ))
384 })?;
385
386 stab.phase = (stab.phase + 1) % 4; }
389 }
390
391 Ok(())
392 }
393
394 #[must_use]
396 pub fn get_stabilizers(&self) -> Vec<String> {
397 self.stabilizers
398 .iter()
399 .map(|stab| {
400 let mut s = String::new();
401 s.push(match stab.phase {
402 0 => '+',
403 1 => 'i',
404 2 => '-',
405 3 => '-',
406 _ => '+',
407 });
408
409 for j in 0..self.num_qubits {
410 let has_x = stab.x_sparse.get(0, j) > 0;
411 let has_z = stab.z_sparse.get(0, j) > 0;
412
413 s.push(match (has_x, has_z) {
414 (false, false) => 'I',
415 (true, false) => 'X',
416 (false, true) => 'Z',
417 (true, true) => 'Y',
418 });
419 }
420
421 s
422 })
423 .collect()
424 }
425
426 #[must_use]
428 pub fn get_sparsity_info(&self) -> (f64, f64) {
429 let total_entries = self.num_qubits * self.num_qubits;
430
431 let stab_nonzero: usize = self
432 .stabilizers
433 .iter()
434 .map(|s| s.x_sparse.nnz() + s.z_sparse.nnz())
435 .sum();
436
437 let destab_nonzero: usize = self
438 .destabilizers
439 .iter()
440 .map(|s| s.x_sparse.nnz() + s.z_sparse.nnz())
441 .sum();
442
443 let stab_sparsity = 1.0 - (stab_nonzero as f64 / total_entries as f64);
444 let destab_sparsity = 1.0 - (destab_nonzero as f64 / total_entries as f64);
445
446 (stab_sparsity, destab_sparsity)
447 }
448}
449
450pub struct SparseCliffordSimulator {
452 tableau: SparseStabilizerTableau,
453 measurement_record: Vec<(usize, bool)>,
454}
455
456impl SparseCliffordSimulator {
457 #[must_use]
459 pub fn new(num_qubits: usize) -> Self {
460 Self {
461 tableau: SparseStabilizerTableau::new(num_qubits),
462 measurement_record: Vec::new(),
463 }
464 }
465
466 pub fn apply_gate(&mut self, gate: CliffordGate) -> Result<(), QuantRS2Error> {
468 match gate {
469 CliffordGate::H(q) => self.tableau.apply_h(q),
470 CliffordGate::S(q) => self.tableau.apply_s(q),
471 CliffordGate::CNOT(c, t) => self.tableau.apply_cnot(c, t),
472 CliffordGate::X(q) | CliffordGate::Y(q) | CliffordGate::Z(q) => {
473 Ok(())
475 }
476 }
477 }
478
479 #[must_use]
481 pub fn get_stabilizers(&self) -> Vec<String> {
482 self.tableau.get_stabilizers()
483 }
484
485 #[must_use]
487 pub fn get_sparsity_info(&self) -> (f64, f64) {
488 self.tableau.get_sparsity_info()
489 }
490
491 #[must_use]
493 pub const fn num_qubits(&self) -> usize {
494 self.tableau.num_qubits
495 }
496}
497
498#[derive(Debug, Clone, Copy)]
500pub enum CliffordGate {
501 H(usize),
502 S(usize),
503 X(usize),
504 Y(usize),
505 Z(usize),
506 CNOT(usize, usize),
507}
508
509#[cfg(test)]
510mod tests {
511 use super::*;
512
513 #[test]
514 fn test_sparse_init() {
515 let sim = SparseCliffordSimulator::new(100);
516 let (stab_sparsity, destab_sparsity) = sim.get_sparsity_info();
517
518 assert!(stab_sparsity > 0.98);
520 assert!(destab_sparsity > 0.98);
521 }
522
523 #[test]
524 fn test_sparse_hadamard() {
525 let mut sim = SparseCliffordSimulator::new(5);
526 sim.apply_gate(CliffordGate::H(0))
527 .expect("Failed to apply Hadamard gate");
528
529 let stabs = sim.get_stabilizers();
530 assert_eq!(stabs[0], "+XIIII");
531 }
532
533 #[test]
534 fn test_sparse_cnot_chain() {
535 let mut sim = SparseCliffordSimulator::new(10);
536
537 for i in 0..9 {
539 sim.apply_gate(CliffordGate::CNOT(i, i + 1))
540 .expect("Failed to apply CNOT gate");
541 }
542
543 let (stab_sparsity, _) = sim.get_sparsity_info();
544 assert!(stab_sparsity > 0.8);
546 }
547}