1use scirs2_core::ndarray::{Array1, Array2};
7use scirs2_core::Complex64;
8use std::f64::consts::PI;
9
10use crate::error::{Result, SimulatorError};
11use crate::sparse::{CSRMatrix, SparseMatrixBuilder};
12use quantrs2_core::gate::{
13 multi::CNOT,
14 single::{Hadamard, Phase, PhaseDagger, RotationX, RotationY, RotationZ},
15 GateOp,
16};
17use quantrs2_core::qubit::QubitId;
18
19#[derive(Debug)]
21pub struct DynamicCircuit {
22 gates: Vec<Box<dyn GateOp>>,
24 num_qubits: usize,
26}
27
28impl Clone for DynamicCircuit {
30 fn clone(&self) -> Self {
31 Self {
34 gates: Vec::new(),
35 num_qubits: self.num_qubits,
36 }
37 }
38}
39
40impl DynamicCircuit {
41 #[must_use]
43 pub fn new(num_qubits: usize) -> Self {
44 Self {
45 gates: Vec::new(),
46 num_qubits,
47 }
48 }
49
50 pub fn add_gate(&mut self, gate: Box<dyn GateOp>) -> Result<()> {
52 for qubit in gate.qubits() {
54 if qubit.id() as usize >= self.num_qubits {
55 return Err(SimulatorError::IndexOutOfBounds(qubit.id() as usize));
56 }
57 }
58 self.gates.push(gate);
59 Ok(())
60 }
61
62 #[must_use]
64 pub fn gates(&self) -> &[Box<dyn GateOp>] {
65 &self.gates
66 }
67
68 #[must_use]
70 pub fn gate_count(&self) -> usize {
71 self.gates.len()
72 }
73}
74
75#[derive(Debug, Clone)]
77pub enum HamiltonianTerm {
78 SinglePauli {
80 qubit: usize,
81 pauli: String,
82 coefficient: f64,
83 },
84 TwoPauli {
86 qubit1: usize,
87 qubit2: usize,
88 pauli1: String,
89 pauli2: String,
90 coefficient: f64,
91 },
92 PauliString {
94 qubits: Vec<usize>,
95 paulis: Vec<String>,
96 coefficient: f64,
97 },
98 Custom {
100 qubits: Vec<usize>,
101 matrix: CSRMatrix,
102 coefficient: f64,
103 },
104}
105
106#[derive(Debug, Clone)]
108pub struct Hamiltonian {
109 pub terms: Vec<HamiltonianTerm>,
111 pub num_qubits: usize,
113}
114
115impl Hamiltonian {
116 #[must_use]
118 pub const fn new(num_qubits: usize) -> Self {
119 Self {
120 terms: Vec::new(),
121 num_qubits,
122 }
123 }
124
125 pub fn add_single_pauli(&mut self, qubit: usize, pauli: &str, coefficient: f64) -> Result<()> {
127 if qubit >= self.num_qubits {
128 return Err(SimulatorError::IndexOutOfBounds(qubit));
129 }
130
131 self.terms.push(HamiltonianTerm::SinglePauli {
132 qubit,
133 pauli: pauli.to_uppercase(),
134 coefficient,
135 });
136 Ok(())
137 }
138
139 pub fn add_two_pauli(
141 &mut self,
142 qubit1: usize,
143 qubit2: usize,
144 pauli1: &str,
145 pauli2: &str,
146 coefficient: f64,
147 ) -> Result<()> {
148 if qubit1 >= self.num_qubits || qubit2 >= self.num_qubits {
149 return Err(SimulatorError::InvalidInput(
150 "Qubit index out of bounds".to_string(),
151 ));
152 }
153
154 self.terms.push(HamiltonianTerm::TwoPauli {
155 qubit1,
156 qubit2,
157 pauli1: pauli1.to_uppercase(),
158 pauli2: pauli2.to_uppercase(),
159 coefficient,
160 });
161 Ok(())
162 }
163
164 pub fn add_pauli_string(
166 &mut self,
167 qubits: Vec<usize>,
168 paulis: Vec<String>,
169 coefficient: f64,
170 ) -> Result<()> {
171 if qubits.len() != paulis.len() {
172 return Err(SimulatorError::InvalidInput(
173 "Qubits and Paulis must have the same length".to_string(),
174 ));
175 }
176
177 for &q in &qubits {
178 if q >= self.num_qubits {
179 return Err(SimulatorError::IndexOutOfBounds(q));
180 }
181 }
182
183 self.terms.push(HamiltonianTerm::PauliString {
184 qubits,
185 paulis: paulis.iter().map(|p| p.to_uppercase()).collect(),
186 coefficient,
187 });
188 Ok(())
189 }
190
191 #[must_use]
193 pub const fn get_num_qubits(&self) -> usize {
194 self.num_qubits
195 }
196
197 pub fn add_term(&mut self, term: HamiltonianTerm) {
199 self.terms.push(term);
200 }
201
202 pub fn add_pauli_term(&mut self, coefficient: f64, paulis: &[(usize, char)]) -> Result<()> {
204 if paulis.is_empty() {
205 return Ok(());
206 }
207
208 if paulis.len() == 1 {
209 let (qubit, pauli) = paulis[0];
210 self.add_single_pauli(qubit, &pauli.to_string(), coefficient)
211 } else if paulis.len() == 2 {
212 let (qubit1, pauli1) = paulis[0];
213 let (qubit2, pauli2) = paulis[1];
214 self.add_two_pauli(
215 qubit1,
216 qubit2,
217 &pauli1.to_string(),
218 &pauli2.to_string(),
219 coefficient,
220 )
221 } else {
222 let qubits: Vec<usize> = paulis.iter().map(|(q, _)| *q).collect();
223 let pauli_strings: Vec<String> = paulis.iter().map(|(_, p)| p.to_string()).collect();
224 self.add_pauli_string(qubits, pauli_strings, coefficient)
225 }
226 }
227
228 fn term_to_circuit(&self, term: &HamiltonianTerm, time: f64) -> Result<DynamicCircuit> {
230 let mut circuit = DynamicCircuit::new(self.num_qubits);
231
232 match term {
233 HamiltonianTerm::SinglePauli {
234 qubit,
235 pauli,
236 coefficient,
237 } => {
238 let angle = -2.0 * coefficient * time;
239 match pauli.as_str() {
240 "X" => circuit.add_gate(Box::new(RotationX {
241 target: QubitId::new(*qubit as u32),
242 theta: angle,
243 }))?,
244 "Y" => circuit.add_gate(Box::new(RotationY {
245 target: QubitId::new(*qubit as u32),
246 theta: angle,
247 }))?,
248 "Z" => circuit.add_gate(Box::new(RotationZ {
249 target: QubitId::new(*qubit as u32),
250 theta: angle,
251 }))?,
252 _ => {
253 return Err(SimulatorError::InvalidInput(format!(
254 "Unknown Pauli operator: {pauli}"
255 )))
256 }
257 }
258 }
259 HamiltonianTerm::TwoPauli {
260 qubit1,
261 qubit2,
262 pauli1,
263 pauli2,
264 coefficient,
265 } => {
266 let angle = -2.0 * coefficient * time;
268
269 self.apply_pauli_basis_change(&mut circuit, *qubit1, pauli1, false)?;
271 self.apply_pauli_basis_change(&mut circuit, *qubit2, pauli2, false)?;
272
273 circuit.add_gate(Box::new(CNOT {
275 control: QubitId::new(*qubit1 as u32),
276 target: QubitId::new(*qubit2 as u32),
277 }))?;
278 circuit.add_gate(Box::new(RotationZ {
279 target: QubitId::new(*qubit2 as u32),
280 theta: angle,
281 }))?;
282 circuit.add_gate(Box::new(CNOT {
283 control: QubitId::new(*qubit1 as u32),
284 target: QubitId::new(*qubit2 as u32),
285 }))?;
286
287 self.apply_pauli_basis_change(&mut circuit, *qubit1, pauli1, true)?;
289 self.apply_pauli_basis_change(&mut circuit, *qubit2, pauli2, true)?;
290 }
291 HamiltonianTerm::PauliString {
292 qubits,
293 paulis,
294 coefficient,
295 } => {
296 let angle = -2.0 * coefficient * time;
297
298 for (q, p) in qubits.iter().zip(paulis.iter()) {
300 self.apply_pauli_basis_change(&mut circuit, *q, p, false)?;
301 }
302
303 for i in 0..qubits.len() - 1 {
305 circuit.add_gate(Box::new(CNOT {
306 control: QubitId::new(qubits[i] as u32),
307 target: QubitId::new(qubits[i + 1] as u32),
308 }))?;
309 }
310
311 circuit.add_gate(Box::new(RotationZ {
312 target: QubitId::new(qubits[qubits.len() - 1] as u32),
313 theta: angle,
314 }))?;
315
316 for i in (0..qubits.len() - 1).rev() {
318 circuit.add_gate(Box::new(CNOT {
319 control: QubitId::new(qubits[i] as u32),
320 target: QubitId::new(qubits[i + 1] as u32),
321 }))?;
322 }
323
324 for (q, p) in qubits.iter().zip(paulis.iter()) {
326 self.apply_pauli_basis_change(&mut circuit, *q, p, true)?;
327 }
328 }
329 HamiltonianTerm::Custom { .. } => {
330 return Err(SimulatorError::InvalidOperation(
331 "Custom terms not yet supported in Trotter decomposition".to_string(),
332 ));
333 }
334 }
335
336 Ok(circuit)
337 }
338
339 fn apply_pauli_basis_change(
341 &self,
342 circuit: &mut DynamicCircuit,
343 qubit: usize,
344 pauli: &str,
345 inverse: bool,
346 ) -> Result<()> {
347 match pauli {
348 "X" => {
349 circuit.add_gate(Box::new(Hadamard {
350 target: QubitId::new(qubit as u32),
351 }))?;
352 }
353 "Y" => {
354 if inverse {
355 circuit.add_gate(Box::new(PhaseDagger {
356 target: QubitId::new(qubit as u32),
357 }))?;
358 circuit.add_gate(Box::new(Hadamard {
359 target: QubitId::new(qubit as u32),
360 }))?;
361 } else {
362 circuit.add_gate(Box::new(Hadamard {
363 target: QubitId::new(qubit as u32),
364 }))?;
365 circuit.add_gate(Box::new(Phase {
366 target: QubitId::new(qubit as u32),
367 }))?;
368 }
369 }
370 "Z" => {
371 }
373 _ => {
374 return Err(SimulatorError::InvalidInput(format!(
375 "Unknown Pauli operator: {pauli}"
376 )))
377 }
378 }
379 Ok(())
380 }
381}
382
383#[derive(Debug, Clone, Copy)]
385pub enum TrotterMethod {
386 FirstOrder,
388 SecondOrder,
390 FourthOrder,
392 SixthOrder,
394 Randomized,
396}
397
398pub struct TrotterDecomposer {
400 method: TrotterMethod,
402 num_steps: usize,
404}
405
406impl TrotterDecomposer {
407 #[must_use]
409 pub const fn new(method: TrotterMethod, num_steps: usize) -> Self {
410 Self { method, num_steps }
411 }
412
413 pub fn decompose(&self, hamiltonian: &Hamiltonian, total_time: f64) -> Result<DynamicCircuit> {
415 let dt = total_time / self.num_steps as f64;
416 let mut full_circuit = DynamicCircuit::new(hamiltonian.num_qubits);
417
418 for _ in 0..self.num_steps {
419 let step_circuit = match self.method {
420 TrotterMethod::FirstOrder => self.first_order_step(hamiltonian, dt)?,
421 TrotterMethod::SecondOrder => self.second_order_step(hamiltonian, dt)?,
422 TrotterMethod::FourthOrder => self.fourth_order_step(hamiltonian, dt)?,
423 TrotterMethod::SixthOrder => self.sixth_order_step(hamiltonian, dt)?,
424 TrotterMethod::Randomized => self.randomized_step(hamiltonian, dt)?,
425 };
426
427 for gate in step_circuit.gates() {
429 full_circuit.add_gate(gate.clone())?;
430 }
431 }
432
433 Ok(full_circuit)
434 }
435
436 fn first_order_step(&self, hamiltonian: &Hamiltonian, dt: f64) -> Result<DynamicCircuit> {
438 let mut circuit = DynamicCircuit::new(hamiltonian.num_qubits);
439
440 for term in &hamiltonian.terms {
441 let term_circuit = hamiltonian.term_to_circuit(term, dt)?;
442 for gate in term_circuit.gates() {
443 circuit.add_gate(gate.clone())?;
444 }
445 }
446
447 Ok(circuit)
448 }
449
450 fn second_order_step(&self, hamiltonian: &Hamiltonian, dt: f64) -> Result<DynamicCircuit> {
452 let mut circuit = DynamicCircuit::new(hamiltonian.num_qubits);
453
454 for term in &hamiltonian.terms {
456 let term_circuit = hamiltonian.term_to_circuit(term, dt / 2.0)?;
457 for gate in term_circuit.gates() {
458 circuit.add_gate(gate.clone())?;
459 }
460 }
461
462 for term in hamiltonian.terms.iter().rev() {
464 let term_circuit = hamiltonian.term_to_circuit(term, dt / 2.0)?;
465 for gate in term_circuit.gates() {
466 circuit.add_gate(gate.clone())?;
467 }
468 }
469
470 Ok(circuit)
471 }
472
473 fn fourth_order_step(&self, hamiltonian: &Hamiltonian, dt: f64) -> Result<DynamicCircuit> {
475 let mut circuit = DynamicCircuit::new(hamiltonian.num_qubits);
476
477 let s = 1.0 / (4.0 - 4.0_f64.cbrt());
480
481 for _ in 0..2 {
483 let step = self.second_order_step(hamiltonian, s * dt)?;
484 for gate in step.gates() {
485 circuit.add_gate(gate.clone())?;
486 }
487 }
488
489 let middle_step = self.second_order_step(hamiltonian, 4.0f64.mul_add(-s, 1.0) * dt)?;
491 for gate in middle_step.gates() {
492 circuit.add_gate(gate.clone())?;
493 }
494
495 for _ in 0..2 {
497 let step = self.second_order_step(hamiltonian, s * dt)?;
498 for gate in step.gates() {
499 circuit.add_gate(gate.clone())?;
500 }
501 }
502
503 Ok(circuit)
504 }
505
506 fn sixth_order_step(&self, hamiltonian: &Hamiltonian, dt: f64) -> Result<DynamicCircuit> {
508 let mut circuit = DynamicCircuit::new(hamiltonian.num_qubits);
509
510 let w1: f64 = 1.0 / (2.0 - (1.0_f64 / 5.0).exp2());
512 let w0 = 2.0f64.mul_add(-w1, 1.0);
513
514 for _ in 0..2 {
516 let step = self.fourth_order_step(hamiltonian, w1 * dt)?;
517 for gate in step.gates() {
518 circuit.add_gate(gate.clone())?;
519 }
520 }
521
522 let middle_step = self.fourth_order_step(hamiltonian, w0 * dt)?;
524 for gate in middle_step.gates() {
525 circuit.add_gate(gate.clone())?;
526 }
527
528 for _ in 0..2 {
530 let step = self.fourth_order_step(hamiltonian, w1 * dt)?;
531 for gate in step.gates() {
532 circuit.add_gate(gate.clone())?;
533 }
534 }
535
536 Ok(circuit)
537 }
538
539 fn randomized_step(&self, hamiltonian: &Hamiltonian, dt: f64) -> Result<DynamicCircuit> {
541 let mut circuit = DynamicCircuit::new(hamiltonian.num_qubits);
542
543 let mut indices: Vec<usize> = (0..hamiltonian.terms.len()).collect();
545 fastrand::shuffle(&mut indices);
546
547 for &idx in &indices {
549 let term_circuit = hamiltonian.term_to_circuit(&hamiltonian.terms[idx], dt)?;
550 for gate in term_circuit.gates() {
551 circuit.add_gate(gate.clone())?;
552 }
553 }
554
555 Ok(circuit)
556 }
557
558 #[must_use]
560 pub fn error_bound(&self, hamiltonian: &Hamiltonian, total_time: f64) -> f64 {
561 let dt = total_time / self.num_steps as f64;
562 let num_terms = hamiltonian.terms.len();
563
564 let coeff_sum: f64 = hamiltonian
566 .terms
567 .iter()
568 .map(|term| match term {
569 HamiltonianTerm::SinglePauli { coefficient, .. }
570 | HamiltonianTerm::TwoPauli { coefficient, .. }
571 | HamiltonianTerm::PauliString { coefficient, .. }
572 | HamiltonianTerm::Custom { coefficient, .. } => coefficient.abs(),
573 })
574 .sum();
575
576 match self.method {
578 TrotterMethod::FirstOrder => {
579 0.5 * coeff_sum.powi(2) * dt.powi(2) * self.num_steps as f64
581 }
582 TrotterMethod::SecondOrder => {
583 coeff_sum.powi(3) * dt.powi(3) * self.num_steps as f64 / 12.0
585 }
586 TrotterMethod::FourthOrder => {
587 coeff_sum.powi(5) * dt.powi(5) * self.num_steps as f64 / 360.0
589 }
590 TrotterMethod::SixthOrder => {
591 coeff_sum.powi(7) * dt.powi(7) * self.num_steps as f64 / 20_160.0
593 }
594 TrotterMethod::Randomized => {
595 0.5 * coeff_sum * (total_time / (self.num_steps as f64).sqrt())
597 }
598 }
599 }
600}
601
602pub struct HamiltonianLibrary;
604
605impl HamiltonianLibrary {
606 pub fn transverse_ising_1d(
608 num_qubits: usize,
609 j: f64,
610 h: f64,
611 periodic: bool,
612 ) -> Result<Hamiltonian> {
613 let mut ham = Hamiltonian::new(num_qubits);
614
615 for i in 0..num_qubits - 1 {
617 ham.add_two_pauli(i, i + 1, "Z", "Z", -j)?;
618 }
619
620 if periodic && num_qubits > 2 {
622 ham.add_two_pauli(num_qubits - 1, 0, "Z", "Z", -j)?;
623 }
624
625 for i in 0..num_qubits {
627 ham.add_single_pauli(i, "X", -h)?;
628 }
629
630 Ok(ham)
631 }
632
633 pub fn heisenberg_1d(
635 num_qubits: usize,
636 j: f64,
637 delta: f64,
638 periodic: bool,
639 ) -> Result<Hamiltonian> {
640 let mut ham = Hamiltonian::new(num_qubits);
641
642 for i in 0..num_qubits - 1 {
643 ham.add_two_pauli(i, i + 1, "X", "X", j)?;
644 ham.add_two_pauli(i, i + 1, "Y", "Y", j)?;
645 ham.add_two_pauli(i, i + 1, "Z", "Z", j * delta)?;
646 }
647
648 if periodic && num_qubits > 2 {
649 ham.add_two_pauli(num_qubits - 1, 0, "X", "X", j)?;
650 ham.add_two_pauli(num_qubits - 1, 0, "Y", "Y", j)?;
651 ham.add_two_pauli(num_qubits - 1, 0, "Z", "Z", j * delta)?;
652 }
653
654 Ok(ham)
655 }
656
657 pub fn xy_model(num_qubits: usize, j: f64, periodic: bool) -> Result<Hamiltonian> {
659 Self::heisenberg_1d(num_qubits, j, 0.0, periodic)
660 }
661}
662
663#[cfg(test)]
664mod tests {
665 use super::*;
666
667 #[test]
668 fn test_hamiltonian_construction() {
669 let mut ham = Hamiltonian::new(3);
670
671 ham.add_single_pauli(0, "X", 0.5)
672 .expect("add_single_pauli should succeed");
673 ham.add_two_pauli(0, 1, "Z", "Z", -1.0)
674 .expect("add_two_pauli should succeed");
675 ham.add_pauli_string(
676 vec![0, 1, 2],
677 vec!["X".to_string(), "Y".to_string(), "Z".to_string()],
678 0.25,
679 )
680 .expect("add_pauli_string should succeed");
681
682 assert_eq!(ham.terms.len(), 3);
683 }
684
685 #[test]
686 fn test_ising_model() {
687 let ham = HamiltonianLibrary::transverse_ising_1d(4, 1.0, 0.5, false)
688 .expect("transverse_ising_1d should succeed");
689
690 assert_eq!(ham.terms.len(), 7);
692 }
693
694 #[test]
695 fn test_trotter_decomposition() {
696 let ham = HamiltonianLibrary::transverse_ising_1d(3, 1.0, 0.5, false)
697 .expect("transverse_ising_1d should succeed");
698 let decomposer = TrotterDecomposer::new(TrotterMethod::SecondOrder, 10);
699
700 let circuit = decomposer
701 .decompose(&ham, 1.0)
702 .expect("decompose should succeed");
703 assert!(circuit.gate_count() > 0);
704 }
705
706 #[test]
707 fn test_error_bounds() {
708 let ham = HamiltonianLibrary::xy_model(4, 1.0, true).expect("xy_model should succeed");
709 let decomposer = TrotterDecomposer::new(TrotterMethod::FourthOrder, 100);
710
711 let error = decomposer.error_bound(&ham, 1.0);
712 assert!(error < 1e-6); }
714
715 #[test]
716 fn test_heisenberg_model() {
717 let ham = HamiltonianLibrary::heisenberg_1d(3, 1.0, 1.0, false)
718 .expect("heisenberg_1d should succeed");
719
720 assert_eq!(ham.terms.len(), 6);
722 }
723}