1use scirs2_core::ndarray::{Array1, Array2};
9use scirs2_core::Complex64;
10use serde::{Deserialize, Serialize};
11
12use crate::error::{Result, SimulatorError};
13use crate::scirs2_integration::SciRS2Backend;
14use crate::trotter::{Hamiltonian, HamiltonianTerm};
15
16#[derive(Debug, Clone)]
18pub struct AdiabaticConfig {
19 pub total_time: f64,
21 pub time_steps: usize,
23 pub schedule_type: ScheduleType,
25 pub initial_hamiltonian: Hamiltonian,
27 pub final_hamiltonian: Hamiltonian,
29 pub gap_tracking: GapTrackingConfig,
31 pub energy_tolerance: f64,
33 pub max_iterations: usize,
35 pub adaptive_stepping: bool,
37 pub monitor_diabatic_transitions: bool,
39}
40
41impl Default for AdiabaticConfig {
42 fn default() -> Self {
43 Self {
44 total_time: 100.0,
45 time_steps: 1000,
46 schedule_type: ScheduleType::Linear,
47 initial_hamiltonian: Hamiltonian::new(1), final_hamiltonian: Hamiltonian::new(1),
49 gap_tracking: GapTrackingConfig::default(),
50 energy_tolerance: 1e-12,
51 max_iterations: 1000,
52 adaptive_stepping: true,
53 monitor_diabatic_transitions: false,
54 }
55 }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum ScheduleType {
61 Linear,
63 Quadratic,
65 Cubic,
67 Exponential,
69 Optimal,
71 Polynomial(u32),
73 LandauZener,
75}
76
77#[derive(Debug, Clone)]
79pub struct GapTrackingConfig {
80 pub enabled: bool,
82 pub min_gap_threshold: f64,
84 pub num_eigenvalues: usize,
86 pub smoothing_window: usize,
88 pub detect_diabatic_transitions: bool,
90 pub lookahead_steps: usize,
92}
93
94impl Default for GapTrackingConfig {
95 fn default() -> Self {
96 Self {
97 enabled: true,
98 min_gap_threshold: 1e-6,
99 num_eigenvalues: 10,
100 smoothing_window: 5,
101 detect_diabatic_transitions: true,
102 lookahead_steps: 3,
103 }
104 }
105}
106
107pub struct AdiabaticQuantumComputer {
109 config: AdiabaticConfig,
111 state: Array1<Complex64>,
113 current_time: f64,
115 evolution_history: Vec<AdiabaticSnapshot>,
117 gap_history: Vec<GapMeasurement>,
119 backend: Option<SciRS2Backend>,
121 stats: AdiabaticStats,
123}
124
125#[derive(Debug, Clone)]
127pub struct AdiabaticSnapshot {
128 pub time: f64,
130 pub schedule_parameter: f64,
132 pub state: Array1<Complex64>,
134 pub energy: f64,
136 pub gap: Option<f64>,
138 pub instantaneous_ground_state: Option<Array1<Complex64>>,
140 pub ground_state_fidelity: Option<f64>,
142 pub adiabatic_parameter: Option<f64>,
144}
145
146#[derive(Debug, Clone)]
148pub struct GapMeasurement {
149 pub time: f64,
151 pub schedule_parameter: f64,
153 pub gap: f64,
155 pub ground_energy: f64,
157 pub first_excited_energy: f64,
159 pub gap_derivative: Option<f64>,
161 pub predicted_min_gap: Option<f64>,
163}
164
165#[derive(Debug, Clone, Default, Serialize, Deserialize)]
167pub struct AdiabaticStats {
168 pub total_evolution_time_ms: f64,
170 pub steps_completed: usize,
172 pub eigenvalue_computations: usize,
174 pub avg_eigenvalue_time_ms: f64,
176 pub min_gap: f64,
178 pub max_gap: f64,
180 pub avg_gap: f64,
182 pub diabatic_transitions: usize,
184 pub final_ground_state_fidelity: f64,
186 pub success_probability: f64,
188}
189
190impl AdiabaticQuantumComputer {
191 pub fn new(config: AdiabaticConfig) -> Result<Self> {
193 let num_qubits = config.initial_hamiltonian.get_num_qubits();
195 let state_size = 1 << num_qubits;
196
197 let mut state = Array1::zeros(state_size);
198 state[0] = Complex64::new(1.0, 0.0); Ok(Self {
201 config,
202 state,
203 current_time: 0.0,
204 evolution_history: Vec::new(),
205 gap_history: Vec::new(),
206 backend: None,
207 stats: AdiabaticStats::default(),
208 })
209 }
210
211 pub fn with_backend(mut self) -> Result<Self> {
213 self.backend = Some(SciRS2Backend::new());
214 Ok(self)
215 }
216
217 pub fn initialize_ground_state(&mut self) -> Result<()> {
219 let initial_matrix = self.build_hamiltonian_matrix(&self.config.initial_hamiltonian)?;
220 let (eigenvalues, eigenvectors) = self.compute_eigendecomposition(&initial_matrix)?;
221
222 let ground_idx = eigenvalues
224 .iter()
225 .enumerate()
226 .min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
227 .map_or(0, |(idx, _)| idx);
228
229 self.state = eigenvectors.column(ground_idx).to_owned();
231
232 Ok(())
233 }
234
235 pub fn evolve(&mut self) -> Result<AdiabaticResult> {
237 let start_time = std::time::Instant::now();
238
239 self.initialize_ground_state()?;
241
242 let initial_snapshot = self.take_snapshot(0.0)?;
244 self.evolution_history.push(initial_snapshot);
245
246 let dt = self.config.total_time / self.config.time_steps as f64;
247
248 for step in 1..=self.config.time_steps {
249 let step_start = std::time::Instant::now();
250
251 let t = step as f64 * dt;
252 let s = self.schedule_function(t);
253
254 let actual_dt = if self.config.adaptive_stepping {
256 self.calculate_adaptive_timestep(t, dt)?
257 } else {
258 dt
259 };
260
261 let hamiltonian = self.interpolate_hamiltonian(s)?;
263
264 self.evolve_step(&hamiltonian, actual_dt)?;
266
267 if self.config.gap_tracking.enabled {
269 let gap_measurement = self.measure_gap(t, s, &hamiltonian)?;
270
271 if self.config.monitor_diabatic_transitions {
273 self.check_diabatic_transition(&gap_measurement)?;
274 }
275
276 self.gap_history.push(gap_measurement);
277 }
278
279 if step % 10 == 0 || step == self.config.time_steps {
281 let snapshot = self.take_snapshot(t)?;
282 self.evolution_history.push(snapshot);
283 }
284
285 self.current_time = t;
286 self.stats.steps_completed += 1;
287
288 let step_time = step_start.elapsed().as_secs_f64() * 1000.0;
289 println!(
290 "Step {}/{}: t={:.3}, s={:.3}, time={:.2}ms",
291 step, self.config.time_steps, t, s, step_time
292 );
293 }
294
295 self.compute_final_statistics()?;
297
298 let total_time = start_time.elapsed().as_secs_f64() * 1000.0;
299 self.stats.total_evolution_time_ms = total_time;
300
301 Ok(AdiabaticResult {
302 final_state: self.state.clone(),
303 evolution_history: self.evolution_history.clone(),
304 gap_history: self.gap_history.clone(),
305 total_time_ms: total_time,
306 success_probability: self.stats.success_probability,
307 min_gap: self.stats.min_gap,
308 final_energy: self.calculate_current_energy()?,
309 })
310 }
311
312 fn schedule_function(&self, t: f64) -> f64 {
314 let s = t / self.config.total_time;
315
316 match self.config.schedule_type {
317 ScheduleType::Linear => s,
318 ScheduleType::Quadratic => s * s,
319 ScheduleType::Cubic => s * s * s,
320 ScheduleType::Exponential => s.exp_m1() / 1f64.exp_m1(),
321 ScheduleType::Polynomial(n) => s.powi(n as i32),
322 ScheduleType::LandauZener => {
323 if s < 0.5 {
325 2.0 * s * s
326 } else {
327 (2.0 * (1.0 - s)).mul_add(-(1.0 - s), 1.0)
328 }
329 }
330 ScheduleType::Optimal => {
331 s }
334 }
335 }
336
337 fn interpolate_hamiltonian(&self, s: f64) -> Result<Hamiltonian> {
339 let num_qubits = self
340 .config
341 .initial_hamiltonian
342 .get_num_qubits()
343 .max(self.config.final_hamiltonian.get_num_qubits());
344 let mut interpolated = Hamiltonian::new(num_qubits);
345
346 for term in &self.config.initial_hamiltonian.terms {
348 let scaled_term = match term {
349 HamiltonianTerm::SinglePauli {
350 qubit,
351 pauli,
352 coefficient,
353 } => HamiltonianTerm::SinglePauli {
354 qubit: *qubit,
355 pauli: pauli.clone(),
356 coefficient: coefficient * (1.0 - s),
357 },
358 HamiltonianTerm::TwoPauli {
359 qubit1,
360 qubit2,
361 pauli1,
362 pauli2,
363 coefficient,
364 } => HamiltonianTerm::TwoPauli {
365 qubit1: *qubit1,
366 qubit2: *qubit2,
367 pauli1: pauli1.clone(),
368 pauli2: pauli2.clone(),
369 coefficient: coefficient * (1.0 - s),
370 },
371 HamiltonianTerm::PauliString {
372 qubits,
373 paulis,
374 coefficient,
375 } => HamiltonianTerm::PauliString {
376 qubits: qubits.clone(),
377 paulis: paulis.clone(),
378 coefficient: coefficient * (1.0 - s),
379 },
380 HamiltonianTerm::Custom {
381 qubits,
382 matrix,
383 coefficient,
384 } => HamiltonianTerm::Custom {
385 qubits: qubits.clone(),
386 matrix: matrix.clone(),
387 coefficient: coefficient * (1.0 - s),
388 },
389 };
390 interpolated.add_term(scaled_term);
391 }
392
393 for term in &self.config.final_hamiltonian.terms {
394 let scaled_term = match term {
395 HamiltonianTerm::SinglePauli {
396 qubit,
397 pauli,
398 coefficient,
399 } => HamiltonianTerm::SinglePauli {
400 qubit: *qubit,
401 pauli: pauli.clone(),
402 coefficient: coefficient * s,
403 },
404 HamiltonianTerm::TwoPauli {
405 qubit1,
406 qubit2,
407 pauli1,
408 pauli2,
409 coefficient,
410 } => HamiltonianTerm::TwoPauli {
411 qubit1: *qubit1,
412 qubit2: *qubit2,
413 pauli1: pauli1.clone(),
414 pauli2: pauli2.clone(),
415 coefficient: coefficient * s,
416 },
417 HamiltonianTerm::PauliString {
418 qubits,
419 paulis,
420 coefficient,
421 } => HamiltonianTerm::PauliString {
422 qubits: qubits.clone(),
423 paulis: paulis.clone(),
424 coefficient: coefficient * s,
425 },
426 HamiltonianTerm::Custom {
427 qubits,
428 matrix,
429 coefficient,
430 } => HamiltonianTerm::Custom {
431 qubits: qubits.clone(),
432 matrix: matrix.clone(),
433 coefficient: coefficient * s,
434 },
435 };
436 interpolated.add_term(scaled_term);
437 }
438
439 Ok(interpolated)
440 }
441
442 fn evolve_step(&mut self, hamiltonian: &Hamiltonian, dt: f64) -> Result<()> {
444 let h_matrix = self.build_hamiltonian_matrix(hamiltonian)?;
446
447 let evolution_operator = self.compute_evolution_operator(&h_matrix, dt)?;
449
450 self.state = evolution_operator.dot(&self.state);
452
453 let norm: f64 = self.state.iter().map(|x| x.norm_sqr()).sum::<f64>().sqrt();
455 if norm > 1e-15 {
456 self.state.mapv_inplace(|x| x / norm);
457 }
458
459 Ok(())
460 }
461
462 fn build_hamiltonian_matrix(&self, hamiltonian: &Hamiltonian) -> Result<Array2<Complex64>> {
464 let num_qubits = hamiltonian.get_num_qubits();
465 let dim = 1 << num_qubits;
466 let mut matrix = Array2::zeros((dim, dim));
467
468 for term in &hamiltonian.terms {
469 let (term_matrix, coefficient) = self.build_term_matrix(term, num_qubits)?;
470 matrix = matrix + term_matrix.mapv(|x| x * coefficient);
471 }
472
473 Ok(matrix)
474 }
475
476 fn build_term_matrix(
478 &self,
479 term: &HamiltonianTerm,
480 num_qubits: usize,
481 ) -> Result<(Array2<Complex64>, f64)> {
482 let dim = 1 << num_qubits;
483
484 match term {
485 HamiltonianTerm::SinglePauli {
486 qubit,
487 pauli,
488 coefficient,
489 } => {
490 let mut matrix = Array2::eye(dim);
491 let pauli_matrix = self.get_pauli_matrix(pauli)?;
492 matrix = self.apply_single_qubit_to_full_matrix(
493 &matrix,
494 &pauli_matrix,
495 *qubit,
496 num_qubits,
497 )?;
498 Ok((matrix, *coefficient))
499 }
500 HamiltonianTerm::TwoPauli {
501 qubit1,
502 qubit2,
503 pauli1,
504 pauli2,
505 coefficient,
506 } => {
507 let mut matrix = Array2::eye(dim);
508 let pauli1_matrix = self.get_pauli_matrix(pauli1)?;
509 let pauli2_matrix = self.get_pauli_matrix(pauli2)?;
510
511 matrix = self.apply_single_qubit_to_full_matrix(
512 &matrix,
513 &pauli1_matrix,
514 *qubit1,
515 num_qubits,
516 )?;
517 matrix = self.apply_single_qubit_to_full_matrix(
518 &matrix,
519 &pauli2_matrix,
520 *qubit2,
521 num_qubits,
522 )?;
523 Ok((matrix, *coefficient))
524 }
525 HamiltonianTerm::PauliString {
526 qubits,
527 paulis,
528 coefficient,
529 } => {
530 let mut matrix = Array2::eye(dim);
531
532 for (qubit, pauli) in qubits.iter().zip(paulis.iter()) {
533 let pauli_matrix = self.get_pauli_matrix(pauli)?;
534 matrix = self.apply_single_qubit_to_full_matrix(
535 &matrix,
536 &pauli_matrix,
537 *qubit,
538 num_qubits,
539 )?;
540 }
541 Ok((matrix, *coefficient))
542 }
543 HamiltonianTerm::Custom {
544 qubits: _,
545 matrix: _,
546 coefficient,
547 } => {
548 Ok((Array2::eye(dim), *coefficient))
550 }
551 }
552 }
553
554 fn get_pauli_matrix(&self, pauli: &str) -> Result<Array2<Complex64>> {
556 match pauli.to_uppercase().as_str() {
557 "I" => Ok(Array2::eye(2)),
558 "X" => Ok(Array2::from_shape_vec(
559 (2, 2),
560 vec![
561 Complex64::new(0.0, 0.0),
562 Complex64::new(1.0, 0.0),
563 Complex64::new(1.0, 0.0),
564 Complex64::new(0.0, 0.0),
565 ],
566 )
567 .unwrap()),
568 "Y" => Ok(Array2::from_shape_vec(
569 (2, 2),
570 vec![
571 Complex64::new(0.0, 0.0),
572 Complex64::new(0.0, -1.0),
573 Complex64::new(0.0, 1.0),
574 Complex64::new(0.0, 0.0),
575 ],
576 )
577 .unwrap()),
578 "Z" => Ok(Array2::from_shape_vec(
579 (2, 2),
580 vec![
581 Complex64::new(1.0, 0.0),
582 Complex64::new(0.0, 0.0),
583 Complex64::new(0.0, 0.0),
584 Complex64::new(-1.0, 0.0),
585 ],
586 )
587 .unwrap()),
588 _ => Err(SimulatorError::InvalidInput(format!(
589 "Unknown Pauli operator: {pauli}"
590 ))),
591 }
592 }
593
594 fn apply_single_qubit_to_full_matrix(
596 &self,
597 full_matrix: &Array2<Complex64>,
598 single_qubit_op: &Array2<Complex64>,
599 target_qubit: usize,
600 num_qubits: usize,
601 ) -> Result<Array2<Complex64>> {
602 let dim = 1 << num_qubits;
603 let mut result = Array2::zeros((dim, dim));
604
605 for i in 0..dim {
606 for j in 0..dim {
607 let i_bit = (i >> target_qubit) & 1;
608 let j_bit = (j >> target_qubit) & 1;
609
610 result[[i, j]] = full_matrix[[i, j]] * single_qubit_op[[i_bit, j_bit]];
611 }
612 }
613
614 Ok(result)
615 }
616
617 fn compute_evolution_operator(
619 &self,
620 hamiltonian: &Array2<Complex64>,
621 dt: f64,
622 ) -> Result<Array2<Complex64>> {
623 let dim = hamiltonian.dim().0;
627 if dim <= 64 {
628 self.matrix_exponential(hamiltonian, -Complex64::new(0.0, dt))
630 } else {
631 self.trotter_evolution(hamiltonian, dt)
633 }
634 }
635
636 fn matrix_exponential(
638 &self,
639 matrix: &Array2<Complex64>,
640 factor: Complex64,
641 ) -> Result<Array2<Complex64>> {
642 let dim = matrix.dim().0;
643
644 let scaled_matrix = matrix.mapv(|x| x * factor);
646
647 let mut result = Array2::eye(dim);
649 let mut term = Array2::eye(dim);
650
651 for n in 1..=20 {
652 term = term.dot(&scaled_matrix) / (n as f64);
654 let term_norm: f64 = term.iter().map(|x| x.norm_sqr()).sum::<f64>().sqrt();
655
656 result += &term;
657
658 if term_norm < 1e-15 {
660 break;
661 }
662 }
663
664 Ok(result)
665 }
666
667 fn trotter_evolution(
669 &self,
670 hamiltonian: &Array2<Complex64>,
671 dt: f64,
672 ) -> Result<Array2<Complex64>> {
673 self.matrix_exponential(hamiltonian, -Complex64::new(0.0, dt))
675 }
676
677 fn measure_gap(&mut self, t: f64, s: f64, hamiltonian: &Hamiltonian) -> Result<GapMeasurement> {
679 let start_time = std::time::Instant::now();
680
681 let h_matrix = self.build_hamiltonian_matrix(hamiltonian)?;
682 let (eigenvalues, _) = self.compute_eigendecomposition(&h_matrix)?;
683
684 let mut sorted_eigenvalues = eigenvalues;
686 sorted_eigenvalues.sort_by(|a, b| a.partial_cmp(b).unwrap());
687
688 let ground_energy = sorted_eigenvalues[0];
689 let first_excited_energy = sorted_eigenvalues.get(1).copied().unwrap_or(ground_energy);
690 let gap = first_excited_energy - ground_energy;
691
692 if self.stats.min_gap == 0.0 || gap < self.stats.min_gap {
694 self.stats.min_gap = gap;
695 }
696 if gap > self.stats.max_gap {
697 self.stats.max_gap = gap;
698 }
699
700 let gap_count = self.gap_history.len() as f64;
701 self.stats.avg_gap = self.stats.avg_gap.mul_add(gap_count, gap) / (gap_count + 1.0);
702
703 let computation_time = start_time.elapsed().as_secs_f64() * 1000.0;
704 self.stats.avg_eigenvalue_time_ms = self
705 .stats
706 .avg_eigenvalue_time_ms
707 .mul_add(self.stats.eigenvalue_computations as f64, computation_time)
708 / (self.stats.eigenvalue_computations + 1) as f64;
709 self.stats.eigenvalue_computations += 1;
710
711 Ok(GapMeasurement {
712 time: t,
713 schedule_parameter: s,
714 gap,
715 ground_energy,
716 first_excited_energy,
717 gap_derivative: self.estimate_gap_derivative(gap),
718 predicted_min_gap: self.predict_minimum_gap(),
719 })
720 }
721
722 fn estimate_gap_derivative(&self, current_gap: f64) -> Option<f64> {
724 if self.gap_history.len() < 2 {
725 return None;
726 }
727
728 let prev_gap = self.gap_history.last().unwrap().gap;
729 let prev_time = self.gap_history.last().unwrap().time;
730 let dt = self.current_time - prev_time;
731
732 if dt > 1e-15 {
733 Some((current_gap - prev_gap) / dt)
734 } else {
735 None
736 }
737 }
738
739 fn predict_minimum_gap(&self) -> Option<f64> {
741 if self.gap_history.len() < 3 {
742 return None;
743 }
744
745 let n = self.gap_history.len();
747 let recent_gaps: Vec<f64> = self.gap_history[n - 3..].iter().map(|g| g.gap).collect();
748 let recent_times: Vec<f64> = self.gap_history[n - 3..].iter().map(|g| g.time).collect();
749
750 recent_gaps.into_iter().fold(f64::INFINITY, f64::min).into()
753 }
754
755 fn check_diabatic_transition(&mut self, gap_measurement: &GapMeasurement) -> Result<()> {
757 if let Some(gap_derivative) = gap_measurement.gap_derivative {
759 let gap = gap_measurement.gap;
760 let dt = self.config.total_time / self.config.time_steps as f64;
761
762 let diabatic_prob = if gap_derivative.abs() > 1e-15 {
764 (-2.0 * std::f64::consts::PI * gap * gap / gap_derivative.abs()).exp()
765 } else {
766 0.0
767 };
768
769 if diabatic_prob > 0.01 {
771 self.stats.diabatic_transitions += 1;
773 println!(
774 "Warning: Potential diabatic transition detected at t={:.3}, P_diabatic={:.4}",
775 gap_measurement.time, diabatic_prob
776 );
777 }
778 }
779
780 Ok(())
781 }
782
783 fn calculate_adaptive_timestep(&self, t: f64, default_dt: f64) -> Result<f64> {
785 if self.gap_history.is_empty() {
786 return Ok(default_dt);
787 }
788
789 let current_gap = self.gap_history.last().unwrap().gap;
790
791 let gap_factor = (current_gap / self.config.gap_tracking.min_gap_threshold).sqrt();
793 let adaptive_dt = default_dt * gap_factor.min(2.0).max(0.1); Ok(adaptive_dt)
796 }
797
798 fn take_snapshot(&mut self, t: f64) -> Result<AdiabaticSnapshot> {
800 let s = self.schedule_function(t);
801 let energy = self.calculate_current_energy()?;
802
803 let gap = self.gap_history.last().map(|g| g.gap);
805
806 let (instantaneous_ground_state, ground_state_fidelity, adiabatic_parameter) =
808 if self.config.gap_tracking.enabled {
809 let hamiltonian = self.interpolate_hamiltonian(s)?;
810 let h_matrix = self.build_hamiltonian_matrix(&hamiltonian)?;
811 let (eigenvalues, eigenvectors) = self.compute_eigendecomposition(&h_matrix)?;
812
813 let ground_idx = eigenvalues
815 .iter()
816 .enumerate()
817 .min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
818 .map_or(0, |(idx, _)| idx);
819
820 let ground_state = eigenvectors.column(ground_idx).to_owned();
821
822 let fidelity = self.calculate_fidelity(&self.state, &ground_state);
824
825 let adiabatic_param = gap.map(|g| g * g * self.config.total_time);
827
828 (Some(ground_state), Some(fidelity), adiabatic_param)
829 } else {
830 (None, None, None)
831 };
832
833 Ok(AdiabaticSnapshot {
834 time: t,
835 schedule_parameter: s,
836 state: self.state.clone(),
837 energy,
838 gap,
839 instantaneous_ground_state,
840 ground_state_fidelity,
841 adiabatic_parameter,
842 })
843 }
844
845 fn calculate_current_energy(&self) -> Result<f64> {
847 let s = self.schedule_function(self.current_time);
848 let hamiltonian = self.interpolate_hamiltonian(s)?;
849 let h_matrix = self.build_hamiltonian_matrix(&hamiltonian)?;
850
851 let h_psi = h_matrix.dot(&self.state);
853 let energy: Complex64 = self
854 .state
855 .iter()
856 .zip(h_psi.iter())
857 .map(|(psi, h_psi)| psi.conj() * h_psi)
858 .sum();
859
860 Ok(energy.re)
861 }
862
863 fn calculate_fidelity(&self, state1: &Array1<Complex64>, state2: &Array1<Complex64>) -> f64 {
865 let overlap: Complex64 = state1
866 .iter()
867 .zip(state2.iter())
868 .map(|(a, b)| a.conj() * b)
869 .sum();
870
871 overlap.norm_sqr()
872 }
873
874 fn compute_eigendecomposition(
876 &self,
877 matrix: &Array2<Complex64>,
878 ) -> Result<(Vec<f64>, Array2<Complex64>)> {
879 let dim = matrix.dim().0;
883 if dim > 16 {
884 return self.compute_approximate_eigenvalues(matrix);
886 }
887
888 let mut eigenvalues = Vec::new();
890 let mut eigenvectors = Array2::eye(dim);
891
892 for i in 0..dim {
894 eigenvalues.push(matrix[[i, i]].re);
895 }
896
897 eigenvalues.sort_by(|a, b| a.partial_cmp(b).unwrap());
899
900 Ok((eigenvalues, eigenvectors))
901 }
902
903 fn compute_approximate_eigenvalues(
905 &self,
906 matrix: &Array2<Complex64>,
907 ) -> Result<(Vec<f64>, Array2<Complex64>)> {
908 let dim = matrix.dim().0;
909
910 let mut eigenvalues = Vec::new();
913 for i in 0..dim.min(self.config.gap_tracking.num_eigenvalues) {
914 eigenvalues.push(matrix[[i, i]].re);
915 }
916
917 eigenvalues.sort_by(|a, b| a.partial_cmp(b).unwrap());
918 let eigenvectors = Array2::eye(dim);
919
920 Ok((eigenvalues, eigenvectors))
921 }
922
923 fn compute_final_statistics(&mut self) -> Result<()> {
925 if let Some(final_snapshot) = self.evolution_history.last() {
927 if let Some(fidelity) = final_snapshot.ground_state_fidelity {
928 self.stats.final_ground_state_fidelity = fidelity;
929 }
930 }
931
932 self.stats.success_probability = self.stats.final_ground_state_fidelity;
935
936 Ok(())
937 }
938
939 pub const fn get_state(&self) -> &Array1<Complex64> {
941 &self.state
942 }
943
944 pub fn get_evolution_history(&self) -> &[AdiabaticSnapshot] {
946 &self.evolution_history
947 }
948
949 pub fn get_gap_history(&self) -> &[GapMeasurement] {
951 &self.gap_history
952 }
953
954 pub const fn get_stats(&self) -> &AdiabaticStats {
956 &self.stats
957 }
958
959 pub fn reset(&mut self) -> Result<()> {
961 let num_qubits = self.config.initial_hamiltonian.get_num_qubits();
962 let state_size = 1 << num_qubits;
963
964 self.state = Array1::zeros(state_size);
965 self.state[0] = Complex64::new(1.0, 0.0);
966 self.current_time = 0.0;
967 self.evolution_history.clear();
968 self.gap_history.clear();
969 self.stats = AdiabaticStats::default();
970
971 Ok(())
972 }
973}
974
975#[derive(Debug, Clone)]
977pub struct AdiabaticResult {
978 pub final_state: Array1<Complex64>,
980 pub evolution_history: Vec<AdiabaticSnapshot>,
982 pub gap_history: Vec<GapMeasurement>,
984 pub total_time_ms: f64,
986 pub success_probability: f64,
988 pub min_gap: f64,
990 pub final_energy: f64,
992}
993
994pub struct AdiabaticUtils;
996
997impl AdiabaticUtils {
998 pub fn create_max_cut_hamiltonian(
1000 graph_edges: &[(usize, usize)],
1001 weights: &[f64],
1002 ) -> Hamiltonian {
1003 let max_vertex = graph_edges
1004 .iter()
1005 .flat_map(|&(u, v)| [u, v])
1006 .max()
1007 .unwrap_or(0)
1008 + 1;
1009 let mut hamiltonian = Hamiltonian::new(max_vertex);
1010
1011 for (i, &(u, v)) in graph_edges.iter().enumerate() {
1012 let weight = weights.get(i).copied().unwrap_or(1.0);
1013
1014 hamiltonian
1019 .add_two_pauli(u, v, "Z", "Z", weight / 2.0)
1020 .unwrap();
1021 }
1022
1023 hamiltonian
1024 }
1025
1026 pub fn create_3sat_hamiltonian(clauses: &[Vec<i32>]) -> Hamiltonian {
1028 let max_var = clauses
1029 .iter()
1030 .flat_map(|clause| clause.iter())
1031 .map(|&lit| lit.unsigned_abs() as usize)
1032 .max()
1033 .unwrap_or(0)
1034 + 1;
1035 let mut hamiltonian = Hamiltonian::new(max_var);
1036
1037 for clause in clauses {
1038 if clause.len() != 3 {
1039 continue; }
1041
1042 for i in 0..clause.len() {
1048 for j in i + 1..clause.len() {
1049 let var1 = clause[i].unsigned_abs() as usize;
1050 let var2 = clause[j].unsigned_abs() as usize;
1051
1052 if var1 < max_var && var2 < max_var {
1054 hamiltonian
1055 .add_two_pauli(var1, var2, "Z", "Z", 0.1)
1056 .unwrap();
1057 }
1058 }
1059 }
1060 }
1061
1062 hamiltonian
1063 }
1064
1065 pub fn create_tfim_hamiltonian(
1067 num_qubits: usize,
1068 j_coupling: f64,
1069 h_field: f64,
1070 ) -> Hamiltonian {
1071 let mut hamiltonian = Hamiltonian::new(num_qubits);
1072
1073 for i in 0..num_qubits - 1 {
1075 hamiltonian
1076 .add_two_pauli(i, i + 1, "Z", "Z", -j_coupling)
1077 .unwrap();
1078 }
1079
1080 for i in 0..num_qubits {
1082 hamiltonian.add_single_pauli(i, "X", -h_field).unwrap();
1083 }
1084
1085 hamiltonian
1086 }
1087
1088 pub fn create_mixing_hamiltonian(num_qubits: usize) -> Hamiltonian {
1090 let mut hamiltonian = Hamiltonian::new(num_qubits);
1091
1092 for i in 0..num_qubits {
1093 hamiltonian.add_single_pauli(i, "X", 1.0).unwrap();
1094 }
1095
1096 hamiltonian
1097 }
1098
1099 pub fn benchmark_adiabatic_qc() -> Result<AdiabaticBenchmarkResults> {
1101 let mut results = AdiabaticBenchmarkResults::default();
1102
1103 let problem_sizes = vec![4, 6, 8];
1105 let schedule_types = vec![
1106 ScheduleType::Linear,
1107 ScheduleType::Quadratic,
1108 ScheduleType::LandauZener,
1109 ];
1110
1111 for &num_qubits in &problem_sizes {
1112 for &schedule_type in &schedule_types {
1113 let initial_h = Self::create_mixing_hamiltonian(num_qubits);
1115 let final_h = Self::create_tfim_hamiltonian(num_qubits, 1.0, 0.1);
1116
1117 let config = AdiabaticConfig {
1118 total_time: 10.0,
1119 time_steps: 100,
1120 schedule_type,
1121 initial_hamiltonian: initial_h,
1122 final_hamiltonian: final_h,
1123 gap_tracking: GapTrackingConfig {
1124 enabled: true,
1125 min_gap_threshold: 1e-3,
1126 ..Default::default()
1127 },
1128 ..Default::default()
1129 };
1130
1131 let mut adiabatic_qc = AdiabaticQuantumComputer::new(config)?;
1132
1133 let start = std::time::Instant::now();
1134 let result = adiabatic_qc.evolve()?;
1135 let execution_time = start.elapsed().as_secs_f64() * 1000.0;
1136
1137 let key = format!("{num_qubits}q_{schedule_type:?}");
1138 results.execution_times.push((key.clone(), execution_time));
1139 results
1140 .success_probabilities
1141 .push((key.clone(), result.success_probability));
1142 results.min_gaps.push((key, result.min_gap));
1143 }
1144 }
1145
1146 Ok(results)
1147 }
1148}
1149
1150#[derive(Debug, Clone, Default)]
1152pub struct AdiabaticBenchmarkResults {
1153 pub execution_times: Vec<(String, f64)>,
1155 pub success_probabilities: Vec<(String, f64)>,
1157 pub min_gaps: Vec<(String, f64)>,
1159}
1160
1161#[cfg(test)]
1162mod tests {
1163 use super::*;
1164 use approx::assert_abs_diff_eq;
1165
1166 #[test]
1167 fn test_adiabatic_qc_creation() {
1168 let config = AdiabaticConfig::default();
1169 let adiabatic_qc = AdiabaticQuantumComputer::new(config);
1170 assert!(adiabatic_qc.is_ok());
1171 }
1172
1173 #[test]
1174 fn test_schedule_functions() {
1175 let config = AdiabaticConfig {
1176 total_time: 10.0,
1177 schedule_type: ScheduleType::Linear,
1178 ..Default::default()
1179 };
1180 let adiabatic_qc = AdiabaticQuantumComputer::new(config).unwrap();
1181
1182 assert_abs_diff_eq!(adiabatic_qc.schedule_function(0.0), 0.0, epsilon = 1e-10);
1183 assert_abs_diff_eq!(adiabatic_qc.schedule_function(5.0), 0.5, epsilon = 1e-10);
1184 assert_abs_diff_eq!(adiabatic_qc.schedule_function(10.0), 1.0, epsilon = 1e-10);
1185 }
1186
1187 #[test]
1188 fn test_hamiltonian_interpolation() {
1189 let mut initial_h = Hamiltonian::new(1);
1190 initial_h.add_pauli_term(1.0, &[(0, 'X')]).unwrap();
1191
1192 let mut final_h = Hamiltonian::new(1);
1193 final_h.add_pauli_term(1.0, &[(0, 'Z')]).unwrap();
1194
1195 let config = AdiabaticConfig {
1196 initial_hamiltonian: initial_h,
1197 final_hamiltonian: final_h,
1198 ..Default::default()
1199 };
1200
1201 let adiabatic_qc = AdiabaticQuantumComputer::new(config).unwrap();
1202
1203 let h_mid = adiabatic_qc.interpolate_hamiltonian(0.5).unwrap();
1204 assert_eq!(h_mid.terms.len(), 2); }
1206
1207 #[test]
1208 fn test_tfim_hamiltonian() {
1209 let hamiltonian = AdiabaticUtils::create_tfim_hamiltonian(3, 1.0, 0.5);
1210
1211 let num_zz_terms = hamiltonian.terms.iter().filter(|t| {
1213 matches!(t, HamiltonianTerm::TwoPauli { pauli1, pauli2, .. } if pauli1 == "Z" && pauli2 == "Z")
1214 }).count();
1215
1216 let num_x_terms = hamiltonian
1217 .terms
1218 .iter()
1219 .filter(|t| matches!(t, HamiltonianTerm::SinglePauli { pauli, .. } if pauli == "X"))
1220 .count();
1221
1222 assert_eq!(num_zz_terms, 2); assert_eq!(num_x_terms, 3); }
1225
1226 #[test]
1227 fn test_max_cut_hamiltonian() {
1228 let edges = vec![(0, 1), (1, 2), (2, 0)]; let weights = vec![1.0, 1.0, 1.0];
1230
1231 let hamiltonian = AdiabaticUtils::create_max_cut_hamiltonian(&edges, &weights);
1232 assert!(!hamiltonian.terms.is_empty());
1233 }
1234
1235 #[test]
1236 fn test_mixing_hamiltonian() {
1237 let hamiltonian = AdiabaticUtils::create_mixing_hamiltonian(2);
1238
1239 let num_x_terms = hamiltonian
1240 .terms
1241 .iter()
1242 .filter(|t| matches!(t, HamiltonianTerm::SinglePauli { pauli, .. } if pauli == "X"))
1243 .count();
1244
1245 assert_eq!(num_x_terms, 2); }
1247
1248 #[test]
1249 fn test_adiabatic_evolution() {
1250 let initial_h = AdiabaticUtils::create_mixing_hamiltonian(2);
1251 let final_h = AdiabaticUtils::create_tfim_hamiltonian(2, 1.0, 0.1);
1252
1253 let config = AdiabaticConfig {
1254 total_time: 1.0,
1255 time_steps: 10,
1256 initial_hamiltonian: initial_h,
1257 final_hamiltonian: final_h,
1258 gap_tracking: GapTrackingConfig {
1259 enabled: false, ..Default::default()
1261 },
1262 ..Default::default()
1263 };
1264
1265 let mut adiabatic_qc = AdiabaticQuantumComputer::new(config).unwrap();
1266 let result = adiabatic_qc.evolve();
1267 assert!(result.is_ok());
1268
1269 let evolution_result = result.unwrap();
1270 assert_eq!(evolution_result.evolution_history.len(), 2); }
1272
1273 #[test]
1274 fn test_gap_tracking() {
1275 let initial_h = AdiabaticUtils::create_mixing_hamiltonian(2);
1276 let final_h = AdiabaticUtils::create_tfim_hamiltonian(2, 1.0, 0.1);
1277
1278 let config = AdiabaticConfig {
1279 total_time: 1.0,
1280 time_steps: 5,
1281 initial_hamiltonian: initial_h,
1282 final_hamiltonian: final_h,
1283 gap_tracking: GapTrackingConfig {
1284 enabled: true,
1285 ..Default::default()
1286 },
1287 ..Default::default()
1288 };
1289
1290 let mut adiabatic_qc = AdiabaticQuantumComputer::new(config).unwrap();
1291 let result = adiabatic_qc.evolve();
1292 assert!(result.is_ok());
1293
1294 let evolution_result = result.unwrap();
1295 assert!(!evolution_result.gap_history.is_empty());
1296 assert!(evolution_result.min_gap >= 0.0);
1297 }
1298
1299 #[test]
1300 fn test_energy_calculation() {
1301 let initial_h = AdiabaticUtils::create_mixing_hamiltonian(1);
1302 let final_h = AdiabaticUtils::create_tfim_hamiltonian(1, 1.0, 0.1);
1303
1304 let config = AdiabaticConfig {
1305 initial_hamiltonian: initial_h,
1306 final_hamiltonian: final_h,
1307 ..Default::default()
1308 };
1309
1310 let adiabatic_qc = AdiabaticQuantumComputer::new(config).unwrap();
1311 let energy = adiabatic_qc.calculate_current_energy();
1312 assert!(energy.is_ok());
1313 }
1314
1315 #[test]
1316 fn test_fidelity_calculation() {
1317 let config = AdiabaticConfig::default();
1318 let adiabatic_qc = AdiabaticQuantumComputer::new(config).unwrap();
1319
1320 let state1 = Array1::from_vec(vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]);
1321 let state2 = Array1::from_vec(vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]);
1322
1323 let fidelity = adiabatic_qc.calculate_fidelity(&state1, &state2);
1324 assert_abs_diff_eq!(fidelity, 1.0, epsilon = 1e-10);
1325 }
1326}