1use 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#[derive(Error, Debug)]
20pub enum QuantumWalkError {
21 #[error("Ising error: {0}")]
23 IsingError(#[from] IsingError),
24
25 #[error("Invalid parameter: {0}")]
27 InvalidParameter(String),
28
29 #[error("Evolution error: {0}")]
31 EvolutionError(String),
32
33 #[error("Measurement error: {0}")]
35 MeasurementError(String),
36}
37
38pub type QuantumWalkResult<T> = Result<T, QuantumWalkError>;
40
41#[derive(Debug, Clone)]
43pub enum QuantumWalkAlgorithm {
44 ContinuousTime {
46 evolution_time: f64,
47 time_steps: usize,
48 },
49
50 DiscreteTime {
52 coin_operator: CoinOperator,
53 steps: usize,
54 },
55
56 Adiabatic {
58 initial_hamiltonian: AdiabaticHamiltonian,
59 final_hamiltonian: AdiabaticHamiltonian,
60 evolution_time: f64,
61 time_steps: usize,
62 },
63
64 QaoaWalk {
66 layers: usize,
67 beta_schedule: Vec<f64>,
68 gamma_schedule: Vec<f64>,
69 },
70}
71
72#[derive(Debug, Clone)]
74pub enum CoinOperator {
75 Hadamard,
77
78 Grover,
80
81 Custom(Vec<Vec<Complex64>>),
83}
84
85#[derive(Debug, Clone)]
87pub enum AdiabaticHamiltonian {
88 Mixing,
90
91 Problem,
93
94 Custom(Vec<Vec<Complex64>>),
96}
97
98#[derive(Debug, Clone)]
100pub struct QuantumWalkConfig {
101 pub algorithm: QuantumWalkAlgorithm,
103
104 pub num_measurements: usize,
106
107 pub seed: Option<u64>,
109
110 pub max_evolution_time: f64,
112
113 pub convergence_tolerance: f64,
115
116 pub amplitude_amplification: bool,
118
119 pub amplification_iterations: usize,
121
122 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#[derive(Debug, Clone)]
146pub struct QuantumState {
147 pub amplitudes: Vec<Complex64>,
149
150 pub num_qubits: usize,
152}
153
154impl QuantumState {
155 #[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 amplitudes[0] = Complex64::new(1.0, 0.0);
163
164 Self {
165 amplitudes,
166 num_qubits,
167 }
168 }
169
170 #[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 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 #[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 #[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 #[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
236pub struct QuantumWalkOptimizer {
238 config: QuantumWalkConfig,
240
241 rng: ChaCha8Rng,
243}
244
245impl QuantumWalkOptimizer {
246 #[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 pub fn solve(&mut self, model: &IsingModel) -> QuantumWalkResult<AnnealingSolution> {
259 let start_time = Instant::now();
260
261 let mut state = QuantumState::uniform_superposition(model.num_qubits);
263
264 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 if self.config.amplitude_amplification {
314 self.amplitude_amplification(model, &mut state)?;
315 }
316
317 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 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 let hamiltonian = self.build_problem_hamiltonian(model)?;
347
348 for _ in 0..time_steps {
350 self.apply_hamiltonian_evolution(state, &hamiltonian, dt)?;
351
352 self.apply_decoherence(state, dt * 0.01);
354 }
355
356 Ok(())
357 }
358
359 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 self.apply_coin_operator(state, coin_operator)?;
370
371 self.apply_shift_operator(state, model)?;
373
374 self.apply_conditional_phase(state, model)?;
376 }
377
378 Ok(())
379 }
380
381 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 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 self.apply_hamiltonian_evolution(state, ¤t_hamiltonian, dt)?;
413 }
414
415 Ok(())
416 }
417
418 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 self.apply_hamiltonian_evolution(state, &problem_hamiltonian, gamma)?;
445
446 self.apply_hamiltonian_evolution(state, &mixing_hamiltonian, beta)?;
448 }
449
450 Ok(())
451 }
452
453 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 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 for (qubit, bias) in &model.biases() {
468 energy += bias * f64::from(bits[*qubit]);
469 }
470
471 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 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 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 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 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 for i in 0..num_states {
528 new_amplitudes[i] = state.amplitudes[i]; 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 fn apply_coin_operator(
544 &self,
545 state: &mut QuantumState,
546 coin: &CoinOperator,
547 ) -> QuantumWalkResult<()> {
548 match coin {
549 CoinOperator::Hadamard => {
550 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 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 return Err(QuantumWalkError::EvolutionError(
571 "Custom coin operators not yet implemented".to_string(),
572 ));
573 }
574 }
575
576 Ok(())
577 }
578
579 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 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 const fn apply_shift_operator(
609 &self,
610 _state: &mut QuantumState,
611 _model: &IsingModel,
612 ) -> QuantumWalkResult<()> {
613 Ok(())
615 }
616
617 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 let phase = -energy * 0.1; *amplitude *= Complex64::new(phase.cos(), phase.sin());
630 }
631
632 Ok(())
633 }
634
635 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 fn amplitude_amplification(
648 &self,
649 model: &IsingModel,
650 state: &mut QuantumState,
651 ) -> QuantumWalkResult<()> {
652 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 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; }
669 }
670
671 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 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 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 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 state.amplitudes.len() - 1
722 }
723
724 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); 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); }
818}