use scirs2_core::random::prelude::*;
use scirs2_core::random::ChaCha8Rng;
use scirs2_core::random::{Rng, SeedableRng};
use scirs2_core::Complex64;
use std::collections::HashMap;
use std::time::{Duration, Instant};
use thiserror::Error;
use crate::ising::{IsingError, IsingModel};
use crate::simulator::AnnealingSolution;
#[derive(Error, Debug)]
pub enum QuantumWalkError {
#[error("Ising error: {0}")]
IsingError(#[from] IsingError),
#[error("Invalid parameter: {0}")]
InvalidParameter(String),
#[error("Evolution error: {0}")]
EvolutionError(String),
#[error("Measurement error: {0}")]
MeasurementError(String),
}
pub type QuantumWalkResult<T> = Result<T, QuantumWalkError>;
#[derive(Debug, Clone)]
pub enum QuantumWalkAlgorithm {
ContinuousTime {
evolution_time: f64,
time_steps: usize,
},
DiscreteTime {
coin_operator: CoinOperator,
steps: usize,
},
Adiabatic {
initial_hamiltonian: AdiabaticHamiltonian,
final_hamiltonian: AdiabaticHamiltonian,
evolution_time: f64,
time_steps: usize,
},
QaoaWalk {
layers: usize,
beta_schedule: Vec<f64>,
gamma_schedule: Vec<f64>,
},
}
#[derive(Debug, Clone)]
pub enum CoinOperator {
Hadamard,
Grover,
Custom(Vec<Vec<Complex64>>),
}
#[derive(Debug, Clone)]
pub enum AdiabaticHamiltonian {
Mixing,
Problem,
Custom(Vec<Vec<Complex64>>),
}
#[derive(Debug, Clone)]
pub struct QuantumWalkConfig {
pub algorithm: QuantumWalkAlgorithm,
pub num_measurements: usize,
pub seed: Option<u64>,
pub max_evolution_time: f64,
pub convergence_tolerance: f64,
pub amplitude_amplification: bool,
pub amplification_iterations: usize,
pub timeout: Option<Duration>,
}
impl Default for QuantumWalkConfig {
fn default() -> Self {
Self {
algorithm: QuantumWalkAlgorithm::ContinuousTime {
evolution_time: 1.0,
time_steps: 100,
},
num_measurements: 1000,
seed: None,
max_evolution_time: 10.0,
convergence_tolerance: 1e-6,
amplitude_amplification: false,
amplification_iterations: 3,
timeout: Some(Duration::from_secs(300)),
}
}
}
#[derive(Debug, Clone)]
pub struct QuantumState {
pub amplitudes: Vec<Complex64>,
pub num_qubits: usize,
}
impl QuantumState {
#[must_use]
pub fn new(num_qubits: usize) -> Self {
let num_states = 2_usize.pow(num_qubits as u32);
let mut amplitudes = vec![Complex64::new(0.0, 0.0); num_states];
amplitudes[0] = Complex64::new(1.0, 0.0);
Self {
amplitudes,
num_qubits,
}
}
#[must_use]
pub fn uniform_superposition(num_qubits: usize) -> Self {
let num_states = 2_usize.pow(num_qubits as u32);
let amplitude = Complex64::new(1.0 / (num_states as f64).sqrt(), 0.0);
let amplitudes = vec![amplitude; num_states];
Self {
amplitudes,
num_qubits,
}
}
pub fn normalize(&mut self) {
let norm_squared: f64 = self
.amplitudes
.iter()
.map(scirs2_core::Complex::norm_sqr)
.sum();
if norm_squared > 0.0 {
let norm = norm_squared.sqrt();
for amp in &mut self.amplitudes {
*amp /= norm;
}
}
}
#[must_use]
pub fn probability(&self, state_index: usize) -> f64 {
if state_index < self.amplitudes.len() {
self.amplitudes[state_index].norm_sqr()
} else {
0.0
}
}
#[must_use]
pub fn state_to_bits(&self, state_index: usize) -> Vec<i8> {
let mut bits = vec![0; self.num_qubits];
let mut index = state_index;
for i in 0..self.num_qubits {
bits[self.num_qubits - 1 - i] = if (index & 1) == 1 { 1 } else { -1 };
index >>= 1;
}
bits
}
#[must_use]
pub fn bits_to_state(&self, bits: &[i8]) -> usize {
let mut index = 0;
for (i, &bit) in bits.iter().enumerate() {
if bit > 0 {
index |= 1 << (self.num_qubits - 1 - i);
}
}
index
}
}
pub struct QuantumWalkOptimizer {
config: QuantumWalkConfig,
rng: ChaCha8Rng,
}
impl QuantumWalkOptimizer {
#[must_use]
pub fn new(config: QuantumWalkConfig) -> Self {
let rng = match config.seed {
Some(seed) => ChaCha8Rng::seed_from_u64(seed),
None => ChaCha8Rng::seed_from_u64(thread_rng().random()),
};
Self { config, rng }
}
pub fn solve(&mut self, model: &IsingModel) -> QuantumWalkResult<AnnealingSolution> {
let start_time = Instant::now();
let mut state = QuantumState::uniform_superposition(model.num_qubits);
let algorithm = self.config.algorithm.clone();
match algorithm {
QuantumWalkAlgorithm::ContinuousTime {
evolution_time,
time_steps,
} => {
self.continuous_time_evolution(model, &mut state, evolution_time, time_steps)?;
}
QuantumWalkAlgorithm::DiscreteTime {
coin_operator,
steps,
} => {
self.discrete_time_evolution(model, &mut state, &coin_operator, steps)?;
}
QuantumWalkAlgorithm::Adiabatic {
initial_hamiltonian,
final_hamiltonian,
evolution_time,
time_steps,
} => {
self.adiabatic_evolution(
model,
&mut state,
&initial_hamiltonian,
&final_hamiltonian,
evolution_time,
time_steps,
)?;
}
QuantumWalkAlgorithm::QaoaWalk {
layers,
beta_schedule,
gamma_schedule,
} => {
self.qaoa_walk_evolution(
model,
&mut state,
layers,
&beta_schedule,
&gamma_schedule,
)?;
}
}
if self.config.amplitude_amplification {
self.amplitude_amplification(model, &mut state)?;
}
let (best_spins, best_energy) = self.measure_and_optimize(model, &state)?;
let runtime = start_time.elapsed();
Ok(AnnealingSolution {
best_spins,
best_energy,
repetitions: 1,
total_sweeps: self.config.num_measurements,
runtime,
info: format!(
"Quantum walk optimization with {:?} in {:?}",
self.config.algorithm, runtime
),
})
}
fn continuous_time_evolution(
&mut self,
model: &IsingModel,
state: &mut QuantumState,
evolution_time: f64,
time_steps: usize,
) -> QuantumWalkResult<()> {
let dt = evolution_time / time_steps as f64;
let hamiltonian = self.build_problem_hamiltonian(model)?;
for _ in 0..time_steps {
self.apply_hamiltonian_evolution(state, &hamiltonian, dt)?;
self.apply_decoherence(state, dt * 0.01);
}
Ok(())
}
fn discrete_time_evolution(
&self,
model: &IsingModel,
state: &mut QuantumState,
coin_operator: &CoinOperator,
steps: usize,
) -> QuantumWalkResult<()> {
for _ in 0..steps {
self.apply_coin_operator(state, coin_operator)?;
self.apply_shift_operator(state, model)?;
self.apply_conditional_phase(state, model)?;
}
Ok(())
}
fn adiabatic_evolution(
&self,
model: &IsingModel,
state: &mut QuantumState,
initial_ham: &AdiabaticHamiltonian,
final_ham: &AdiabaticHamiltonian,
evolution_time: f64,
time_steps: usize,
) -> QuantumWalkResult<()> {
let dt = evolution_time / time_steps as f64;
let initial_hamiltonian = self.build_hamiltonian(model, initial_ham)?;
let final_hamiltonian = self.build_hamiltonian(model, final_ham)?;
for step in 0..time_steps {
let s = step as f64 / time_steps as f64;
let mut current_hamiltonian = Vec::new();
for i in 0..initial_hamiltonian.len() {
let mut row = Vec::new();
for j in 0..initial_hamiltonian[i].len() {
let interpolated =
(1.0 - s) * initial_hamiltonian[i][j] + s * final_hamiltonian[i][j];
row.push(interpolated);
}
current_hamiltonian.push(row);
}
self.apply_hamiltonian_evolution(state, ¤t_hamiltonian, dt)?;
}
Ok(())
}
fn qaoa_walk_evolution(
&self,
model: &IsingModel,
state: &mut QuantumState,
layers: usize,
beta_schedule: &[f64],
gamma_schedule: &[f64],
) -> QuantumWalkResult<()> {
let problem_hamiltonian = self.build_problem_hamiltonian(model)?;
let mixing_hamiltonian = self.build_mixing_hamiltonian(model.num_qubits)?;
for layer in 0..layers {
let gamma = if layer < gamma_schedule.len() {
gamma_schedule[layer]
} else {
gamma_schedule.last().copied().unwrap_or(0.5)
};
let beta = if layer < beta_schedule.len() {
beta_schedule[layer]
} else {
beta_schedule.last().copied().unwrap_or(0.5)
};
self.apply_hamiltonian_evolution(state, &problem_hamiltonian, gamma)?;
self.apply_hamiltonian_evolution(state, &mixing_hamiltonian, beta)?;
}
Ok(())
}
fn build_problem_hamiltonian(
&self,
model: &IsingModel,
) -> QuantumWalkResult<Vec<Vec<Complex64>>> {
let num_states = 2_usize.pow(model.num_qubits as u32);
let mut hamiltonian = vec![vec![Complex64::new(0.0, 0.0); num_states]; num_states];
for state_idx in 0..num_states {
let mut energy = 0.0;
let bits = self.index_to_bits(state_idx, model.num_qubits);
for (qubit, bias) in &model.biases() {
energy += bias * f64::from(bits[*qubit]);
}
for coupling in model.couplings() {
energy +=
coupling.strength * f64::from(bits[coupling.i]) * f64::from(bits[coupling.j]);
}
hamiltonian[state_idx][state_idx] = Complex64::new(energy, 0.0);
}
Ok(hamiltonian)
}
fn build_mixing_hamiltonian(
&self,
num_qubits: usize,
) -> QuantumWalkResult<Vec<Vec<Complex64>>> {
let num_states = 2_usize.pow(num_qubits as u32);
let mut hamiltonian = vec![vec![Complex64::new(0.0, 0.0); num_states]; num_states];
for qubit in 0..num_qubits {
for state_idx in 0..num_states {
let flipped_idx = state_idx ^ (1 << (num_qubits - 1 - qubit));
hamiltonian[state_idx][flipped_idx] += Complex64::new(1.0, 0.0);
}
}
Ok(hamiltonian)
}
fn build_hamiltonian(
&self,
model: &IsingModel,
ham_type: &AdiabaticHamiltonian,
) -> QuantumWalkResult<Vec<Vec<Complex64>>> {
match ham_type {
AdiabaticHamiltonian::Mixing => self.build_mixing_hamiltonian(model.num_qubits),
AdiabaticHamiltonian::Problem => self.build_problem_hamiltonian(model),
AdiabaticHamiltonian::Custom(matrix) => Ok(matrix.clone()),
}
}
fn apply_hamiltonian_evolution(
&self,
state: &mut QuantumState,
hamiltonian: &[Vec<Complex64>],
dt: f64,
) -> QuantumWalkResult<()> {
let num_states = state.amplitudes.len();
let mut new_amplitudes = vec![Complex64::new(0.0, 0.0); num_states];
for i in 0..num_states {
new_amplitudes[i] = state.amplitudes[i];
for j in 0..num_states {
let evolution_term = -Complex64::i() * hamiltonian[i][j] * dt;
new_amplitudes[i] += evolution_term * state.amplitudes[j];
}
}
state.amplitudes = new_amplitudes;
state.normalize();
Ok(())
}
fn apply_coin_operator(
&self,
state: &mut QuantumState,
coin: &CoinOperator,
) -> QuantumWalkResult<()> {
match coin {
CoinOperator::Hadamard => {
for qubit in 0..state.num_qubits {
self.apply_single_qubit_gate(
state,
qubit,
&[[1.0, 1.0], [1.0, -1.0]],
1.0 / 2.0_f64.sqrt(),
)?;
}
}
CoinOperator::Grover => {
for qubit in 0..state.num_qubits {
self.apply_single_qubit_gate(state, qubit, &[[0.0, 1.0], [1.0, 0.0]], 1.0)?;
}
}
CoinOperator::Custom(_matrix) => {
return Err(QuantumWalkError::EvolutionError(
"Custom coin operators not yet implemented".to_string(),
));
}
}
Ok(())
}
fn apply_single_qubit_gate(
&self,
state: &mut QuantumState,
qubit: usize,
gate: &[[f64; 2]; 2],
normalization: f64,
) -> QuantumWalkResult<()> {
let num_states = state.amplitudes.len();
let mut new_amplitudes = vec![Complex64::new(0.0, 0.0); num_states];
for state_idx in 0..num_states {
let bit = (state_idx >> (state.num_qubits - 1 - qubit)) & 1;
let flipped_idx = state_idx ^ (1 << (state.num_qubits - 1 - qubit));
new_amplitudes[state_idx] +=
Complex64::new(gate[bit][bit] * normalization, 0.0) * state.amplitudes[state_idx];
new_amplitudes[flipped_idx] += Complex64::new(gate[1 - bit][bit] * normalization, 0.0)
* state.amplitudes[state_idx];
}
state.amplitudes = new_amplitudes;
state.normalize();
Ok(())
}
const fn apply_shift_operator(
&self,
_state: &mut QuantumState,
_model: &IsingModel,
) -> QuantumWalkResult<()> {
Ok(())
}
fn apply_conditional_phase(
&self,
state: &mut QuantumState,
model: &IsingModel,
) -> QuantumWalkResult<()> {
for (state_idx, amplitude) in state.amplitudes.iter_mut().enumerate() {
let bits = self.index_to_bits(state_idx, model.num_qubits);
let energy = model.energy(&bits).map_err(QuantumWalkError::IsingError)?;
let phase = -energy * 0.1; *amplitude *= Complex64::new(phase.cos(), phase.sin());
}
Ok(())
}
fn apply_decoherence(&mut self, state: &mut QuantumState, strength: f64) {
for amplitude in &mut state.amplitudes {
let noise_real = (self.rng.random::<f64>() - 0.5) * strength;
let noise_imag = (self.rng.random::<f64>() - 0.5) * strength;
*amplitude += Complex64::new(noise_real, noise_imag);
}
state.normalize();
}
fn amplitude_amplification(
&self,
model: &IsingModel,
state: &mut QuantumState,
) -> QuantumWalkResult<()> {
let mut mean_energy = 0.0;
for (state_idx, amplitude) in state.amplitudes.iter().enumerate() {
let bits = self.index_to_bits(state_idx, model.num_qubits);
let energy = model.energy(&bits).map_err(QuantumWalkError::IsingError)?;
mean_energy += amplitude.norm_sqr() * energy;
}
for _ in 0..self.config.amplification_iterations {
for (state_idx, amplitude) in state.amplitudes.iter_mut().enumerate() {
let bits = self.index_to_bits(state_idx, model.num_qubits);
let energy = model.energy(&bits).map_err(QuantumWalkError::IsingError)?;
if energy < mean_energy {
*amplitude *= -1.0; }
}
let avg_amplitude: Complex64 =
state.amplitudes.iter().sum::<Complex64>() / state.amplitudes.len() as f64;
for amplitude in &mut state.amplitudes {
*amplitude = 2.0 * avg_amplitude - *amplitude;
}
state.normalize();
}
Ok(())
}
fn measure_and_optimize(
&mut self,
model: &IsingModel,
state: &QuantumState,
) -> QuantumWalkResult<(Vec<i8>, f64)> {
let mut best_spins = vec![1; model.num_qubits];
let mut best_energy = f64::INFINITY;
for _ in 0..self.config.num_measurements {
let measured_state = self.sample_state(state);
let spins = self.index_to_bits(measured_state, model.num_qubits);
let energy = model.energy(&spins).map_err(QuantumWalkError::IsingError)?;
if energy < best_energy {
best_energy = energy;
best_spins = spins;
}
}
Ok((best_spins, best_energy))
}
fn sample_state(&mut self, state: &QuantumState) -> usize {
let random_value = self.rng.random::<f64>();
let mut cumulative_prob = 0.0;
for (state_idx, amplitude) in state.amplitudes.iter().enumerate() {
cumulative_prob += amplitude.norm_sqr();
if random_value <= cumulative_prob {
return state_idx;
}
}
state.amplitudes.len() - 1
}
fn index_to_bits(&self, state_index: usize, num_qubits: usize) -> Vec<i8> {
let mut bits = vec![0; num_qubits];
let mut index = state_index;
for i in 0..num_qubits {
bits[num_qubits - 1 - i] = if (index & 1) == 1 { 1 } else { -1 };
index >>= 1;
}
bits
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_quantum_state_creation() {
let state = QuantumState::new(2);
assert_eq!(state.num_qubits, 2);
assert_eq!(state.amplitudes.len(), 4);
assert_eq!(state.amplitudes[0], Complex64::new(1.0, 0.0));
}
#[test]
fn test_uniform_superposition() {
let state = QuantumState::uniform_superposition(2);
let expected_amplitude = Complex64::new(0.5, 0.0);
for amplitude in &state.amplitudes {
assert!((amplitude - expected_amplitude).norm() < 1e-10);
}
}
#[test]
fn test_state_normalization() {
let mut state = QuantumState::new(2);
state.amplitudes[0] = Complex64::new(2.0, 0.0);
state.amplitudes[1] = Complex64::new(1.0, 0.0);
state.normalize();
let norm_squared: f64 = state.amplitudes.iter().map(|amp| amp.norm_sqr()).sum();
assert!((norm_squared - 1.0).abs() < 1e-10);
}
#[test]
fn test_bits_conversion() {
let state = QuantumState::new(3);
let bits = state.state_to_bits(5); assert_eq!(bits, vec![1, -1, 1]);
let index = state.bits_to_state(&bits);
assert_eq!(index, 5);
}
#[test]
fn test_quantum_walk_config() {
let config = QuantumWalkConfig::default();
assert!(matches!(
config.algorithm,
QuantumWalkAlgorithm::ContinuousTime { .. }
));
assert_eq!(config.num_measurements, 1000);
}
#[test]
fn test_simple_optimization() {
let mut model = IsingModel::new(2);
model
.set_coupling(0, 1, -1.0)
.expect("Failed to set coupling");
let config = QuantumWalkConfig {
algorithm: QuantumWalkAlgorithm::ContinuousTime {
evolution_time: 0.5,
time_steps: 50,
},
num_measurements: 100,
seed: Some(42),
..Default::default()
};
let mut optimizer = QuantumWalkOptimizer::new(config);
let result = optimizer
.solve(&model)
.expect("Optimization should succeed");
assert_eq!(result.best_spins.len(), 2);
assert!(result.best_energy <= 0.0); }
}