scirs2_optimize/
quantum_inspired.rs

1//! Quantum-Inspired Optimization Methods
2//!
3//! This module implements cutting-edge quantum-inspired optimization algorithms:
4//! - Quantum superposition for parallel parameter exploration
5//! - Quantum entanglement for correlated parameter updates
6//! - Quantum annealing with sophisticated cooling schedules
7//! - Quantum tunneling for escaping local minima
8//! - Quantum interference patterns for optimization landscapes
9//! - Quantum state collapse for solution selection
10
11use crate::error::OptimizeResult;
12use crate::result::OptimizeResults;
13use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
14use scirs2_core::numeric::Zero;
15use scirs2_core::random::{rng, Rng};
16use std::collections::VecDeque;
17use std::f64::consts::PI;
18
19/// Complex number representation for quantum states
20#[derive(Debug, Clone, Copy)]
21pub struct Complex {
22    pub real: f64,
23    pub imag: f64,
24}
25
26impl Complex {
27    pub fn new(real: f64, imag: f64) -> Self {
28        Self { real, imag }
29    }
30
31    pub fn magnitude(&self) -> f64 {
32        (self.real * self.real + self.imag * self.imag).sqrt()
33    }
34
35    pub fn phase(&self) -> f64 {
36        self.imag.atan2(self.real)
37    }
38
39    pub fn conjugate(&self) -> Complex {
40        Complex::new(self.real, -self.imag)
41    }
42}
43
44impl std::ops::Add for Complex {
45    type Output = Complex;
46
47    fn add(self, other: Complex) -> Complex {
48        Complex::new(self.real + other.real, self.imag + other.imag)
49    }
50}
51
52impl std::ops::Mul for Complex {
53    type Output = Complex;
54
55    fn mul(self, other: Complex) -> Complex {
56        Complex::new(
57            self.real * other.real - self.imag * other.imag,
58            self.real * other.imag + self.imag * other.real,
59        )
60    }
61}
62
63impl std::ops::Mul<f64> for Complex {
64    type Output = Complex;
65
66    fn mul(self, scalar: f64) -> Complex {
67        Complex::new(self.real * scalar, self.imag * scalar)
68    }
69}
70
71impl Zero for Complex {
72    fn zero() -> Self {
73        Complex::new(0.0, 0.0)
74    }
75
76    fn is_zero(&self) -> bool {
77        self.real == 0.0 && self.imag == 0.0
78    }
79}
80
81/// Quantum state representation for optimization parameters
82#[derive(Debug, Clone)]
83pub struct QuantumState {
84    /// Amplitude coefficients for superposition states
85    pub amplitudes: Array1<Complex>,
86    /// Parameter values for each basis state
87    pub basis_states: Array2<f64>,
88    /// Quantum register size (number of qubits)
89    pub num_qubits: usize,
90    /// Entanglement correlations between parameters
91    pub entanglement_matrix: Array2<Complex>,
92    /// Decoherence time constant
93    pub decoherence_time: f64,
94    /// Current evolution time
95    pub evolution_time: f64,
96}
97
98impl QuantumState {
99    /// Create new quantum state with random superposition
100    pub fn new(num_params: usize, num_basis_states: usize) -> Self {
101        let num_qubits = (num_basis_states as f64).log2().ceil() as usize;
102        let actual_states = 1 << num_qubits;
103
104        // Initialize random amplitudes (normalized)
105        let mut amplitudes = Array1::from_shape_fn(actual_states, |_| {
106            Complex::new(
107                scirs2_core::random::rng().random_range(-1.0..1.0),
108                scirs2_core::random::rng().random_range(-1.0..1.0),
109            )
110        });
111
112        // Normalize amplitudes
113        let norm: f64 = amplitudes
114            .iter()
115            .map(|c| c.magnitude().powi(2))
116            .sum::<f64>()
117            .sqrt();
118        for amp in amplitudes.iter_mut() {
119            *amp = *amp * (1.0 / norm);
120        }
121
122        // Initialize basis states around reasonable search space
123        let basis_states = Array2::from_shape_fn((actual_states, num_params), |_| {
124            scirs2_core::random::rng().random_range(-2.0..5.0)
125        });
126
127        // Initialize entanglement matrix
128        let entanglement_matrix = Array2::from_shape_fn((num_params, num_params), |(i, j)| {
129            if i == j {
130                Complex::new(1.0, 0.0)
131            } else {
132                let correlation = scirs2_core::random::rng().random_range(-0.1..0.1);
133                Complex::new(correlation, correlation * 0.1)
134            }
135        });
136
137        Self {
138            amplitudes,
139            basis_states,
140            num_qubits,
141            entanglement_matrix,
142            decoherence_time: 1000.0,
143            evolution_time: 0.0,
144        }
145    }
146
147    /// Measure quantum state to collapse to classical parameters
148    pub fn measure(&self) -> Array1<f64> {
149        // Compute measurement probabilities
150        let probabilities: Vec<f64> = self
151            .amplitudes
152            .iter()
153            .map(|c| c.magnitude().powi(2))
154            .collect();
155
156        // Quantum measurement (random collapse based on probabilities)
157        let mut cumulative = 0.0;
158        let random_value = scirs2_core::random::rng().random_range(0.0..1.0);
159
160        for (i, &prob) in probabilities.iter().enumerate() {
161            cumulative += prob;
162            if random_value <= cumulative {
163                return self.basis_states.row(i).to_owned();
164            }
165        }
166
167        // Fallback to most probable state
168        let max_prob_idx = probabilities
169            .iter()
170            .enumerate()
171            .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
172            .map(|(i, _)| i)
173            .unwrap_or(0);
174
175        self.basis_states.row(max_prob_idx).to_owned()
176    }
177
178    /// Apply quantum evolution based on objective function landscape
179    pub fn evolve(&mut self, objective_gradients: &Array1<f64>, dt: f64) -> OptimizeResult<()> {
180        self.evolution_time += dt;
181
182        // Compute Hamiltonian from objective landscape
183        let hamiltonian = self.compute_hamiltonian(objective_gradients)?;
184
185        // Apply time evolution operator U = exp(-iHt)
186        self.apply_time_evolution(&hamiltonian, dt)?;
187
188        // Apply decoherence
189        self.apply_decoherence(dt)?;
190
191        // Update entanglement based on parameter correlations
192        self.update_entanglement(objective_gradients)?;
193
194        Ok(())
195    }
196
197    fn compute_hamiltonian(
198        &self,
199        objective_gradients: &Array1<f64>,
200    ) -> OptimizeResult<Array2<Complex>> {
201        let n_states = self.amplitudes.len();
202        let mut hamiltonian = Array2::zeros((n_states, n_states));
203
204        for i in 0..n_states {
205            for j in 0..n_states {
206                if i == j {
207                    // Diagonal elements: potential energy from objective
208                    let params = self.basis_states.row(i);
209                    let potential = params
210                        .iter()
211                        .zip(objective_gradients.iter())
212                        .map(|(&p, &g)| p * g)
213                        .sum::<f64>();
214
215                    hamiltonian[[i, j]] = Complex::new(potential, 0.0);
216                } else {
217                    // Off-diagonal elements: tunneling between states
218                    let distance = (&self.basis_states.row(i) - &self.basis_states.row(j))
219                        .mapv(|x| x * x)
220                        .sum()
221                        .sqrt();
222
223                    let tunneling_amplitude = (-distance * 0.1).exp();
224                    hamiltonian[[i, j]] = Complex::new(0.0, tunneling_amplitude);
225                }
226            }
227        }
228
229        Ok(hamiltonian)
230    }
231
232    fn apply_time_evolution(
233        &mut self,
234        hamiltonian: &Array2<Complex>,
235        dt: f64,
236    ) -> OptimizeResult<()> {
237        // Simplified time evolution: ψ(t+dt) = exp(-iH*dt) * ψ(t)
238        // Using first-order approximation: exp(-iH*dt) ≈ 1 - iH*dt
239
240        let n_states = self.amplitudes.len();
241        let mut new_amplitudes = self.amplitudes.clone();
242
243        for i in 0..n_states {
244            let mut evolved_amp = Complex::new(0.0, 0.0);
245
246            for j in 0..n_states {
247                let h_element = hamiltonian[[i, j]];
248                let evolution_factor = if i == j {
249                    // Diagonal: 1 - iH_ii*dt
250                    Complex::new(1.0, 0.0) + Complex::new(0.0, -h_element.real * dt)
251                } else {
252                    // Off-diagonal: -iH_ij*dt
253                    Complex::new(0.0, -h_element.real * dt) + Complex::new(h_element.imag * dt, 0.0)
254                };
255
256                evolved_amp = evolved_amp + evolution_factor * self.amplitudes[j];
257            }
258
259            new_amplitudes[i] = evolved_amp;
260        }
261
262        // Renormalize
263        let norm: f64 = new_amplitudes
264            .iter()
265            .map(|c| c.magnitude().powi(2))
266            .sum::<f64>()
267            .sqrt();
268        for amp in new_amplitudes.iter_mut() {
269            *amp = *amp * (1.0 / norm.max(1e-12));
270        }
271
272        self.amplitudes = new_amplitudes;
273        Ok(())
274    }
275
276    fn apply_decoherence(&mut self, dt: f64) -> OptimizeResult<()> {
277        // Exponential decoherence: amplitude decay
278        let decoherence_factor = (-dt / self.decoherence_time).exp();
279
280        for amp in self.amplitudes.iter_mut() {
281            *amp = *amp * decoherence_factor;
282        }
283
284        // Add small random noise to simulate environmental interaction
285        let noise_strength = 1.0 - decoherence_factor;
286        for amp in self.amplitudes.iter_mut() {
287            let noise_real =
288                scirs2_core::random::rng().random_range(-0.5..0.5) * noise_strength * 0.01;
289            let noise_imag =
290                scirs2_core::random::rng().random_range(-0.5..0.5) * noise_strength * 0.01;
291            *amp = *amp + Complex::new(noise_real, noise_imag);
292        }
293
294        // Renormalize after decoherence
295        let norm: f64 = self
296            .amplitudes
297            .iter()
298            .map(|c| c.magnitude().powi(2))
299            .sum::<f64>()
300            .sqrt();
301        for amp in self.amplitudes.iter_mut() {
302            *amp = *amp * (1.0 / norm.max(1e-12));
303        }
304
305        Ok(())
306    }
307
308    fn update_entanglement(&mut self, objective_gradients: &Array1<f64>) -> OptimizeResult<()> {
309        let n_params = self.entanglement_matrix.nrows();
310
311        // Update entanglement based on gradient correlations
312        for i in 0..n_params {
313            for j in i + 1..n_params {
314                if i < objective_gradients.len() && j < objective_gradients.len() {
315                    let correlation = objective_gradients[i] * objective_gradients[j];
316                    let phase_update = correlation * 0.01;
317
318                    let current = self.entanglement_matrix[[i, j]];
319                    let new_phase = current.phase() + phase_update;
320                    let magnitude = current.magnitude() * 0.99 + correlation.abs() * 0.01;
321
322                    self.entanglement_matrix[[i, j]] =
323                        Complex::new(magnitude * new_phase.cos(), magnitude * new_phase.sin());
324                    self.entanglement_matrix[[j, i]] = self.entanglement_matrix[[i, j]].conjugate();
325                }
326            }
327        }
328
329        Ok(())
330    }
331
332    /// Apply quantum superposition principle to explore multiple states
333    pub fn create_superposition(&mut self, exploration_radius: f64) -> OptimizeResult<()> {
334        let n_states = self.basis_states.nrows();
335        let n_params = self.basis_states.ncols();
336
337        // Create new basis states around current ones
338        for i in 0..n_states {
339            for j in 0..n_params {
340                let perturbation = scirs2_core::random::rng()
341                    .random_range(-exploration_radius..exploration_radius);
342                self.basis_states[[i, j]] += perturbation;
343            }
344        }
345
346        // Update amplitudes to maintain superposition
347        let equal_amplitude = Complex::new(1.0 / (n_states as f64).sqrt(), 0.0);
348        for i in 0..n_states {
349            self.amplitudes[i] = equal_amplitude;
350        }
351
352        Ok(())
353    }
354
355    /// Apply quantum tunneling to escape local minima
356    pub fn quantum_tunnel(
357        &mut self,
358        barrier_height: f64,
359        tunnel_probability: f64,
360    ) -> OptimizeResult<()> {
361        if scirs2_core::random::rng().random_range(0.0..1.0) < tunnel_probability {
362            // Quantum tunneling: create new basis states beyond energy barriers
363            let n_states = self.basis_states.nrows();
364            let n_params = self.basis_states.ncols();
365
366            // Select a random state to tunnel from
367            let source_state = scirs2_core::random::rng().random_range(0..n_states);
368
369            // Create tunneled state
370            for j in 0..n_params {
371                let tunnel_distance =
372                    barrier_height * scirs2_core::random::rng().random_range(-0.5..0.5);
373                self.basis_states[[source_state, j]] += tunnel_distance;
374            }
375
376            // Redistribute amplitudes to account for tunneling
377            let tunnel_amplitude_factor = (-barrier_height * 0.1).exp();
378            self.amplitudes[source_state] = self.amplitudes[source_state] * tunnel_amplitude_factor;
379
380            // Renormalize
381            let norm: f64 = self
382                .amplitudes
383                .iter()
384                .map(|c| c.magnitude().powi(2))
385                .sum::<f64>()
386                .sqrt();
387            for amp in self.amplitudes.iter_mut() {
388                *amp = *amp * (1.0 / norm.max(1e-12));
389            }
390        }
391
392        Ok(())
393    }
394}
395
396/// Quantum annealing schedule for cooling
397#[derive(Debug, Clone)]
398pub struct QuantumAnnealingSchedule {
399    /// Initial temperature
400    pub initial_temperature: f64,
401    /// Final temperature
402    pub final_temperature: f64,
403    /// Current temperature
404    pub current_temperature: f64,
405    /// Annealing progress (0 to 1)
406    pub progress: f64,
407    /// Cooling schedule type
408    pub schedule_type: CoolingSchedule,
409    /// Quantum fluctuation strength
410    pub quantum_fluctuation: f64,
411}
412
413#[derive(Debug, Clone)]
414pub enum CoolingSchedule {
415    Linear,
416    Exponential,
417    Logarithmic,
418    QuantumAdaptive,
419}
420
421impl QuantumAnnealingSchedule {
422    pub fn new(initial_temp: f64, final_temp: f64, schedule: CoolingSchedule) -> Self {
423        Self {
424            initial_temperature: initial_temp,
425            final_temperature: final_temp,
426            current_temperature: initial_temp,
427            progress: 0.0,
428            schedule_type: schedule,
429            quantum_fluctuation: 1.0,
430        }
431    }
432
433    pub fn update(&mut self, iteration: usize, max_nit: usize, energy_change: f64) {
434        self.progress = iteration as f64 / max_nit as f64;
435
436        self.current_temperature = match self.schedule_type {
437            CoolingSchedule::Linear => {
438                self.initial_temperature * (1.0 - self.progress)
439                    + self.final_temperature * self.progress
440            }
441            CoolingSchedule::Exponential => {
442                self.initial_temperature
443                    * (self.final_temperature / self.initial_temperature).powf(self.progress)
444            }
445            CoolingSchedule::Logarithmic => self.initial_temperature / (1.0 + self.progress).ln(),
446            CoolingSchedule::QuantumAdaptive => {
447                // Adaptive cooling based on energy landscape and quantum effects
448                let base_temp = self.initial_temperature * (0.1_f64).powf(self.progress);
449                let quantum_correction = self.quantum_fluctuation * (-energy_change.abs()).exp();
450                base_temp * (1.0 + quantum_correction * 0.1)
451            }
452        };
453
454        // Update quantum fluctuation strength
455        self.quantum_fluctuation =
456            1.0 - self.progress + 0.1 * (2.0 * PI * self.progress * 10.0).sin();
457    }
458
459    pub fn should_accept(&self, energy_delta: f64) -> bool {
460        if energy_delta <= 0.0 {
461            true // Always accept improvements
462        } else {
463            // Quantum-enhanced Boltzmann acceptance with fluctuations
464            let classical_prob = (-energy_delta / self.current_temperature).exp();
465            let quantum_prob = classical_prob * (1.0 + self.quantum_fluctuation * 0.1);
466
467            scirs2_core::random::rng().random_range(0.0..1.0) < quantum_prob.min(1.0)
468        }
469    }
470}
471
472/// Advanced Quantum-Inspired Optimizer
473#[derive(Debug, Clone)]
474pub struct QuantumInspiredOptimizer {
475    /// Quantum state representation
476    pub quantum_state: QuantumState,
477    /// Annealing schedule
478    pub annealing_schedule: QuantumAnnealingSchedule,
479    /// Best solution found
480    pub best_solution: Array1<f64>,
481    /// Best objective value
482    pub best_objective: f64,
483    /// Current iteration
484    pub iteration: usize,
485    /// Maximum iterations
486    pub max_nit: usize,
487    /// Objective function evaluations
488    pub function_evaluations: usize,
489    /// Quantum interference patterns
490    pub interference_history: VecDeque<f64>,
491    /// Entanglement strength tracker
492    pub entanglement_strength: f64,
493    /// Tunneling event counter
494    pub tunneling_events: usize,
495    /// Convergence tolerance
496    pub tolerance: f64,
497}
498
499impl QuantumInspiredOptimizer {
500    /// Create new quantum-inspired optimizer
501    pub fn new(
502        initial_params: &ArrayView1<f64>,
503        max_nit: usize,
504        num_quantum_states: usize,
505    ) -> Self {
506        let n_params = initial_params.len();
507        let quantum_state = QuantumState::new(n_params, num_quantum_states);
508        let annealing_schedule =
509            QuantumAnnealingSchedule::new(10.0, 0.01, CoolingSchedule::QuantumAdaptive);
510
511        Self {
512            quantum_state,
513            annealing_schedule,
514            best_solution: initial_params.to_owned(),
515            best_objective: f64::INFINITY,
516            iteration: 0,
517            max_nit,
518            function_evaluations: 0,
519            interference_history: VecDeque::with_capacity(1000),
520            entanglement_strength: 1.0,
521            tunneling_events: 0,
522            tolerance: 1e-8,
523        }
524    }
525
526    /// Run quantum-inspired optimization
527    pub fn optimize<F>(&mut self, objective: F) -> OptimizeResult<OptimizeResults<f64>>
528    where
529        F: Fn(&ArrayView1<f64>) -> f64,
530    {
531        // Evaluate initial point if not already done
532        if self.best_objective == f64::INFINITY {
533            self.best_objective = objective(&self.best_solution.view());
534            self.function_evaluations += 1;
535        }
536
537        let mut prev_objective = self.best_objective;
538        let mut stagnation_counter = 0;
539
540        for iteration in 0..self.max_nit {
541            self.iteration = iteration;
542
543            // Measure quantum state to get candidate solution
544            let candidate_params = self.quantum_state.measure();
545            let candidate_objective = objective(&candidate_params.view());
546            self.function_evaluations += 1;
547
548            // Update best solution
549            if candidate_objective < self.best_objective {
550                self.best_objective = candidate_objective;
551                self.best_solution = candidate_params.clone();
552                stagnation_counter = 0;
553            } else {
554                stagnation_counter += 1;
555            }
556
557            // Compute energy change and gradients (finite difference)
558            let energy_change = candidate_objective - prev_objective;
559            let gradients =
560                self.compute_finite_difference_gradient(&objective, &candidate_params.view())?;
561
562            // Quantum evolution based on objective landscape
563            self.quantum_state.evolve(&gradients, 0.1)?;
564
565            // Update annealing schedule
566            self.annealing_schedule
567                .update(iteration, self.max_nit, energy_change);
568
569            // Quantum tunneling for local minima escape
570            if stagnation_counter > 20 {
571                let barrier_height = self.annealing_schedule.current_temperature;
572                let tunnel_prob = 0.1 * self.annealing_schedule.quantum_fluctuation;
573                self.quantum_state
574                    .quantum_tunnel(barrier_height, tunnel_prob)?;
575
576                if scirs2_core::random::rng().random_range(0.0..1.0) < tunnel_prob {
577                    self.tunneling_events += 1;
578                    stagnation_counter = 0;
579                }
580            }
581
582            // Quantum superposition for exploration
583            if iteration % 50 == 0 {
584                let exploration_radius = self.annealing_schedule.current_temperature * 0.1;
585                self.quantum_state
586                    .create_superposition(exploration_radius)?;
587            }
588
589            // Track interference patterns
590            let interference = self.compute_quantum_interference();
591            self.interference_history.push_back(interference);
592            if self.interference_history.len() > 1000 {
593                self.interference_history.pop_front();
594            }
595
596            // Update entanglement strength
597            self.entanglement_strength = self.compute_entanglement_strength();
598
599            // Convergence check
600            if (prev_objective - candidate_objective).abs() < self.tolerance && iteration > 100 {
601                break;
602            }
603
604            prev_objective = candidate_objective;
605        }
606
607        Ok(OptimizeResults::<f64> {
608            x: self.best_solution.clone(),
609            fun: self.best_objective,
610            success: self.best_objective < f64::INFINITY,
611            nit: self.iteration,
612            message: format!(
613                "Quantum optimization completed. Tunneling events: {}, Final entanglement: {:.3}",
614                self.tunneling_events, self.entanglement_strength
615            ),
616            jac: None,
617            hess: None,
618            constr: None,
619            nfev: self.iteration * self.quantum_state.num_qubits, // Iteration count times qubits
620            njev: 0,
621            nhev: 0,
622            maxcv: 0,
623            status: if self.best_objective < f64::INFINITY {
624                0
625            } else {
626                1
627            },
628        })
629    }
630
631    fn compute_finite_difference_gradient<F>(
632        &self,
633        objective: &F,
634        params: &ArrayView1<f64>,
635    ) -> OptimizeResult<Array1<f64>>
636    where
637        F: Fn(&ArrayView1<f64>) -> f64,
638    {
639        let eps = 1e-8;
640        let mut gradient = Array1::zeros(params.len());
641        let base_value = objective(params);
642
643        for i in 0..params.len() {
644            let mut params_plus = params.to_owned();
645            params_plus[i] += eps;
646            let value_plus = objective(&params_plus.view());
647
648            gradient[i] = (value_plus - base_value) / eps;
649        }
650
651        Ok(gradient)
652    }
653
654    fn compute_quantum_interference(&self) -> f64 {
655        // Compute interference pattern from amplitude overlaps
656        let mut interference = 0.0;
657        let n_states = self.quantum_state.amplitudes.len();
658
659        for i in 0..n_states {
660            for j in i + 1..n_states {
661                let amp_i = self.quantum_state.amplitudes[i];
662                let amp_j = self.quantum_state.amplitudes[j];
663
664                // Interference term: Re(ψ_i* ψ_j)
665                let interference_term = (amp_i.conjugate() * amp_j).real;
666                interference += interference_term.abs();
667            }
668        }
669
670        interference / (n_states * (n_states - 1) / 2) as f64
671    }
672
673    fn compute_entanglement_strength(&self) -> f64 {
674        // Simple entanglement measure based on off-diagonal elements
675        let mut total_entanglement = 0.0;
676        let n_params = self.quantum_state.entanglement_matrix.nrows();
677        let mut count = 0;
678
679        for i in 0..n_params {
680            for j in i + 1..n_params {
681                total_entanglement += self.quantum_state.entanglement_matrix[[i, j]].magnitude();
682                count += 1;
683            }
684        }
685
686        if count > 0 {
687            total_entanglement / count as f64
688        } else {
689            0.0
690        }
691    }
692
693    /// Get optimization statistics
694    pub fn get_quantum_stats(&self) -> QuantumOptimizationStats {
695        QuantumOptimizationStats {
696            function_evaluations: self.function_evaluations,
697            tunneling_events: self.tunneling_events,
698            current_temperature: self.annealing_schedule.current_temperature,
699            entanglement_strength: self.entanglement_strength,
700            quantum_interference: self.interference_history.back().copied().unwrap_or(0.0),
701            superposition_dimension: self.quantum_state.amplitudes.len(),
702            evolution_time: self.quantum_state.evolution_time,
703            decoherence_level: 1.0
704                - (-self.quantum_state.evolution_time / self.quantum_state.decoherence_time).exp(),
705        }
706    }
707}
708
709/// Statistics for quantum optimization
710#[derive(Debug, Clone)]
711pub struct QuantumOptimizationStats {
712    pub function_evaluations: usize,
713    pub tunneling_events: usize,
714    pub current_temperature: f64,
715    pub entanglement_strength: f64,
716    pub quantum_interference: f64,
717    pub superposition_dimension: usize,
718    pub evolution_time: f64,
719    pub decoherence_level: f64,
720}
721
722/// Convenience function for quantum-inspired optimization
723#[allow(dead_code)]
724pub fn quantum_optimize<F>(
725    objective: F,
726    initial_params: &ArrayView1<f64>,
727    max_nit: Option<usize>,
728    num_quantum_states: Option<usize>,
729) -> OptimizeResult<OptimizeResults<f64>>
730where
731    F: Fn(&ArrayView1<f64>) -> f64,
732{
733    let max_iter = max_nit.unwrap_or(1000);
734    let num_states = num_quantum_states.unwrap_or(32);
735
736    let mut optimizer = QuantumInspiredOptimizer::new(initial_params, max_iter, num_states);
737    optimizer.optimize(objective)
738}
739
740/// Quantum-enhanced particle swarm optimization
741#[allow(dead_code)]
742pub fn quantum_particle_swarm_optimize<F>(
743    objective: F,
744    initial_params: &ArrayView1<f64>,
745    numparticles: usize,
746    max_nit: usize,
747) -> OptimizeResult<OptimizeResults<f64>>
748where
749    F: Fn(&ArrayView1<f64>) -> f64,
750{
751    let mut particles = Vec::new();
752
753    // Create quantum-enhanced particles
754    for _ in 0..numparticles {
755        let particle_optimizer = QuantumInspiredOptimizer::new(initial_params, max_nit, 8);
756        particles.push(particle_optimizer);
757    }
758
759    let mut global_best_solution = initial_params.to_owned();
760    let mut global_best_objective = f64::INFINITY;
761
762    for iteration in 0..max_nit {
763        for particle in &mut particles {
764            // Run a few quantum optimization steps per particle
765            for _ in 0..5 {
766                let candidate = particle.quantum_state.measure();
767                let obj_value = objective(&candidate.view());
768
769                if obj_value < particle.best_objective {
770                    particle.best_objective = obj_value;
771                    particle.best_solution = candidate.clone();
772                }
773
774                if obj_value < global_best_objective {
775                    global_best_objective = obj_value;
776                    global_best_solution = candidate.clone();
777                }
778
779                // Quantum evolution with global best influence
780                let gradients =
781                    particle.compute_finite_difference_gradient(&objective, &candidate.view())?;
782                particle.quantum_state.evolve(&gradients, 0.01)?;
783
784                // Entangle particles with global best
785                if scirs2_core::random::rng().random_range(0.0..1.0) < 0.1 {
786                    let n_params = initial_params.len();
787                    for i in 0..n_params.min(particle.quantum_state.basis_states.ncols()) {
788                        let entanglement_strength = 0.1;
789                        for state_idx in 0..particle.quantum_state.basis_states.nrows() {
790                            particle.quantum_state.basis_states[[state_idx, i]] = (1.0
791                                - entanglement_strength)
792                                * particle.quantum_state.basis_states[[state_idx, i]]
793                                + entanglement_strength * global_best_solution[i];
794                        }
795                    }
796                }
797            }
798        }
799
800        // Quantum tunneling for swarm diversity
801        if iteration % 100 == 0 {
802            for particle in &mut particles {
803                particle.quantum_state.quantum_tunnel(1.0, 0.05)?;
804            }
805        }
806    }
807
808    Ok(OptimizeResults::<f64> {
809        x: global_best_solution,
810        fun: global_best_objective,
811        success: global_best_objective < f64::INFINITY,
812        nit: max_nit,
813        message: format!(
814            "Quantum particle swarm optimization completed with {} particles",
815            numparticles
816        ),
817        jac: None,
818        hess: None,
819        constr: None,
820        nfev: max_nit * numparticles, // Iterations times particles
821        njev: 0,
822        nhev: 0,
823        maxcv: 0,
824        status: if global_best_objective < f64::INFINITY {
825            0
826        } else {
827            1
828        },
829    })
830}
831
832#[cfg(test)]
833mod tests {
834    use super::*;
835
836    #[test]
837    fn test_complex_arithmetic() {
838        let c1 = Complex::new(1.0, 2.0);
839        let c2 = Complex::new(3.0, 4.0);
840
841        let sum = c1 + c2;
842        assert_eq!(sum.real, 4.0);
843        assert_eq!(sum.imag, 6.0);
844
845        let product = c1 * c2;
846        assert_eq!(product.real, -5.0); // 1*3 - 2*4
847        assert_eq!(product.imag, 10.0); // 1*4 + 2*3
848
849        assert!((c1.magnitude() - (5.0_f64).sqrt()).abs() < 1e-10);
850    }
851
852    #[test]
853    fn test_quantum_state_creation() {
854        let state = QuantumState::new(3, 8);
855        assert_eq!(state.num_qubits, 3);
856        assert_eq!(state.amplitudes.len(), 8);
857        assert_eq!(state.basis_states.nrows(), 8);
858        assert_eq!(state.basis_states.ncols(), 3);
859
860        // Check normalization
861        let norm_squared: f64 = state.amplitudes.iter().map(|c| c.magnitude().powi(2)).sum();
862        assert!((norm_squared - 1.0).abs() < 1e-10);
863    }
864
865    #[test]
866    fn test_quantum_measurement() {
867        let state = QuantumState::new(2, 4);
868        let measured_params = state.measure();
869        assert_eq!(measured_params.len(), 2);
870    }
871
872    #[test]
873    fn test_quantum_annealing_schedule() {
874        let mut schedule = QuantumAnnealingSchedule::new(10.0, 0.1, CoolingSchedule::Linear);
875
876        schedule.update(0, 100, 0.0);
877        assert!((schedule.current_temperature - 10.0).abs() < 1e-10);
878
879        schedule.update(50, 100, -1.0);
880        assert!(schedule.current_temperature < 10.0);
881        assert!(schedule.current_temperature > 0.1);
882
883        schedule.update(100, 100, 0.0);
884        assert!((schedule.current_temperature - 0.1).abs() < 1e-10);
885    }
886
887    #[test]
888    fn test_quantum_optimizer_creation() {
889        let initial_params = Array1::from(vec![1.0, 2.0]);
890        let optimizer = QuantumInspiredOptimizer::new(&initial_params.view(), 100, 16);
891
892        assert_eq!(optimizer.best_solution.len(), 2);
893        assert_eq!(optimizer.max_nit, 100);
894        assert_eq!(optimizer.quantum_state.amplitudes.len(), 16);
895    }
896
897    #[test]
898    fn test_quantum_optimization() {
899        let objective = |x: &ArrayView1<f64>| (x[0] - 1.0).powi(2) + (x[1] - 2.0).powi(2);
900        let initial = Array1::from(vec![0.0, 0.0]);
901
902        let result = quantum_optimize(objective, &initial.view(), Some(200), Some(16)).unwrap();
903
904        assert!(result.nit > 0);
905        // Test that the algorithm runs and produces finite results, even if not optimal
906        assert!(result.fun.is_finite());
907        assert!(result.success);
908        assert_eq!(result.x.len(), 2);
909
910        // Basic sanity checks - results should be reasonable
911        assert!(result.x[0].abs() < 10.0);
912        assert!(result.x[1].abs() < 10.0);
913    }
914
915    #[test]
916    fn test_quantum_tunneling() {
917        let mut state = QuantumState::new(2, 4);
918        let original_states = state.basis_states.clone();
919
920        state.quantum_tunnel(5.0, 1.0).unwrap(); // Force tunneling
921
922        // States should have changed due to tunneling
923        let mut changed = false;
924        for i in 0..state.basis_states.nrows() {
925            for j in 0..state.basis_states.ncols() {
926                if (state.basis_states[[i, j]] - original_states[[i, j]]).abs() > 1e-10 {
927                    changed = true;
928                    break;
929                }
930            }
931        }
932
933        assert!(changed);
934    }
935
936    #[test]
937    fn test_quantum_particle_swarm() {
938        let objective = |x: &ArrayView1<f64>| x[0].powi(2) + x[1].powi(2);
939        let initial = Array1::from(vec![5.0, 5.0]);
940
941        let result = quantum_particle_swarm_optimize(objective, &initial.view(), 5, 20).unwrap();
942
943        assert!(result.nit > 0);
944        assert!(result.fun < objective(&initial.view()));
945        assert!(result.success);
946    }
947
948    #[test]
949    fn test_quantum_superposition() {
950        let mut state = QuantumState::new(2, 4);
951        state.create_superposition(1.0).unwrap();
952
953        // Check that amplitudes are approximately equal (superposition)
954        let n_states = state.amplitudes.len();
955        let expected_magnitude = 1.0 / (n_states as f64).sqrt();
956
957        for amp in &state.amplitudes {
958            assert!((amp.magnitude() - expected_magnitude).abs() < 0.1);
959        }
960    }
961
962    #[test]
963    fn test_quantum_evolution() {
964        let mut state = QuantumState::new(2, 4);
965        let gradients = Array1::from(vec![1.0, -1.0]);
966
967        let original_amplitudes = state.amplitudes.clone();
968        state.evolve(&gradients, 0.1).unwrap();
969
970        // Amplitudes should have evolved
971        let mut evolved = false;
972        for i in 0..state.amplitudes.len() {
973            if (state.amplitudes[i].real - original_amplitudes[i].real).abs() > 1e-10
974                || (state.amplitudes[i].imag - original_amplitudes[i].imag).abs() > 1e-10
975            {
976                evolved = true;
977                break;
978            }
979        }
980
981        assert!(evolved);
982    }
983}