scirs2_spatial/quantum_inspired/algorithms/
quantum_optimization.rs1use crate::error::{SpatialError, SpatialResult};
9use scirs2_core::ndarray::Array2;
10
11use super::super::concepts::QuantumState;
13use std::f64::consts::PI;
14
15#[derive(Debug, Clone)]
46pub struct QuantumSpatialOptimizer {
47 num_layers: usize,
49 beta_params: Vec<f64>,
51 gamma_params: Vec<f64>,
53 max_iterations: usize,
55 learning_rate: f64,
57 tolerance: f64,
59 cost_history: Vec<f64>,
61}
62
63impl QuantumSpatialOptimizer {
64 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 pub fn with_max_iterations(mut self, max_iter: usize) -> Self {
91 self.max_iterations = max_iter;
92 self
93 }
94
95 pub fn with_learning_rate(mut self, lr: f64) -> Self {
100 self.learning_rate = lr;
101 self
102 }
103
104 pub fn with_tolerance(mut self, tol: f64) -> Self {
109 self.tolerance = tol;
110 self
111 }
112
113 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 let num_qubits = n_cities * (n_cities - 1);
139 let mut quantum_state = QuantumState::uniform_superposition(num_qubits.min(20)); self.cost_history.clear();
142
143 for iteration in 0..self.max_iterations {
145 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 let expectation = self.calculate_tsp_expectation(&quantum_state, distance_matrix);
160 self.cost_history.push(expectation);
161
162 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 self.update_parameters(expectation, iteration);
172 }
173
174 let solution = self.extract_tsp_solution(&quantum_state, n_cities);
176 Ok(solution)
177 }
178
179 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 let num_qubits = n * n;
205 let mut quantum_state = QuantumState::uniform_superposition(num_qubits.min(16));
206
207 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 let assignment = self.extract_qap_solution(&quantum_state, n);
229 Ok(assignment)
230 }
231
232 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 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; let phase_angle = gamma * cost_weight;
249
250 if j < state.numqubits {
252 state.controlled_rotation(i, j, phase_angle)?;
253 }
254 }
255 }
256
257 Ok(())
258 }
259
260 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 fn apply_mixer_hamiltonian(state: &mut QuantumState, beta: f64) -> SpatialResult<()> {
302 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 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 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 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 fn decode_tsp_cost(
357 &self,
358 measurement: usize,
359 distance_matrix: &Array2<f64>,
360 n_cities: usize,
361 ) -> f64 {
362 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; 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 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 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 fn update_parameters(&mut self, expectation: f64, iteration: usize) {
415 let gradient_noise = 0.1 * ((iteration as f64) * 0.1).sin();
417
418 for i in 0..self.num_layers {
419 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 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 self.learning_rate *= 0.999;
430 }
431
432 fn extract_tsp_solution(&self, state: &QuantumState, n_cities: usize) -> Vec<usize> {
434 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 if best_tour.is_empty() {
449 best_tour = (0..n_cities).collect();
450 }
451
452 best_tour
453 }
454
455 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 #[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; let city_bits = (measurement >> (bit_position * 3)) & 0b111; 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 for city in 0..n_cities {
480 if !used_cities[city] {
481 tour.push(city);
482 }
483 }
484
485 tour
486 }
487
488 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 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 pub fn num_layers(&self) -> usize {
511 self.num_layers
512 }
513
514 pub fn beta_params(&self) -> &[f64] {
516 &self.beta_params
517 }
518
519 pub fn gamma_params(&self) -> &[f64] {
521 &self.gamma_params
522 }
523
524 pub fn cost_history(&self) -> &[f64] {
526 &self.cost_history
527 }
528
529 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 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}