1use crate::prelude::SimulatorError;
10use scirs2_core::ndarray::Array2;
11use scirs2_core::parallel_ops::{IndexedParallelIterator, ParallelIterator};
12use scirs2_core::Complex64;
13use std::collections::HashMap;
14use std::fmt;
15
16use crate::error::Result;
17use crate::trotter::{DynamicCircuit, Hamiltonian, TrotterDecomposer, TrotterMethod};
18use quantrs2_core::gate::{
19 multi::CNOT,
20 single::{Hadamard, Phase, PhaseDagger, RotationX, RotationY, RotationZ},
21 GateOp,
22};
23use quantrs2_core::qubit::QubitId;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub enum PauliOperator {
28 I,
30 X,
32 Y,
34 Z,
36}
37
38impl fmt::Display for PauliOperator {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 Self::I => write!(f, "I"),
42 Self::X => write!(f, "X"),
43 Self::Y => write!(f, "Y"),
44 Self::Z => write!(f, "Z"),
45 }
46 }
47}
48
49impl PauliOperator {
50 pub fn from_str(s: &str) -> Result<Self> {
52 match s.to_uppercase().as_str() {
53 "I" => Ok(Self::I),
54 "X" => Ok(Self::X),
55 "Y" => Ok(Self::Y),
56 "Z" => Ok(Self::Z),
57 _ => Err(SimulatorError::InvalidInput(format!(
58 "Invalid Pauli operator: {s}"
59 ))),
60 }
61 }
62
63 #[must_use]
65 pub fn matrix(&self) -> Array2<Complex64> {
66 match self {
67 Self::I => Array2::from_shape_vec(
68 (2, 2),
69 vec![
70 Complex64::new(1.0, 0.0),
71 Complex64::new(0.0, 0.0),
72 Complex64::new(0.0, 0.0),
73 Complex64::new(1.0, 0.0),
74 ],
75 )
76 .expect("Pauli I matrix has valid shape"),
77 Self::X => Array2::from_shape_vec(
78 (2, 2),
79 vec![
80 Complex64::new(0.0, 0.0),
81 Complex64::new(1.0, 0.0),
82 Complex64::new(1.0, 0.0),
83 Complex64::new(0.0, 0.0),
84 ],
85 )
86 .expect("Pauli X matrix has valid shape"),
87 Self::Y => Array2::from_shape_vec(
88 (2, 2),
89 vec![
90 Complex64::new(0.0, 0.0),
91 Complex64::new(0.0, -1.0),
92 Complex64::new(0.0, 1.0),
93 Complex64::new(0.0, 0.0),
94 ],
95 )
96 .expect("Pauli Y matrix has valid shape"),
97 Self::Z => Array2::from_shape_vec(
98 (2, 2),
99 vec![
100 Complex64::new(1.0, 0.0),
101 Complex64::new(0.0, 0.0),
102 Complex64::new(0.0, 0.0),
103 Complex64::new(-1.0, 0.0),
104 ],
105 )
106 .expect("Pauli Z matrix has valid shape"),
107 }
108 }
109
110 #[must_use]
112 pub fn commutes_with(&self, other: &Self) -> bool {
113 match (self, other) {
114 (Self::I, _) | (_, Self::I) => true,
115 (a, b) if a == b => true,
116 _ => false,
117 }
118 }
119
120 #[must_use]
122 pub const fn multiply(&self, other: &Self) -> (Self, Complex64) {
123 match (self, other) {
124 (Self::I, p) | (p, Self::I) => (*p, Complex64::new(1.0, 0.0)),
125 (Self::X, Self::X) | (Self::Y, Self::Y) | (Self::Z, Self::Z) => {
126 (Self::I, Complex64::new(1.0, 0.0))
127 }
128 (Self::X, Self::Y) => (Self::Z, Complex64::new(0.0, 1.0)),
129 (Self::Y, Self::X) => (Self::Z, Complex64::new(0.0, -1.0)),
130 (Self::Y, Self::Z) => (Self::X, Complex64::new(0.0, 1.0)),
131 (Self::Z, Self::Y) => (Self::X, Complex64::new(0.0, -1.0)),
132 (Self::Z, Self::X) => (Self::Y, Complex64::new(0.0, 1.0)),
133 (Self::X, Self::Z) => (Self::Y, Complex64::new(0.0, -1.0)),
134 }
135 }
136}
137
138#[derive(Debug, Clone)]
140pub struct PauliString {
141 pub operators: Vec<PauliOperator>,
143 pub coefficient: Complex64,
145 pub num_qubits: usize,
147}
148
149impl PauliString {
150 #[must_use]
152 pub fn new(num_qubits: usize) -> Self {
153 Self {
154 operators: vec![PauliOperator::I; num_qubits],
155 coefficient: Complex64::new(1.0, 0.0),
156 num_qubits,
157 }
158 }
159
160 pub fn from_string(pauli_str: &str, coefficient: Complex64) -> Result<Self> {
162 let operators: Result<Vec<_>> = pauli_str
163 .chars()
164 .map(|c| PauliOperator::from_str(&c.to_string()))
165 .collect();
166
167 Ok(Self {
168 operators: operators?,
169 coefficient,
170 num_qubits: pauli_str.len(),
171 })
172 }
173
174 pub fn from_ops(
176 num_qubits: usize,
177 ops: &[(usize, PauliOperator)],
178 coefficient: Complex64,
179 ) -> Result<Self> {
180 let mut pauli_string = Self::new(num_qubits);
181 pauli_string.coefficient = coefficient;
182
183 for &(qubit, op) in ops {
184 if qubit >= num_qubits {
185 return Err(SimulatorError::IndexOutOfBounds(qubit));
186 }
187 pauli_string.operators[qubit] = op;
188 }
189
190 Ok(pauli_string)
191 }
192
193 pub fn set_operator(&mut self, qubit: usize, op: PauliOperator) -> Result<()> {
195 if qubit >= self.num_qubits {
196 return Err(SimulatorError::IndexOutOfBounds(qubit));
197 }
198 self.operators[qubit] = op;
199 Ok(())
200 }
201
202 pub fn get_operator(&self, qubit: usize) -> Result<PauliOperator> {
204 if qubit >= self.num_qubits {
205 return Err(SimulatorError::IndexOutOfBounds(qubit));
206 }
207 Ok(self.operators[qubit])
208 }
209
210 #[must_use]
212 pub fn non_identity_ops(&self) -> Vec<(usize, PauliOperator)> {
213 self.operators
214 .iter()
215 .enumerate()
216 .filter(|(_, &op)| op != PauliOperator::I)
217 .map(|(i, &op)| (i, op))
218 .collect()
219 }
220
221 #[must_use]
223 pub fn commutes_with(&self, other: &Self) -> bool {
224 if self.num_qubits != other.num_qubits {
225 return false;
226 }
227
228 let mut anti_commute_count = 0;
229 for i in 0..self.num_qubits {
230 if !self.operators[i].commutes_with(&other.operators[i]) {
231 anti_commute_count += 1;
232 }
233 }
234
235 anti_commute_count % 2 == 0
237 }
238
239 pub fn multiply(&self, other: &Self) -> Result<Self> {
241 if self.num_qubits != other.num_qubits {
242 return Err(SimulatorError::DimensionMismatch(format!(
243 "Pauli strings have different lengths: {} vs {}",
244 self.num_qubits, other.num_qubits
245 )));
246 }
247
248 let mut result = Self::new(self.num_qubits);
249 let mut total_phase = self.coefficient * other.coefficient;
250
251 for i in 0..self.num_qubits {
252 let (op, phase) = self.operators[i].multiply(&other.operators[i]);
253 result.operators[i] = op;
254 total_phase *= phase;
255 }
256
257 result.coefficient = total_phase;
258 Ok(result)
259 }
260
261 #[must_use]
263 pub fn weight(&self) -> usize {
264 self.operators
265 .iter()
266 .filter(|&&op| op != PauliOperator::I)
267 .count()
268 }
269
270 #[must_use]
272 pub fn pauli_string(&self) -> String {
273 self.operators
274 .iter()
275 .map(std::string::ToString::to_string)
276 .collect()
277 }
278
279 pub fn evolution_circuit(&self, time: f64) -> Result<DynamicCircuit> {
281 let mut circuit = DynamicCircuit::new(self.num_qubits);
282
283 if self.weight() == 0 {
284 return Ok(circuit);
286 }
287
288 let non_identity = self.non_identity_ops();
289 let angle = -2.0 * self.coefficient.re * time;
290
291 if non_identity.len() == 1 {
292 let (qubit, op) = non_identity[0];
294 match op {
295 PauliOperator::X => circuit.add_gate(Box::new(RotationX {
296 target: QubitId::new(qubit as u32),
297 theta: angle,
298 }))?,
299 PauliOperator::Y => circuit.add_gate(Box::new(RotationY {
300 target: QubitId::new(qubit as u32),
301 theta: angle,
302 }))?,
303 PauliOperator::Z => circuit.add_gate(Box::new(RotationZ {
304 target: QubitId::new(qubit as u32),
305 theta: angle,
306 }))?,
307 PauliOperator::I => {} }
309 } else {
310 for &(qubit, op) in &non_identity {
314 match op {
315 PauliOperator::X => circuit.add_gate(Box::new(Hadamard {
316 target: QubitId::new(qubit as u32),
317 }))?,
318 PauliOperator::Y => {
319 circuit.add_gate(Box::new(Hadamard {
320 target: QubitId::new(qubit as u32),
321 }))?;
322 circuit.add_gate(Box::new(Phase {
323 target: QubitId::new(qubit as u32),
324 }))?;
325 }
326 PauliOperator::Z => {} PauliOperator::I => {} }
329 }
330
331 for i in 0..non_identity.len() - 1 {
333 circuit.add_gate(Box::new(CNOT {
334 control: QubitId::new(non_identity[i].0 as u32),
335 target: QubitId::new(non_identity[i + 1].0 as u32),
336 }))?;
337 }
338
339 circuit.add_gate(Box::new(RotationZ {
341 target: QubitId::new(non_identity[non_identity.len() - 1].0 as u32),
342 theta: angle,
343 }))?;
344
345 for i in (0..non_identity.len() - 1).rev() {
347 circuit.add_gate(Box::new(CNOT {
348 control: QubitId::new(non_identity[i].0 as u32),
349 target: QubitId::new(non_identity[i + 1].0 as u32),
350 }))?;
351 }
352
353 for &(qubit, op) in non_identity.iter().rev() {
355 match op {
356 PauliOperator::X => circuit.add_gate(Box::new(Hadamard {
357 target: QubitId::new(qubit as u32),
358 }))?,
359 PauliOperator::Y => {
360 circuit.add_gate(Box::new(PhaseDagger {
361 target: QubitId::new(qubit as u32),
362 }))?;
363 circuit.add_gate(Box::new(Hadamard {
364 target: QubitId::new(qubit as u32),
365 }))?;
366 }
367 PauliOperator::Z => {} PauliOperator::I => {} }
370 }
371 }
372
373 Ok(circuit)
374 }
375}
376
377impl fmt::Display for PauliString {
378 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379 write!(f, "({}) {}", self.coefficient, self.pauli_string())
380 }
381}
382
383#[derive(Debug, Clone)]
385pub struct PauliOperatorSum {
386 pub terms: Vec<PauliString>,
388 pub num_qubits: usize,
390}
391
392impl PauliOperatorSum {
393 #[must_use]
395 pub const fn new(num_qubits: usize) -> Self {
396 Self {
397 terms: Vec::new(),
398 num_qubits,
399 }
400 }
401
402 pub fn add_term(&mut self, pauli_string: PauliString) -> Result<()> {
404 if pauli_string.num_qubits != self.num_qubits {
405 return Err(SimulatorError::DimensionMismatch(format!(
406 "Pauli string has {} qubits, expected {}",
407 pauli_string.num_qubits, self.num_qubits
408 )));
409 }
410 self.terms.push(pauli_string);
411 Ok(())
412 }
413
414 pub fn simplify(&mut self) {
416 let mut simplified_terms: HashMap<String, Complex64> = HashMap::new();
417
418 for term in &self.terms {
419 let key = format!("{term}");
420 *simplified_terms
421 .entry(key)
422 .or_insert(Complex64::new(0.0, 0.0)) += term.coefficient;
423 }
424
425 self.terms.clear();
426 for (pauli_str, coeff) in simplified_terms {
427 if coeff.norm() > 1e-15 {
428 if let Ok(term) = PauliString::from_string(&pauli_str, coeff) {
429 self.terms.push(term);
430 }
431 }
432 }
433 }
434
435 #[must_use]
437 pub fn to_hamiltonian(&self) -> Hamiltonian {
438 let mut ham = Hamiltonian::new(self.num_qubits);
439
440 for term in &self.terms {
441 let non_identity = term.non_identity_ops();
442
443 match non_identity.len() {
444 0 => {} 1 => {
446 let (qubit, op) = non_identity[0];
447 let pauli_str = match op {
448 PauliOperator::X => "X",
449 PauliOperator::Y => "Y",
450 PauliOperator::Z => "Z",
451 PauliOperator::I => continue,
452 };
453 let _ = ham.add_single_pauli(qubit, pauli_str, term.coefficient.re);
454 }
455 2 => {
456 let (q1, op1) = non_identity[0];
457 let (q2, op2) = non_identity[1];
458 let pauli1 = match op1 {
459 PauliOperator::X => "X",
460 PauliOperator::Y => "Y",
461 PauliOperator::Z => "Z",
462 PauliOperator::I => continue,
463 };
464 let pauli2 = match op2 {
465 PauliOperator::X => "X",
466 PauliOperator::Y => "Y",
467 PauliOperator::Z => "Z",
468 PauliOperator::I => continue,
469 };
470 let _ = ham.add_two_pauli(q1, q2, pauli1, pauli2, term.coefficient.re);
471 }
472 _ => {
473 let qubits: Vec<usize> = non_identity.iter().map(|&(q, _)| q).collect();
475 let paulis: Vec<String> = non_identity
476 .iter()
477 .map(|&(_, op)| match op {
478 PauliOperator::X => "X".to_string(),
479 PauliOperator::Y => "Y".to_string(),
480 PauliOperator::Z => "Z".to_string(),
481 PauliOperator::I => "I".to_string(),
482 })
483 .collect();
484 let _ = ham.add_pauli_string(qubits, paulis, term.coefficient.re);
485 }
486 }
487 }
488
489 ham
490 }
491
492 pub fn time_evolution_circuit(
494 &self,
495 time: f64,
496 trotter_steps: usize,
497 method: TrotterMethod,
498 ) -> Result<DynamicCircuit> {
499 let hamiltonian = self.to_hamiltonian();
500 let decomposer = TrotterDecomposer::new(method, trotter_steps);
501 decomposer.decompose(&hamiltonian, time)
502 }
503
504 pub fn exact_evolution_circuit(&self, time: f64) -> Result<DynamicCircuit> {
506 let mut circuit = DynamicCircuit::new(self.num_qubits);
507
508 for term in &self.terms {
509 let term_circuit = term.evolution_circuit(time)?;
510 for gate in term_circuit.gates() {
511 circuit.add_gate(gate.clone())?;
512 }
513 }
514
515 Ok(circuit)
516 }
517}
518
519impl fmt::Display for PauliOperatorSum {
520 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
521 if self.terms.is_empty() {
522 write!(f, "0")
523 } else {
524 let term_strs: Vec<String> = self.terms.iter().map(|t| format!("{t}")).collect();
525 write!(f, "{}", term_strs.join(" + "))
526 }
527 }
528}
529
530pub struct PauliUtils;
532
533impl PauliUtils {
534 pub fn single_qubit(
536 num_qubits: usize,
537 qubit: usize,
538 op: PauliOperator,
539 coeff: Complex64,
540 ) -> Result<PauliString> {
541 PauliString::from_ops(num_qubits, &[(qubit, op)], coeff)
542 }
543
544 #[must_use]
546 pub fn all_x(num_qubits: usize, coeff: Complex64) -> PauliString {
547 let mut pauli = PauliString::new(num_qubits);
548 pauli.coefficient = coeff;
549 for i in 0..num_qubits {
550 pauli.operators[i] = PauliOperator::X;
551 }
552 pauli
553 }
554
555 #[must_use]
557 pub fn all_z(num_qubits: usize, coeff: Complex64) -> PauliString {
558 let mut pauli = PauliString::new(num_qubits);
559 pauli.coefficient = coeff;
560 for i in 0..num_qubits {
561 pauli.operators[i] = PauliOperator::Z;
562 }
563 pauli
564 }
565
566 pub fn random(num_qubits: usize, weight: usize, coeff: Complex64) -> Result<PauliString> {
568 if weight > num_qubits {
569 return Err(SimulatorError::InvalidInput(
570 "Weight cannot exceed number of qubits".to_string(),
571 ));
572 }
573
574 let mut pauli = PauliString::new(num_qubits);
575 pauli.coefficient = coeff;
576
577 let mut positions: Vec<usize> = (0..num_qubits).collect();
579 fastrand::shuffle(&mut positions);
580
581 let ops = [PauliOperator::X, PauliOperator::Y, PauliOperator::Z];
582
583 for &pos in &positions[..weight] {
584 pauli.operators[pos] = ops[fastrand::usize(0..3)];
585 }
586
587 Ok(pauli)
588 }
589
590 #[must_use]
592 pub fn are_mutually_commuting(pauli_strings: &[PauliString]) -> bool {
593 for i in 0..pauli_strings.len() {
594 for j in i + 1..pauli_strings.len() {
595 if !pauli_strings[i].commutes_with(&pauli_strings[j]) {
596 return false;
597 }
598 }
599 }
600 true
601 }
602
603 #[must_use]
605 pub fn maximal_commuting_set(pauli_strings: &[PauliString]) -> Vec<usize> {
606 let mut commuting_set = Vec::new();
607
608 for (i, pauli) in pauli_strings.iter().enumerate() {
609 let mut can_add = true;
610 for &j in &commuting_set {
611 if !pauli.commutes_with(&pauli_strings[j]) {
612 can_add = false;
613 break;
614 }
615 }
616
617 if can_add {
618 commuting_set.push(i);
619 }
620 }
621
622 commuting_set
623 }
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629
630 #[test]
631 fn test_pauli_operator_multiply() {
632 let (result, phase) = PauliOperator::X.multiply(&PauliOperator::Y);
633 assert_eq!(result, PauliOperator::Z);
634 assert_eq!(phase, Complex64::new(0.0, 1.0));
635
636 let (result, phase) = PauliOperator::Y.multiply(&PauliOperator::X);
637 assert_eq!(result, PauliOperator::Z);
638 assert_eq!(phase, Complex64::new(0.0, -1.0));
639 }
640
641 #[test]
642 fn test_pauli_string_creation() {
643 let pauli = PauliString::from_string("XYZ", Complex64::new(1.0, 0.0))
644 .expect("Pauli string 'XYZ' should be parsed successfully");
645 assert_eq!(pauli.num_qubits, 3);
646 assert_eq!(pauli.operators[0], PauliOperator::X);
647 assert_eq!(pauli.operators[1], PauliOperator::Y);
648 assert_eq!(pauli.operators[2], PauliOperator::Z);
649 }
650
651 #[test]
652 fn test_pauli_string_multiply() {
653 let p1 = PauliString::from_string("XY", Complex64::new(1.0, 0.0))
654 .expect("Pauli string 'XY' should be parsed successfully");
655 let p2 = PauliString::from_string("YZ", Complex64::new(1.0, 0.0))
656 .expect("Pauli string 'YZ' should be parsed successfully");
657
658 let result = p1
659 .multiply(&p2)
660 .expect("Pauli string multiplication should succeed");
661 assert_eq!(result.operators[0], PauliOperator::Z);
662 assert_eq!(result.operators[1], PauliOperator::X);
663 assert_eq!(result.coefficient, Complex64::new(-1.0, 0.0));
664 }
665
666 #[test]
667 fn test_pauli_string_commutation() {
668 let p1 = PauliString::from_string("XY", Complex64::new(1.0, 0.0))
669 .expect("Pauli string 'XY' should be parsed successfully");
670 let p2 = PauliString::from_string("ZI", Complex64::new(1.0, 0.0))
671 .expect("Pauli string 'ZI' should be parsed successfully");
672 let p3 = PauliString::from_string("XI", Complex64::new(1.0, 0.0))
673 .expect("Pauli string 'XI' should be parsed successfully");
674
675 assert!(!p1.commutes_with(&p2)); assert!(p1.commutes_with(&p3)); }
678
679 #[test]
680 fn test_pauli_string_weight() {
681 let p1 = PauliString::from_string("XIYZ", Complex64::new(1.0, 0.0))
682 .expect("Pauli string 'XIYZ' should be parsed successfully");
683 assert_eq!(p1.weight(), 3);
684
685 let p2 = PauliString::from_string("IIII", Complex64::new(1.0, 0.0))
686 .expect("Pauli string 'IIII' should be parsed successfully");
687 assert_eq!(p2.weight(), 0);
688 }
689
690 #[test]
691 fn test_pauli_operator_sum() {
692 let mut sum = PauliOperatorSum::new(2);
693
694 let p1 = PauliString::from_string("XX", Complex64::new(1.0, 0.0))
695 .expect("Pauli string 'XX' should be parsed successfully");
696 let p2 = PauliString::from_string("YY", Complex64::new(0.5, 0.0))
697 .expect("Pauli string 'YY' should be parsed successfully");
698
699 sum.add_term(p1)
700 .expect("Adding term 'XX' to sum should succeed");
701 sum.add_term(p2)
702 .expect("Adding term 'YY' to sum should succeed");
703
704 assert_eq!(sum.terms.len(), 2);
705 }
706
707 #[test]
708 fn test_evolution_circuit_single_qubit() {
709 let pauli = PauliString::from_string("X", Complex64::new(1.0, 0.0))
710 .expect("Pauli string 'X' should be parsed successfully");
711 let circuit = pauli
712 .evolution_circuit(1.0)
713 .expect("Evolution circuit creation should succeed");
714 assert!(circuit.gate_count() > 0);
715 }
716
717 #[test]
718 fn test_evolution_circuit_multi_qubit() {
719 let pauli = PauliString::from_string("XYZ", Complex64::new(1.0, 0.0))
720 .expect("Pauli string 'XYZ' should be parsed successfully");
721 let circuit = pauli
722 .evolution_circuit(1.0)
723 .expect("Evolution circuit creation should succeed");
724 assert!(circuit.gate_count() > 0);
725 }
726
727 #[test]
728 fn test_utils_single_qubit() {
729 let pauli = PauliUtils::single_qubit(3, 1, PauliOperator::X, Complex64::new(1.0, 0.0))
730 .expect("Single qubit Pauli creation should succeed");
731 assert_eq!(pauli.operators[0], PauliOperator::I);
732 assert_eq!(pauli.operators[1], PauliOperator::X);
733 assert_eq!(pauli.operators[2], PauliOperator::I);
734 }
735
736 #[test]
737 fn test_mutually_commuting() {
738 let p1 = PauliString::from_string("XX", Complex64::new(1.0, 0.0))
739 .expect("Pauli string 'XX' should be parsed successfully");
740 let p2 = PauliString::from_string("ZZ", Complex64::new(1.0, 0.0))
741 .expect("Pauli string 'ZZ' should be parsed successfully");
742 let p3 = PauliString::from_string("YY", Complex64::new(1.0, 0.0))
743 .expect("Pauli string 'YY' should be parsed successfully");
744
745 let paulis = vec![p1, p2, p3];
746 assert!(PauliUtils::are_mutually_commuting(&paulis));
747 }
748}