1use ndarray::{Array1, Array2};
7use num_complex::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 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 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: {}",
246 pauli
247 )))
248 }
249 }
250 }
251 HamiltonianTerm::TwoPauli {
252 qubit1,
253 qubit2,
254 pauli1,
255 pauli2,
256 coefficient,
257 } => {
258 let angle = -2.0 * coefficient * time;
260
261 self.apply_pauli_basis_change(&mut circuit, *qubit1, pauli1, false)?;
263 self.apply_pauli_basis_change(&mut circuit, *qubit2, pauli2, false)?;
264
265 circuit.add_gate(Box::new(CNOT {
267 control: QubitId::new(*qubit1 as u32),
268 target: QubitId::new(*qubit2 as u32),
269 }))?;
270 circuit.add_gate(Box::new(RotationZ {
271 target: QubitId::new(*qubit2 as u32),
272 theta: angle,
273 }))?;
274 circuit.add_gate(Box::new(CNOT {
275 control: QubitId::new(*qubit1 as u32),
276 target: QubitId::new(*qubit2 as u32),
277 }))?;
278
279 self.apply_pauli_basis_change(&mut circuit, *qubit1, pauli1, true)?;
281 self.apply_pauli_basis_change(&mut circuit, *qubit2, pauli2, true)?;
282 }
283 HamiltonianTerm::PauliString {
284 qubits,
285 paulis,
286 coefficient,
287 } => {
288 let angle = -2.0 * coefficient * time;
289
290 for (q, p) in qubits.iter().zip(paulis.iter()) {
292 self.apply_pauli_basis_change(&mut circuit, *q, p, false)?;
293 }
294
295 for i in 0..qubits.len() - 1 {
297 circuit.add_gate(Box::new(CNOT {
298 control: QubitId::new(qubits[i] as u32),
299 target: QubitId::new(qubits[i + 1] as u32),
300 }))?;
301 }
302
303 circuit.add_gate(Box::new(RotationZ {
304 target: QubitId::new(qubits[qubits.len() - 1] as u32),
305 theta: angle,
306 }))?;
307
308 for i in (0..qubits.len() - 1).rev() {
310 circuit.add_gate(Box::new(CNOT {
311 control: QubitId::new(qubits[i] as u32),
312 target: QubitId::new(qubits[i + 1] as u32),
313 }))?;
314 }
315
316 for (q, p) in qubits.iter().zip(paulis.iter()) {
318 self.apply_pauli_basis_change(&mut circuit, *q, p, true)?;
319 }
320 }
321 HamiltonianTerm::Custom { .. } => {
322 return Err(SimulatorError::InvalidOperation(
323 "Custom terms not yet supported in Trotter decomposition".to_string(),
324 ));
325 }
326 }
327
328 Ok(circuit)
329 }
330
331 fn apply_pauli_basis_change(
333 &self,
334 circuit: &mut DynamicCircuit,
335 qubit: usize,
336 pauli: &str,
337 inverse: bool,
338 ) -> Result<()> {
339 match pauli {
340 "X" => {
341 circuit.add_gate(Box::new(Hadamard {
342 target: QubitId::new(qubit as u32),
343 }))?;
344 }
345 "Y" => {
346 if inverse {
347 circuit.add_gate(Box::new(PhaseDagger {
348 target: QubitId::new(qubit as u32),
349 }))?;
350 circuit.add_gate(Box::new(Hadamard {
351 target: QubitId::new(qubit as u32),
352 }))?;
353 } else {
354 circuit.add_gate(Box::new(Hadamard {
355 target: QubitId::new(qubit as u32),
356 }))?;
357 circuit.add_gate(Box::new(Phase {
358 target: QubitId::new(qubit as u32),
359 }))?;
360 }
361 }
362 "Z" => {
363 }
365 _ => {
366 return Err(SimulatorError::InvalidInput(format!(
367 "Unknown Pauli operator: {}",
368 pauli
369 )))
370 }
371 }
372 Ok(())
373 }
374}
375
376#[derive(Debug, Clone, Copy)]
378pub enum TrotterMethod {
379 FirstOrder,
381 SecondOrder,
383 FourthOrder,
385 SixthOrder,
387 Randomized,
389}
390
391pub struct TrotterDecomposer {
393 method: TrotterMethod,
395 num_steps: usize,
397}
398
399impl TrotterDecomposer {
400 pub fn new(method: TrotterMethod, num_steps: usize) -> Self {
402 Self { method, num_steps }
403 }
404
405 pub fn decompose(&self, hamiltonian: &Hamiltonian, total_time: f64) -> Result<DynamicCircuit> {
407 let dt = total_time / self.num_steps as f64;
408 let mut full_circuit = DynamicCircuit::new(hamiltonian.num_qubits);
409
410 for _ in 0..self.num_steps {
411 let step_circuit = match self.method {
412 TrotterMethod::FirstOrder => self.first_order_step(hamiltonian, dt)?,
413 TrotterMethod::SecondOrder => self.second_order_step(hamiltonian, dt)?,
414 TrotterMethod::FourthOrder => self.fourth_order_step(hamiltonian, dt)?,
415 TrotterMethod::SixthOrder => self.sixth_order_step(hamiltonian, dt)?,
416 TrotterMethod::Randomized => self.randomized_step(hamiltonian, dt)?,
417 };
418
419 for gate in step_circuit.gates() {
421 full_circuit.add_gate(gate.clone())?;
422 }
423 }
424
425 Ok(full_circuit)
426 }
427
428 fn first_order_step(&self, hamiltonian: &Hamiltonian, dt: f64) -> Result<DynamicCircuit> {
430 let mut circuit = DynamicCircuit::new(hamiltonian.num_qubits);
431
432 for term in &hamiltonian.terms {
433 let term_circuit = hamiltonian.term_to_circuit(term, dt)?;
434 for gate in term_circuit.gates() {
435 circuit.add_gate(gate.clone())?;
436 }
437 }
438
439 Ok(circuit)
440 }
441
442 fn second_order_step(&self, hamiltonian: &Hamiltonian, dt: f64) -> Result<DynamicCircuit> {
444 let mut circuit = DynamicCircuit::new(hamiltonian.num_qubits);
445
446 for term in &hamiltonian.terms {
448 let term_circuit = hamiltonian.term_to_circuit(term, dt / 2.0)?;
449 for gate in term_circuit.gates() {
450 circuit.add_gate(gate.clone())?;
451 }
452 }
453
454 for term in hamiltonian.terms.iter().rev() {
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 Ok(circuit)
463 }
464
465 fn fourth_order_step(&self, hamiltonian: &Hamiltonian, dt: f64) -> Result<DynamicCircuit> {
467 let mut circuit = DynamicCircuit::new(hamiltonian.num_qubits);
468
469 let s = 1.0 / (4.0 - 4.0_f64.powf(1.0 / 3.0));
472
473 for _ in 0..2 {
475 let step = self.second_order_step(hamiltonian, s * dt)?;
476 for gate in step.gates() {
477 circuit.add_gate(gate.clone())?;
478 }
479 }
480
481 let middle_step = self.second_order_step(hamiltonian, (1.0 - 4.0 * s) * dt)?;
483 for gate in middle_step.gates() {
484 circuit.add_gate(gate.clone())?;
485 }
486
487 for _ in 0..2 {
489 let step = self.second_order_step(hamiltonian, s * dt)?;
490 for gate in step.gates() {
491 circuit.add_gate(gate.clone())?;
492 }
493 }
494
495 Ok(circuit)
496 }
497
498 fn sixth_order_step(&self, hamiltonian: &Hamiltonian, dt: f64) -> Result<DynamicCircuit> {
500 let mut circuit = DynamicCircuit::new(hamiltonian.num_qubits);
501
502 let w1 = 1.0 / (2.0 - 2.0_f64.powf(1.0 / 5.0));
504 let w0 = 1.0 - 2.0 * w1;
505
506 for _ in 0..2 {
508 let step = self.fourth_order_step(hamiltonian, w1 * dt)?;
509 for gate in step.gates() {
510 circuit.add_gate(gate.clone())?;
511 }
512 }
513
514 let middle_step = self.fourth_order_step(hamiltonian, w0 * dt)?;
516 for gate in middle_step.gates() {
517 circuit.add_gate(gate.clone())?;
518 }
519
520 for _ in 0..2 {
522 let step = self.fourth_order_step(hamiltonian, w1 * dt)?;
523 for gate in step.gates() {
524 circuit.add_gate(gate.clone())?;
525 }
526 }
527
528 Ok(circuit)
529 }
530
531 fn randomized_step(&self, hamiltonian: &Hamiltonian, dt: f64) -> Result<DynamicCircuit> {
533 let mut circuit = DynamicCircuit::new(hamiltonian.num_qubits);
534
535 let mut indices: Vec<usize> = (0..hamiltonian.terms.len()).collect();
537 fastrand::shuffle(&mut indices);
538
539 for &idx in &indices {
541 let term_circuit = hamiltonian.term_to_circuit(&hamiltonian.terms[idx], dt)?;
542 for gate in term_circuit.gates() {
543 circuit.add_gate(gate.clone())?;
544 }
545 }
546
547 Ok(circuit)
548 }
549
550 pub fn error_bound(&self, hamiltonian: &Hamiltonian, total_time: f64) -> f64 {
552 let dt = total_time / self.num_steps as f64;
553 let num_terms = hamiltonian.terms.len();
554
555 let coeff_sum: f64 = hamiltonian
557 .terms
558 .iter()
559 .map(|term| match term {
560 HamiltonianTerm::SinglePauli { coefficient, .. }
561 | HamiltonianTerm::TwoPauli { coefficient, .. }
562 | HamiltonianTerm::PauliString { coefficient, .. }
563 | HamiltonianTerm::Custom { coefficient, .. } => coefficient.abs(),
564 })
565 .sum();
566
567 match self.method {
569 TrotterMethod::FirstOrder => {
570 0.5 * coeff_sum.powi(2) * dt.powi(2) * self.num_steps as f64
572 }
573 TrotterMethod::SecondOrder => {
574 coeff_sum.powi(3) * dt.powi(3) * self.num_steps as f64 / 12.0
576 }
577 TrotterMethod::FourthOrder => {
578 coeff_sum.powi(5) * dt.powi(5) * self.num_steps as f64 / 360.0
580 }
581 TrotterMethod::SixthOrder => {
582 coeff_sum.powi(7) * dt.powi(7) * self.num_steps as f64 / 20160.0
584 }
585 TrotterMethod::Randomized => {
586 0.5 * coeff_sum * (total_time / (self.num_steps as f64).sqrt())
588 }
589 }
590 }
591}
592
593pub struct HamiltonianLibrary;
595
596impl HamiltonianLibrary {
597 pub fn transverse_ising_1d(
599 num_qubits: usize,
600 j: f64,
601 h: f64,
602 periodic: bool,
603 ) -> Result<Hamiltonian> {
604 let mut ham = Hamiltonian::new(num_qubits);
605
606 for i in 0..num_qubits - 1 {
608 ham.add_two_pauli(i, i + 1, "Z", "Z", -j)?;
609 }
610
611 if periodic && num_qubits > 2 {
613 ham.add_two_pauli(num_qubits - 1, 0, "Z", "Z", -j)?;
614 }
615
616 for i in 0..num_qubits {
618 ham.add_single_pauli(i, "X", -h)?;
619 }
620
621 Ok(ham)
622 }
623
624 pub fn heisenberg_1d(
626 num_qubits: usize,
627 j: f64,
628 delta: f64,
629 periodic: bool,
630 ) -> Result<Hamiltonian> {
631 let mut ham = Hamiltonian::new(num_qubits);
632
633 for i in 0..num_qubits - 1 {
634 ham.add_two_pauli(i, i + 1, "X", "X", j)?;
635 ham.add_two_pauli(i, i + 1, "Y", "Y", j)?;
636 ham.add_two_pauli(i, i + 1, "Z", "Z", j * delta)?;
637 }
638
639 if periodic && num_qubits > 2 {
640 ham.add_two_pauli(num_qubits - 1, 0, "X", "X", j)?;
641 ham.add_two_pauli(num_qubits - 1, 0, "Y", "Y", j)?;
642 ham.add_two_pauli(num_qubits - 1, 0, "Z", "Z", j * delta)?;
643 }
644
645 Ok(ham)
646 }
647
648 pub fn xy_model(num_qubits: usize, j: f64, periodic: bool) -> Result<Hamiltonian> {
650 Self::heisenberg_1d(num_qubits, j, 0.0, periodic)
651 }
652}
653
654#[cfg(test)]
655mod tests {
656 use super::*;
657
658 #[test]
659 fn test_hamiltonian_construction() {
660 let mut ham = Hamiltonian::new(3);
661
662 ham.add_single_pauli(0, "X", 0.5).unwrap();
663 ham.add_two_pauli(0, 1, "Z", "Z", -1.0).unwrap();
664 ham.add_pauli_string(
665 vec![0, 1, 2],
666 vec!["X".to_string(), "Y".to_string(), "Z".to_string()],
667 0.25,
668 )
669 .unwrap();
670
671 assert_eq!(ham.terms.len(), 3);
672 }
673
674 #[test]
675 fn test_ising_model() {
676 let ham = HamiltonianLibrary::transverse_ising_1d(4, 1.0, 0.5, false).unwrap();
677
678 assert_eq!(ham.terms.len(), 7);
680 }
681
682 #[test]
683 fn test_trotter_decomposition() {
684 let ham = HamiltonianLibrary::transverse_ising_1d(3, 1.0, 0.5, false).unwrap();
685 let decomposer = TrotterDecomposer::new(TrotterMethod::SecondOrder, 10);
686
687 let circuit = decomposer.decompose(&ham, 1.0).unwrap();
688 assert!(circuit.gate_count() > 0);
689 }
690
691 #[test]
692 fn test_error_bounds() {
693 let ham = HamiltonianLibrary::xy_model(4, 1.0, true).unwrap();
694 let decomposer = TrotterDecomposer::new(TrotterMethod::FourthOrder, 100);
695
696 let error = decomposer.error_bound(&ham, 1.0);
697 assert!(error < 1e-6); }
699
700 #[test]
701 fn test_heisenberg_model() {
702 let ham = HamiltonianLibrary::heisenberg_1d(3, 1.0, 1.0, false).unwrap();
703
704 assert_eq!(ham.terms.len(), 6);
706 }
707}