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::{multi::*, single::*, GateOp};
13use quantrs2_core::qubit::QubitId;
14
15#[derive(Debug)]
17pub struct DynamicCircuit {
18 gates: Vec<Box<dyn GateOp>>,
20 num_qubits: usize,
22}
23
24impl Clone for DynamicCircuit {
26 fn clone(&self) -> Self {
27 Self {
30 gates: Vec::new(),
31 num_qubits: self.num_qubits,
32 }
33 }
34}
35
36impl DynamicCircuit {
37 pub fn new(num_qubits: usize) -> Self {
39 Self {
40 gates: Vec::new(),
41 num_qubits,
42 }
43 }
44
45 pub fn add_gate(&mut self, gate: Box<dyn GateOp>) -> Result<()> {
47 for qubit in gate.qubits() {
49 if qubit.id() as usize >= self.num_qubits {
50 return Err(SimulatorError::IndexOutOfBounds(qubit.id() as usize));
51 }
52 }
53 self.gates.push(gate);
54 Ok(())
55 }
56
57 pub fn gates(&self) -> &[Box<dyn GateOp>] {
59 &self.gates
60 }
61
62 pub fn gate_count(&self) -> usize {
64 self.gates.len()
65 }
66}
67
68#[derive(Debug, Clone)]
70pub enum HamiltonianTerm {
71 SinglePauli {
73 qubit: usize,
74 pauli: String,
75 coefficient: f64,
76 },
77 TwoPauli {
79 qubit1: usize,
80 qubit2: usize,
81 pauli1: String,
82 pauli2: String,
83 coefficient: f64,
84 },
85 PauliString {
87 qubits: Vec<usize>,
88 paulis: Vec<String>,
89 coefficient: f64,
90 },
91 Custom {
93 qubits: Vec<usize>,
94 matrix: CSRMatrix,
95 coefficient: f64,
96 },
97}
98
99#[derive(Debug, Clone)]
101pub struct Hamiltonian {
102 pub terms: Vec<HamiltonianTerm>,
104 pub num_qubits: usize,
106}
107
108impl Hamiltonian {
109 pub const fn new(num_qubits: usize) -> Self {
111 Self {
112 terms: Vec::new(),
113 num_qubits,
114 }
115 }
116
117 pub fn add_single_pauli(&mut self, qubit: usize, pauli: &str, coefficient: f64) -> Result<()> {
119 if qubit >= self.num_qubits {
120 return Err(SimulatorError::IndexOutOfBounds(qubit));
121 }
122
123 self.terms.push(HamiltonianTerm::SinglePauli {
124 qubit,
125 pauli: pauli.to_uppercase(),
126 coefficient,
127 });
128 Ok(())
129 }
130
131 pub fn add_two_pauli(
133 &mut self,
134 qubit1: usize,
135 qubit2: usize,
136 pauli1: &str,
137 pauli2: &str,
138 coefficient: f64,
139 ) -> Result<()> {
140 if qubit1 >= self.num_qubits || qubit2 >= self.num_qubits {
141 return Err(SimulatorError::InvalidInput(
142 "Qubit index out of bounds".to_string(),
143 ));
144 }
145
146 self.terms.push(HamiltonianTerm::TwoPauli {
147 qubit1,
148 qubit2,
149 pauli1: pauli1.to_uppercase(),
150 pauli2: pauli2.to_uppercase(),
151 coefficient,
152 });
153 Ok(())
154 }
155
156 pub fn add_pauli_string(
158 &mut self,
159 qubits: Vec<usize>,
160 paulis: Vec<String>,
161 coefficient: f64,
162 ) -> Result<()> {
163 if qubits.len() != paulis.len() {
164 return Err(SimulatorError::InvalidInput(
165 "Qubits and Paulis must have the same length".to_string(),
166 ));
167 }
168
169 for &q in &qubits {
170 if q >= self.num_qubits {
171 return Err(SimulatorError::IndexOutOfBounds(q));
172 }
173 }
174
175 self.terms.push(HamiltonianTerm::PauliString {
176 qubits,
177 paulis: paulis.iter().map(|p| p.to_uppercase()).collect(),
178 coefficient,
179 });
180 Ok(())
181 }
182
183 pub const fn get_num_qubits(&self) -> usize {
185 self.num_qubits
186 }
187
188 pub fn add_term(&mut self, term: HamiltonianTerm) {
190 self.terms.push(term);
191 }
192
193 pub fn add_pauli_term(&mut self, coefficient: f64, paulis: &[(usize, char)]) -> Result<()> {
195 if paulis.is_empty() {
196 return Ok(());
197 }
198
199 if paulis.len() == 1 {
200 let (qubit, pauli) = paulis[0];
201 self.add_single_pauli(qubit, &pauli.to_string(), coefficient)
202 } else if paulis.len() == 2 {
203 let (qubit1, pauli1) = paulis[0];
204 let (qubit2, pauli2) = paulis[1];
205 self.add_two_pauli(
206 qubit1,
207 qubit2,
208 &pauli1.to_string(),
209 &pauli2.to_string(),
210 coefficient,
211 )
212 } else {
213 let qubits: Vec<usize> = paulis.iter().map(|(q, _)| *q).collect();
214 let pauli_strings: Vec<String> = paulis.iter().map(|(_, p)| p.to_string()).collect();
215 self.add_pauli_string(qubits, pauli_strings, coefficient)
216 }
217 }
218
219 fn term_to_circuit(&self, term: &HamiltonianTerm, time: f64) -> Result<DynamicCircuit> {
221 let mut circuit = DynamicCircuit::new(self.num_qubits);
222
223 match term {
224 HamiltonianTerm::SinglePauli {
225 qubit,
226 pauli,
227 coefficient,
228 } => {
229 let angle = -2.0 * coefficient * time;
230 match pauli.as_str() {
231 "X" => circuit.add_gate(Box::new(RotationX {
232 target: QubitId::new(*qubit as u32),
233 theta: angle,
234 }))?,
235 "Y" => circuit.add_gate(Box::new(RotationY {
236 target: QubitId::new(*qubit as u32),
237 theta: angle,
238 }))?,
239 "Z" => circuit.add_gate(Box::new(RotationZ {
240 target: QubitId::new(*qubit as u32),
241 theta: angle,
242 }))?,
243 _ => {
244 return Err(SimulatorError::InvalidInput(format!(
245 "Unknown Pauli operator: {pauli}"
246 )))
247 }
248 }
249 }
250 HamiltonianTerm::TwoPauli {
251 qubit1,
252 qubit2,
253 pauli1,
254 pauli2,
255 coefficient,
256 } => {
257 let angle = -2.0 * coefficient * time;
259
260 self.apply_pauli_basis_change(&mut circuit, *qubit1, pauli1, false)?;
262 self.apply_pauli_basis_change(&mut circuit, *qubit2, pauli2, false)?;
263
264 circuit.add_gate(Box::new(CNOT {
266 control: QubitId::new(*qubit1 as u32),
267 target: QubitId::new(*qubit2 as u32),
268 }))?;
269 circuit.add_gate(Box::new(RotationZ {
270 target: QubitId::new(*qubit2 as u32),
271 theta: angle,
272 }))?;
273 circuit.add_gate(Box::new(CNOT {
274 control: QubitId::new(*qubit1 as u32),
275 target: QubitId::new(*qubit2 as u32),
276 }))?;
277
278 self.apply_pauli_basis_change(&mut circuit, *qubit1, pauli1, true)?;
280 self.apply_pauli_basis_change(&mut circuit, *qubit2, pauli2, true)?;
281 }
282 HamiltonianTerm::PauliString {
283 qubits,
284 paulis,
285 coefficient,
286 } => {
287 let angle = -2.0 * coefficient * time;
288
289 for (q, p) in qubits.iter().zip(paulis.iter()) {
291 self.apply_pauli_basis_change(&mut circuit, *q, p, false)?;
292 }
293
294 for i in 0..qubits.len() - 1 {
296 circuit.add_gate(Box::new(CNOT {
297 control: QubitId::new(qubits[i] as u32),
298 target: QubitId::new(qubits[i + 1] as u32),
299 }))?;
300 }
301
302 circuit.add_gate(Box::new(RotationZ {
303 target: QubitId::new(qubits[qubits.len() - 1] as u32),
304 theta: angle,
305 }))?;
306
307 for i in (0..qubits.len() - 1).rev() {
309 circuit.add_gate(Box::new(CNOT {
310 control: QubitId::new(qubits[i] as u32),
311 target: QubitId::new(qubits[i + 1] as u32),
312 }))?;
313 }
314
315 for (q, p) in qubits.iter().zip(paulis.iter()) {
317 self.apply_pauli_basis_change(&mut circuit, *q, p, true)?;
318 }
319 }
320 HamiltonianTerm::Custom { .. } => {
321 return Err(SimulatorError::InvalidOperation(
322 "Custom terms not yet supported in Trotter decomposition".to_string(),
323 ));
324 }
325 }
326
327 Ok(circuit)
328 }
329
330 fn apply_pauli_basis_change(
332 &self,
333 circuit: &mut DynamicCircuit,
334 qubit: usize,
335 pauli: &str,
336 inverse: bool,
337 ) -> Result<()> {
338 match pauli {
339 "X" => {
340 circuit.add_gate(Box::new(Hadamard {
341 target: QubitId::new(qubit as u32),
342 }))?;
343 }
344 "Y" => {
345 if inverse {
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 } else {
353 circuit.add_gate(Box::new(Hadamard {
354 target: QubitId::new(qubit as u32),
355 }))?;
356 circuit.add_gate(Box::new(Phase {
357 target: QubitId::new(qubit as u32),
358 }))?;
359 }
360 }
361 "Z" => {
362 }
364 _ => {
365 return Err(SimulatorError::InvalidInput(format!(
366 "Unknown Pauli operator: {pauli}"
367 )))
368 }
369 }
370 Ok(())
371 }
372}
373
374#[derive(Debug, Clone, Copy)]
376pub enum TrotterMethod {
377 FirstOrder,
379 SecondOrder,
381 FourthOrder,
383 SixthOrder,
385 Randomized,
387}
388
389pub struct TrotterDecomposer {
391 method: TrotterMethod,
393 num_steps: usize,
395}
396
397impl TrotterDecomposer {
398 pub const fn new(method: TrotterMethod, num_steps: usize) -> Self {
400 Self { method, num_steps }
401 }
402
403 pub fn decompose(&self, hamiltonian: &Hamiltonian, total_time: f64) -> Result<DynamicCircuit> {
405 let dt = total_time / self.num_steps as f64;
406 let mut full_circuit = DynamicCircuit::new(hamiltonian.num_qubits);
407
408 for _ in 0..self.num_steps {
409 let step_circuit = match self.method {
410 TrotterMethod::FirstOrder => self.first_order_step(hamiltonian, dt)?,
411 TrotterMethod::SecondOrder => self.second_order_step(hamiltonian, dt)?,
412 TrotterMethod::FourthOrder => self.fourth_order_step(hamiltonian, dt)?,
413 TrotterMethod::SixthOrder => self.sixth_order_step(hamiltonian, dt)?,
414 TrotterMethod::Randomized => self.randomized_step(hamiltonian, dt)?,
415 };
416
417 for gate in step_circuit.gates() {
419 full_circuit.add_gate(gate.clone())?;
420 }
421 }
422
423 Ok(full_circuit)
424 }
425
426 fn first_order_step(&self, hamiltonian: &Hamiltonian, dt: f64) -> Result<DynamicCircuit> {
428 let mut circuit = DynamicCircuit::new(hamiltonian.num_qubits);
429
430 for term in &hamiltonian.terms {
431 let term_circuit = hamiltonian.term_to_circuit(term, dt)?;
432 for gate in term_circuit.gates() {
433 circuit.add_gate(gate.clone())?;
434 }
435 }
436
437 Ok(circuit)
438 }
439
440 fn second_order_step(&self, hamiltonian: &Hamiltonian, dt: f64) -> Result<DynamicCircuit> {
442 let mut circuit = DynamicCircuit::new(hamiltonian.num_qubits);
443
444 for term in &hamiltonian.terms {
446 let term_circuit = hamiltonian.term_to_circuit(term, dt / 2.0)?;
447 for gate in term_circuit.gates() {
448 circuit.add_gate(gate.clone())?;
449 }
450 }
451
452 for term in hamiltonian.terms.iter().rev() {
454 let term_circuit = hamiltonian.term_to_circuit(term, dt / 2.0)?;
455 for gate in term_circuit.gates() {
456 circuit.add_gate(gate.clone())?;
457 }
458 }
459
460 Ok(circuit)
461 }
462
463 fn fourth_order_step(&self, hamiltonian: &Hamiltonian, dt: f64) -> Result<DynamicCircuit> {
465 let mut circuit = DynamicCircuit::new(hamiltonian.num_qubits);
466
467 let s = 1.0 / (4.0 - 4.0_f64.cbrt());
470
471 for _ in 0..2 {
473 let step = self.second_order_step(hamiltonian, s * dt)?;
474 for gate in step.gates() {
475 circuit.add_gate(gate.clone())?;
476 }
477 }
478
479 let middle_step = self.second_order_step(hamiltonian, 4.0f64.mul_add(-s, 1.0) * dt)?;
481 for gate in middle_step.gates() {
482 circuit.add_gate(gate.clone())?;
483 }
484
485 for _ in 0..2 {
487 let step = self.second_order_step(hamiltonian, s * dt)?;
488 for gate in step.gates() {
489 circuit.add_gate(gate.clone())?;
490 }
491 }
492
493 Ok(circuit)
494 }
495
496 fn sixth_order_step(&self, hamiltonian: &Hamiltonian, dt: f64) -> Result<DynamicCircuit> {
498 let mut circuit = DynamicCircuit::new(hamiltonian.num_qubits);
499
500 let w1: f64 = 1.0 / (2.0 - (1.0_f64 / 5.0).exp2());
502 let w0 = 2.0f64.mul_add(-w1, 1.0);
503
504 for _ in 0..2 {
506 let step = self.fourth_order_step(hamiltonian, w1 * dt)?;
507 for gate in step.gates() {
508 circuit.add_gate(gate.clone())?;
509 }
510 }
511
512 let middle_step = self.fourth_order_step(hamiltonian, w0 * dt)?;
514 for gate in middle_step.gates() {
515 circuit.add_gate(gate.clone())?;
516 }
517
518 for _ in 0..2 {
520 let step = self.fourth_order_step(hamiltonian, w1 * dt)?;
521 for gate in step.gates() {
522 circuit.add_gate(gate.clone())?;
523 }
524 }
525
526 Ok(circuit)
527 }
528
529 fn randomized_step(&self, hamiltonian: &Hamiltonian, dt: f64) -> Result<DynamicCircuit> {
531 let mut circuit = DynamicCircuit::new(hamiltonian.num_qubits);
532
533 let mut indices: Vec<usize> = (0..hamiltonian.terms.len()).collect();
535 fastrand::shuffle(&mut indices);
536
537 for &idx in &indices {
539 let term_circuit = hamiltonian.term_to_circuit(&hamiltonian.terms[idx], dt)?;
540 for gate in term_circuit.gates() {
541 circuit.add_gate(gate.clone())?;
542 }
543 }
544
545 Ok(circuit)
546 }
547
548 pub fn error_bound(&self, hamiltonian: &Hamiltonian, total_time: f64) -> f64 {
550 let dt = total_time / self.num_steps as f64;
551 let num_terms = hamiltonian.terms.len();
552
553 let coeff_sum: f64 = hamiltonian
555 .terms
556 .iter()
557 .map(|term| match term {
558 HamiltonianTerm::SinglePauli { coefficient, .. }
559 | HamiltonianTerm::TwoPauli { coefficient, .. }
560 | HamiltonianTerm::PauliString { coefficient, .. }
561 | HamiltonianTerm::Custom { coefficient, .. } => coefficient.abs(),
562 })
563 .sum();
564
565 match self.method {
567 TrotterMethod::FirstOrder => {
568 0.5 * coeff_sum.powi(2) * dt.powi(2) * self.num_steps as f64
570 }
571 TrotterMethod::SecondOrder => {
572 coeff_sum.powi(3) * dt.powi(3) * self.num_steps as f64 / 12.0
574 }
575 TrotterMethod::FourthOrder => {
576 coeff_sum.powi(5) * dt.powi(5) * self.num_steps as f64 / 360.0
578 }
579 TrotterMethod::SixthOrder => {
580 coeff_sum.powi(7) * dt.powi(7) * self.num_steps as f64 / 20160.0
582 }
583 TrotterMethod::Randomized => {
584 0.5 * coeff_sum * (total_time / (self.num_steps as f64).sqrt())
586 }
587 }
588 }
589}
590
591pub struct HamiltonianLibrary;
593
594impl HamiltonianLibrary {
595 pub fn transverse_ising_1d(
597 num_qubits: usize,
598 j: f64,
599 h: f64,
600 periodic: bool,
601 ) -> Result<Hamiltonian> {
602 let mut ham = Hamiltonian::new(num_qubits);
603
604 for i in 0..num_qubits - 1 {
606 ham.add_two_pauli(i, i + 1, "Z", "Z", -j)?;
607 }
608
609 if periodic && num_qubits > 2 {
611 ham.add_two_pauli(num_qubits - 1, 0, "Z", "Z", -j)?;
612 }
613
614 for i in 0..num_qubits {
616 ham.add_single_pauli(i, "X", -h)?;
617 }
618
619 Ok(ham)
620 }
621
622 pub fn heisenberg_1d(
624 num_qubits: usize,
625 j: f64,
626 delta: f64,
627 periodic: bool,
628 ) -> Result<Hamiltonian> {
629 let mut ham = Hamiltonian::new(num_qubits);
630
631 for i in 0..num_qubits - 1 {
632 ham.add_two_pauli(i, i + 1, "X", "X", j)?;
633 ham.add_two_pauli(i, i + 1, "Y", "Y", j)?;
634 ham.add_two_pauli(i, i + 1, "Z", "Z", j * delta)?;
635 }
636
637 if periodic && num_qubits > 2 {
638 ham.add_two_pauli(num_qubits - 1, 0, "X", "X", j)?;
639 ham.add_two_pauli(num_qubits - 1, 0, "Y", "Y", j)?;
640 ham.add_two_pauli(num_qubits - 1, 0, "Z", "Z", j * delta)?;
641 }
642
643 Ok(ham)
644 }
645
646 pub fn xy_model(num_qubits: usize, j: f64, periodic: bool) -> Result<Hamiltonian> {
648 Self::heisenberg_1d(num_qubits, j, 0.0, periodic)
649 }
650}
651
652#[cfg(test)]
653mod tests {
654 use super::*;
655
656 #[test]
657 fn test_hamiltonian_construction() {
658 let mut ham = Hamiltonian::new(3);
659
660 ham.add_single_pauli(0, "X", 0.5).unwrap();
661 ham.add_two_pauli(0, 1, "Z", "Z", -1.0).unwrap();
662 ham.add_pauli_string(
663 vec![0, 1, 2],
664 vec!["X".to_string(), "Y".to_string(), "Z".to_string()],
665 0.25,
666 )
667 .unwrap();
668
669 assert_eq!(ham.terms.len(), 3);
670 }
671
672 #[test]
673 fn test_ising_model() {
674 let ham = HamiltonianLibrary::transverse_ising_1d(4, 1.0, 0.5, false).unwrap();
675
676 assert_eq!(ham.terms.len(), 7);
678 }
679
680 #[test]
681 fn test_trotter_decomposition() {
682 let ham = HamiltonianLibrary::transverse_ising_1d(3, 1.0, 0.5, false).unwrap();
683 let decomposer = TrotterDecomposer::new(TrotterMethod::SecondOrder, 10);
684
685 let circuit = decomposer.decompose(&ham, 1.0).unwrap();
686 assert!(circuit.gate_count() > 0);
687 }
688
689 #[test]
690 fn test_error_bounds() {
691 let ham = HamiltonianLibrary::xy_model(4, 1.0, true).unwrap();
692 let decomposer = TrotterDecomposer::new(TrotterMethod::FourthOrder, 100);
693
694 let error = decomposer.error_bound(&ham, 1.0);
695 assert!(error < 1e-6); }
697
698 #[test]
699 fn test_heisenberg_model() {
700 let ham = HamiltonianLibrary::heisenberg_1d(3, 1.0, 1.0, false).unwrap();
701
702 assert_eq!(ham.terms.len(), 6);
704 }
705}