quantrs2_anneal/
quantum_walk.rs

1//! Quantum walk-based optimization algorithms
2//!
3//! This module implements quantum walk algorithms for solving optimization
4//! problems, providing an alternative approach to traditional annealing
5//! that leverages quantum interference effects.
6
7use scirs2_core::random::prelude::*;
8use scirs2_core::random::ChaCha8Rng;
9use scirs2_core::random::{Rng, SeedableRng};
10use scirs2_core::Complex64;
11use std::collections::HashMap;
12use std::time::{Duration, Instant};
13use thiserror::Error;
14
15use crate::ising::{IsingError, IsingModel};
16use crate::simulator::AnnealingSolution;
17
18/// Errors that can occur during quantum walk optimization
19#[derive(Error, Debug)]
20pub enum QuantumWalkError {
21    /// Ising model error
22    #[error("Ising error: {0}")]
23    IsingError(#[from] IsingError),
24
25    /// Invalid parameter
26    #[error("Invalid parameter: {0}")]
27    InvalidParameter(String),
28
29    /// Evolution error
30    #[error("Evolution error: {0}")]
31    EvolutionError(String),
32
33    /// Measurement error
34    #[error("Measurement error: {0}")]
35    MeasurementError(String),
36}
37
38/// Result type for quantum walk operations
39pub type QuantumWalkResult<T> = Result<T, QuantumWalkError>;
40
41/// Quantum walk algorithm type
42#[derive(Debug, Clone)]
43pub enum QuantumWalkAlgorithm {
44    /// Continuous-time quantum walk (CTQW)
45    ContinuousTime {
46        evolution_time: f64,
47        time_steps: usize,
48    },
49
50    /// Discrete-time quantum walk (DTQW)
51    DiscreteTime {
52        coin_operator: CoinOperator,
53        steps: usize,
54    },
55
56    /// Adiabatic quantum walk optimization (AQWO)
57    Adiabatic {
58        initial_hamiltonian: AdiabaticHamiltonian,
59        final_hamiltonian: AdiabaticHamiltonian,
60        evolution_time: f64,
61        time_steps: usize,
62    },
63
64    /// Quantum approximate optimization algorithm (QAOA) with walk
65    QaoaWalk {
66        layers: usize,
67        beta_schedule: Vec<f64>,
68        gamma_schedule: Vec<f64>,
69    },
70}
71
72/// Coin operator for discrete-time quantum walks
73#[derive(Debug, Clone)]
74pub enum CoinOperator {
75    /// Hadamard coin
76    Hadamard,
77
78    /// Grover coin
79    Grover,
80
81    /// Custom unitary coin
82    Custom(Vec<Vec<Complex64>>),
83}
84
85/// Hamiltonian specification for adiabatic evolution
86#[derive(Debug, Clone)]
87pub enum AdiabaticHamiltonian {
88    /// Mixing Hamiltonian (typically sum of X operators)
89    Mixing,
90
91    /// Problem Hamiltonian (from Ising model)
92    Problem,
93
94    /// Custom Hamiltonian
95    Custom(Vec<Vec<Complex64>>),
96}
97
98/// Configuration for quantum walk optimization
99#[derive(Debug, Clone)]
100pub struct QuantumWalkConfig {
101    /// Algorithm to use
102    pub algorithm: QuantumWalkAlgorithm,
103
104    /// Number of measurements/samples
105    pub num_measurements: usize,
106
107    /// Random seed
108    pub seed: Option<u64>,
109
110    /// Maximum evolution time
111    pub max_evolution_time: f64,
112
113    /// Convergence tolerance
114    pub convergence_tolerance: f64,
115
116    /// Enable amplitude amplification
117    pub amplitude_amplification: bool,
118
119    /// Number of amplitude amplification iterations
120    pub amplification_iterations: usize,
121
122    /// Timeout for optimization
123    pub timeout: Option<Duration>,
124}
125
126impl Default for QuantumWalkConfig {
127    fn default() -> Self {
128        Self {
129            algorithm: QuantumWalkAlgorithm::ContinuousTime {
130                evolution_time: 1.0,
131                time_steps: 100,
132            },
133            num_measurements: 1000,
134            seed: None,
135            max_evolution_time: 10.0,
136            convergence_tolerance: 1e-6,
137            amplitude_amplification: false,
138            amplification_iterations: 3,
139            timeout: Some(Duration::from_secs(300)),
140        }
141    }
142}
143
144/// Quantum state representation
145#[derive(Debug, Clone)]
146pub struct QuantumState {
147    /// Amplitudes for each computational basis state
148    pub amplitudes: Vec<Complex64>,
149
150    /// Number of qubits
151    pub num_qubits: usize,
152}
153
154impl QuantumState {
155    /// Create a new quantum state
156    #[must_use]
157    pub fn new(num_qubits: usize) -> Self {
158        let num_states = 2_usize.pow(num_qubits as u32);
159        let mut amplitudes = vec![Complex64::new(0.0, 0.0); num_states];
160
161        // Initialize to |0...0⟩ state
162        amplitudes[0] = Complex64::new(1.0, 0.0);
163
164        Self {
165            amplitudes,
166            num_qubits,
167        }
168    }
169
170    /// Create uniform superposition state
171    #[must_use]
172    pub fn uniform_superposition(num_qubits: usize) -> Self {
173        let num_states = 2_usize.pow(num_qubits as u32);
174        let amplitude = Complex64::new(1.0 / (num_states as f64).sqrt(), 0.0);
175        let amplitudes = vec![amplitude; num_states];
176
177        Self {
178            amplitudes,
179            num_qubits,
180        }
181    }
182
183    /// Normalize the quantum state
184    pub fn normalize(&mut self) {
185        let norm_squared: f64 = self
186            .amplitudes
187            .iter()
188            .map(scirs2_core::Complex::norm_sqr)
189            .sum();
190
191        if norm_squared > 0.0 {
192            let norm = norm_squared.sqrt();
193            for amp in &mut self.amplitudes {
194                *amp /= norm;
195            }
196        }
197    }
198
199    /// Get probability of measuring state i
200    #[must_use]
201    pub fn probability(&self, state_index: usize) -> f64 {
202        if state_index < self.amplitudes.len() {
203            self.amplitudes[state_index].norm_sqr()
204        } else {
205            0.0
206        }
207    }
208
209    /// Convert state index to bit string
210    #[must_use]
211    pub fn state_to_bits(&self, state_index: usize) -> Vec<i8> {
212        let mut bits = vec![0; self.num_qubits];
213        let mut index = state_index;
214
215        for i in 0..self.num_qubits {
216            bits[self.num_qubits - 1 - i] = if (index & 1) == 1 { 1 } else { -1 };
217            index >>= 1;
218        }
219
220        bits
221    }
222
223    /// Convert bit string to state index
224    #[must_use]
225    pub fn bits_to_state(&self, bits: &[i8]) -> usize {
226        let mut index = 0;
227        for (i, &bit) in bits.iter().enumerate() {
228            if bit > 0 {
229                index |= 1 << (self.num_qubits - 1 - i);
230            }
231        }
232        index
233    }
234}
235
236/// Quantum walk optimizer
237pub struct QuantumWalkOptimizer {
238    /// Configuration
239    config: QuantumWalkConfig,
240
241    /// Random number generator
242    rng: ChaCha8Rng,
243}
244
245impl QuantumWalkOptimizer {
246    /// Create a new quantum walk optimizer
247    #[must_use]
248    pub fn new(config: QuantumWalkConfig) -> Self {
249        let rng = match config.seed {
250            Some(seed) => ChaCha8Rng::seed_from_u64(seed),
251            None => ChaCha8Rng::seed_from_u64(thread_rng().gen()),
252        };
253
254        Self { config, rng }
255    }
256
257    /// Solve an Ising model using quantum walk optimization
258    pub fn solve(&mut self, model: &IsingModel) -> QuantumWalkResult<AnnealingSolution> {
259        let start_time = Instant::now();
260
261        // Initialize quantum state
262        let mut state = QuantumState::uniform_superposition(model.num_qubits);
263
264        // Evolve according to algorithm
265        let algorithm = self.config.algorithm.clone();
266        match algorithm {
267            QuantumWalkAlgorithm::ContinuousTime {
268                evolution_time,
269                time_steps,
270            } => {
271                self.continuous_time_evolution(model, &mut state, evolution_time, time_steps)?;
272            }
273
274            QuantumWalkAlgorithm::DiscreteTime {
275                coin_operator,
276                steps,
277            } => {
278                self.discrete_time_evolution(model, &mut state, &coin_operator, steps)?;
279            }
280
281            QuantumWalkAlgorithm::Adiabatic {
282                initial_hamiltonian,
283                final_hamiltonian,
284                evolution_time,
285                time_steps,
286            } => {
287                self.adiabatic_evolution(
288                    model,
289                    &mut state,
290                    &initial_hamiltonian,
291                    &final_hamiltonian,
292                    evolution_time,
293                    time_steps,
294                )?;
295            }
296
297            QuantumWalkAlgorithm::QaoaWalk {
298                layers,
299                beta_schedule,
300                gamma_schedule,
301            } => {
302                self.qaoa_walk_evolution(
303                    model,
304                    &mut state,
305                    layers,
306                    &beta_schedule,
307                    &gamma_schedule,
308                )?;
309            }
310        }
311
312        // Apply amplitude amplification if enabled
313        if self.config.amplitude_amplification {
314            self.amplitude_amplification(model, &mut state)?;
315        }
316
317        // Perform measurements and find best solution
318        let (best_spins, best_energy) = self.measure_and_optimize(model, &state)?;
319
320        let runtime = start_time.elapsed();
321
322        Ok(AnnealingSolution {
323            best_spins,
324            best_energy,
325            repetitions: 1,
326            total_sweeps: self.config.num_measurements,
327            runtime,
328            info: format!(
329                "Quantum walk optimization with {:?} in {:?}",
330                self.config.algorithm, runtime
331            ),
332        })
333    }
334
335    /// Continuous-time quantum walk evolution
336    fn continuous_time_evolution(
337        &mut self,
338        model: &IsingModel,
339        state: &mut QuantumState,
340        evolution_time: f64,
341        time_steps: usize,
342    ) -> QuantumWalkResult<()> {
343        let dt = evolution_time / time_steps as f64;
344
345        // Build Hamiltonian matrix
346        let hamiltonian = self.build_problem_hamiltonian(model)?;
347
348        // Time evolution using Trotter decomposition
349        for _ in 0..time_steps {
350            self.apply_hamiltonian_evolution(state, &hamiltonian, dt)?;
351
352            // Add some decoherence/noise
353            self.apply_decoherence(state, dt * 0.01);
354        }
355
356        Ok(())
357    }
358
359    /// Discrete-time quantum walk evolution
360    fn discrete_time_evolution(
361        &self,
362        model: &IsingModel,
363        state: &mut QuantumState,
364        coin_operator: &CoinOperator,
365        steps: usize,
366    ) -> QuantumWalkResult<()> {
367        for _ in 0..steps {
368            // Apply coin operator
369            self.apply_coin_operator(state, coin_operator)?;
370
371            // Apply shift operator based on problem structure
372            self.apply_shift_operator(state, model)?;
373
374            // Apply conditional phase based on energy
375            self.apply_conditional_phase(state, model)?;
376        }
377
378        Ok(())
379    }
380
381    /// Adiabatic quantum walk evolution
382    fn adiabatic_evolution(
383        &self,
384        model: &IsingModel,
385        state: &mut QuantumState,
386        initial_ham: &AdiabaticHamiltonian,
387        final_ham: &AdiabaticHamiltonian,
388        evolution_time: f64,
389        time_steps: usize,
390    ) -> QuantumWalkResult<()> {
391        let dt = evolution_time / time_steps as f64;
392
393        let initial_hamiltonian = self.build_hamiltonian(model, initial_ham)?;
394        let final_hamiltonian = self.build_hamiltonian(model, final_ham)?;
395
396        for step in 0..time_steps {
397            let s = step as f64 / time_steps as f64;
398
399            // Interpolate Hamiltonian: H(s) = (1-s)H_initial + s*H_final
400            let mut current_hamiltonian = Vec::new();
401            for i in 0..initial_hamiltonian.len() {
402                let mut row = Vec::new();
403                for j in 0..initial_hamiltonian[i].len() {
404                    let interpolated =
405                        (1.0 - s) * initial_hamiltonian[i][j] + s * final_hamiltonian[i][j];
406                    row.push(interpolated);
407                }
408                current_hamiltonian.push(row);
409            }
410
411            // Evolve for time dt
412            self.apply_hamiltonian_evolution(state, &current_hamiltonian, dt)?;
413        }
414
415        Ok(())
416    }
417
418    /// QAOA with quantum walk evolution
419    fn qaoa_walk_evolution(
420        &self,
421        model: &IsingModel,
422        state: &mut QuantumState,
423        layers: usize,
424        beta_schedule: &[f64],
425        gamma_schedule: &[f64],
426    ) -> QuantumWalkResult<()> {
427        let problem_hamiltonian = self.build_problem_hamiltonian(model)?;
428        let mixing_hamiltonian = self.build_mixing_hamiltonian(model.num_qubits)?;
429
430        for layer in 0..layers {
431            let gamma = if layer < gamma_schedule.len() {
432                gamma_schedule[layer]
433            } else {
434                gamma_schedule.last().copied().unwrap_or(0.5)
435            };
436
437            let beta = if layer < beta_schedule.len() {
438                beta_schedule[layer]
439            } else {
440                beta_schedule.last().copied().unwrap_or(0.5)
441            };
442
443            // Apply problem Hamiltonian evolution
444            self.apply_hamiltonian_evolution(state, &problem_hamiltonian, gamma)?;
445
446            // Apply mixing Hamiltonian evolution
447            self.apply_hamiltonian_evolution(state, &mixing_hamiltonian, beta)?;
448        }
449
450        Ok(())
451    }
452
453    /// Build problem Hamiltonian from Ising model
454    fn build_problem_hamiltonian(
455        &self,
456        model: &IsingModel,
457    ) -> QuantumWalkResult<Vec<Vec<Complex64>>> {
458        let num_states = 2_usize.pow(model.num_qubits as u32);
459        let mut hamiltonian = vec![vec![Complex64::new(0.0, 0.0); num_states]; num_states];
460
461        // Add diagonal terms (Z operators for biases and ZZ for couplings)
462        for state_idx in 0..num_states {
463            let mut energy = 0.0;
464            let bits = self.index_to_bits(state_idx, model.num_qubits);
465
466            // Add bias terms
467            for (qubit, bias) in &model.biases() {
468                energy += bias * f64::from(bits[*qubit]);
469            }
470
471            // Add coupling terms
472            for coupling in model.couplings() {
473                energy +=
474                    coupling.strength * f64::from(bits[coupling.i]) * f64::from(bits[coupling.j]);
475            }
476
477            hamiltonian[state_idx][state_idx] = Complex64::new(energy, 0.0);
478        }
479
480        Ok(hamiltonian)
481    }
482
483    /// Build mixing Hamiltonian (sum of X operators)
484    fn build_mixing_hamiltonian(
485        &self,
486        num_qubits: usize,
487    ) -> QuantumWalkResult<Vec<Vec<Complex64>>> {
488        let num_states = 2_usize.pow(num_qubits as u32);
489        let mut hamiltonian = vec![vec![Complex64::new(0.0, 0.0); num_states]; num_states];
490
491        // Add X operators for each qubit
492        for qubit in 0..num_qubits {
493            for state_idx in 0..num_states {
494                let flipped_idx = state_idx ^ (1 << (num_qubits - 1 - qubit));
495                hamiltonian[state_idx][flipped_idx] += Complex64::new(1.0, 0.0);
496            }
497        }
498
499        Ok(hamiltonian)
500    }
501
502    /// Build Hamiltonian from specification
503    fn build_hamiltonian(
504        &self,
505        model: &IsingModel,
506        ham_type: &AdiabaticHamiltonian,
507    ) -> QuantumWalkResult<Vec<Vec<Complex64>>> {
508        match ham_type {
509            AdiabaticHamiltonian::Mixing => self.build_mixing_hamiltonian(model.num_qubits),
510            AdiabaticHamiltonian::Problem => self.build_problem_hamiltonian(model),
511            AdiabaticHamiltonian::Custom(matrix) => Ok(matrix.clone()),
512        }
513    }
514
515    /// Apply Hamiltonian evolution for time dt
516    fn apply_hamiltonian_evolution(
517        &self,
518        state: &mut QuantumState,
519        hamiltonian: &[Vec<Complex64>],
520        dt: f64,
521    ) -> QuantumWalkResult<()> {
522        let num_states = state.amplitudes.len();
523        let mut new_amplitudes = vec![Complex64::new(0.0, 0.0); num_states];
524
525        // Simple first-order evolution: |ψ(t+dt)⟩ = exp(-i*H*dt)|ψ(t)⟩
526        // Approximated as: |ψ(t+dt)⟩ ≈ (I - i*H*dt)|ψ(t)⟩
527        for i in 0..num_states {
528            new_amplitudes[i] = state.amplitudes[i]; // Identity term
529
530            for j in 0..num_states {
531                let evolution_term = -Complex64::i() * hamiltonian[i][j] * dt;
532                new_amplitudes[i] += evolution_term * state.amplitudes[j];
533            }
534        }
535
536        state.amplitudes = new_amplitudes;
537        state.normalize();
538
539        Ok(())
540    }
541
542    /// Apply coin operator to the state
543    fn apply_coin_operator(
544        &self,
545        state: &mut QuantumState,
546        coin: &CoinOperator,
547    ) -> QuantumWalkResult<()> {
548        match coin {
549            CoinOperator::Hadamard => {
550                // Apply Hadamard to each qubit
551                for qubit in 0..state.num_qubits {
552                    self.apply_single_qubit_gate(
553                        state,
554                        qubit,
555                        &[[1.0, 1.0], [1.0, -1.0]],
556                        1.0 / 2.0_f64.sqrt(),
557                    )?;
558                }
559            }
560
561            CoinOperator::Grover => {
562                // Apply Grover coin (more complex, simplified here)
563                for qubit in 0..state.num_qubits {
564                    self.apply_single_qubit_gate(state, qubit, &[[0.0, 1.0], [1.0, 0.0]], 1.0)?;
565                }
566            }
567
568            CoinOperator::Custom(_matrix) => {
569                // Custom coin operator implementation would go here
570                return Err(QuantumWalkError::EvolutionError(
571                    "Custom coin operators not yet implemented".to_string(),
572                ));
573            }
574        }
575
576        Ok(())
577    }
578
579    /// Apply single-qubit gate
580    fn apply_single_qubit_gate(
581        &self,
582        state: &mut QuantumState,
583        qubit: usize,
584        gate: &[[f64; 2]; 2],
585        normalization: f64,
586    ) -> QuantumWalkResult<()> {
587        let num_states = state.amplitudes.len();
588        let mut new_amplitudes = vec![Complex64::new(0.0, 0.0); num_states];
589
590        for state_idx in 0..num_states {
591            let bit = (state_idx >> (state.num_qubits - 1 - qubit)) & 1;
592            let flipped_idx = state_idx ^ (1 << (state.num_qubits - 1 - qubit));
593
594            // Apply gate matrix
595            new_amplitudes[state_idx] +=
596                Complex64::new(gate[bit][bit] * normalization, 0.0) * state.amplitudes[state_idx];
597            new_amplitudes[flipped_idx] += Complex64::new(gate[1 - bit][bit] * normalization, 0.0)
598                * state.amplitudes[state_idx];
599        }
600
601        state.amplitudes = new_amplitudes;
602        state.normalize();
603
604        Ok(())
605    }
606
607    /// Apply shift operator based on problem graph
608    const fn apply_shift_operator(
609        &self,
610        _state: &mut QuantumState,
611        _model: &IsingModel,
612    ) -> QuantumWalkResult<()> {
613        // Simplified implementation - would normally implement graph-based shifts
614        Ok(())
615    }
616
617    /// Apply conditional phase based on energy
618    fn apply_conditional_phase(
619        &self,
620        state: &mut QuantumState,
621        model: &IsingModel,
622    ) -> QuantumWalkResult<()> {
623        for (state_idx, amplitude) in state.amplitudes.iter_mut().enumerate() {
624            let bits = self.index_to_bits(state_idx, model.num_qubits);
625            let energy = model.energy(&bits).map_err(QuantumWalkError::IsingError)?;
626
627            // Apply phase proportional to energy
628            let phase = -energy * 0.1; // Small phase to avoid numerical issues
629            *amplitude *= Complex64::new(phase.cos(), phase.sin());
630        }
631
632        Ok(())
633    }
634
635    /// Apply decoherence/noise
636    fn apply_decoherence(&mut self, state: &mut QuantumState, strength: f64) {
637        for amplitude in &mut state.amplitudes {
638            let noise_real = (self.rng.gen::<f64>() - 0.5) * strength;
639            let noise_imag = (self.rng.gen::<f64>() - 0.5) * strength;
640            *amplitude += Complex64::new(noise_real, noise_imag);
641        }
642
643        state.normalize();
644    }
645
646    /// Amplitude amplification to boost good solutions
647    fn amplitude_amplification(
648        &self,
649        model: &IsingModel,
650        state: &mut QuantumState,
651    ) -> QuantumWalkResult<()> {
652        // Find mean energy
653        let mut mean_energy = 0.0;
654        for (state_idx, amplitude) in state.amplitudes.iter().enumerate() {
655            let bits = self.index_to_bits(state_idx, model.num_qubits);
656            let energy = model.energy(&bits).map_err(QuantumWalkError::IsingError)?;
657            mean_energy += amplitude.norm_sqr() * energy;
658        }
659
660        for _ in 0..self.config.amplification_iterations {
661            // Reflect about good solutions (below mean energy)
662            for (state_idx, amplitude) in state.amplitudes.iter_mut().enumerate() {
663                let bits = self.index_to_bits(state_idx, model.num_qubits);
664                let energy = model.energy(&bits).map_err(QuantumWalkError::IsingError)?;
665
666                if energy < mean_energy {
667                    *amplitude *= -1.0; // Flip sign for good solutions
668                }
669            }
670
671            // Reflect about average (inversion about average)
672            let avg_amplitude: Complex64 =
673                state.amplitudes.iter().sum::<Complex64>() / state.amplitudes.len() as f64;
674            for amplitude in &mut state.amplitudes {
675                *amplitude = 2.0 * avg_amplitude - *amplitude;
676            }
677
678            state.normalize();
679        }
680
681        Ok(())
682    }
683
684    /// Measure the state and find the best solution
685    fn measure_and_optimize(
686        &mut self,
687        model: &IsingModel,
688        state: &QuantumState,
689    ) -> QuantumWalkResult<(Vec<i8>, f64)> {
690        let mut best_spins = vec![1; model.num_qubits];
691        let mut best_energy = f64::INFINITY;
692
693        // Sample from the quantum state
694        for _ in 0..self.config.num_measurements {
695            let measured_state = self.sample_state(state);
696            let spins = self.index_to_bits(measured_state, model.num_qubits);
697            let energy = model.energy(&spins).map_err(QuantumWalkError::IsingError)?;
698
699            if energy < best_energy {
700                best_energy = energy;
701                best_spins = spins;
702            }
703        }
704
705        Ok((best_spins, best_energy))
706    }
707
708    /// Sample a computational basis state from the quantum state
709    fn sample_state(&mut self, state: &QuantumState) -> usize {
710        let random_value = self.rng.gen::<f64>();
711        let mut cumulative_prob = 0.0;
712
713        for (state_idx, amplitude) in state.amplitudes.iter().enumerate() {
714            cumulative_prob += amplitude.norm_sqr();
715            if random_value <= cumulative_prob {
716                return state_idx;
717            }
718        }
719
720        // Fallback (shouldn't reach here with proper normalization)
721        state.amplitudes.len() - 1
722    }
723
724    /// Convert state index to bit string
725    fn index_to_bits(&self, state_index: usize, num_qubits: usize) -> Vec<i8> {
726        let mut bits = vec![0; num_qubits];
727        let mut index = state_index;
728
729        for i in 0..num_qubits {
730            bits[num_qubits - 1 - i] = if (index & 1) == 1 { 1 } else { -1 };
731            index >>= 1;
732        }
733
734        bits
735    }
736}
737
738#[cfg(test)]
739mod tests {
740    use super::*;
741
742    #[test]
743    fn test_quantum_state_creation() {
744        let state = QuantumState::new(2);
745        assert_eq!(state.num_qubits, 2);
746        assert_eq!(state.amplitudes.len(), 4);
747        assert_eq!(state.amplitudes[0], Complex64::new(1.0, 0.0));
748    }
749
750    #[test]
751    fn test_uniform_superposition() {
752        let state = QuantumState::uniform_superposition(2);
753        let expected_amplitude = Complex64::new(0.5, 0.0);
754
755        for amplitude in &state.amplitudes {
756            assert!((amplitude - expected_amplitude).norm() < 1e-10);
757        }
758    }
759
760    #[test]
761    fn test_state_normalization() {
762        let mut state = QuantumState::new(2);
763        state.amplitudes[0] = Complex64::new(2.0, 0.0);
764        state.amplitudes[1] = Complex64::new(1.0, 0.0);
765
766        state.normalize();
767
768        let norm_squared: f64 = state.amplitudes.iter().map(|amp| amp.norm_sqr()).sum();
769
770        assert!((norm_squared - 1.0).abs() < 1e-10);
771    }
772
773    #[test]
774    fn test_bits_conversion() {
775        let state = QuantumState::new(3);
776        let bits = state.state_to_bits(5); // 5 = 101 in binary
777        assert_eq!(bits, vec![1, -1, 1]);
778
779        let index = state.bits_to_state(&bits);
780        assert_eq!(index, 5);
781    }
782
783    #[test]
784    fn test_quantum_walk_config() {
785        let config = QuantumWalkConfig::default();
786        assert!(matches!(
787            config.algorithm,
788            QuantumWalkAlgorithm::ContinuousTime { .. }
789        ));
790        assert_eq!(config.num_measurements, 1000);
791    }
792
793    #[test]
794    fn test_simple_optimization() {
795        let mut model = IsingModel::new(2);
796        model
797            .set_coupling(0, 1, -1.0)
798            .expect("Failed to set coupling");
799
800        let config = QuantumWalkConfig {
801            algorithm: QuantumWalkAlgorithm::ContinuousTime {
802                evolution_time: 0.5,
803                time_steps: 50,
804            },
805            num_measurements: 100,
806            seed: Some(42),
807            ..Default::default()
808        };
809
810        let mut optimizer = QuantumWalkOptimizer::new(config);
811        let result = optimizer
812            .solve(&model)
813            .expect("Optimization should succeed");
814
815        assert_eq!(result.best_spins.len(), 2);
816        assert!(result.best_energy <= 0.0); // Should find a good solution
817    }
818}