1use crate::error::SpatialResult;
58use crate::quantum_inspired::{QuantumClusterer, QuantumState};
59use scirs2_core::ndarray::{Array1, Array2, ArrayView2};
60use scirs2_core::random::quick::random_f64;
61use std::time::Instant;
62
63#[allow(dead_code)]
65#[derive(Debug)]
66pub struct HybridSpatialOptimizer {
67 quantum_weight: f64,
69 classical_weight: f64,
71 adaptive_switching: bool,
73 quantum_error_correction: bool,
75 classical_state: ClassicalOptimizerState,
79 coupling_parameters: HybridCouplingParameters,
81 performance_metrics: HybridPerformanceMetrics,
83 quantum_advantage_threshold: f64,
85}
86
87#[derive(Debug, Clone)]
89pub struct ClassicalOptimizerState {
90 pub parameters: Array1<f64>,
92 pub gradients: Array1<f64>,
94 pub hessian_approx: Array2<f64>,
96 pub learning_rate: f64,
98 pub momentum: Array1<f64>,
100 pub adam_state: AdamOptimizerState,
102}
103
104#[derive(Debug, Clone)]
106pub struct AdamOptimizerState {
107 pub m: Array1<f64>,
109 pub v: Array1<f64>,
111 pub beta1: f64,
113 pub beta2: f64,
115 pub epsilon: f64,
117 pub t: usize,
119}
120
121#[derive(Debug, Clone)]
123pub struct HybridCouplingParameters {
124 pub exchange_rate: f64,
126 pub coupling_strength: f64,
128 pub sync_frequency: usize,
130 pub cross_validation: bool,
132 pub quantum_feedback: bool,
134 pub classical_bias: bool,
136}
137
138#[derive(Debug, Clone)]
140pub struct HybridPerformanceMetrics {
141 pub quantum_runtime_ms: f64,
143 pub classical_runtime_ms: f64,
145 pub total_runtime_ms: f64,
147 pub speedup_factor: f64,
149 pub convergence_rate: f64,
151 pub solution_quality: f64,
153 pub quantum_advantage_episodes: usize,
155 pub classical_advantage_episodes: usize,
157}
158
159impl Default for HybridSpatialOptimizer {
160 fn default() -> Self {
161 Self::new()
162 }
163}
164
165impl HybridSpatialOptimizer {
166 pub fn new() -> Self {
168 Self {
169 quantum_weight: 0.5,
170 classical_weight: 0.5,
171 adaptive_switching: true,
172 quantum_error_correction: false,
173 classical_state: ClassicalOptimizerState {
175 parameters: Array1::zeros(0),
176 gradients: Array1::zeros(0),
177 hessian_approx: Array2::zeros((0, 0)),
178 learning_rate: 0.01,
179 momentum: Array1::zeros(0),
180 adam_state: AdamOptimizerState {
181 m: Array1::zeros(0),
182 v: Array1::zeros(0),
183 beta1: 0.9,
184 beta2: 0.999,
185 epsilon: 1e-8,
186 t: 0,
187 },
188 },
189 coupling_parameters: HybridCouplingParameters {
190 exchange_rate: 0.1,
191 coupling_strength: 0.5,
192 sync_frequency: 10,
193 cross_validation: true,
194 quantum_feedback: true,
195 classical_bias: true,
196 },
197 performance_metrics: HybridPerformanceMetrics {
198 quantum_runtime_ms: 0.0,
199 classical_runtime_ms: 0.0,
200 total_runtime_ms: 0.0,
201 speedup_factor: 1.0,
202 convergence_rate: 0.0,
203 solution_quality: 0.0,
204 quantum_advantage_episodes: 0,
205 classical_advantage_episodes: 0,
206 },
207 quantum_advantage_threshold: 1.2,
208 }
209 }
210
211 pub fn with_quantum_classical_coupling(mut self, quantumweight: f64) -> Self {
213 self.quantum_weight = quantumweight.clamp(0.0, 1.0);
214 self.classical_weight = 1.0 - self.quantum_weight;
215 self
216 }
217
218 pub fn with_variational_quantum_component(self, enabled: bool) -> Self {
220 if enabled {
221 } else {
223 }
225 self
226 }
227
228 pub fn with_adaptive_switching(mut self, enabled: bool) -> Self {
230 self.adaptive_switching = enabled;
231 self
232 }
233
234 pub async fn optimize_spatial_function<F>(
236 &mut self,
237 objective_function: F,
238 ) -> SpatialResult<HybridOptimizationResult>
239 where
240 F: Fn(&Array1<f64>) -> f64 + Send + Sync,
241 {
242 let start_time = Instant::now();
243
244 let paramdim = 10; self.initialize_parameters(paramdim);
247
248 let mut best_solution = self.classical_state.parameters.clone();
249 let mut best_value = f64::INFINITY;
250 let mut iteration = 0;
251 let max_iterations = 1000;
252
253 while iteration < max_iterations {
255 let _iteration_start = Instant::now();
256
257 let use_quantum = self
259 .select_optimal_paradigm(iteration, &objective_function)
260 .await?;
261
262 if use_quantum {
263 let quantum_start = Instant::now();
265 let quantum_result = self.quantum_optimization_step(&objective_function).await?;
266 self.performance_metrics.quantum_runtime_ms +=
267 quantum_start.elapsed().as_millis() as f64;
268
269 if quantum_result.value < best_value {
270 best_value = quantum_result.value;
271 best_solution = quantum_result.parameters.clone();
272 self.performance_metrics.quantum_advantage_episodes += 1;
273 }
274
275 self.transfer_quantum_information(&quantum_result).await?;
277 } else {
278 let classical_start = Instant::now();
280 let classical_result = self.classical_optimization_step(&objective_function)?;
281 self.performance_metrics.classical_runtime_ms +=
282 classical_start.elapsed().as_millis() as f64;
283
284 if classical_result.value < best_value {
285 best_value = classical_result.value;
286 best_solution = classical_result.parameters.clone();
287 self.performance_metrics.classical_advantage_episodes += 1;
288 }
289
290 self.transfer_classical_information(&classical_result)
292 .await?;
293 }
294
295 if iteration % self.coupling_parameters.sync_frequency == 0 {
297 self.synchronize_quantum_classical_states().await?;
298 }
299
300 iteration += 1;
301
302 if self.check_convergence(&best_solution, iteration) {
304 break;
305 }
306 }
307
308 self.performance_metrics.total_runtime_ms = start_time.elapsed().as_millis() as f64;
309 self.performance_metrics.speedup_factor = self.calculate_speedup_factor();
310 self.performance_metrics.solution_quality =
311 HybridSpatialOptimizer::evaluate_solution_quality(&best_solution, &objective_function);
312
313 Ok(HybridOptimizationResult {
314 optimal_parameters: best_solution,
315 optimal_value: best_value,
316 iterations: iteration,
317 quantum_advantage_ratio: self.performance_metrics.quantum_advantage_episodes as f64
318 / iteration as f64,
319 performance_metrics: self.performance_metrics.clone(),
320 })
321 }
322
323 fn initialize_parameters(&mut self, dim: usize) {
325 self.classical_state.parameters = Array1::from_shape_fn(dim, |_| random_f64() * 2.0 - 1.0);
326 self.classical_state.gradients = Array1::zeros(dim);
327 self.classical_state.hessian_approx = Array2::eye(dim);
328 self.classical_state.momentum = Array1::zeros(dim);
329 self.classical_state.adam_state.m = Array1::zeros(dim);
330 self.classical_state.adam_state.v = Array1::zeros(dim);
331 self.classical_state.adam_state.t = 0;
332 }
333
334 async fn select_optimal_paradigm<F>(
336 &self,
337 iteration: usize,
338 objective_function: &F,
339 ) -> SpatialResult<bool>
340 where
341 F: Fn(&Array1<f64>) -> f64 + Send + Sync,
342 {
343 if !self.adaptive_switching {
344 return Ok(random_f64() < self.quantum_weight);
346 }
347
348 let quantum_success_rate = if self.performance_metrics.quantum_advantage_episodes
350 + self.performance_metrics.classical_advantage_episodes
351 > 0
352 {
353 self.performance_metrics.quantum_advantage_episodes as f64
354 / (self.performance_metrics.quantum_advantage_episodes
355 + self.performance_metrics.classical_advantage_episodes)
356 as f64
357 } else {
358 0.5
359 };
360
361 let exploration_phase = iteration < 100;
363 let use_quantum =
364 exploration_phase || quantum_success_rate > 0.6 || random_f64() < self.quantum_weight;
365
366 Ok(use_quantum)
367 }
368
369 async fn quantum_optimization_step<F>(
371 &mut self,
372 objective_function: &F,
373 ) -> SpatialResult<OptimizationStepResult>
374 where
375 F: Fn(&Array1<f64>) -> f64 + Send + Sync,
376 {
377 let spatial_data = self.encode_optimization_problem_as_spatial_data();
379
380 {
405 let mut quantum_clusterer = QuantumClusterer::new(2);
407 let dummy_data = Array2::from_shape_fn((10, 2), |(i, j)| {
408 self.classical_state.parameters[i.min(self.classical_state.parameters.len() - 1)]
409 + j as f64
410 });
411 let (centroids_, _) = quantum_clusterer.fit(&dummy_data.view())?;
412
413 let quantum_parameters = centroids_.row(0).to_owned();
414 let value = objective_function(&quantum_parameters);
415
416 Ok(OptimizationStepResult {
417 parameters: quantum_parameters,
418 value,
419 gradient: None,
420 convergence_info: QuantumConvergenceInfo {
421 ground_energy: value,
422 quantum_variance: 0.1,
423 entanglement_entropy: 0.5,
424 },
425 })
426 }
427 }
428
429 fn classical_optimization_step<F>(
431 &mut self,
432 objective_function: &F,
433 ) -> SpatialResult<OptimizationStepResult>
434 where
435 F: Fn(&Array1<f64>) -> f64,
436 {
437 let epsilon = 1e-6;
439 let mut gradients = Array1::zeros(self.classical_state.parameters.len());
440
441 for i in 0..self.classical_state.parameters.len() {
442 let mut params_plus = self.classical_state.parameters.clone();
443 let mut params_minus = self.classical_state.parameters.clone();
444
445 params_plus[i] += epsilon;
446 params_minus[i] -= epsilon;
447
448 let value_plus = objective_function(¶ms_plus);
449 let value_minus = objective_function(¶ms_minus);
450
451 gradients[i] = (value_plus - value_minus) / (2.0 * epsilon);
452 }
453
454 self.classical_state.gradients = gradients.clone();
455
456 self.classical_state.adam_state.t += 1;
458
459 self.classical_state.adam_state.m = self.classical_state.adam_state.beta1
461 * &self.classical_state.adam_state.m
462 + (1.0 - self.classical_state.adam_state.beta1) * &gradients;
463
464 let gradients_squared = gradients.mapv(|x| x * x);
466 self.classical_state.adam_state.v = self.classical_state.adam_state.beta2
467 * &self.classical_state.adam_state.v
468 + (1.0 - self.classical_state.adam_state.beta2) * &gradients_squared;
469
470 let m_hat = &self.classical_state.adam_state.m
472 / (1.0
473 - self
474 .classical_state
475 .adam_state
476 .beta1
477 .powi(self.classical_state.adam_state.t as i32));
478
479 let v_hat = &self.classical_state.adam_state.v
481 / (1.0
482 - self
483 .classical_state
484 .adam_state
485 .beta2
486 .powi(self.classical_state.adam_state.t as i32));
487
488 let update = &m_hat / (v_hat.mapv(|x| x.sqrt()) + self.classical_state.adam_state.epsilon);
490 self.classical_state.parameters =
491 &self.classical_state.parameters - self.classical_state.learning_rate * &update;
492
493 let value = objective_function(&self.classical_state.parameters);
494
495 Ok(OptimizationStepResult {
496 parameters: self.classical_state.parameters.clone(),
497 value,
498 gradient: Some(gradients),
499 convergence_info: QuantumConvergenceInfo {
500 ground_energy: value,
501 quantum_variance: 0.0, entanglement_entropy: 0.0,
503 },
504 })
505 }
506
507 fn encode_optimization_problem_as_spatial_data(&self) -> Array2<f64> {
509 let n_points = 20;
510 let ndims = self.classical_state.parameters.len().min(4); Array2::from_shape_fn((n_points, ndims), |(i, j)| {
513 let param_idx = j % self.classical_state.parameters.len();
514 self.classical_state.parameters[param_idx] + (i as f64 / n_points as f64 - 0.5) * 0.1
515 })
517 }
518
519 #[allow(dead_code)]
521 fn extract_parameters_from_quantum_state(
522 &self,
523 quantumstate: &QuantumState,
524 ) -> SpatialResult<Array1<f64>> {
525 let targetdim = self.classical_state.parameters.len();
526 let mut parameters = Array1::zeros(targetdim);
527
528 for i in 0..targetdim {
530 let amplitude_idx = i % quantumstate.amplitudes.len();
531 let amplitude = quantumstate.amplitudes[amplitude_idx];
532
533 let real_part = amplitude.re;
535 let imag_part = amplitude.im;
536 let magnitude = (real_part * real_part + imag_part * imag_part).sqrt();
537
538 parameters[i] = magnitude * 2.0 - 1.0; }
540
541 Ok(parameters)
542 }
543
544 #[allow(dead_code)]
546 fn calculate_quantum_variance(quantumstate: &QuantumState) -> f64 {
547 let mut variance = 0.0;
548 let mean_amplitude = quantumstate
549 .amplitudes
550 .iter()
551 .map(|a| a.norm())
552 .sum::<f64>()
553 / quantumstate.amplitudes.len() as f64;
554
555 for amplitude in &quantumstate.amplitudes {
556 let deviation = amplitude.norm() - mean_amplitude;
557 variance += deviation * deviation;
558 }
559
560 variance / quantumstate.amplitudes.len() as f64
561 }
562
563 async fn transfer_quantum_information(
565 &mut self,
566 quantum_result: &OptimizationStepResult,
567 ) -> SpatialResult<()> {
568 if self.coupling_parameters.quantum_feedback {
569 let coupling_strength = self.coupling_parameters.coupling_strength;
571
572 for i in 0..self
573 .classical_state
574 .parameters
575 .len()
576 .min(quantum_result.parameters.len())
577 {
578 self.classical_state.parameters[i] = (1.0 - coupling_strength)
579 * self.classical_state.parameters[i]
580 + coupling_strength * quantum_result.parameters[i];
581 }
582
583 let quantum_convergence =
585 1.0 / (1.0 + quantum_result.convergence_info.quantum_variance);
586 self.classical_state.learning_rate *= 0.9 + 0.2 * quantum_convergence;
587 }
588
589 Ok(())
590 }
591
592 async fn transfer_classical_information(
594 &mut self,
595 classical_result: &OptimizationStepResult,
596 ) -> SpatialResult<()> {
597 if self.coupling_parameters.classical_bias {
598 }
617
618 Ok(())
619 }
620
621 async fn synchronize_quantum_classical_states(&mut self) -> SpatialResult<()> {
623 if self.coupling_parameters.cross_validation {
624 let quantum_performance = self.performance_metrics.quantum_advantage_episodes as f64;
629 let classical_performance =
630 self.performance_metrics.classical_advantage_episodes as f64;
631 let total_performance = quantum_performance + classical_performance;
632
633 if total_performance > 0.0 {
634 let quantum_confidence = quantum_performance / total_performance;
635 self.quantum_weight = 0.5 * self.quantum_weight + 0.5 * quantum_confidence;
636 self.classical_weight = 1.0 - self.quantum_weight;
637 }
638 }
639
640 Ok(())
641 }
642
643 fn check_convergence(&self, solution: &Array1<f64>, iteration: usize) -> bool {
645 iteration > 10
647 && (self
648 .classical_state
649 .gradients
650 .iter()
651 .map(|x| x.abs())
652 .sum::<f64>()
653 < 1e-6
654 || iteration > 1000)
655 }
656
657 fn calculate_speedup_factor(&self) -> f64 {
659 let _quantum_time = self.performance_metrics.quantum_runtime_ms.max(1.0);
660 let _classical_time = self.performance_metrics.classical_runtime_ms.max(1.0);
661
662 let quantum_advantage_ratio = self.performance_metrics.quantum_advantage_episodes as f64
664 / (self.performance_metrics.quantum_advantage_episodes
665 + self.performance_metrics.classical_advantage_episodes)
666 .max(1) as f64;
667
668 1.0 + quantum_advantage_ratio * 2.0 }
670
671 fn evaluate_solution_quality<F>(_solution: &Array1<f64>, objectivefunction: &F) -> f64
673 where
674 F: Fn(&Array1<f64>) -> f64,
675 {
676 let value = objectivefunction(_solution);
677 1.0 / (1.0 + value.abs())
679 }
680}
681
682#[derive(Debug, Clone)]
684pub struct OptimizationStepResult {
685 pub parameters: Array1<f64>,
687 pub value: f64,
689 pub gradient: Option<Array1<f64>>,
691 pub convergence_info: QuantumConvergenceInfo,
693}
694
695#[derive(Debug, Clone)]
697pub struct QuantumConvergenceInfo {
698 pub ground_energy: f64,
700 pub quantum_variance: f64,
702 pub entanglement_entropy: f64,
704}
705
706#[derive(Debug, Clone)]
708pub struct HybridOptimizationResult {
709 pub optimal_parameters: Array1<f64>,
711 pub optimal_value: f64,
713 pub iterations: usize,
715 pub quantum_advantage_ratio: f64,
717 pub performance_metrics: HybridPerformanceMetrics,
719}
720
721#[derive(Debug)]
723pub struct HybridClusterer {
724 _numclusters: usize,
726 quantum_exploration_ratio: f64,
728 classical_refinement: bool,
730 adaptive_switching: bool,
732 quantum_error_correction: bool,
734 quantum_clusterer: QuantumClusterer,
736 performance_metrics: HybridClusteringMetrics,
738}
739
740#[derive(Debug, Clone)]
742pub struct HybridClusteringMetrics {
743 pub quantum_time_ms: f64,
745 pub classical_time_ms: f64,
747 pub total_time_ms: f64,
749 pub speedup_factor: f64,
751 pub clustering_quality: f64,
753 pub quantum_advantage: bool,
755}
756
757impl HybridClusterer {
758 pub fn new(_numclusters: usize) -> Self {
760 Self {
761 _numclusters,
762 quantum_exploration_ratio: 0.7,
763 classical_refinement: true,
764 adaptive_switching: true,
765 quantum_error_correction: false,
766 quantum_clusterer: QuantumClusterer::new(_numclusters),
767 performance_metrics: HybridClusteringMetrics {
768 quantum_time_ms: 0.0,
769 classical_time_ms: 0.0,
770 total_time_ms: 0.0,
771 speedup_factor: 1.0,
772 clustering_quality: 0.0,
773 quantum_advantage: false,
774 },
775 }
776 }
777
778 pub fn with_quantum_exploration_ratio(mut self, ratio: f64) -> Self {
780 self.quantum_exploration_ratio = ratio.clamp(0.0, 1.0);
781 self
782 }
783
784 pub fn with_classical_refinement(mut self, enabled: bool) -> Self {
786 self.classical_refinement = enabled;
787 self
788 }
789
790 pub fn with_adaptive_switching(mut self, enabled: bool) -> Self {
792 self.adaptive_switching = enabled;
793 self
794 }
795
796 pub fn with_quantum_error_correction(mut self, enabled: bool) -> Self {
798 self.quantum_error_correction = enabled;
799 self
800 }
801
802 pub async fn fit(
804 &mut self,
805 points: &ArrayView2<'_, f64>,
806 ) -> SpatialResult<(Array2<f64>, Array1<usize>, HybridClusteringMetrics)> {
807 let start_time = Instant::now();
808
809 let quantum_start = Instant::now();
811 let (quantum_centroids, quantum_assignments) = self.quantum_clusterer.fit(points)?;
812 self.performance_metrics.quantum_time_ms = quantum_start.elapsed().as_millis() as f64;
813
814 let (final_centroids, final_assignments) = if self.classical_refinement {
816 let classical_start = Instant::now();
817 let refined_result = self
818 .classical_refinement_step(points, &quantum_centroids)
819 .await?;
820 self.performance_metrics.classical_time_ms =
821 classical_start.elapsed().as_millis() as f64;
822 refined_result
823 } else {
824 (quantum_centroids, quantum_assignments)
825 };
826
827 self.performance_metrics.total_time_ms = start_time.elapsed().as_millis() as f64;
828 self.performance_metrics.clustering_quality =
829 self.calculate_silhouette_score(points, &final_centroids, &final_assignments);
830 self.performance_metrics.speedup_factor = self.calculate_clustering_speedup();
831 self.performance_metrics.quantum_advantage = self.performance_metrics.speedup_factor > 1.2;
832
833 Ok((
834 final_centroids,
835 final_assignments,
836 self.performance_metrics.clone(),
837 ))
838 }
839
840 async fn classical_refinement_step(
842 &self,
843 points: &ArrayView2<'_, f64>,
844 initial_centroids: &Array2<f64>,
845 ) -> SpatialResult<(Array2<f64>, Array1<usize>)> {
846 let (n_points, ndims) = points.dim();
847 let mut centroids = initial_centroids.clone();
848 let mut assignments = Array1::zeros(n_points);
849
850 for _iteration in 0..50 {
852 for (i, point) in points.outer_iter().enumerate() {
855 let mut best_cluster = 0;
856 let mut best_distance = f64::INFINITY;
857
858 for (j, centroid) in centroids.outer_iter().enumerate() {
859 let distance: f64 = point
860 .iter()
861 .zip(centroid.iter())
862 .map(|(&a, &b)| (a - b).powi(2))
863 .sum::<f64>()
864 .sqrt();
865
866 if distance < best_distance {
867 best_distance = distance;
868 best_cluster = j;
869 }
870 }
871
872 assignments[i] = best_cluster;
873 }
874
875 let mut new_centroids = Array2::zeros((self._numclusters, ndims));
877 let mut cluster_counts = vec![0; self._numclusters];
878
879 for (i, point) in points.outer_iter().enumerate() {
880 let cluster = assignments[i];
881 cluster_counts[cluster] += 1;
882
883 for j in 0..ndims {
884 new_centroids[[cluster, j]] += point[j];
885 }
886 }
887
888 for i in 0..self._numclusters {
890 if cluster_counts[i] > 0 {
891 for j in 0..ndims {
892 new_centroids[[i, j]] /= cluster_counts[i] as f64;
893 }
894 }
895 }
896
897 let centroid_change = self.calculate_centroid_change(¢roids, &new_centroids);
899 centroids = new_centroids;
900
901 if centroid_change < 1e-6 {
902 break;
903 }
904 }
905
906 Ok((centroids, assignments))
907 }
908
909 fn calculate_centroid_change(
911 &self,
912 old_centroids: &Array2<f64>,
913 new_centroids: &Array2<f64>,
914 ) -> f64 {
915 let mut total_change = 0.0;
916
917 for (old_row, new_row) in old_centroids.outer_iter().zip(new_centroids.outer_iter()) {
918 let change: f64 = old_row
919 .iter()
920 .zip(new_row.iter())
921 .map(|(&a, &b)| (a - b).powi(2))
922 .sum::<f64>()
923 .sqrt();
924 total_change += change;
925 }
926
927 total_change / old_centroids.nrows() as f64
928 }
929
930 fn calculate_silhouette_score(
932 &self,
933 points: &ArrayView2<'_, f64>,
934 _centroids: &Array2<f64>,
935 assignments: &Array1<usize>,
936 ) -> f64 {
937 let n_points = points.nrows();
938 let mut silhouette_scores = Vec::new();
939
940 for i in 0..n_points {
941 let point_i = points.row(i);
942 let cluster_i = assignments[i];
943
944 let mut intra_cluster_distance = 0.0;
946 let mut intra_cluster_count = 0;
947
948 for j in 0..n_points {
949 if i != j && assignments[j] == cluster_i {
950 let distance: f64 = point_i
951 .iter()
952 .zip(points.row(j).iter())
953 .map(|(&a, &b)| (a - b).powi(2))
954 .sum::<f64>()
955 .sqrt();
956
957 intra_cluster_distance += distance;
958 intra_cluster_count += 1;
959 }
960 }
961
962 let a = if intra_cluster_count > 0 {
963 intra_cluster_distance / intra_cluster_count as f64
964 } else {
965 0.0
966 };
967
968 let mut min_inter_cluster_distance = f64::INFINITY;
970
971 for cluster_k in 0..self._numclusters {
972 if cluster_k != cluster_i {
973 let mut inter_cluster_distance = 0.0;
974 let mut inter_cluster_count = 0;
975
976 for j in 0..n_points {
977 if assignments[j] == cluster_k {
978 let distance: f64 = point_i
979 .iter()
980 .zip(points.row(j).iter())
981 .map(|(&a, &b)| (a - b).powi(2))
982 .sum::<f64>()
983 .sqrt();
984
985 inter_cluster_distance += distance;
986 inter_cluster_count += 1;
987 }
988 }
989
990 if inter_cluster_count > 0 {
991 let avg_inter_distance =
992 inter_cluster_distance / inter_cluster_count as f64;
993 min_inter_cluster_distance =
994 min_inter_cluster_distance.min(avg_inter_distance);
995 }
996 }
997 }
998
999 let b = min_inter_cluster_distance;
1000
1001 let silhouette = if a.max(b) > 0.0 {
1003 (b - a) / a.max(b)
1004 } else {
1005 0.0
1006 };
1007
1008 silhouette_scores.push(silhouette);
1009 }
1010
1011 silhouette_scores.iter().sum::<f64>() / silhouette_scores.len() as f64
1013 }
1014
1015 fn calculate_clustering_speedup(&self) -> f64 {
1017 let _quantum_time = self.performance_metrics.quantum_time_ms.max(1.0);
1019 let total_time = self.performance_metrics.total_time_ms.max(1.0);
1020
1021 let estimated_classical_time = self.performance_metrics.classical_time_ms * 2.0;
1023
1024 if estimated_classical_time > 0.0 {
1025 estimated_classical_time / total_time
1026 } else {
1027 1.0
1028 }
1029 }
1030}
1031
1032#[cfg(test)]
1033mod tests {
1034 use super::*;
1035 use scirs2_core::ndarray::array;
1036
1037 #[tokio::test]
1038 async fn test_hybrid_spatial_optimizer() {
1039 let mut optimizer = HybridSpatialOptimizer::new()
1040 .with_quantum_classical_coupling(0.5)
1041 .with_adaptive_switching(true);
1042
1043 let objective = |x: &Array1<f64>| -> f64 { x.iter().map(|&val| val * val).sum() };
1045
1046 let result = optimizer.optimize_spatial_function(objective).await;
1047 assert!(result.is_ok());
1048
1049 let opt_result = result.unwrap();
1050 assert!(opt_result.optimal_value < 10.0); assert!(opt_result.iterations > 0);
1052 assert!(
1053 opt_result.quantum_advantage_ratio >= 0.0 && opt_result.quantum_advantage_ratio <= 1.0
1054 );
1055 }
1056
1057 #[tokio::test]
1058 #[ignore]
1059 async fn test_hybrid_clusterer() {
1060 let points = array![
1061 [0.0, 0.0],
1062 [1.0, 0.0],
1063 [0.0, 1.0],
1064 [1.0, 1.0],
1065 [10.0, 10.0],
1066 [11.0, 10.0]
1067 ];
1068 let mut clusterer = HybridClusterer::new(2)
1069 .with_quantum_exploration_ratio(0.7)
1070 .with_classical_refinement(true);
1071
1072 let result = clusterer.fit(&points.view()).await;
1073 assert!(result.is_ok());
1074
1075 let (centroids, assignments, metrics) = result.unwrap();
1076 assert_eq!(centroids.nrows(), 2);
1077 assert_eq!(assignments.len(), 6);
1078 assert!(metrics.clustering_quality > -1.0 && metrics.clustering_quality <= 1.0);
1079 assert!(metrics.total_time_ms > 0.0);
1080 }
1081
1082 #[test]
1083 fn test_hybrid_coupling_parameters() {
1084 let optimizer = HybridSpatialOptimizer::new().with_quantum_classical_coupling(0.3);
1085
1086 assert!((optimizer.quantum_weight - 0.3).abs() < 1e-10);
1087 assert!((optimizer.classical_weight - 0.7).abs() < 1e-10);
1088 }
1089
1090 #[test]
1091 fn test_clustering_quality_metrics() {
1092 let clusterer = HybridClusterer::new(2);
1093 let points = array![[0.0, 0.0], [1.0, 1.0], [10.0, 10.0], [11.0, 11.0]];
1094 let centroids = array![[0.5, 0.5], [10.5, 10.5]];
1095 let assignments = array![0, 0, 1, 1];
1096
1097 let silhouette =
1098 clusterer.calculate_silhouette_score(&points.view(), ¢roids, &assignments);
1099 assert!(silhouette > 0.0); }
1101}