1use crate::error::SpatialResult;
58use crate::quantum_inspired::{QuantumClusterer, QuantumState};
59use ndarray::{Array1, Array2, ArrayView2};
60use std::time::Instant;
61
62#[allow(dead_code)]
64#[derive(Debug)]
65pub struct HybridSpatialOptimizer {
66 quantum_weight: f64,
68 classical_weight: f64,
70 adaptive_switching: bool,
72 quantum_error_correction: bool,
74 classical_state: ClassicalOptimizerState,
78 coupling_parameters: HybridCouplingParameters,
80 performance_metrics: HybridPerformanceMetrics,
82 quantum_advantage_threshold: f64,
84}
85
86#[derive(Debug, Clone)]
88pub struct ClassicalOptimizerState {
89 pub parameters: Array1<f64>,
91 pub gradients: Array1<f64>,
93 pub hessian_approx: Array2<f64>,
95 pub learning_rate: f64,
97 pub momentum: Array1<f64>,
99 pub adam_state: AdamOptimizerState,
101}
102
103#[derive(Debug, Clone)]
105pub struct AdamOptimizerState {
106 pub m: Array1<f64>,
108 pub v: Array1<f64>,
110 pub beta1: f64,
112 pub beta2: f64,
114 pub epsilon: f64,
116 pub t: usize,
118}
119
120#[derive(Debug, Clone)]
122pub struct HybridCouplingParameters {
123 pub exchange_rate: f64,
125 pub coupling_strength: f64,
127 pub sync_frequency: usize,
129 pub cross_validation: bool,
131 pub quantum_feedback: bool,
133 pub classical_bias: bool,
135}
136
137#[derive(Debug, Clone)]
139pub struct HybridPerformanceMetrics {
140 pub quantum_runtime_ms: f64,
142 pub classical_runtime_ms: f64,
144 pub total_runtime_ms: f64,
146 pub speedup_factor: f64,
148 pub convergence_rate: f64,
150 pub solution_quality: f64,
152 pub quantum_advantage_episodes: usize,
154 pub classical_advantage_episodes: usize,
156}
157
158impl Default for HybridSpatialOptimizer {
159 fn default() -> Self {
160 Self::new()
161 }
162}
163
164impl HybridSpatialOptimizer {
165 pub fn new() -> Self {
167 Self {
168 quantum_weight: 0.5,
169 classical_weight: 0.5,
170 adaptive_switching: true,
171 quantum_error_correction: false,
172 classical_state: ClassicalOptimizerState {
174 parameters: Array1::zeros(0),
175 gradients: Array1::zeros(0),
176 hessian_approx: Array2::zeros((0, 0)),
177 learning_rate: 0.01,
178 momentum: Array1::zeros(0),
179 adam_state: AdamOptimizerState {
180 m: Array1::zeros(0),
181 v: Array1::zeros(0),
182 beta1: 0.9,
183 beta2: 0.999,
184 epsilon: 1e-8,
185 t: 0,
186 },
187 },
188 coupling_parameters: HybridCouplingParameters {
189 exchange_rate: 0.1,
190 coupling_strength: 0.5,
191 sync_frequency: 10,
192 cross_validation: true,
193 quantum_feedback: true,
194 classical_bias: true,
195 },
196 performance_metrics: HybridPerformanceMetrics {
197 quantum_runtime_ms: 0.0,
198 classical_runtime_ms: 0.0,
199 total_runtime_ms: 0.0,
200 speedup_factor: 1.0,
201 convergence_rate: 0.0,
202 solution_quality: 0.0,
203 quantum_advantage_episodes: 0,
204 classical_advantage_episodes: 0,
205 },
206 quantum_advantage_threshold: 1.2,
207 }
208 }
209
210 pub fn with_quantum_classical_coupling(mut self, quantumweight: f64) -> Self {
212 self.quantum_weight = quantumweight.clamp(0.0, 1.0);
213 self.classical_weight = 1.0 - self.quantum_weight;
214 self
215 }
216
217 pub fn with_variational_quantum_component(self, enabled: bool) -> Self {
219 if enabled {
220 } else {
222 }
224 self
225 }
226
227 pub fn with_adaptive_switching(mut self, enabled: bool) -> Self {
229 self.adaptive_switching = enabled;
230 self
231 }
232
233 pub async fn optimize_spatial_function<F>(
235 &mut self,
236 objective_function: F,
237 ) -> SpatialResult<HybridOptimizationResult>
238 where
239 F: Fn(&Array1<f64>) -> f64 + Send + Sync,
240 {
241 let start_time = Instant::now();
242
243 let paramdim = 10; self.initialize_parameters(paramdim);
246
247 let mut best_solution = self.classical_state.parameters.clone();
248 let mut best_value = f64::INFINITY;
249 let mut iteration = 0;
250 let max_iterations = 1000;
251
252 while iteration < max_iterations {
254 let _iteration_start = Instant::now();
255
256 let use_quantum = self
258 .select_optimal_paradigm(iteration, &objective_function)
259 .await?;
260
261 if use_quantum {
262 let quantum_start = Instant::now();
264 let quantum_result = self.quantum_optimization_step(&objective_function).await?;
265 self.performance_metrics.quantum_runtime_ms +=
266 quantum_start.elapsed().as_millis() as f64;
267
268 if quantum_result.value < best_value {
269 best_value = quantum_result.value;
270 best_solution = quantum_result.parameters.clone();
271 self.performance_metrics.quantum_advantage_episodes += 1;
272 }
273
274 self.transfer_quantum_information(&quantum_result).await?;
276 } else {
277 let classical_start = Instant::now();
279 let classical_result = self.classical_optimization_step(&objective_function)?;
280 self.performance_metrics.classical_runtime_ms +=
281 classical_start.elapsed().as_millis() as f64;
282
283 if classical_result.value < best_value {
284 best_value = classical_result.value;
285 best_solution = classical_result.parameters.clone();
286 self.performance_metrics.classical_advantage_episodes += 1;
287 }
288
289 self.transfer_classical_information(&classical_result)
291 .await?;
292 }
293
294 if iteration % self.coupling_parameters.sync_frequency == 0 {
296 self.synchronize_quantum_classical_states().await?;
297 }
298
299 iteration += 1;
300
301 if self.check_convergence(&best_solution, iteration) {
303 break;
304 }
305 }
306
307 self.performance_metrics.total_runtime_ms = start_time.elapsed().as_millis() as f64;
308 self.performance_metrics.speedup_factor = self.calculate_speedup_factor();
309 self.performance_metrics.solution_quality =
310 HybridSpatialOptimizer::evaluate_solution_quality(&best_solution, &objective_function);
311
312 Ok(HybridOptimizationResult {
313 optimal_parameters: best_solution,
314 optimal_value: best_value,
315 iterations: iteration,
316 quantum_advantage_ratio: self.performance_metrics.quantum_advantage_episodes as f64
317 / iteration as f64,
318 performance_metrics: self.performance_metrics.clone(),
319 })
320 }
321
322 fn initialize_parameters(&mut self, dim: usize) {
324 self.classical_state.parameters =
325 Array1::from_shape_fn(dim, |_| rand::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(rand::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 = exploration_phase
364 || quantum_success_rate > 0.6
365 || rand::random::<f64>() < self.quantum_weight;
366
367 Ok(use_quantum)
368 }
369
370 async fn quantum_optimization_step<F>(
372 &mut self,
373 objective_function: &F,
374 ) -> SpatialResult<OptimizationStepResult>
375 where
376 F: Fn(&Array1<f64>) -> f64 + Send + Sync,
377 {
378 let spatial_data = self.encode_optimization_problem_as_spatial_data();
380
381 {
406 let mut quantum_clusterer = QuantumClusterer::new(2);
408 let dummy_data = Array2::from_shape_fn((10, 2), |(i, j)| {
409 self.classical_state.parameters[i.min(self.classical_state.parameters.len() - 1)]
410 + j as f64
411 });
412 let (centroids_, _) = quantum_clusterer.fit(&dummy_data.view())?;
413
414 let quantum_parameters = centroids_.row(0).to_owned();
415 let value = objective_function(&quantum_parameters);
416
417 Ok(OptimizationStepResult {
418 parameters: quantum_parameters,
419 value,
420 gradient: None,
421 convergence_info: QuantumConvergenceInfo {
422 ground_energy: value,
423 quantum_variance: 0.1,
424 entanglement_entropy: 0.5,
425 },
426 })
427 }
428 }
429
430 fn classical_optimization_step<F>(
432 &mut self,
433 objective_function: &F,
434 ) -> SpatialResult<OptimizationStepResult>
435 where
436 F: Fn(&Array1<f64>) -> f64,
437 {
438 let epsilon = 1e-6;
440 let mut gradients = Array1::zeros(self.classical_state.parameters.len());
441
442 for i in 0..self.classical_state.parameters.len() {
443 let mut params_plus = self.classical_state.parameters.clone();
444 let mut params_minus = self.classical_state.parameters.clone();
445
446 params_plus[i] += epsilon;
447 params_minus[i] -= epsilon;
448
449 let value_plus = objective_function(¶ms_plus);
450 let value_minus = objective_function(¶ms_minus);
451
452 gradients[i] = (value_plus - value_minus) / (2.0 * epsilon);
453 }
454
455 self.classical_state.gradients = gradients.clone();
456
457 self.classical_state.adam_state.t += 1;
459
460 self.classical_state.adam_state.m = self.classical_state.adam_state.beta1
462 * &self.classical_state.adam_state.m
463 + (1.0 - self.classical_state.adam_state.beta1) * &gradients;
464
465 let gradients_squared = gradients.mapv(|x| x * x);
467 self.classical_state.adam_state.v = self.classical_state.adam_state.beta2
468 * &self.classical_state.adam_state.v
469 + (1.0 - self.classical_state.adam_state.beta2) * &gradients_squared;
470
471 let m_hat = &self.classical_state.adam_state.m
473 / (1.0
474 - self
475 .classical_state
476 .adam_state
477 .beta1
478 .powi(self.classical_state.adam_state.t as i32));
479
480 let v_hat = &self.classical_state.adam_state.v
482 / (1.0
483 - self
484 .classical_state
485 .adam_state
486 .beta2
487 .powi(self.classical_state.adam_state.t as i32));
488
489 let update = &m_hat / (v_hat.mapv(|x| x.sqrt()) + self.classical_state.adam_state.epsilon);
491 self.classical_state.parameters =
492 &self.classical_state.parameters - self.classical_state.learning_rate * &update;
493
494 let value = objective_function(&self.classical_state.parameters);
495
496 Ok(OptimizationStepResult {
497 parameters: self.classical_state.parameters.clone(),
498 value,
499 gradient: Some(gradients),
500 convergence_info: QuantumConvergenceInfo {
501 ground_energy: value,
502 quantum_variance: 0.0, entanglement_entropy: 0.0,
504 },
505 })
506 }
507
508 fn encode_optimization_problem_as_spatial_data(&self) -> Array2<f64> {
510 let n_points = 20;
511 let ndims = self.classical_state.parameters.len().min(4); Array2::from_shape_fn((n_points, ndims), |(i, j)| {
514 let param_idx = j % self.classical_state.parameters.len();
515 self.classical_state.parameters[param_idx] + (i as f64 / n_points as f64 - 0.5) * 0.1
516 })
518 }
519
520 #[allow(dead_code)]
522 fn extract_parameters_from_quantum_state(
523 &self,
524 quantumstate: &QuantumState,
525 ) -> SpatialResult<Array1<f64>> {
526 let targetdim = self.classical_state.parameters.len();
527 let mut parameters = Array1::zeros(targetdim);
528
529 for i in 0..targetdim {
531 let amplitude_idx = i % quantumstate.amplitudes.len();
532 let amplitude = quantumstate.amplitudes[amplitude_idx];
533
534 let real_part = amplitude.re;
536 let imag_part = amplitude.im;
537 let magnitude = (real_part * real_part + imag_part * imag_part).sqrt();
538
539 parameters[i] = magnitude * 2.0 - 1.0; }
541
542 Ok(parameters)
543 }
544
545 #[allow(dead_code)]
547 fn calculate_quantum_variance(quantumstate: &QuantumState) -> f64 {
548 let mut variance = 0.0;
549 let mean_amplitude = quantumstate
550 .amplitudes
551 .iter()
552 .map(|a| a.norm())
553 .sum::<f64>()
554 / quantumstate.amplitudes.len() as f64;
555
556 for amplitude in &quantumstate.amplitudes {
557 let deviation = amplitude.norm() - mean_amplitude;
558 variance += deviation * deviation;
559 }
560
561 variance / quantumstate.amplitudes.len() as f64
562 }
563
564 async fn transfer_quantum_information(
566 &mut self,
567 quantum_result: &OptimizationStepResult,
568 ) -> SpatialResult<()> {
569 if self.coupling_parameters.quantum_feedback {
570 let coupling_strength = self.coupling_parameters.coupling_strength;
572
573 for i in 0..self
574 .classical_state
575 .parameters
576 .len()
577 .min(quantum_result.parameters.len())
578 {
579 self.classical_state.parameters[i] = (1.0 - coupling_strength)
580 * self.classical_state.parameters[i]
581 + coupling_strength * quantum_result.parameters[i];
582 }
583
584 let quantum_convergence =
586 1.0 / (1.0 + quantum_result.convergence_info.quantum_variance);
587 self.classical_state.learning_rate *= 0.9 + 0.2 * quantum_convergence;
588 }
589
590 Ok(())
591 }
592
593 async fn transfer_classical_information(
595 &mut self,
596 classical_result: &OptimizationStepResult,
597 ) -> SpatialResult<()> {
598 if self.coupling_parameters.classical_bias {
599 }
618
619 Ok(())
620 }
621
622 async fn synchronize_quantum_classical_states(&mut self) -> SpatialResult<()> {
624 if self.coupling_parameters.cross_validation {
625 let quantum_performance = self.performance_metrics.quantum_advantage_episodes as f64;
630 let classical_performance =
631 self.performance_metrics.classical_advantage_episodes as f64;
632 let total_performance = quantum_performance + classical_performance;
633
634 if total_performance > 0.0 {
635 let quantum_confidence = quantum_performance / total_performance;
636 self.quantum_weight = 0.5 * self.quantum_weight + 0.5 * quantum_confidence;
637 self.classical_weight = 1.0 - self.quantum_weight;
638 }
639 }
640
641 Ok(())
642 }
643
644 fn check_convergence(&self, solution: &Array1<f64>, iteration: usize) -> bool {
646 iteration > 10
648 && (self
649 .classical_state
650 .gradients
651 .iter()
652 .map(|x| x.abs())
653 .sum::<f64>()
654 < 1e-6
655 || iteration > 1000)
656 }
657
658 fn calculate_speedup_factor(&self) -> f64 {
660 let _quantum_time = self.performance_metrics.quantum_runtime_ms.max(1.0);
661 let _classical_time = self.performance_metrics.classical_runtime_ms.max(1.0);
662
663 let quantum_advantage_ratio = self.performance_metrics.quantum_advantage_episodes as f64
665 / (self.performance_metrics.quantum_advantage_episodes
666 + self.performance_metrics.classical_advantage_episodes)
667 .max(1) as f64;
668
669 1.0 + quantum_advantage_ratio * 2.0 }
671
672 fn evaluate_solution_quality<F>(_solution: &Array1<f64>, objectivefunction: &F) -> f64
674 where
675 F: Fn(&Array1<f64>) -> f64,
676 {
677 let value = objectivefunction(_solution);
678 1.0 / (1.0 + value.abs())
680 }
681}
682
683#[derive(Debug, Clone)]
685pub struct OptimizationStepResult {
686 pub parameters: Array1<f64>,
688 pub value: f64,
690 pub gradient: Option<Array1<f64>>,
692 pub convergence_info: QuantumConvergenceInfo,
694}
695
696#[derive(Debug, Clone)]
698pub struct QuantumConvergenceInfo {
699 pub ground_energy: f64,
701 pub quantum_variance: f64,
703 pub entanglement_entropy: f64,
705}
706
707#[derive(Debug, Clone)]
709pub struct HybridOptimizationResult {
710 pub optimal_parameters: Array1<f64>,
712 pub optimal_value: f64,
714 pub iterations: usize,
716 pub quantum_advantage_ratio: f64,
718 pub performance_metrics: HybridPerformanceMetrics,
720}
721
722#[derive(Debug)]
724pub struct HybridClusterer {
725 _numclusters: usize,
727 quantum_exploration_ratio: f64,
729 classical_refinement: bool,
731 adaptive_switching: bool,
733 quantum_error_correction: bool,
735 quantum_clusterer: QuantumClusterer,
737 performance_metrics: HybridClusteringMetrics,
739}
740
741#[derive(Debug, Clone)]
743pub struct HybridClusteringMetrics {
744 pub quantum_time_ms: f64,
746 pub classical_time_ms: f64,
748 pub total_time_ms: f64,
750 pub speedup_factor: f64,
752 pub clustering_quality: f64,
754 pub quantum_advantage: bool,
756}
757
758impl HybridClusterer {
759 pub fn new(_numclusters: usize) -> Self {
761 Self {
762 _numclusters,
763 quantum_exploration_ratio: 0.7,
764 classical_refinement: true,
765 adaptive_switching: true,
766 quantum_error_correction: false,
767 quantum_clusterer: QuantumClusterer::new(_numclusters),
768 performance_metrics: HybridClusteringMetrics {
769 quantum_time_ms: 0.0,
770 classical_time_ms: 0.0,
771 total_time_ms: 0.0,
772 speedup_factor: 1.0,
773 clustering_quality: 0.0,
774 quantum_advantage: false,
775 },
776 }
777 }
778
779 pub fn with_quantum_exploration_ratio(mut self, ratio: f64) -> Self {
781 self.quantum_exploration_ratio = ratio.clamp(0.0, 1.0);
782 self
783 }
784
785 pub fn with_classical_refinement(mut self, enabled: bool) -> Self {
787 self.classical_refinement = enabled;
788 self
789 }
790
791 pub fn with_adaptive_switching(mut self, enabled: bool) -> Self {
793 self.adaptive_switching = enabled;
794 self
795 }
796
797 pub fn with_quantum_error_correction(mut self, enabled: bool) -> Self {
799 self.quantum_error_correction = enabled;
800 self
801 }
802
803 pub async fn fit(
805 &mut self,
806 points: &ArrayView2<'_, f64>,
807 ) -> SpatialResult<(Array2<f64>, Array1<usize>, HybridClusteringMetrics)> {
808 let start_time = Instant::now();
809
810 let quantum_start = Instant::now();
812 let (quantum_centroids, quantum_assignments) = self.quantum_clusterer.fit(points)?;
813 self.performance_metrics.quantum_time_ms = quantum_start.elapsed().as_millis() as f64;
814
815 let (final_centroids, final_assignments) = if self.classical_refinement {
817 let classical_start = Instant::now();
818 let refined_result = self
819 .classical_refinement_step(points, &quantum_centroids)
820 .await?;
821 self.performance_metrics.classical_time_ms =
822 classical_start.elapsed().as_millis() as f64;
823 refined_result
824 } else {
825 (quantum_centroids, quantum_assignments)
826 };
827
828 self.performance_metrics.total_time_ms = start_time.elapsed().as_millis() as f64;
829 self.performance_metrics.clustering_quality =
830 self.calculate_silhouette_score(points, &final_centroids, &final_assignments);
831 self.performance_metrics.speedup_factor = self.calculate_clustering_speedup();
832 self.performance_metrics.quantum_advantage = self.performance_metrics.speedup_factor > 1.2;
833
834 Ok((
835 final_centroids,
836 final_assignments,
837 self.performance_metrics.clone(),
838 ))
839 }
840
841 async fn classical_refinement_step(
843 &self,
844 points: &ArrayView2<'_, f64>,
845 initial_centroids: &Array2<f64>,
846 ) -> SpatialResult<(Array2<f64>, Array1<usize>)> {
847 let (n_points, ndims) = points.dim();
848 let mut centroids = initial_centroids.clone();
849 let mut assignments = Array1::zeros(n_points);
850
851 for _iteration in 0..50 {
853 for (i, point) in points.outer_iter().enumerate() {
856 let mut best_cluster = 0;
857 let mut best_distance = f64::INFINITY;
858
859 for (j, centroid) in centroids.outer_iter().enumerate() {
860 let distance: f64 = point
861 .iter()
862 .zip(centroid.iter())
863 .map(|(&a, &b)| (a - b).powi(2))
864 .sum::<f64>()
865 .sqrt();
866
867 if distance < best_distance {
868 best_distance = distance;
869 best_cluster = j;
870 }
871 }
872
873 assignments[i] = best_cluster;
874 }
875
876 let mut new_centroids = Array2::zeros((self._numclusters, ndims));
878 let mut cluster_counts = vec![0; self._numclusters];
879
880 for (i, point) in points.outer_iter().enumerate() {
881 let cluster = assignments[i];
882 cluster_counts[cluster] += 1;
883
884 for j in 0..ndims {
885 new_centroids[[cluster, j]] += point[j];
886 }
887 }
888
889 for i in 0..self._numclusters {
891 if cluster_counts[i] > 0 {
892 for j in 0..ndims {
893 new_centroids[[i, j]] /= cluster_counts[i] as f64;
894 }
895 }
896 }
897
898 let centroid_change = self.calculate_centroid_change(¢roids, &new_centroids);
900 centroids = new_centroids;
901
902 if centroid_change < 1e-6 {
903 break;
904 }
905 }
906
907 Ok((centroids, assignments))
908 }
909
910 fn calculate_centroid_change(
912 &self,
913 old_centroids: &Array2<f64>,
914 new_centroids: &Array2<f64>,
915 ) -> f64 {
916 let mut total_change = 0.0;
917
918 for (old_row, new_row) in old_centroids.outer_iter().zip(new_centroids.outer_iter()) {
919 let change: f64 = old_row
920 .iter()
921 .zip(new_row.iter())
922 .map(|(&a, &b)| (a - b).powi(2))
923 .sum::<f64>()
924 .sqrt();
925 total_change += change;
926 }
927
928 total_change / old_centroids.nrows() as f64
929 }
930
931 fn calculate_silhouette_score(
933 &self,
934 points: &ArrayView2<'_, f64>,
935 _centroids: &Array2<f64>,
936 assignments: &Array1<usize>,
937 ) -> f64 {
938 let n_points = points.nrows();
939 let mut silhouette_scores = Vec::new();
940
941 for i in 0..n_points {
942 let point_i = points.row(i);
943 let cluster_i = assignments[i];
944
945 let mut intra_cluster_distance = 0.0;
947 let mut intra_cluster_count = 0;
948
949 for j in 0..n_points {
950 if i != j && assignments[j] == cluster_i {
951 let distance: f64 = point_i
952 .iter()
953 .zip(points.row(j).iter())
954 .map(|(&a, &b)| (a - b).powi(2))
955 .sum::<f64>()
956 .sqrt();
957
958 intra_cluster_distance += distance;
959 intra_cluster_count += 1;
960 }
961 }
962
963 let a = if intra_cluster_count > 0 {
964 intra_cluster_distance / intra_cluster_count as f64
965 } else {
966 0.0
967 };
968
969 let mut min_inter_cluster_distance = f64::INFINITY;
971
972 for cluster_k in 0..self._numclusters {
973 if cluster_k != cluster_i {
974 let mut inter_cluster_distance = 0.0;
975 let mut inter_cluster_count = 0;
976
977 for j in 0..n_points {
978 if assignments[j] == cluster_k {
979 let distance: f64 = point_i
980 .iter()
981 .zip(points.row(j).iter())
982 .map(|(&a, &b)| (a - b).powi(2))
983 .sum::<f64>()
984 .sqrt();
985
986 inter_cluster_distance += distance;
987 inter_cluster_count += 1;
988 }
989 }
990
991 if inter_cluster_count > 0 {
992 let avg_inter_distance =
993 inter_cluster_distance / inter_cluster_count as f64;
994 min_inter_cluster_distance =
995 min_inter_cluster_distance.min(avg_inter_distance);
996 }
997 }
998 }
999
1000 let b = min_inter_cluster_distance;
1001
1002 let silhouette = if a.max(b) > 0.0 {
1004 (b - a) / a.max(b)
1005 } else {
1006 0.0
1007 };
1008
1009 silhouette_scores.push(silhouette);
1010 }
1011
1012 silhouette_scores.iter().sum::<f64>() / silhouette_scores.len() as f64
1014 }
1015
1016 fn calculate_clustering_speedup(&self) -> f64 {
1018 let _quantum_time = self.performance_metrics.quantum_time_ms.max(1.0);
1020 let total_time = self.performance_metrics.total_time_ms.max(1.0);
1021
1022 let estimated_classical_time = self.performance_metrics.classical_time_ms * 2.0;
1024
1025 if estimated_classical_time > 0.0 {
1026 estimated_classical_time / total_time
1027 } else {
1028 1.0
1029 }
1030 }
1031}
1032
1033#[cfg(test)]
1034mod tests {
1035 use super::*;
1036 use ndarray::array;
1037
1038 #[tokio::test]
1039 async fn test_hybrid_spatial_optimizer() {
1040 let mut optimizer = HybridSpatialOptimizer::new()
1041 .with_quantum_classical_coupling(0.5)
1042 .with_adaptive_switching(true);
1043
1044 let objective = |x: &Array1<f64>| -> f64 { x.iter().map(|&val| val * val).sum() };
1046
1047 let result = optimizer.optimize_spatial_function(objective).await;
1048 assert!(result.is_ok());
1049
1050 let opt_result = result.unwrap();
1051 assert!(opt_result.optimal_value < 10.0); assert!(opt_result.iterations > 0);
1053 assert!(
1054 opt_result.quantum_advantage_ratio >= 0.0 && opt_result.quantum_advantage_ratio <= 1.0
1055 );
1056 }
1057
1058 #[tokio::test]
1059 #[ignore]
1060 async fn test_hybrid_clusterer() {
1061 let points = array![
1062 [0.0, 0.0],
1063 [1.0, 0.0],
1064 [0.0, 1.0],
1065 [1.0, 1.0],
1066 [10.0, 10.0],
1067 [11.0, 10.0]
1068 ];
1069 let mut clusterer = HybridClusterer::new(2)
1070 .with_quantum_exploration_ratio(0.7)
1071 .with_classical_refinement(true);
1072
1073 let result = clusterer.fit(&points.view()).await;
1074 assert!(result.is_ok());
1075
1076 let (centroids, assignments, metrics) = result.unwrap();
1077 assert_eq!(centroids.nrows(), 2);
1078 assert_eq!(assignments.len(), 6);
1079 assert!(metrics.clustering_quality > -1.0 && metrics.clustering_quality <= 1.0);
1080 assert!(metrics.total_time_ms > 0.0);
1081 }
1082
1083 #[test]
1084 fn test_hybrid_coupling_parameters() {
1085 let optimizer = HybridSpatialOptimizer::new().with_quantum_classical_coupling(0.3);
1086
1087 assert!((optimizer.quantum_weight - 0.3).abs() < 1e-10);
1088 assert!((optimizer.classical_weight - 0.7).abs() < 1e-10);
1089 }
1090
1091 #[test]
1092 fn test_clustering_quality_metrics() {
1093 let clusterer = HybridClusterer::new(2);
1094 let points = array![[0.0, 0.0], [1.0, 1.0], [10.0, 10.0], [11.0, 11.0]];
1095 let centroids = array![[0.5, 0.5], [10.5, 10.5]];
1096 let assignments = array![0, 0, 1, 1];
1097
1098 let silhouette =
1099 clusterer.calculate_silhouette_score(&points.view(), ¢roids, &assignments);
1100 assert!(silhouette > 0.0); }
1102}