use crate::error::OptimizeResult;
use crate::result::OptimizeResults;
use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
use scirs2_core::numeric::Zero;
use scirs2_core::random::{rng, Rng, RngExt};
use std::collections::VecDeque;
use std::f64::consts::PI;
#[derive(Debug, Clone, Copy)]
pub struct Complex {
pub real: f64,
pub imag: f64,
}
impl Complex {
pub fn new(real: f64, imag: f64) -> Self {
Self { real, imag }
}
pub fn magnitude(&self) -> f64 {
(self.real * self.real + self.imag * self.imag).sqrt()
}
pub fn phase(&self) -> f64 {
self.imag.atan2(self.real)
}
pub fn conjugate(&self) -> Complex {
Complex::new(self.real, -self.imag)
}
}
impl std::ops::Add for Complex {
type Output = Complex;
fn add(self, other: Complex) -> Complex {
Complex::new(self.real + other.real, self.imag + other.imag)
}
}
impl std::ops::Mul for Complex {
type Output = Complex;
fn mul(self, other: Complex) -> Complex {
Complex::new(
self.real * other.real - self.imag * other.imag,
self.real * other.imag + self.imag * other.real,
)
}
}
impl std::ops::Mul<f64> for Complex {
type Output = Complex;
fn mul(self, scalar: f64) -> Complex {
Complex::new(self.real * scalar, self.imag * scalar)
}
}
impl Zero for Complex {
fn zero() -> Self {
Complex::new(0.0, 0.0)
}
fn is_zero(&self) -> bool {
self.real == 0.0 && self.imag == 0.0
}
}
#[derive(Debug, Clone)]
pub struct QuantumState {
pub amplitudes: Array1<Complex>,
pub basis_states: Array2<f64>,
pub num_qubits: usize,
pub entanglement_matrix: Array2<Complex>,
pub decoherence_time: f64,
pub evolution_time: f64,
}
impl QuantumState {
pub fn new(num_params: usize, num_basis_states: usize) -> Self {
let num_qubits = (num_basis_states as f64).log2().ceil() as usize;
let actual_states = 1 << num_qubits;
let mut amplitudes = Array1::from_shape_fn(actual_states, |_| {
Complex::new(
scirs2_core::random::rng().random_range(-1.0..1.0),
scirs2_core::random::rng().random_range(-1.0..1.0),
)
});
let norm: f64 = amplitudes
.iter()
.map(|c| c.magnitude().powi(2))
.sum::<f64>()
.sqrt();
for amp in amplitudes.iter_mut() {
*amp = *amp * (1.0 / norm);
}
let basis_states = Array2::from_shape_fn((actual_states, num_params), |_| {
scirs2_core::random::rng().random_range(-2.0..5.0)
});
let entanglement_matrix = Array2::from_shape_fn((num_params, num_params), |(i, j)| {
if i == j {
Complex::new(1.0, 0.0)
} else {
let correlation = scirs2_core::random::rng().random_range(-0.1..0.1);
Complex::new(correlation, correlation * 0.1)
}
});
Self {
amplitudes,
basis_states,
num_qubits,
entanglement_matrix,
decoherence_time: 1000.0,
evolution_time: 0.0,
}
}
pub fn measure(&self) -> Array1<f64> {
let probabilities: Vec<f64> = self
.amplitudes
.iter()
.map(|c| c.magnitude().powi(2))
.collect();
let mut cumulative = 0.0;
let random_value = scirs2_core::random::rng().random_range(0.0..1.0);
for (i, &prob) in probabilities.iter().enumerate() {
cumulative += prob;
if random_value <= cumulative {
return self.basis_states.row(i).to_owned();
}
}
let max_prob_idx = probabilities
.iter()
.enumerate()
.max_by(|a, b| a.1.partial_cmp(b.1).expect("Operation failed"))
.map(|(i, _)| i)
.unwrap_or(0);
self.basis_states.row(max_prob_idx).to_owned()
}
pub fn evolve(&mut self, objective_gradients: &Array1<f64>, dt: f64) -> OptimizeResult<()> {
self.evolution_time += dt;
let hamiltonian = self.compute_hamiltonian(objective_gradients)?;
self.apply_time_evolution(&hamiltonian, dt)?;
self.apply_decoherence(dt)?;
self.update_entanglement(objective_gradients)?;
Ok(())
}
fn compute_hamiltonian(
&self,
objective_gradients: &Array1<f64>,
) -> OptimizeResult<Array2<Complex>> {
let n_states = self.amplitudes.len();
let mut hamiltonian = Array2::zeros((n_states, n_states));
for i in 0..n_states {
for j in 0..n_states {
if i == j {
let params = self.basis_states.row(i);
let potential = params
.iter()
.zip(objective_gradients.iter())
.map(|(&p, &g)| p * g)
.sum::<f64>();
hamiltonian[[i, j]] = Complex::new(potential, 0.0);
} else {
let distance = (&self.basis_states.row(i) - &self.basis_states.row(j))
.mapv(|x| x * x)
.sum()
.sqrt();
let tunneling_amplitude = (-distance * 0.1).exp();
hamiltonian[[i, j]] = Complex::new(0.0, tunneling_amplitude);
}
}
}
Ok(hamiltonian)
}
fn apply_time_evolution(
&mut self,
hamiltonian: &Array2<Complex>,
dt: f64,
) -> OptimizeResult<()> {
let n_states = self.amplitudes.len();
let mut new_amplitudes = self.amplitudes.clone();
for i in 0..n_states {
let mut evolved_amp = Complex::new(0.0, 0.0);
for j in 0..n_states {
let h_element = hamiltonian[[i, j]];
let evolution_factor = if i == j {
Complex::new(1.0, 0.0) + Complex::new(0.0, -h_element.real * dt)
} else {
Complex::new(0.0, -h_element.real * dt) + Complex::new(h_element.imag * dt, 0.0)
};
evolved_amp = evolved_amp + evolution_factor * self.amplitudes[j];
}
new_amplitudes[i] = evolved_amp;
}
let norm: f64 = new_amplitudes
.iter()
.map(|c| c.magnitude().powi(2))
.sum::<f64>()
.sqrt();
for amp in new_amplitudes.iter_mut() {
*amp = *amp * (1.0 / norm.max(1e-12));
}
self.amplitudes = new_amplitudes;
Ok(())
}
fn apply_decoherence(&mut self, dt: f64) -> OptimizeResult<()> {
let decoherence_factor = (-dt / self.decoherence_time).exp();
for amp in self.amplitudes.iter_mut() {
*amp = *amp * decoherence_factor;
}
let noise_strength = 1.0 - decoherence_factor;
for amp in self.amplitudes.iter_mut() {
let noise_real =
scirs2_core::random::rng().random_range(-0.5..0.5) * noise_strength * 0.01;
let noise_imag =
scirs2_core::random::rng().random_range(-0.5..0.5) * noise_strength * 0.01;
*amp = *amp + Complex::new(noise_real, noise_imag);
}
let norm: f64 = self
.amplitudes
.iter()
.map(|c| c.magnitude().powi(2))
.sum::<f64>()
.sqrt();
for amp in self.amplitudes.iter_mut() {
*amp = *amp * (1.0 / norm.max(1e-12));
}
Ok(())
}
fn update_entanglement(&mut self, objective_gradients: &Array1<f64>) -> OptimizeResult<()> {
let n_params = self.entanglement_matrix.nrows();
for i in 0..n_params {
for j in i + 1..n_params {
if i < objective_gradients.len() && j < objective_gradients.len() {
let correlation = objective_gradients[i] * objective_gradients[j];
let phase_update = correlation * 0.01;
let current = self.entanglement_matrix[[i, j]];
let new_phase = current.phase() + phase_update;
let magnitude = current.magnitude() * 0.99 + correlation.abs() * 0.01;
self.entanglement_matrix[[i, j]] =
Complex::new(magnitude * new_phase.cos(), magnitude * new_phase.sin());
self.entanglement_matrix[[j, i]] = self.entanglement_matrix[[i, j]].conjugate();
}
}
}
Ok(())
}
pub fn create_superposition(&mut self, exploration_radius: f64) -> OptimizeResult<()> {
let n_states = self.basis_states.nrows();
let n_params = self.basis_states.ncols();
for i in 0..n_states {
for j in 0..n_params {
let perturbation = scirs2_core::random::rng()
.random_range(-exploration_radius..exploration_radius);
self.basis_states[[i, j]] += perturbation;
}
}
let equal_amplitude = Complex::new(1.0 / (n_states as f64).sqrt(), 0.0);
for i in 0..n_states {
self.amplitudes[i] = equal_amplitude;
}
Ok(())
}
pub fn quantum_tunnel(
&mut self,
barrier_height: f64,
tunnel_probability: f64,
) -> OptimizeResult<()> {
if scirs2_core::random::rng().random_range(0.0..1.0) < tunnel_probability {
let n_states = self.basis_states.nrows();
let n_params = self.basis_states.ncols();
let source_state = scirs2_core::random::rng().random_range(0..n_states);
for j in 0..n_params {
let tunnel_distance =
barrier_height * scirs2_core::random::rng().random_range(-0.5..0.5);
self.basis_states[[source_state, j]] += tunnel_distance;
}
let tunnel_amplitude_factor = (-barrier_height * 0.1).exp();
self.amplitudes[source_state] = self.amplitudes[source_state] * tunnel_amplitude_factor;
let norm: f64 = self
.amplitudes
.iter()
.map(|c| c.magnitude().powi(2))
.sum::<f64>()
.sqrt();
for amp in self.amplitudes.iter_mut() {
*amp = *amp * (1.0 / norm.max(1e-12));
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct QuantumAnnealingSchedule {
pub initial_temperature: f64,
pub final_temperature: f64,
pub current_temperature: f64,
pub progress: f64,
pub schedule_type: CoolingSchedule,
pub quantum_fluctuation: f64,
}
#[derive(Debug, Clone)]
pub enum CoolingSchedule {
Linear,
Exponential,
Logarithmic,
QuantumAdaptive,
}
impl QuantumAnnealingSchedule {
pub fn new(initial_temp: f64, final_temp: f64, schedule: CoolingSchedule) -> Self {
Self {
initial_temperature: initial_temp,
final_temperature: final_temp,
current_temperature: initial_temp,
progress: 0.0,
schedule_type: schedule,
quantum_fluctuation: 1.0,
}
}
pub fn update(&mut self, iteration: usize, max_nit: usize, energy_change: f64) {
self.progress = iteration as f64 / max_nit as f64;
self.current_temperature = match self.schedule_type {
CoolingSchedule::Linear => {
self.initial_temperature * (1.0 - self.progress)
+ self.final_temperature * self.progress
}
CoolingSchedule::Exponential => {
self.initial_temperature
* (self.final_temperature / self.initial_temperature).powf(self.progress)
}
CoolingSchedule::Logarithmic => self.initial_temperature / (1.0 + self.progress).ln(),
CoolingSchedule::QuantumAdaptive => {
let base_temp = self.initial_temperature * (0.1_f64).powf(self.progress);
let quantum_correction = self.quantum_fluctuation * (-energy_change.abs()).exp();
base_temp * (1.0 + quantum_correction * 0.1)
}
};
self.quantum_fluctuation =
1.0 - self.progress + 0.1 * (2.0 * PI * self.progress * 10.0).sin();
}
pub fn should_accept(&self, energy_delta: f64) -> bool {
if energy_delta <= 0.0 {
true } else {
let classical_prob = (-energy_delta / self.current_temperature).exp();
let quantum_prob = classical_prob * (1.0 + self.quantum_fluctuation * 0.1);
scirs2_core::random::rng().random_range(0.0..1.0) < quantum_prob.min(1.0)
}
}
}
#[derive(Debug, Clone)]
pub struct QuantumInspiredOptimizer {
pub quantum_state: QuantumState,
pub annealing_schedule: QuantumAnnealingSchedule,
pub best_solution: Array1<f64>,
pub best_objective: f64,
pub iteration: usize,
pub max_nit: usize,
pub function_evaluations: usize,
pub interference_history: VecDeque<f64>,
pub entanglement_strength: f64,
pub tunneling_events: usize,
pub tolerance: f64,
}
impl QuantumInspiredOptimizer {
pub fn new(
initial_params: &ArrayView1<f64>,
max_nit: usize,
num_quantum_states: usize,
) -> Self {
let n_params = initial_params.len();
let quantum_state = QuantumState::new(n_params, num_quantum_states);
let annealing_schedule =
QuantumAnnealingSchedule::new(10.0, 0.01, CoolingSchedule::QuantumAdaptive);
Self {
quantum_state,
annealing_schedule,
best_solution: initial_params.to_owned(),
best_objective: f64::INFINITY,
iteration: 0,
max_nit,
function_evaluations: 0,
interference_history: VecDeque::with_capacity(1000),
entanglement_strength: 1.0,
tunneling_events: 0,
tolerance: 1e-8,
}
}
pub fn optimize<F>(&mut self, objective: F) -> OptimizeResult<OptimizeResults<f64>>
where
F: Fn(&ArrayView1<f64>) -> f64,
{
if self.best_objective == f64::INFINITY {
self.best_objective = objective(&self.best_solution.view());
self.function_evaluations += 1;
}
let mut prev_objective = self.best_objective;
let mut stagnation_counter = 0;
for iteration in 0..self.max_nit {
self.iteration = iteration;
let candidate_params = self.quantum_state.measure();
let candidate_objective = objective(&candidate_params.view());
self.function_evaluations += 1;
if candidate_objective < self.best_objective {
self.best_objective = candidate_objective;
self.best_solution = candidate_params.clone();
stagnation_counter = 0;
} else {
stagnation_counter += 1;
}
let energy_change = candidate_objective - prev_objective;
let gradients =
self.compute_finite_difference_gradient(&objective, &candidate_params.view())?;
self.quantum_state.evolve(&gradients, 0.1)?;
self.annealing_schedule
.update(iteration, self.max_nit, energy_change);
if stagnation_counter > 20 {
let barrier_height = self.annealing_schedule.current_temperature;
let tunnel_prob = 0.1 * self.annealing_schedule.quantum_fluctuation;
self.quantum_state
.quantum_tunnel(barrier_height, tunnel_prob)?;
if scirs2_core::random::rng().random_range(0.0..1.0) < tunnel_prob {
self.tunneling_events += 1;
stagnation_counter = 0;
}
}
if iteration % 50 == 0 {
let exploration_radius = self.annealing_schedule.current_temperature * 0.1;
self.quantum_state
.create_superposition(exploration_radius)?;
}
let interference = self.compute_quantum_interference();
self.interference_history.push_back(interference);
if self.interference_history.len() > 1000 {
self.interference_history.pop_front();
}
self.entanglement_strength = self.compute_entanglement_strength();
if (prev_objective - candidate_objective).abs() < self.tolerance && iteration > 100 {
break;
}
prev_objective = candidate_objective;
}
Ok(OptimizeResults::<f64> {
x: self.best_solution.clone(),
fun: self.best_objective,
success: self.best_objective < f64::INFINITY,
nit: self.iteration,
message: format!(
"Quantum optimization completed. Tunneling events: {}, Final entanglement: {:.3}",
self.tunneling_events, self.entanglement_strength
),
jac: None,
hess: None,
constr: None,
nfev: self.iteration * self.quantum_state.num_qubits, njev: 0,
nhev: 0,
maxcv: 0,
status: if self.best_objective < f64::INFINITY {
0
} else {
1
},
})
}
fn compute_finite_difference_gradient<F>(
&self,
objective: &F,
params: &ArrayView1<f64>,
) -> OptimizeResult<Array1<f64>>
where
F: Fn(&ArrayView1<f64>) -> f64,
{
let eps = 1e-8;
let mut gradient = Array1::zeros(params.len());
let base_value = objective(params);
for i in 0..params.len() {
let mut params_plus = params.to_owned();
params_plus[i] += eps;
let value_plus = objective(¶ms_plus.view());
gradient[i] = (value_plus - base_value) / eps;
}
Ok(gradient)
}
fn compute_quantum_interference(&self) -> f64 {
let mut interference = 0.0;
let n_states = self.quantum_state.amplitudes.len();
for i in 0..n_states {
for j in i + 1..n_states {
let amp_i = self.quantum_state.amplitudes[i];
let amp_j = self.quantum_state.amplitudes[j];
let interference_term = (amp_i.conjugate() * amp_j).real;
interference += interference_term.abs();
}
}
interference / (n_states * (n_states - 1) / 2) as f64
}
fn compute_entanglement_strength(&self) -> f64 {
let mut total_entanglement = 0.0;
let n_params = self.quantum_state.entanglement_matrix.nrows();
let mut count = 0;
for i in 0..n_params {
for j in i + 1..n_params {
total_entanglement += self.quantum_state.entanglement_matrix[[i, j]].magnitude();
count += 1;
}
}
if count > 0 {
total_entanglement / count as f64
} else {
0.0
}
}
pub fn get_quantum_stats(&self) -> QuantumOptimizationStats {
QuantumOptimizationStats {
function_evaluations: self.function_evaluations,
tunneling_events: self.tunneling_events,
current_temperature: self.annealing_schedule.current_temperature,
entanglement_strength: self.entanglement_strength,
quantum_interference: self.interference_history.back().copied().unwrap_or(0.0),
superposition_dimension: self.quantum_state.amplitudes.len(),
evolution_time: self.quantum_state.evolution_time,
decoherence_level: 1.0
- (-self.quantum_state.evolution_time / self.quantum_state.decoherence_time).exp(),
}
}
}
#[derive(Debug, Clone)]
pub struct QuantumOptimizationStats {
pub function_evaluations: usize,
pub tunneling_events: usize,
pub current_temperature: f64,
pub entanglement_strength: f64,
pub quantum_interference: f64,
pub superposition_dimension: usize,
pub evolution_time: f64,
pub decoherence_level: f64,
}
#[allow(dead_code)]
pub fn quantum_optimize<F>(
objective: F,
initial_params: &ArrayView1<f64>,
max_nit: Option<usize>,
num_quantum_states: Option<usize>,
) -> OptimizeResult<OptimizeResults<f64>>
where
F: Fn(&ArrayView1<f64>) -> f64,
{
let max_iter = max_nit.unwrap_or(1000);
let num_states = num_quantum_states.unwrap_or(32);
let mut optimizer = QuantumInspiredOptimizer::new(initial_params, max_iter, num_states);
optimizer.optimize(objective)
}
#[allow(dead_code)]
pub fn quantum_particle_swarm_optimize<F>(
objective: F,
initial_params: &ArrayView1<f64>,
numparticles: usize,
max_nit: usize,
) -> OptimizeResult<OptimizeResults<f64>>
where
F: Fn(&ArrayView1<f64>) -> f64,
{
let mut particles = Vec::new();
for _ in 0..numparticles {
let particle_optimizer = QuantumInspiredOptimizer::new(initial_params, max_nit, 8);
particles.push(particle_optimizer);
}
let mut global_best_solution = initial_params.to_owned();
let mut global_best_objective = f64::INFINITY;
for iteration in 0..max_nit {
for particle in &mut particles {
for _ in 0..5 {
let candidate = particle.quantum_state.measure();
let obj_value = objective(&candidate.view());
if obj_value < particle.best_objective {
particle.best_objective = obj_value;
particle.best_solution = candidate.clone();
}
if obj_value < global_best_objective {
global_best_objective = obj_value;
global_best_solution = candidate.clone();
}
let gradients =
particle.compute_finite_difference_gradient(&objective, &candidate.view())?;
particle.quantum_state.evolve(&gradients, 0.01)?;
if scirs2_core::random::rng().random_range(0.0..1.0) < 0.1 {
let n_params = initial_params.len();
for i in 0..n_params.min(particle.quantum_state.basis_states.ncols()) {
let entanglement_strength = 0.1;
for state_idx in 0..particle.quantum_state.basis_states.nrows() {
particle.quantum_state.basis_states[[state_idx, i]] = (1.0
- entanglement_strength)
* particle.quantum_state.basis_states[[state_idx, i]]
+ entanglement_strength * global_best_solution[i];
}
}
}
}
}
if iteration % 100 == 0 {
for particle in &mut particles {
particle.quantum_state.quantum_tunnel(1.0, 0.05)?;
}
}
}
Ok(OptimizeResults::<f64> {
x: global_best_solution,
fun: global_best_objective,
success: global_best_objective < f64::INFINITY,
nit: max_nit,
message: format!(
"Quantum particle swarm optimization completed with {} particles",
numparticles
),
jac: None,
hess: None,
constr: None,
nfev: max_nit * numparticles, njev: 0,
nhev: 0,
maxcv: 0,
status: if global_best_objective < f64::INFINITY {
0
} else {
1
},
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_complex_arithmetic() {
let c1 = Complex::new(1.0, 2.0);
let c2 = Complex::new(3.0, 4.0);
let sum = c1 + c2;
assert_eq!(sum.real, 4.0);
assert_eq!(sum.imag, 6.0);
let product = c1 * c2;
assert_eq!(product.real, -5.0); assert_eq!(product.imag, 10.0);
assert!((c1.magnitude() - (5.0_f64).sqrt()).abs() < 1e-10);
}
#[test]
fn test_quantum_state_creation() {
let state = QuantumState::new(3, 8);
assert_eq!(state.num_qubits, 3);
assert_eq!(state.amplitudes.len(), 8);
assert_eq!(state.basis_states.nrows(), 8);
assert_eq!(state.basis_states.ncols(), 3);
let norm_squared: f64 = state.amplitudes.iter().map(|c| c.magnitude().powi(2)).sum();
assert!((norm_squared - 1.0).abs() < 1e-10);
}
#[test]
fn test_quantum_measurement() {
let state = QuantumState::new(2, 4);
let measured_params = state.measure();
assert_eq!(measured_params.len(), 2);
}
#[test]
fn test_quantum_annealing_schedule() {
let mut schedule = QuantumAnnealingSchedule::new(10.0, 0.1, CoolingSchedule::Linear);
schedule.update(0, 100, 0.0);
assert!((schedule.current_temperature - 10.0).abs() < 1e-10);
schedule.update(50, 100, -1.0);
assert!(schedule.current_temperature < 10.0);
assert!(schedule.current_temperature > 0.1);
schedule.update(100, 100, 0.0);
assert!((schedule.current_temperature - 0.1).abs() < 1e-10);
}
#[test]
fn test_quantum_optimizer_creation() {
let initial_params = Array1::from(vec![1.0, 2.0]);
let optimizer = QuantumInspiredOptimizer::new(&initial_params.view(), 100, 16);
assert_eq!(optimizer.best_solution.len(), 2);
assert_eq!(optimizer.max_nit, 100);
assert_eq!(optimizer.quantum_state.amplitudes.len(), 16);
}
#[test]
fn test_quantum_optimization() {
let objective = |x: &ArrayView1<f64>| (x[0] - 1.0).powi(2) + (x[1] - 2.0).powi(2);
let initial = Array1::from(vec![0.0, 0.0]);
let result = quantum_optimize(objective, &initial.view(), Some(200), Some(16))
.expect("Operation failed");
assert!(result.nit > 0);
assert!(result.fun.is_finite());
assert!(result.success);
assert_eq!(result.x.len(), 2);
assert!(result.x[0].abs() < 10.0);
assert!(result.x[1].abs() < 10.0);
}
#[test]
fn test_quantum_tunneling() {
let mut state = QuantumState::new(2, 4);
let original_states = state.basis_states.clone();
state.quantum_tunnel(5.0, 1.0).expect("Operation failed");
let mut changed = false;
for i in 0..state.basis_states.nrows() {
for j in 0..state.basis_states.ncols() {
if (state.basis_states[[i, j]] - original_states[[i, j]]).abs() > 1e-10 {
changed = true;
break;
}
}
}
assert!(changed);
}
#[test]
fn test_quantum_particle_swarm() {
let objective = |x: &ArrayView1<f64>| x[0].powi(2) + x[1].powi(2);
let initial = Array1::from(vec![5.0, 5.0]);
let result = quantum_particle_swarm_optimize(objective, &initial.view(), 5, 20)
.expect("Operation failed");
assert!(result.nit > 0);
assert!(result.fun < objective(&initial.view()));
assert!(result.success);
}
#[test]
fn test_quantum_superposition() {
let mut state = QuantumState::new(2, 4);
state.create_superposition(1.0).expect("Operation failed");
let n_states = state.amplitudes.len();
let expected_magnitude = 1.0 / (n_states as f64).sqrt();
for amp in &state.amplitudes {
assert!((amp.magnitude() - expected_magnitude).abs() < 0.1);
}
}
#[test]
fn test_quantum_evolution() {
let mut state = QuantumState::new(2, 4);
let gradients = Array1::from(vec![1.0, -1.0]);
let original_amplitudes = state.amplitudes.clone();
state.evolve(&gradients, 0.1).expect("Operation failed");
let mut evolved = false;
for i in 0..state.amplitudes.len() {
if (state.amplitudes[i].real - original_amplitudes[i].real).abs() > 1e-10
|| (state.amplitudes[i].imag - original_amplitudes[i].imag).abs() > 1e-10
{
evolved = true;
break;
}
}
assert!(evolved);
}
}