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