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_or(std::cmp::Ordering::Equal))
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
455 .state
456 .iter()
457 .map(scirs2_core::Complex::norm_sqr)
458 .sum::<f64>()
459 .sqrt();
460 if norm > 1e-15 {
461 self.state.mapv_inplace(|x| x / norm);
462 }
463
464 Ok(())
465 }
466
467 fn build_hamiltonian_matrix(&self, hamiltonian: &Hamiltonian) -> Result<Array2<Complex64>> {
469 let num_qubits = hamiltonian.get_num_qubits();
470 let dim = 1 << num_qubits;
471 let mut matrix = Array2::zeros((dim, dim));
472
473 for term in &hamiltonian.terms {
474 let (term_matrix, coefficient) = self.build_term_matrix(term, num_qubits)?;
475 matrix = matrix + term_matrix.mapv(|x| x * coefficient);
476 }
477
478 Ok(matrix)
479 }
480
481 fn build_term_matrix(
483 &self,
484 term: &HamiltonianTerm,
485 num_qubits: usize,
486 ) -> Result<(Array2<Complex64>, f64)> {
487 let dim = 1 << num_qubits;
488
489 match term {
490 HamiltonianTerm::SinglePauli {
491 qubit,
492 pauli,
493 coefficient,
494 } => {
495 let mut matrix = Array2::eye(dim);
496 let pauli_matrix = self.get_pauli_matrix(pauli)?;
497 matrix = self.apply_single_qubit_to_full_matrix(
498 &matrix,
499 &pauli_matrix,
500 *qubit,
501 num_qubits,
502 )?;
503 Ok((matrix, *coefficient))
504 }
505 HamiltonianTerm::TwoPauli {
506 qubit1,
507 qubit2,
508 pauli1,
509 pauli2,
510 coefficient,
511 } => {
512 let mut matrix = Array2::eye(dim);
513 let pauli1_matrix = self.get_pauli_matrix(pauli1)?;
514 let pauli2_matrix = self.get_pauli_matrix(pauli2)?;
515
516 matrix = self.apply_single_qubit_to_full_matrix(
517 &matrix,
518 &pauli1_matrix,
519 *qubit1,
520 num_qubits,
521 )?;
522 matrix = self.apply_single_qubit_to_full_matrix(
523 &matrix,
524 &pauli2_matrix,
525 *qubit2,
526 num_qubits,
527 )?;
528 Ok((matrix, *coefficient))
529 }
530 HamiltonianTerm::PauliString {
531 qubits,
532 paulis,
533 coefficient,
534 } => {
535 let mut matrix = Array2::eye(dim);
536
537 for (qubit, pauli) in qubits.iter().zip(paulis.iter()) {
538 let pauli_matrix = self.get_pauli_matrix(pauli)?;
539 matrix = self.apply_single_qubit_to_full_matrix(
540 &matrix,
541 &pauli_matrix,
542 *qubit,
543 num_qubits,
544 )?;
545 }
546 Ok((matrix, *coefficient))
547 }
548 HamiltonianTerm::Custom {
549 qubits: _,
550 matrix: _,
551 coefficient,
552 } => {
553 Ok((Array2::eye(dim), *coefficient))
555 }
556 }
557 }
558
559 fn get_pauli_matrix(&self, pauli: &str) -> Result<Array2<Complex64>> {
561 match pauli.to_uppercase().as_str() {
562 "I" => Ok(Array2::eye(2)),
563 "X" => Array2::from_shape_vec(
564 (2, 2),
565 vec![
566 Complex64::new(0.0, 0.0),
567 Complex64::new(1.0, 0.0),
568 Complex64::new(1.0, 0.0),
569 Complex64::new(0.0, 0.0),
570 ],
571 )
572 .map_err(|e| SimulatorError::InvalidInput(format!("Pauli X matrix error: {e}"))),
573 "Y" => Array2::from_shape_vec(
574 (2, 2),
575 vec![
576 Complex64::new(0.0, 0.0),
577 Complex64::new(0.0, -1.0),
578 Complex64::new(0.0, 1.0),
579 Complex64::new(0.0, 0.0),
580 ],
581 )
582 .map_err(|e| SimulatorError::InvalidInput(format!("Pauli Y matrix error: {e}"))),
583 "Z" => Array2::from_shape_vec(
584 (2, 2),
585 vec![
586 Complex64::new(1.0, 0.0),
587 Complex64::new(0.0, 0.0),
588 Complex64::new(0.0, 0.0),
589 Complex64::new(-1.0, 0.0),
590 ],
591 )
592 .map_err(|e| SimulatorError::InvalidInput(format!("Pauli Z matrix error: {e}"))),
593 _ => Err(SimulatorError::InvalidInput(format!(
594 "Unknown Pauli operator: {pauli}"
595 ))),
596 }
597 }
598
599 fn apply_single_qubit_to_full_matrix(
601 &self,
602 full_matrix: &Array2<Complex64>,
603 single_qubit_op: &Array2<Complex64>,
604 target_qubit: usize,
605 num_qubits: usize,
606 ) -> Result<Array2<Complex64>> {
607 let dim = 1 << num_qubits;
608 let mut result = Array2::zeros((dim, dim));
609
610 for i in 0..dim {
611 for j in 0..dim {
612 let i_bit = (i >> target_qubit) & 1;
613 let j_bit = (j >> target_qubit) & 1;
614
615 result[[i, j]] = full_matrix[[i, j]] * single_qubit_op[[i_bit, j_bit]];
616 }
617 }
618
619 Ok(result)
620 }
621
622 fn compute_evolution_operator(
624 &self,
625 hamiltonian: &Array2<Complex64>,
626 dt: f64,
627 ) -> Result<Array2<Complex64>> {
628 let dim = hamiltonian.dim().0;
632 if dim <= 64 {
633 self.matrix_exponential(hamiltonian, -Complex64::new(0.0, dt))
635 } else {
636 self.trotter_evolution(hamiltonian, dt)
638 }
639 }
640
641 fn matrix_exponential(
643 &self,
644 matrix: &Array2<Complex64>,
645 factor: Complex64,
646 ) -> Result<Array2<Complex64>> {
647 let dim = matrix.dim().0;
648
649 let scaled_matrix = matrix.mapv(|x| x * factor);
651
652 let mut result = Array2::eye(dim);
654 let mut term = Array2::eye(dim);
655
656 for n in 1..=20 {
657 term = term.dot(&scaled_matrix) / f64::from(n);
659 let term_norm: f64 = term
660 .iter()
661 .map(scirs2_core::Complex::norm_sqr)
662 .sum::<f64>()
663 .sqrt();
664
665 result += &term;
666
667 if term_norm < 1e-15 {
669 break;
670 }
671 }
672
673 Ok(result)
674 }
675
676 fn trotter_evolution(
678 &self,
679 hamiltonian: &Array2<Complex64>,
680 dt: f64,
681 ) -> Result<Array2<Complex64>> {
682 self.matrix_exponential(hamiltonian, -Complex64::new(0.0, dt))
684 }
685
686 fn measure_gap(&mut self, t: f64, s: f64, hamiltonian: &Hamiltonian) -> Result<GapMeasurement> {
688 let start_time = std::time::Instant::now();
689
690 let h_matrix = self.build_hamiltonian_matrix(hamiltonian)?;
691 let (eigenvalues, _) = self.compute_eigendecomposition(&h_matrix)?;
692
693 let mut sorted_eigenvalues = eigenvalues;
695 sorted_eigenvalues.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
696
697 let ground_energy = sorted_eigenvalues[0];
698 let first_excited_energy = sorted_eigenvalues.get(1).copied().unwrap_or(ground_energy);
699 let gap = first_excited_energy - ground_energy;
700
701 if self.stats.min_gap == 0.0 || gap < self.stats.min_gap {
703 self.stats.min_gap = gap;
704 }
705 if gap > self.stats.max_gap {
706 self.stats.max_gap = gap;
707 }
708
709 let gap_count = self.gap_history.len() as f64;
710 self.stats.avg_gap = self.stats.avg_gap.mul_add(gap_count, gap) / (gap_count + 1.0);
711
712 let computation_time = start_time.elapsed().as_secs_f64() * 1000.0;
713 self.stats.avg_eigenvalue_time_ms = self
714 .stats
715 .avg_eigenvalue_time_ms
716 .mul_add(self.stats.eigenvalue_computations as f64, computation_time)
717 / (self.stats.eigenvalue_computations + 1) as f64;
718 self.stats.eigenvalue_computations += 1;
719
720 Ok(GapMeasurement {
721 time: t,
722 schedule_parameter: s,
723 gap,
724 ground_energy,
725 first_excited_energy,
726 gap_derivative: self.estimate_gap_derivative(gap),
727 predicted_min_gap: self.predict_minimum_gap(),
728 })
729 }
730
731 fn estimate_gap_derivative(&self, current_gap: f64) -> Option<f64> {
733 if self.gap_history.len() < 2 {
734 return None;
735 }
736
737 let last_entry = self.gap_history.last()?;
738 let prev_gap = last_entry.gap;
739 let prev_time = last_entry.time;
740 let dt = self.current_time - prev_time;
741
742 if dt > 1e-15 {
743 Some((current_gap - prev_gap) / dt)
744 } else {
745 None
746 }
747 }
748
749 fn predict_minimum_gap(&self) -> Option<f64> {
751 if self.gap_history.len() < 3 {
752 return None;
753 }
754
755 let n = self.gap_history.len();
757 let recent_gaps: Vec<f64> = self.gap_history[n - 3..].iter().map(|g| g.gap).collect();
758 let recent_times: Vec<f64> = self.gap_history[n - 3..].iter().map(|g| g.time).collect();
759
760 recent_gaps.into_iter().fold(f64::INFINITY, f64::min).into()
763 }
764
765 fn check_diabatic_transition(&mut self, gap_measurement: &GapMeasurement) -> Result<()> {
767 if let Some(gap_derivative) = gap_measurement.gap_derivative {
769 let gap = gap_measurement.gap;
770 let dt = self.config.total_time / self.config.time_steps as f64;
771
772 let diabatic_prob = if gap_derivative.abs() > 1e-15 {
774 (-2.0 * std::f64::consts::PI * gap * gap / gap_derivative.abs()).exp()
775 } else {
776 0.0
777 };
778
779 if diabatic_prob > 0.01 {
781 self.stats.diabatic_transitions += 1;
783 println!(
784 "Warning: Potential diabatic transition detected at t={:.3}, P_diabatic={:.4}",
785 gap_measurement.time, diabatic_prob
786 );
787 }
788 }
789
790 Ok(())
791 }
792
793 fn calculate_adaptive_timestep(&self, _t: f64, default_dt: f64) -> Result<f64> {
795 if self.gap_history.is_empty() {
796 return Ok(default_dt);
797 }
798
799 let current_gap = self
800 .gap_history
801 .last()
802 .ok_or_else(|| SimulatorError::InvalidState("Gap history is empty".to_string()))?
803 .gap;
804
805 let gap_factor = (current_gap / self.config.gap_tracking.min_gap_threshold).sqrt();
807 let adaptive_dt = default_dt * gap_factor.clamp(0.1, 2.0); Ok(adaptive_dt)
810 }
811
812 fn take_snapshot(&self, t: f64) -> Result<AdiabaticSnapshot> {
814 let s = self.schedule_function(t);
815 let energy = self.calculate_current_energy()?;
816
817 let gap = self.gap_history.last().map(|g| g.gap);
819
820 let (instantaneous_ground_state, ground_state_fidelity, adiabatic_parameter) =
822 if self.config.gap_tracking.enabled {
823 let hamiltonian = self.interpolate_hamiltonian(s)?;
824 let h_matrix = self.build_hamiltonian_matrix(&hamiltonian)?;
825 let (eigenvalues, eigenvectors) = self.compute_eigendecomposition(&h_matrix)?;
826
827 let ground_idx = eigenvalues
829 .iter()
830 .enumerate()
831 .min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
832 .map_or(0, |(idx, _)| idx);
833
834 let ground_state = eigenvectors.column(ground_idx).to_owned();
835
836 let fidelity = self.calculate_fidelity(&self.state, &ground_state);
838
839 let adiabatic_param = gap.map(|g| g * g * self.config.total_time);
841
842 (Some(ground_state), Some(fidelity), adiabatic_param)
843 } else {
844 (None, None, None)
845 };
846
847 Ok(AdiabaticSnapshot {
848 time: t,
849 schedule_parameter: s,
850 state: self.state.clone(),
851 energy,
852 gap,
853 instantaneous_ground_state,
854 ground_state_fidelity,
855 adiabatic_parameter,
856 })
857 }
858
859 fn calculate_current_energy(&self) -> Result<f64> {
861 let s = self.schedule_function(self.current_time);
862 let hamiltonian = self.interpolate_hamiltonian(s)?;
863 let h_matrix = self.build_hamiltonian_matrix(&hamiltonian)?;
864
865 let h_psi = h_matrix.dot(&self.state);
867 let energy: Complex64 = self
868 .state
869 .iter()
870 .zip(h_psi.iter())
871 .map(|(psi, h_psi)| psi.conj() * h_psi)
872 .sum();
873
874 Ok(energy.re)
875 }
876
877 fn calculate_fidelity(&self, state1: &Array1<Complex64>, state2: &Array1<Complex64>) -> f64 {
879 let overlap: Complex64 = state1
880 .iter()
881 .zip(state2.iter())
882 .map(|(a, b)| a.conj() * b)
883 .sum();
884
885 overlap.norm_sqr()
886 }
887
888 fn compute_eigendecomposition(
890 &self,
891 matrix: &Array2<Complex64>,
892 ) -> Result<(Vec<f64>, Array2<Complex64>)> {
893 let dim = matrix.dim().0;
897 if dim > 16 {
898 return self.compute_approximate_eigenvalues(matrix);
900 }
901
902 let mut eigenvalues = Vec::new();
904 let mut eigenvectors = Array2::eye(dim);
905
906 for i in 0..dim {
908 eigenvalues.push(matrix[[i, i]].re);
909 }
910
911 eigenvalues.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
913
914 Ok((eigenvalues, eigenvectors))
915 }
916
917 fn compute_approximate_eigenvalues(
919 &self,
920 matrix: &Array2<Complex64>,
921 ) -> Result<(Vec<f64>, Array2<Complex64>)> {
922 let dim = matrix.dim().0;
923
924 let mut eigenvalues = Vec::new();
927 for i in 0..dim.min(self.config.gap_tracking.num_eigenvalues) {
928 eigenvalues.push(matrix[[i, i]].re);
929 }
930
931 eigenvalues.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
932 let eigenvectors = Array2::eye(dim);
933
934 Ok((eigenvalues, eigenvectors))
935 }
936
937 fn compute_final_statistics(&mut self) -> Result<()> {
939 if let Some(final_snapshot) = self.evolution_history.last() {
941 if let Some(fidelity) = final_snapshot.ground_state_fidelity {
942 self.stats.final_ground_state_fidelity = fidelity;
943 }
944 }
945
946 self.stats.success_probability = self.stats.final_ground_state_fidelity;
949
950 Ok(())
951 }
952
953 #[must_use]
955 pub const fn get_state(&self) -> &Array1<Complex64> {
956 &self.state
957 }
958
959 #[must_use]
961 pub fn get_evolution_history(&self) -> &[AdiabaticSnapshot] {
962 &self.evolution_history
963 }
964
965 #[must_use]
967 pub fn get_gap_history(&self) -> &[GapMeasurement] {
968 &self.gap_history
969 }
970
971 #[must_use]
973 pub const fn get_stats(&self) -> &AdiabaticStats {
974 &self.stats
975 }
976
977 pub fn reset(&mut self) -> Result<()> {
979 let num_qubits = self.config.initial_hamiltonian.get_num_qubits();
980 let state_size = 1 << num_qubits;
981
982 self.state = Array1::zeros(state_size);
983 self.state[0] = Complex64::new(1.0, 0.0);
984 self.current_time = 0.0;
985 self.evolution_history.clear();
986 self.gap_history.clear();
987 self.stats = AdiabaticStats::default();
988
989 Ok(())
990 }
991}
992
993#[derive(Debug, Clone)]
995pub struct AdiabaticResult {
996 pub final_state: Array1<Complex64>,
998 pub evolution_history: Vec<AdiabaticSnapshot>,
1000 pub gap_history: Vec<GapMeasurement>,
1002 pub total_time_ms: f64,
1004 pub success_probability: f64,
1006 pub min_gap: f64,
1008 pub final_energy: f64,
1010}
1011
1012pub struct AdiabaticUtils;
1014
1015impl AdiabaticUtils {
1016 #[must_use]
1018 pub fn create_max_cut_hamiltonian(
1019 graph_edges: &[(usize, usize)],
1020 weights: &[f64],
1021 ) -> Hamiltonian {
1022 let max_vertex = graph_edges
1023 .iter()
1024 .flat_map(|&(u, v)| [u, v])
1025 .max()
1026 .unwrap_or(0)
1027 + 1;
1028 let mut hamiltonian = Hamiltonian::new(max_vertex);
1029
1030 for (i, &(u, v)) in graph_edges.iter().enumerate() {
1031 let weight = weights.get(i).copied().unwrap_or(1.0);
1032
1033 if let Err(e) = hamiltonian.add_two_pauli(u, v, "Z", "Z", weight / 2.0) {
1038 eprintln!("Warning: Failed to add Max-Cut term for edge ({u}, {v}): {e}");
1039 }
1040 }
1041
1042 hamiltonian
1043 }
1044
1045 #[must_use]
1047 pub fn create_3sat_hamiltonian(clauses: &[Vec<i32>]) -> Hamiltonian {
1048 let max_var = clauses
1049 .iter()
1050 .flat_map(|clause| clause.iter())
1051 .map(|&lit| lit.unsigned_abs() as usize)
1052 .max()
1053 .unwrap_or(0)
1054 + 1;
1055 let mut hamiltonian = Hamiltonian::new(max_var);
1056
1057 for clause in clauses {
1058 if clause.len() != 3 {
1059 continue; }
1061
1062 for i in 0..clause.len() {
1068 for j in i + 1..clause.len() {
1069 let var1 = clause[i].unsigned_abs() as usize;
1070 let var2 = clause[j].unsigned_abs() as usize;
1071
1072 if var1 < max_var && var2 < max_var {
1074 if let Err(e) = hamiltonian.add_two_pauli(var1, var2, "Z", "Z", 0.1) {
1075 eprintln!(
1076 "Warning: Failed to add 3-SAT term for vars ({var1}, {var2}): {e}"
1077 );
1078 }
1079 }
1080 }
1081 }
1082 }
1083
1084 hamiltonian
1085 }
1086
1087 #[must_use]
1089 pub fn create_tfim_hamiltonian(
1090 num_qubits: usize,
1091 j_coupling: f64,
1092 h_field: f64,
1093 ) -> Hamiltonian {
1094 let mut hamiltonian = Hamiltonian::new(num_qubits);
1095
1096 for i in 0..num_qubits - 1 {
1098 if let Err(e) = hamiltonian.add_two_pauli(i, i + 1, "Z", "Z", -j_coupling) {
1099 eprintln!(
1100 "Warning: Failed to add TFIM ZZ term for qubits ({i}, {}): {e}",
1101 i + 1
1102 );
1103 }
1104 }
1105
1106 for i in 0..num_qubits {
1108 if let Err(e) = hamiltonian.add_single_pauli(i, "X", -h_field) {
1109 eprintln!("Warning: Failed to add TFIM X term for qubit {i}: {e}");
1110 }
1111 }
1112
1113 hamiltonian
1114 }
1115
1116 #[must_use]
1118 pub fn create_mixing_hamiltonian(num_qubits: usize) -> Hamiltonian {
1119 let mut hamiltonian = Hamiltonian::new(num_qubits);
1120
1121 for i in 0..num_qubits {
1122 if let Err(e) = hamiltonian.add_single_pauli(i, "X", 1.0) {
1123 eprintln!("Warning: Failed to add mixing X term for qubit {i}: {e}");
1124 }
1125 }
1126
1127 hamiltonian
1128 }
1129
1130 pub fn benchmark_adiabatic_qc() -> Result<AdiabaticBenchmarkResults> {
1132 let mut results = AdiabaticBenchmarkResults::default();
1133
1134 let problem_sizes = vec![4, 6, 8];
1136 let schedule_types = vec![
1137 ScheduleType::Linear,
1138 ScheduleType::Quadratic,
1139 ScheduleType::LandauZener,
1140 ];
1141
1142 for &num_qubits in &problem_sizes {
1143 for &schedule_type in &schedule_types {
1144 let initial_h = Self::create_mixing_hamiltonian(num_qubits);
1146 let final_h = Self::create_tfim_hamiltonian(num_qubits, 1.0, 0.1);
1147
1148 let config = AdiabaticConfig {
1149 total_time: 10.0,
1150 time_steps: 100,
1151 schedule_type,
1152 initial_hamiltonian: initial_h,
1153 final_hamiltonian: final_h,
1154 gap_tracking: GapTrackingConfig {
1155 enabled: true,
1156 min_gap_threshold: 1e-3,
1157 ..Default::default()
1158 },
1159 ..Default::default()
1160 };
1161
1162 let mut adiabatic_qc = AdiabaticQuantumComputer::new(config)?;
1163
1164 let start = std::time::Instant::now();
1165 let result = adiabatic_qc.evolve()?;
1166 let execution_time = start.elapsed().as_secs_f64() * 1000.0;
1167
1168 let key = format!("{num_qubits}q_{schedule_type:?}");
1169 results.execution_times.push((key.clone(), execution_time));
1170 results
1171 .success_probabilities
1172 .push((key.clone(), result.success_probability));
1173 results.min_gaps.push((key, result.min_gap));
1174 }
1175 }
1176
1177 Ok(results)
1178 }
1179}
1180
1181#[derive(Debug, Clone, Default)]
1183pub struct AdiabaticBenchmarkResults {
1184 pub execution_times: Vec<(String, f64)>,
1186 pub success_probabilities: Vec<(String, f64)>,
1188 pub min_gaps: Vec<(String, f64)>,
1190}
1191
1192#[cfg(test)]
1193mod tests {
1194 use super::*;
1195 use approx::assert_abs_diff_eq;
1196
1197 #[test]
1198 fn test_adiabatic_qc_creation() {
1199 let config = AdiabaticConfig::default();
1200 let adiabatic_qc = AdiabaticQuantumComputer::new(config);
1201 assert!(adiabatic_qc.is_ok());
1202 }
1203
1204 #[test]
1205 fn test_schedule_functions() {
1206 let config = AdiabaticConfig {
1207 total_time: 10.0,
1208 schedule_type: ScheduleType::Linear,
1209 ..Default::default()
1210 };
1211 let adiabatic_qc =
1212 AdiabaticQuantumComputer::new(config).expect("Failed to create adiabatic QC");
1213
1214 assert_abs_diff_eq!(adiabatic_qc.schedule_function(0.0), 0.0, epsilon = 1e-10);
1215 assert_abs_diff_eq!(adiabatic_qc.schedule_function(5.0), 0.5, epsilon = 1e-10);
1216 assert_abs_diff_eq!(adiabatic_qc.schedule_function(10.0), 1.0, epsilon = 1e-10);
1217 }
1218
1219 #[test]
1220 fn test_hamiltonian_interpolation() {
1221 let mut initial_h = Hamiltonian::new(1);
1222 initial_h
1223 .add_pauli_term(1.0, &[(0, 'X')])
1224 .expect("Failed to add Pauli term");
1225
1226 let mut final_h = Hamiltonian::new(1);
1227 final_h
1228 .add_pauli_term(1.0, &[(0, 'Z')])
1229 .expect("Failed to add Pauli term");
1230
1231 let config = AdiabaticConfig {
1232 initial_hamiltonian: initial_h,
1233 final_hamiltonian: final_h,
1234 ..Default::default()
1235 };
1236
1237 let adiabatic_qc =
1238 AdiabaticQuantumComputer::new(config).expect("Failed to create adiabatic QC");
1239
1240 let h_mid = adiabatic_qc
1241 .interpolate_hamiltonian(0.5)
1242 .expect("Failed to interpolate Hamiltonian");
1243 assert_eq!(h_mid.terms.len(), 2); }
1245
1246 #[test]
1247 fn test_tfim_hamiltonian() {
1248 let hamiltonian = AdiabaticUtils::create_tfim_hamiltonian(3, 1.0, 0.5);
1249
1250 let num_zz_terms = hamiltonian.terms.iter().filter(|t| {
1252 matches!(t, HamiltonianTerm::TwoPauli { pauli1, pauli2, .. } if pauli1 == "Z" && pauli2 == "Z")
1253 }).count();
1254
1255 let num_x_terms = hamiltonian
1256 .terms
1257 .iter()
1258 .filter(|t| matches!(t, HamiltonianTerm::SinglePauli { pauli, .. } if pauli == "X"))
1259 .count();
1260
1261 assert_eq!(num_zz_terms, 2); assert_eq!(num_x_terms, 3); }
1264
1265 #[test]
1266 fn test_max_cut_hamiltonian() {
1267 let edges = vec![(0, 1), (1, 2), (2, 0)]; let weights = vec![1.0, 1.0, 1.0];
1269
1270 let hamiltonian = AdiabaticUtils::create_max_cut_hamiltonian(&edges, &weights);
1271 assert!(!hamiltonian.terms.is_empty());
1272 }
1273
1274 #[test]
1275 fn test_mixing_hamiltonian() {
1276 let hamiltonian = AdiabaticUtils::create_mixing_hamiltonian(2);
1277
1278 let num_x_terms = hamiltonian
1279 .terms
1280 .iter()
1281 .filter(|t| matches!(t, HamiltonianTerm::SinglePauli { pauli, .. } if pauli == "X"))
1282 .count();
1283
1284 assert_eq!(num_x_terms, 2); }
1286
1287 #[test]
1288 fn test_adiabatic_evolution() {
1289 let initial_h = AdiabaticUtils::create_mixing_hamiltonian(2);
1290 let final_h = AdiabaticUtils::create_tfim_hamiltonian(2, 1.0, 0.1);
1291
1292 let config = AdiabaticConfig {
1293 total_time: 1.0,
1294 time_steps: 10,
1295 initial_hamiltonian: initial_h,
1296 final_hamiltonian: final_h,
1297 gap_tracking: GapTrackingConfig {
1298 enabled: false, ..Default::default()
1300 },
1301 ..Default::default()
1302 };
1303
1304 let mut adiabatic_qc =
1305 AdiabaticQuantumComputer::new(config).expect("Failed to create adiabatic QC");
1306 let result = adiabatic_qc.evolve();
1307 assert!(result.is_ok());
1308
1309 let evolution_result = result.expect("Failed to evolve");
1310 assert_eq!(evolution_result.evolution_history.len(), 2); }
1312
1313 #[test]
1314 fn test_gap_tracking() {
1315 let initial_h = AdiabaticUtils::create_mixing_hamiltonian(2);
1316 let final_h = AdiabaticUtils::create_tfim_hamiltonian(2, 1.0, 0.1);
1317
1318 let config = AdiabaticConfig {
1319 total_time: 1.0,
1320 time_steps: 5,
1321 initial_hamiltonian: initial_h,
1322 final_hamiltonian: final_h,
1323 gap_tracking: GapTrackingConfig {
1324 enabled: true,
1325 ..Default::default()
1326 },
1327 ..Default::default()
1328 };
1329
1330 let mut adiabatic_qc =
1331 AdiabaticQuantumComputer::new(config).expect("Failed to create adiabatic QC");
1332 let result = adiabatic_qc.evolve();
1333 assert!(result.is_ok());
1334
1335 let evolution_result = result.expect("Failed to evolve");
1336 assert!(!evolution_result.gap_history.is_empty());
1337 assert!(evolution_result.min_gap >= 0.0);
1338 }
1339
1340 #[test]
1341 fn test_energy_calculation() {
1342 let initial_h = AdiabaticUtils::create_mixing_hamiltonian(1);
1343 let final_h = AdiabaticUtils::create_tfim_hamiltonian(1, 1.0, 0.1);
1344
1345 let config = AdiabaticConfig {
1346 initial_hamiltonian: initial_h,
1347 final_hamiltonian: final_h,
1348 ..Default::default()
1349 };
1350
1351 let adiabatic_qc =
1352 AdiabaticQuantumComputer::new(config).expect("Failed to create adiabatic QC");
1353 let energy = adiabatic_qc.calculate_current_energy();
1354 assert!(energy.is_ok());
1355 }
1356
1357 #[test]
1358 fn test_fidelity_calculation() {
1359 let config = AdiabaticConfig::default();
1360 let adiabatic_qc =
1361 AdiabaticQuantumComputer::new(config).expect("Failed to create adiabatic QC");
1362
1363 let state1 = Array1::from_vec(vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]);
1364 let state2 = Array1::from_vec(vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]);
1365
1366 let fidelity = adiabatic_qc.calculate_fidelity(&state1, &state2);
1367 assert_abs_diff_eq!(fidelity, 1.0, epsilon = 1e-10);
1368 }
1369}