quantrs2_sim/
adiabatic_quantum_computing.rs

1//! Adiabatic quantum computing simulation with gap tracking and optimization.
2//!
3//! This module implements adiabatic quantum computation (AQC), a model of quantum
4//! computation that uses the adiabatic theorem to solve optimization problems.
5//! The system starts in the ground state of a simple Hamiltonian and slowly
6//! evolves to a final Hamiltonian whose ground state encodes the solution.
7
8use 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/// Adiabatic quantum computing configuration
17#[derive(Debug, Clone)]
18pub struct AdiabaticConfig {
19    /// Total evolution time
20    pub total_time: f64,
21    /// Number of time steps
22    pub time_steps: usize,
23    /// Adiabatic schedule function type
24    pub schedule_type: ScheduleType,
25    /// Initial Hamiltonian
26    pub initial_hamiltonian: Hamiltonian,
27    /// Final Hamiltonian (problem Hamiltonian)
28    pub final_hamiltonian: Hamiltonian,
29    /// Gap tracking configuration
30    pub gap_tracking: GapTrackingConfig,
31    /// Energy convergence tolerance
32    pub energy_tolerance: f64,
33    /// Maximum iterations for eigenvalue solving
34    pub max_iterations: usize,
35    /// Enable adaptive time stepping
36    pub adaptive_stepping: bool,
37    /// Diabatic transition monitoring
38    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), // Default to 1 qubit
48            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/// Adiabatic schedule types
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum ScheduleType {
61    /// Linear interpolation s(t) = t/T
62    Linear,
63    /// Quadratic schedule s(t) = (t/T)²
64    Quadratic,
65    /// Cubic schedule s(t) = (t/T)³
66    Cubic,
67    /// Exponential schedule
68    Exponential,
69    /// Optimal schedule based on gap
70    Optimal,
71    /// Custom polynomial schedule
72    Polynomial(u32),
73    /// Landau-Zener schedule
74    LandauZener,
75}
76
77/// Gap tracking configuration
78#[derive(Debug, Clone)]
79pub struct GapTrackingConfig {
80    /// Enable gap tracking
81    pub enabled: bool,
82    /// Minimum gap threshold for adiabatic condition
83    pub min_gap_threshold: f64,
84    /// Number of eigenvalues to track
85    pub num_eigenvalues: usize,
86    /// Gap smoothing window size
87    pub smoothing_window: usize,
88    /// Enable diabatic transition detection
89    pub detect_diabatic_transitions: bool,
90    /// Gap prediction lookahead steps
91    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
107/// Adiabatic quantum computer simulator
108pub struct AdiabaticQuantumComputer {
109    /// Configuration
110    config: AdiabaticConfig,
111    /// Current quantum state
112    state: Array1<Complex64>,
113    /// Current time parameter
114    current_time: f64,
115    /// Evolution history
116    evolution_history: Vec<AdiabaticSnapshot>,
117    /// Gap tracking data
118    gap_history: Vec<GapMeasurement>,
119    /// `SciRS2` backend for optimization
120    backend: Option<SciRS2Backend>,
121    /// Statistics
122    stats: AdiabaticStats,
123}
124
125/// Snapshot of adiabatic evolution
126#[derive(Debug, Clone)]
127pub struct AdiabaticSnapshot {
128    /// Time parameter t
129    pub time: f64,
130    /// Schedule parameter s(t)
131    pub schedule_parameter: f64,
132    /// Current quantum state
133    pub state: Array1<Complex64>,
134    /// Current energy
135    pub energy: f64,
136    /// Energy gap
137    pub gap: Option<f64>,
138    /// Instantaneous ground state
139    pub instantaneous_ground_state: Option<Array1<Complex64>>,
140    /// Fidelity with instantaneous ground state
141    pub ground_state_fidelity: Option<f64>,
142    /// Adiabatic parameter (gap²T/ℏ)
143    pub adiabatic_parameter: Option<f64>,
144}
145
146/// Gap measurement data
147#[derive(Debug, Clone)]
148pub struct GapMeasurement {
149    /// Time
150    pub time: f64,
151    /// Schedule parameter
152    pub schedule_parameter: f64,
153    /// Energy gap
154    pub gap: f64,
155    /// Ground state energy
156    pub ground_energy: f64,
157    /// First excited state energy
158    pub first_excited_energy: f64,
159    /// Gap derivative (dΔ/dt)
160    pub gap_derivative: Option<f64>,
161    /// Predicted minimum gap
162    pub predicted_min_gap: Option<f64>,
163}
164
165/// Adiabatic simulation statistics
166#[derive(Debug, Clone, Default, Serialize, Deserialize)]
167pub struct AdiabaticStats {
168    /// Total evolution time
169    pub total_evolution_time_ms: f64,
170    /// Number of time steps completed
171    pub steps_completed: usize,
172    /// Number of eigenvalue computations
173    pub eigenvalue_computations: usize,
174    /// Average eigenvalue computation time
175    pub avg_eigenvalue_time_ms: f64,
176    /// Minimum gap encountered
177    pub min_gap: f64,
178    /// Maximum gap encountered
179    pub max_gap: f64,
180    /// Average gap
181    pub avg_gap: f64,
182    /// Number of diabatic transitions detected
183    pub diabatic_transitions: usize,
184    /// Final ground state fidelity
185    pub final_ground_state_fidelity: f64,
186    /// Success probability (for optimization problems)
187    pub success_probability: f64,
188}
189
190impl AdiabaticQuantumComputer {
191    /// Create new adiabatic quantum computer
192    pub fn new(config: AdiabaticConfig) -> Result<Self> {
193        // Initialize state in ground state of initial Hamiltonian
194        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); // Start with |0...0⟩
199
200        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    /// Initialize with `SciRS2` backend
212    pub fn with_backend(mut self) -> Result<Self> {
213        self.backend = Some(SciRS2Backend::new());
214        Ok(self)
215    }
216
217    /// Set initial state to ground state of initial Hamiltonian
218    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        // Find ground state (lowest eigenvalue)
223        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        // Set state to ground state
230        self.state = eigenvectors.column(ground_idx).to_owned();
231
232        Ok(())
233    }
234
235    /// Run adiabatic evolution
236    pub fn evolve(&mut self) -> Result<AdiabaticResult> {
237        let start_time = std::time::Instant::now();
238
239        // Initialize in ground state
240        self.initialize_ground_state()?;
241
242        // Take initial snapshot
243        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            // Adaptive time stepping based on gap
255            let actual_dt = if self.config.adaptive_stepping {
256                self.calculate_adaptive_timestep(t, dt)?
257            } else {
258                dt
259            };
260
261            // Build interpolated Hamiltonian
262            let hamiltonian = self.interpolate_hamiltonian(s)?;
263
264            // Evolve state
265            self.evolve_step(&hamiltonian, actual_dt)?;
266
267            // Track gap if enabled
268            if self.config.gap_tracking.enabled {
269                let gap_measurement = self.measure_gap(t, s, &hamiltonian)?;
270
271                // Check for diabatic transitions
272                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            // Take snapshot
280            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        // Compute final statistics
296        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    /// Schedule function s(t)
313    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                // Landau-Zener formula: optimized for avoiding diabatic transitions
324                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                // Would implement optimal control based on gap
332                s // Fallback to linear for now
333            }
334        }
335    }
336
337    /// Interpolate between initial and final Hamiltonians
338    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        // H(s) = (1-s) * H_initial + s * H_final
347        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    /// Evolve system for one time step
443    fn evolve_step(&mut self, hamiltonian: &Hamiltonian, dt: f64) -> Result<()> {
444        // Build Hamiltonian matrix
445        let h_matrix = self.build_hamiltonian_matrix(hamiltonian)?;
446
447        // Compute evolution operator U = exp(-i H dt / ℏ)
448        let evolution_operator = self.compute_evolution_operator(&h_matrix, dt)?;
449
450        // Apply evolution operator to state
451        self.state = evolution_operator.dot(&self.state);
452
453        // Renormalize (to handle numerical errors)
454        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    /// Build Hamiltonian matrix from Hamiltonian terms
468    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    /// Build matrix for a single Hamiltonian term
482    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                // For now, return identity with the coefficient
554                Ok((Array2::eye(dim), *coefficient))
555            }
556        }
557    }
558
559    /// Get Pauli matrix for a given Pauli string
560    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    /// Apply single-qubit operator to full system matrix
600    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    /// Compute evolution operator exp(-i H dt / ℏ)
623    fn compute_evolution_operator(
624        &self,
625        hamiltonian: &Array2<Complex64>,
626        dt: f64,
627    ) -> Result<Array2<Complex64>> {
628        // For small systems, use matrix exponentiation
629        // For larger systems, would use Trotterization
630
631        let dim = hamiltonian.dim().0;
632        if dim <= 64 {
633            // Direct matrix exponentiation
634            self.matrix_exponential(hamiltonian, -Complex64::new(0.0, dt))
635        } else {
636            // Use Trotter decomposition for larger systems
637            self.trotter_evolution(hamiltonian, dt)
638        }
639    }
640
641    /// Direct matrix exponentiation (for small systems)
642    fn matrix_exponential(
643        &self,
644        matrix: &Array2<Complex64>,
645        factor: Complex64,
646    ) -> Result<Array2<Complex64>> {
647        let dim = matrix.dim().0;
648
649        // Scale matrix
650        let scaled_matrix = matrix.mapv(|x| x * factor);
651
652        // Use series expansion: exp(A) = I + A + A²/2! + A³/3! + ...
653        let mut result = Array2::eye(dim);
654        let mut term = Array2::eye(dim);
655
656        for n in 1..=20 {
657            // Limit iterations for convergence
658            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            // Check convergence
668            if term_norm < 1e-15 {
669                break;
670            }
671        }
672
673        Ok(result)
674    }
675
676    /// Trotter evolution for large systems
677    fn trotter_evolution(
678        &self,
679        hamiltonian: &Array2<Complex64>,
680        dt: f64,
681    ) -> Result<Array2<Complex64>> {
682        // Placeholder: would implement proper Trotter decomposition
683        self.matrix_exponential(hamiltonian, -Complex64::new(0.0, dt))
684    }
685
686    /// Measure energy gap
687    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        // Sort eigenvalues
694        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        // Update statistics
702        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    /// Estimate gap derivative
732    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    /// Predict minimum gap using extrapolation
750    fn predict_minimum_gap(&self) -> Option<f64> {
751        if self.gap_history.len() < 3 {
752            return None;
753        }
754
755        // Simple quadratic extrapolation
756        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        // Fit quadratic and find minimum
761        // For simplicity, just return the minimum of recent measurements
762        recent_gaps.into_iter().fold(f64::INFINITY, f64::min).into()
763    }
764
765    /// Check for diabatic transitions
766    fn check_diabatic_transition(&mut self, gap_measurement: &GapMeasurement) -> Result<()> {
767        // Landau-Zener criterion: P_diabatic ≈ exp(-2π Δ²/(ℏ |dH/dt|))
768        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            // Estimate diabatic transition probability
773            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            // Threshold for detecting diabatic transition
780            if diabatic_prob > 0.01 {
781                // 1% threshold
782                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    /// Calculate adaptive time step based on gap
794    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        // Smaller time steps when gap is small
806        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); // Clamp between 0.1 and 2.0 times default
808
809        Ok(adaptive_dt)
810    }
811
812    /// Take snapshot of current state
813    fn take_snapshot(&self, t: f64) -> Result<AdiabaticSnapshot> {
814        let s = self.schedule_function(t);
815        let energy = self.calculate_current_energy()?;
816
817        // Get current gap
818        let gap = self.gap_history.last().map(|g| g.gap);
819
820        // Calculate instantaneous ground state if gap tracking is enabled
821        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                // Find ground state
828                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                // Calculate fidelity
837                let fidelity = self.calculate_fidelity(&self.state, &ground_state);
838
839                // Calculate adiabatic parameter
840                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    /// Calculate current energy expectation value
860    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        // E = ⟨ψ|H|ψ⟩
866        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    /// Calculate fidelity between two states
878    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    /// Compute eigendecomposition
889    fn compute_eigendecomposition(
890        &self,
891        matrix: &Array2<Complex64>,
892    ) -> Result<(Vec<f64>, Array2<Complex64>)> {
893        // Simplified eigenvalue computation
894        // In practice, would use LAPACK or similar high-performance library
895
896        let dim = matrix.dim().0;
897        if dim > 16 {
898            // For large matrices, use iterative methods or approximations
899            return self.compute_approximate_eigenvalues(matrix);
900        }
901
902        // For small matrices, use simple power iteration for dominant eigenvalue
903        let mut eigenvalues = Vec::new();
904        let mut eigenvectors = Array2::eye(dim);
905
906        // Simplified: just compute diagonal elements as eigenvalue approximation
907        for i in 0..dim {
908            eigenvalues.push(matrix[[i, i]].re);
909        }
910
911        // Sort eigenvalues
912        eigenvalues.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
913
914        Ok((eigenvalues, eigenvectors))
915    }
916
917    /// Compute approximate eigenvalues for large matrices
918    fn compute_approximate_eigenvalues(
919        &self,
920        matrix: &Array2<Complex64>,
921    ) -> Result<(Vec<f64>, Array2<Complex64>)> {
922        let dim = matrix.dim().0;
923
924        // Use Lanczos algorithm or similar for large sparse matrices
925        // For now, just return diagonal approximation
926        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    /// Compute final statistics
938    fn compute_final_statistics(&mut self) -> Result<()> {
939        // Calculate final ground state fidelity
940        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        // For optimization problems, estimate success probability
947        // This would depend on the specific problem encoding
948        self.stats.success_probability = self.stats.final_ground_state_fidelity;
949
950        Ok(())
951    }
952
953    /// Get current state
954    #[must_use]
955    pub const fn get_state(&self) -> &Array1<Complex64> {
956        &self.state
957    }
958
959    /// Get evolution history
960    #[must_use]
961    pub fn get_evolution_history(&self) -> &[AdiabaticSnapshot] {
962        &self.evolution_history
963    }
964
965    /// Get gap history
966    #[must_use]
967    pub fn get_gap_history(&self) -> &[GapMeasurement] {
968        &self.gap_history
969    }
970
971    /// Get statistics
972    #[must_use]
973    pub const fn get_stats(&self) -> &AdiabaticStats {
974        &self.stats
975    }
976
977    /// Reset the simulator
978    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/// Adiabatic evolution result
994#[derive(Debug, Clone)]
995pub struct AdiabaticResult {
996    /// Final quantum state
997    pub final_state: Array1<Complex64>,
998    /// Complete evolution history
999    pub evolution_history: Vec<AdiabaticSnapshot>,
1000    /// Gap tracking history
1001    pub gap_history: Vec<GapMeasurement>,
1002    /// Total evolution time in milliseconds
1003    pub total_time_ms: f64,
1004    /// Success probability
1005    pub success_probability: f64,
1006    /// Minimum gap encountered
1007    pub min_gap: f64,
1008    /// Final energy
1009    pub final_energy: f64,
1010}
1011
1012/// Adiabatic quantum computing utilities
1013pub struct AdiabaticUtils;
1014
1015impl AdiabaticUtils {
1016    /// Create Max-Cut problem Hamiltonian
1017    #[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            // Add term: w_ij * (I - Z_i Z_j) / 2
1034            // For Max-Cut, we want to maximize the number of edges cut
1035            // This corresponds to minimizing -weight/2 * (1 - Z_i Z_j) = -weight/2 + weight/2 * Z_i Z_j
1036            // So we add the ZZ interaction term with positive coefficient
1037            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    /// Create 3-SAT problem Hamiltonian
1046    #[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; // Skip non-3-SAT clauses
1060            }
1061
1062            // For clause (x_i ∨ x_j ∨ x_k), add penalty when all literals are false
1063            // This is a simplified implementation - in practice would need more sophisticated encoding
1064
1065            // Add penalty for clause being unsatisfied
1066            // For now, add pairwise interactions between variables in the clause
1067            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                    // Add weak coupling between variables in the same clause
1073                    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    /// Create transverse field Ising model (TFIM) Hamiltonian
1088    #[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        // ZZ coupling terms
1097        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        // X field terms
1107        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    /// Create mixing Hamiltonian (typically all X)
1117    #[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    /// Benchmark adiabatic quantum computing
1131    pub fn benchmark_adiabatic_qc() -> Result<AdiabaticBenchmarkResults> {
1132        let mut results = AdiabaticBenchmarkResults::default();
1133
1134        // Test different problem sizes and schedules
1135        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                // Create simple TFIM problem
1145                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/// Adiabatic benchmark results
1182#[derive(Debug, Clone, Default)]
1183pub struct AdiabaticBenchmarkResults {
1184    /// Execution times by configuration
1185    pub execution_times: Vec<(String, f64)>,
1186    /// Success probabilities by configuration
1187    pub success_probabilities: Vec<(String, f64)>,
1188    /// Minimum gaps by configuration
1189    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); // Should have both X and Z terms
1244    }
1245
1246    #[test]
1247    fn test_tfim_hamiltonian() {
1248        let hamiltonian = AdiabaticUtils::create_tfim_hamiltonian(3, 1.0, 0.5);
1249
1250        // Should have ZZ coupling terms and X field terms
1251        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); // 2 ZZ coupling terms for 3 qubits
1262        assert_eq!(num_x_terms, 3); // 3 X field terms
1263    }
1264
1265    #[test]
1266    fn test_max_cut_hamiltonian() {
1267        let edges = vec![(0, 1), (1, 2), (2, 0)]; // Triangle graph
1268        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); // Should have X on both qubits
1285    }
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, // Disable for simple test
1299                ..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); // Initial + final snapshots
1311    }
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}