scirs2_spatial/quantum_inspired/algorithms/
quantum_optimization.rs

1//! Quantum-Enhanced Optimization Algorithms
2//!
3//! This module provides quantum-inspired optimization algorithms that leverage quantum
4//! computing principles for solving complex combinatorial and continuous optimization
5//! problems in spatial domains. The algorithms use quantum superposition and interference
6//! to explore solution spaces more efficiently than classical methods.
7
8use crate::error::{SpatialError, SpatialResult};
9use scirs2_core::ndarray::Array2;
10
11// Import quantum concepts
12use super::super::concepts::QuantumState;
13use std::f64::consts::PI;
14
15/// Quantum Approximate Optimization Algorithm (QAOA) for Spatial Problems
16///
17/// QAOA is a variational quantum algorithm designed to solve combinatorial optimization
18/// problems. This implementation focuses on spatial optimization problems such as the
19/// traveling salesman problem (TSP), facility location, and graph partitioning.
20///
21/// # Features
22/// - QAOA layers with parameterized quantum gates
23/// - Automatic parameter optimization using gradient descent
24/// - TSP solving with quantum state preparation and measurement
25/// - Cost and mixer Hamiltonian implementations
26/// - Adaptive learning rate scheduling
27///
28/// # Example
29/// ```rust
30/// use scirs2_core::ndarray::Array2;
31/// use scirs2_spatial::quantum_inspired::algorithms::QuantumSpatialOptimizer;
32///
33/// // Create distance matrix for TSP
34/// let distance_matrix = Array2::from_shape_vec((4, 4), vec![
35///     0.0, 1.0, 2.0, 3.0,
36///     1.0, 0.0, 4.0, 2.0,
37///     2.0, 4.0, 0.0, 1.0,
38///     3.0, 2.0, 1.0, 0.0
39/// ]).unwrap();
40///
41/// let mut optimizer = QuantumSpatialOptimizer::new(3);
42/// let tour = optimizer.solve_tsp(&distance_matrix).unwrap();
43/// println!("Optimal tour: {:?}", tour);
44/// ```
45#[derive(Debug, Clone)]
46pub struct QuantumSpatialOptimizer {
47    /// Number of QAOA layers
48    num_layers: usize,
49    /// Optimization parameters β (mixer parameters)
50    beta_params: Vec<f64>,
51    /// Optimization parameters γ (cost parameters)  
52    gamma_params: Vec<f64>,
53    /// Maximum optimization iterations
54    max_iterations: usize,
55    /// Learning rate for parameter optimization
56    learning_rate: f64,
57    /// Convergence tolerance
58    tolerance: f64,
59    /// Cost function history
60    cost_history: Vec<f64>,
61}
62
63impl QuantumSpatialOptimizer {
64    /// Create new QAOA optimizer
65    ///
66    /// # Arguments
67    /// * `num_layers` - Number of QAOA layers (typically 1-10)
68    ///
69    /// # Returns
70    /// A new `QuantumSpatialOptimizer` with default configuration
71    pub fn new(num_layers: usize) -> Self {
72        let beta_params = vec![PI / 4.0; num_layers];
73        let gamma_params = vec![PI / 8.0; num_layers];
74
75        Self {
76            num_layers,
77            beta_params,
78            gamma_params,
79            max_iterations: 100,
80            learning_rate: 0.01,
81            tolerance: 1e-6,
82            cost_history: Vec::new(),
83        }
84    }
85
86    /// Configure maximum iterations
87    ///
88    /// # Arguments
89    /// * `max_iter` - Maximum number of optimization iterations
90    pub fn with_max_iterations(mut self, max_iter: usize) -> Self {
91        self.max_iterations = max_iter;
92        self
93    }
94
95    /// Configure learning rate
96    ///
97    /// # Arguments
98    /// * `lr` - Learning rate for parameter optimization
99    pub fn with_learning_rate(mut self, lr: f64) -> Self {
100        self.learning_rate = lr;
101        self
102    }
103
104    /// Configure convergence tolerance
105    ///
106    /// # Arguments
107    /// * `tol` - Convergence tolerance
108    pub fn with_tolerance(mut self, tol: f64) -> Self {
109        self.tolerance = tol;
110        self
111    }
112
113    /// Solve traveling salesman problem using QAOA
114    ///
115    /// Uses the Quantum Approximate Optimization Algorithm to find an approximate
116    /// solution to the traveling salesman problem. The algorithm encodes the TSP
117    /// as a QUBO (Quadratic Unconstrained Binary Optimization) problem and uses
118    /// quantum superposition to explore multiple tour configurations simultaneously.
119    ///
120    /// # Arguments
121    /// * `distance_matrix` - Square matrix of distances between cities
122    ///
123    /// # Returns
124    /// A tour represented as a vector of city indices
125    ///
126    /// # Errors
127    /// Returns error if the distance matrix is not square
128    pub fn solve_tsp(&mut self, distance_matrix: &Array2<f64>) -> SpatialResult<Vec<usize>> {
129        let n_cities = distance_matrix.nrows();
130
131        if n_cities != distance_matrix.ncols() {
132            return Err(SpatialError::InvalidInput(
133                "Distance matrix must be square".to_string(),
134            ));
135        }
136
137        // Number of qubits needed: n*(n-1) for binary encoding
138        let num_qubits = n_cities * (n_cities - 1);
139        let mut quantum_state = QuantumState::uniform_superposition(num_qubits.min(20)); // Limit for classical simulation
140
141        self.cost_history.clear();
142
143        // QAOA optimization loop
144        for iteration in 0..self.max_iterations {
145            // Apply QAOA circuit
146            for layer in 0..self.num_layers {
147                self.apply_cost_hamiltonian(
148                    &mut quantum_state,
149                    distance_matrix,
150                    self.gamma_params[layer],
151                )?;
152                QuantumSpatialOptimizer::apply_mixer_hamiltonian(
153                    &mut quantum_state,
154                    self.beta_params[layer],
155                )?;
156            }
157
158            // Measure expectation value
159            let expectation = self.calculate_tsp_expectation(&quantum_state, distance_matrix);
160            self.cost_history.push(expectation);
161
162            // Check convergence
163            if iteration > 0 {
164                let prev_cost = self.cost_history[iteration - 1];
165                if (prev_cost - expectation).abs() < self.tolerance {
166                    break;
167                }
168            }
169
170            // Update parameters using gradient descent (simplified)
171            self.update_parameters(expectation, iteration);
172        }
173
174        // Extract solution by measurement
175        let solution = self.extract_tsp_solution(&quantum_state, n_cities);
176        Ok(solution)
177    }
178
179    /// Solve quadratic assignment problem using QAOA
180    ///
181    /// Applies QAOA to solve facility location and assignment problems.
182    ///
183    /// # Arguments
184    /// * `flow_matrix` - Flow between facilities
185    /// * `distance_matrix` - Distance between locations
186    ///
187    /// # Returns
188    /// Assignment of facilities to locations
189    pub fn solve_qap(
190        &mut self,
191        flow_matrix: &Array2<f64>,
192        distance_matrix: &Array2<f64>,
193    ) -> SpatialResult<Vec<usize>> {
194        let n = flow_matrix.nrows();
195
196        if n != flow_matrix.ncols() || n != distance_matrix.nrows() || n != distance_matrix.ncols()
197        {
198            return Err(SpatialError::InvalidInput(
199                "All matrices must be square and of the same size".to_string(),
200            ));
201        }
202
203        // Create quantum state for QAP
204        let num_qubits = n * n;
205        let mut quantum_state = QuantumState::uniform_superposition(num_qubits.min(16));
206
207        // QAOA optimization for QAP
208        for iteration in 0..self.max_iterations {
209            for layer in 0..self.num_layers {
210                self.apply_qap_cost_hamiltonian(
211                    &mut quantum_state,
212                    flow_matrix,
213                    distance_matrix,
214                    self.gamma_params[layer],
215                )?;
216                QuantumSpatialOptimizer::apply_mixer_hamiltonian(
217                    &mut quantum_state,
218                    self.beta_params[layer],
219                )?;
220            }
221
222            let expectation =
223                self.calculate_qap_expectation(&quantum_state, flow_matrix, distance_matrix);
224            self.update_parameters(expectation, iteration);
225        }
226
227        // Extract QAP solution
228        let assignment = self.extract_qap_solution(&quantum_state, n);
229        Ok(assignment)
230    }
231
232    /// Apply cost Hamiltonian for TSP
233    ///
234    /// Implements the problem-specific cost Hamiltonian that encodes the TSP
235    /// objective function into quantum phase rotations.
236    fn apply_cost_hamiltonian(
237        &self,
238        state: &mut QuantumState,
239        distance_matrix: &Array2<f64>,
240        gamma: f64,
241    ) -> SpatialResult<()> {
242        let n_cities = distance_matrix.nrows();
243
244        // Simplified cost Hamiltonian application
245        for i in 0..n_cities.min(state.numqubits) {
246            for j in (i + 1)..n_cities.min(state.numqubits) {
247                let cost_weight = distance_matrix[[i, j]] / 100.0; // Normalize
248                let phase_angle = gamma * cost_weight;
249
250                // Apply controlled phase rotation
251                if j < state.numqubits {
252                    state.controlled_rotation(i, j, phase_angle)?;
253                }
254            }
255        }
256
257        Ok(())
258    }
259
260    /// Apply cost Hamiltonian for QAP
261    fn apply_qap_cost_hamiltonian(
262        &self,
263        state: &mut QuantumState,
264        flow_matrix: &Array2<f64>,
265        distance_matrix: &Array2<f64>,
266        gamma: f64,
267    ) -> SpatialResult<()> {
268        let n = flow_matrix.nrows();
269        let max_qubits = state.numqubits.min(n * n);
270
271        for i in 0..n {
272            for j in 0..n {
273                for k in 0..n {
274                    for l in 0..n {
275                        if i != k && j != l {
276                            let qubit1 = (i * n + j).min(max_qubits - 1);
277                            let qubit2 = (k * n + l).min(max_qubits - 1);
278
279                            if qubit1 < state.numqubits
280                                && qubit2 < state.numqubits
281                                && qubit1 != qubit2
282                            {
283                                let cost_weight =
284                                    flow_matrix[[i, k]] * distance_matrix[[j, l]] / 1000.0;
285                                let phase_angle = gamma * cost_weight;
286                                state.controlled_rotation(qubit1, qubit2, phase_angle)?;
287                            }
288                        }
289                    }
290                }
291            }
292        }
293
294        Ok(())
295    }
296
297    /// Apply mixer Hamiltonian
298    ///
299    /// Implements the driver Hamiltonian that creates superposition and enables
300    /// transitions between different computational basis states.
301    fn apply_mixer_hamiltonian(state: &mut QuantumState, beta: f64) -> SpatialResult<()> {
302        // Apply X-rotations to all qubits
303        for i in 0..state.numqubits {
304            state.hadamard(i)?;
305            state.phase_rotation(i, beta)?;
306            state.hadamard(i)?;
307        }
308
309        Ok(())
310    }
311
312    /// Calculate TSP expectation value
313    ///
314    /// Estimates the expected cost of the TSP solution by sampling multiple
315    /// measurements from the quantum state.
316    fn calculate_tsp_expectation(
317        &self,
318        state: &QuantumState,
319        distance_matrix: &Array2<f64>,
320    ) -> f64 {
321        let mut expectation = 0.0;
322        let n_cities = distance_matrix.nrows();
323        let num_samples = 100;
324
325        // Sample multiple measurements to estimate expectation
326        for _ in 0..num_samples {
327            let measurement = state.measure();
328            let tour_cost = self.decode_tsp_cost(measurement, distance_matrix, n_cities);
329            expectation += tour_cost;
330        }
331
332        expectation / num_samples as f64
333    }
334
335    /// Calculate QAP expectation value
336    fn calculate_qap_expectation(
337        &self,
338        state: &QuantumState,
339        flow_matrix: &Array2<f64>,
340        distance_matrix: &Array2<f64>,
341    ) -> f64 {
342        let mut expectation = 0.0;
343        let n = flow_matrix.nrows();
344
345        for _ in 0..50 {
346            let measurement = state.measure();
347            let assignment = QuantumSpatialOptimizer::decode_qap_assignment(measurement, n);
348            let cost = self.calculate_qap_cost(&assignment, flow_matrix, distance_matrix);
349            expectation += cost;
350        }
351
352        expectation / 50.0
353    }
354
355    /// Decode measurement to TSP tour cost
356    fn decode_tsp_cost(
357        &self,
358        measurement: usize,
359        distance_matrix: &Array2<f64>,
360        n_cities: usize,
361    ) -> f64 {
362        // Simplified decoding: use measurement bits to determine tour
363        let mut tour = Vec::new();
364        let mut remaining_cities: Vec<usize> = (0..n_cities).collect();
365
366        for i in 0..n_cities {
367            if remaining_cities.len() <= 1 {
368                if let Some(city) = remaining_cities.pop() {
369                    tour.push(city);
370                }
371                break;
372            }
373
374            let bit_index = i % 20; // Use a reasonable number of bits for classical simulation
375            let choice_bit = (measurement >> bit_index) & 1;
376            let city_index = choice_bit % remaining_cities.len();
377            let city = remaining_cities.remove(city_index);
378            tour.push(city);
379        }
380
381        // Calculate tour cost
382        let mut total_cost = 0.0;
383        for i in 0..tour.len() {
384            let current_city = tour[i];
385            let next_city = tour[(i + 1) % tour.len()];
386            total_cost += distance_matrix[[current_city, next_city]];
387        }
388
389        total_cost
390    }
391
392    /// Calculate QAP cost for given assignment
393    fn calculate_qap_cost(
394        &self,
395        assignment: &[usize],
396        flow_matrix: &Array2<f64>,
397        distance_matrix: &Array2<f64>,
398    ) -> f64 {
399        let mut cost = 0.0;
400        let n = assignment.len();
401
402        for i in 0..n {
403            for j in 0..n {
404                if i != j && assignment[i] < n && assignment[j] < n {
405                    cost += flow_matrix[[i, j]] * distance_matrix[[assignment[i], assignment[j]]];
406                }
407            }
408        }
409
410        cost
411    }
412
413    /// Update QAOA parameters using gradient descent
414    fn update_parameters(&mut self, expectation: f64, iteration: usize) {
415        // Simplified parameter update using gradient descent
416        let gradient_noise = 0.1 * ((iteration as f64) * 0.1).sin();
417
418        for i in 0..self.num_layers {
419            // Update beta parameters
420            self.beta_params[i] += self.learning_rate * (gradient_noise - expectation / 1000.0);
421            self.beta_params[i] = self.beta_params[i].clamp(0.0, PI);
422
423            // Update gamma parameters
424            self.gamma_params[i] += self.learning_rate * (gradient_noise * 0.5);
425            self.gamma_params[i] = self.gamma_params[i].clamp(0.0, PI);
426        }
427
428        // Decay learning rate
429        self.learning_rate *= 0.999;
430    }
431
432    /// Extract TSP solution from quantum state
433    fn extract_tsp_solution(&self, state: &QuantumState, n_cities: usize) -> Vec<usize> {
434        // Perform multiple measurements and select best tour
435        let mut best_tour = Vec::new();
436
437        for _ in 0..50 {
438            let measurement = state.measure();
439            let tour = QuantumSpatialOptimizer::decode_measurement_to_tour(measurement, n_cities);
440
441            if tour.len() == n_cities {
442                best_tour = tour;
443                break;
444            }
445        }
446
447        // Fallback to simple ordering if no valid tour found
448        if best_tour.is_empty() {
449            best_tour = (0..n_cities).collect();
450        }
451
452        best_tour
453    }
454
455    /// Extract QAP solution from quantum state
456    fn extract_qap_solution(&self, state: &QuantumState, n: usize) -> Vec<usize> {
457        let measurement = state.measure();
458        QuantumSpatialOptimizer::decode_qap_assignment(measurement, n)
459    }
460
461    /// Decode measurement bits to valid tour
462    #[allow(clippy::needless_range_loop)]
463    fn decode_measurement_to_tour(measurement: usize, n_cities: usize) -> Vec<usize> {
464        let mut tour = Vec::new();
465        let mut used_cities = vec![false; n_cities];
466
467        for i in 0..n_cities {
468            let bit_position = i % 20; // Limit bit extraction
469            let city_bits = (measurement >> (bit_position * 3)) & 0b111; // 3 bits per city
470            let city = city_bits % n_cities;
471
472            if !used_cities[city] {
473                tour.push(city);
474                used_cities[city] = true;
475            }
476        }
477
478        // Add remaining cities
479        for city in 0..n_cities {
480            if !used_cities[city] {
481                tour.push(city);
482            }
483        }
484
485        tour
486    }
487
488    /// Decode QAP assignment from measurement
489    fn decode_qap_assignment(measurement: usize, n: usize) -> Vec<usize> {
490        let mut assignment = vec![0; n];
491        let mut used_locations = vec![false; n];
492
493        for i in 0..n {
494            let bits = (measurement >> (i * 3)) & 0b111;
495            let mut location = bits % n;
496
497            // Find first unused location
498            while used_locations[location] {
499                location = (location + 1) % n;
500            }
501
502            assignment[i] = location;
503            used_locations[location] = true;
504        }
505
506        assignment
507    }
508
509    /// Get number of QAOA layers
510    pub fn num_layers(&self) -> usize {
511        self.num_layers
512    }
513
514    /// Get current beta parameters
515    pub fn beta_params(&self) -> &[f64] {
516        &self.beta_params
517    }
518
519    /// Get current gamma parameters
520    pub fn gamma_params(&self) -> &[f64] {
521        &self.gamma_params
522    }
523
524    /// Get cost history
525    pub fn cost_history(&self) -> &[f64] {
526        &self.cost_history
527    }
528
529    /// Get current learning rate
530    pub fn learning_rate(&self) -> f64 {
531        self.learning_rate
532    }
533}
534
535#[cfg(test)]
536mod tests {
537    use super::*;
538    use scirs2_core::ndarray::Array2;
539
540    #[test]
541    fn test_qaoa_optimizer_creation() {
542        let optimizer = QuantumSpatialOptimizer::new(3);
543        assert_eq!(optimizer.num_layers(), 3);
544        assert_eq!(optimizer.beta_params().len(), 3);
545        assert_eq!(optimizer.gamma_params().len(), 3);
546    }
547
548    #[test]
549    fn test_configuration() {
550        let optimizer = QuantumSpatialOptimizer::new(2)
551            .with_max_iterations(200)
552            .with_learning_rate(0.05)
553            .with_tolerance(1e-8);
554
555        assert_eq!(optimizer.max_iterations, 200);
556        assert_eq!(optimizer.learning_rate(), 0.05);
557        assert_eq!(optimizer.tolerance, 1e-8);
558    }
559
560    #[test]
561    fn test_simple_tsp() {
562        let distance_matrix =
563            Array2::from_shape_vec((3, 3), vec![0.0, 1.0, 2.0, 1.0, 0.0, 1.5, 2.0, 1.5, 0.0])
564                .unwrap();
565
566        let mut optimizer = QuantumSpatialOptimizer::new(2).with_max_iterations(10);
567
568        let result = optimizer.solve_tsp(&distance_matrix);
569        assert!(result.is_ok());
570
571        let tour = result.unwrap();
572        assert_eq!(tour.len(), 3);
573
574        // Check that all cities are included
575        let mut cities_included = [false; 3];
576        for &city in &tour {
577            if city < 3 {
578                cities_included[city] = true;
579            }
580        }
581        assert!(cities_included.iter().all(|&x| x));
582    }
583
584    #[test]
585    fn test_invalid_distance_matrix() {
586        let distance_matrix =
587            Array2::from_shape_vec((2, 3), vec![0.0, 1.0, 2.0, 1.0, 0.0, 1.5]).unwrap();
588
589        let mut optimizer = QuantumSpatialOptimizer::new(1);
590        let result = optimizer.solve_tsp(&distance_matrix);
591        assert!(result.is_err());
592    }
593
594    #[test]
595    fn test_qap_solving() {
596        let flow_matrix = Array2::from_shape_vec((2, 2), vec![0.0, 3.0, 2.0, 0.0]).unwrap();
597
598        let distance_matrix = Array2::from_shape_vec((2, 2), vec![0.0, 5.0, 5.0, 0.0]).unwrap();
599
600        let mut optimizer = QuantumSpatialOptimizer::new(1).with_max_iterations(5);
601
602        let result = optimizer.solve_qap(&flow_matrix, &distance_matrix);
603        assert!(result.is_ok());
604
605        let assignment = result.unwrap();
606        assert_eq!(assignment.len(), 2);
607    }
608
609    #[test]
610    fn test_cost_history_tracking() {
611        let distance_matrix =
612            Array2::from_shape_vec((3, 3), vec![0.0, 1.0, 2.0, 1.0, 0.0, 1.5, 2.0, 1.5, 0.0])
613                .unwrap();
614
615        let mut optimizer = QuantumSpatialOptimizer::new(1).with_max_iterations(5);
616
617        optimizer.solve_tsp(&distance_matrix).unwrap();
618        assert!(!optimizer.cost_history().is_empty());
619        assert!(optimizer.cost_history().len() <= 5);
620    }
621}