1use crate::error::{SpatialError, SpatialResult};
59use scirs2_core::ndarray::{Array1, Array2, ArrayView2, Axis};
60use scirs2_core::random::Rng;
61use statrs::statistics::Statistics;
62use std::collections::{HashMap, VecDeque};
63use std::f64::consts::PI;
64
65#[derive(Debug, Clone)]
67pub struct NeuralLayer {
68 pub weights: Array2<f64>,
70 pub biases: Array1<f64>,
72 pub activation: ActivationFunction,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq)]
78pub enum ActivationFunction {
79 ReLU,
81 LeakyReLU(f64),
83 Sigmoid,
85 Tanh,
87 Swish,
89 GELU,
91}
92
93impl ActivationFunction {
94 pub fn apply(&self, x: f64) -> f64 {
96 match self {
97 ActivationFunction::ReLU => x.max(0.0),
98 ActivationFunction::LeakyReLU(alpha) => {
99 if x > 0.0 {
100 x
101 } else {
102 alpha * x
103 }
104 }
105 ActivationFunction::Sigmoid => 1.0 / (1.0 + (-x).exp()),
106 ActivationFunction::Tanh => x.tanh(),
107 ActivationFunction::Swish => x * (1.0 / (1.0 + (-x).exp())),
108 ActivationFunction::GELU => {
109 0.5 * x * (1.0 + ((2.0_f64 / PI).sqrt() * (x + 0.044715 * x.powi(3))).tanh())
110 }
111 }
112 }
113
114 pub fn derivative(&self, x: f64) -> f64 {
116 match self {
117 ActivationFunction::ReLU => {
118 if x > 0.0 {
119 1.0
120 } else {
121 0.0
122 }
123 }
124 ActivationFunction::LeakyReLU(alpha) => {
125 if x > 0.0 {
126 1.0
127 } else {
128 *alpha
129 }
130 }
131 ActivationFunction::Sigmoid => {
132 let sigmoid_x = self.apply(x);
133 sigmoid_x * (1.0 - sigmoid_x)
134 }
135 ActivationFunction::Tanh => 1.0 - x.tanh().powi(2),
136 ActivationFunction::Swish => {
137 let sigmoid_x = 1.0 / (1.0 + (-x).exp());
138 sigmoid_x + x * sigmoid_x * (1.0 - sigmoid_x)
139 }
140 ActivationFunction::GELU => {
141 let sqrt_2_pi = (2.0_f64 / PI).sqrt();
142 let tanh_input = sqrt_2_pi * (x + 0.044715 * x.powi(3));
143 let tanh_val = tanh_input.tanh();
144 0.5 * (1.0 + tanh_val)
145 + 0.5
146 * x
147 * (1.0 - tanh_val.powi(2))
148 * sqrt_2_pi
149 * (1.0 + 3.0 * 0.044715 * x.powi(2))
150 }
151 }
152 }
153}
154
155#[derive(Debug, Clone)]
157pub struct NeuralSpatialOptimizer {
158 layers: Vec<NeuralLayer>,
160 learning_rate: f64,
162 adaptive_learning: bool,
164 experience_buffer: VecDeque<(Array1<f64>, Array1<f64>)>,
166 training_iterations: usize,
168 loss_history: Vec<f64>,
170}
171
172impl Default for NeuralSpatialOptimizer {
173 fn default() -> Self {
174 Self::new()
175 }
176}
177
178impl NeuralSpatialOptimizer {
179 pub fn new() -> Self {
181 Self {
182 layers: Vec::new(),
183 learning_rate: 0.001,
184 adaptive_learning: false,
185 experience_buffer: VecDeque::new(),
186 training_iterations: 0,
187 loss_history: Vec::new(),
188 }
189 }
190
191 pub fn with_network_architecture(mut self, layersizes: impl AsRef<[usize]>) -> Self {
193 let sizes = layersizes.as_ref();
194 self.layers.clear();
195
196 for i in 0..sizes.len() - 1 {
197 let input_size = sizes[i];
198 let output_size = sizes[i + 1];
199
200 let scale = (2.0_f64 / (input_size + output_size) as f64).sqrt();
202 let weights = Array2::from_shape_fn((output_size, input_size), |_| {
203 (scirs2_core::random::random::<f64>() - 0.5) * 2.0 * scale
204 });
205 let biases = Array1::zeros(output_size);
206
207 let activation = if i == sizes.len() - 2 {
208 ActivationFunction::Sigmoid } else {
210 ActivationFunction::ReLU };
212
213 self.layers.push(NeuralLayer {
214 weights,
215 biases,
216 activation,
217 });
218 }
219
220 self
221 }
222
223 pub fn set_network_architecture(&mut self, layersizes: impl AsRef<[usize]>) {
225 let sizes = layersizes.as_ref();
226 self.layers.clear();
227
228 for i in 0..sizes.len() - 1 {
229 let input_size = sizes[i];
230 let output_size = sizes[i + 1];
231
232 let scale = (2.0_f64 / (input_size + output_size) as f64).sqrt();
234 let weights = Array2::from_shape_fn((output_size, input_size), |_| {
235 (scirs2_core::random::random::<f64>() - 0.5) * 2.0 * scale
236 });
237 let biases = Array1::zeros(output_size);
238
239 let activation = if i == sizes.len() - 2 {
240 ActivationFunction::Sigmoid } else {
242 ActivationFunction::ReLU };
244
245 self.layers.push(NeuralLayer {
246 weights,
247 biases,
248 activation,
249 });
250 }
251 }
252
253 pub fn with_learning_rate(&mut self, lr: f64) -> &mut Self {
255 self.learning_rate = lr;
256 self
257 }
258
259 pub fn with_adaptive_learning(&mut self, enabled: bool) -> &mut Self {
261 self.adaptive_learning = enabled;
262 self
263 }
264
265 pub fn optimize_clustering_parameters(
267 &mut self,
268 points: &ArrayView2<'_, f64>,
269 ) -> SpatialResult<ClusteringParameters> {
270 let features = self.extract_spatial_features(points)?;
272
273 if self.layers.is_empty() {
275 let feature_size = features.len();
276 self.set_network_architecture([feature_size, 64, 32, 16, 8]);
277 }
278
279 let output = self.forward_pass(&features)?;
281
282 let params = self.decode_clustering_parameters(&output)?;
284
285 if let Some(qualityscore) = self.evaluate_clustering_quality(points, ¶ms)? {
287 self.update_network(&features, qualityscore)?;
288 }
289
290 Ok(params)
291 }
292
293 fn extract_spatial_features(
295 &self,
296 n_points: &ArrayView2<'_, f64>,
297 ) -> SpatialResult<Array1<f64>> {
298 let (num_points, n_dims) = n_points.dim();
299 let mut features = Vec::new();
300
301 features.push(num_points as f64);
303 features.push(n_dims as f64);
304
305 for dim in 0..n_dims {
307 let column = n_points.column(dim);
308 let mean = column.mean();
309 let std = (column.mapv(|x| (x - mean).powi(2)).mean()).sqrt();
310 let min_val = column.fold(f64::INFINITY, |a, &b| a.min(b));
311 let max_val = column.fold(f64::NEG_INFINITY, |a, &b| a.max(b));
312
313 features.push(mean);
314 features.push(std);
315 features.push(max_val - min_val); features.push(if std > 0.0 {
317 (max_val - min_val) / std
318 } else {
319 0.0
320 }); }
322
323 let mut distances = Vec::new();
325 for i in 0..num_points.min(100) {
326 for j in (i + 1)..num_points.min(100) {
328 let dist: f64 = n_points
329 .row(i)
330 .iter()
331 .zip(n_points.row(j).iter())
332 .map(|(&a, &b)| (a - b).powi(2))
333 .sum::<f64>()
334 .sqrt();
335 distances.push(dist);
336 }
337 }
338
339 if !distances.is_empty() {
340 let mean_dist = distances.iter().sum::<f64>() / distances.len() as f64;
341 let dist_std = {
342 let variance = distances
343 .iter()
344 .map(|&d| (d - mean_dist).powi(2))
345 .sum::<f64>()
346 / distances.len() as f64;
347 variance.sqrt()
348 };
349 features.push(mean_dist);
350 features.push(dist_std);
351 features.push(distances.iter().fold(f64::INFINITY, |a, &b| a.min(b))); features.push(distances.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)));
353 } else {
355 features.extend_from_slice(&[0.0, 0.0, 0.0, 0.0]);
356 }
357
358 let density = NeuralSpatialOptimizer::estimate_local_density(n_points)?;
360 features.push(density);
361
362 let hopkins = self.estimate_clustering_tendency(n_points)?;
364 features.push(hopkins);
365
366 Ok(Array1::from(features))
367 }
368
369 fn estimate_local_density(points: &ArrayView2<'_, f64>) -> SpatialResult<f64> {
371 let (n_points, n_dims) = points.dim();
372
373 if n_points < 2 {
374 return Ok(0.0);
375 }
376
377 let sample_size = n_points.min(50);
379 let mut total_inverse_distance = 0.0;
380 let mut count = 0;
381
382 for i in 0..sample_size {
383 let mut nearest_distance = f64::INFINITY;
384
385 for j in 0..n_points {
386 if i != j {
387 let dist: f64 = points
388 .row(i)
389 .iter()
390 .zip(points.row(j).iter())
391 .map(|(&a, &b)| (a - b).powi(2))
392 .sum::<f64>()
393 .sqrt();
394
395 if dist < nearest_distance {
396 nearest_distance = dist;
397 }
398 }
399 }
400
401 if nearest_distance > 0.0 && nearest_distance < f64::INFINITY {
402 total_inverse_distance += 1.0 / nearest_distance;
403 count += 1;
404 }
405 }
406
407 Ok(if count > 0 {
408 total_inverse_distance / count as f64
409 } else {
410 0.0
411 })
412 }
413
414 fn estimate_clustering_tendency(&self, points: &ArrayView2<'_, f64>) -> SpatialResult<f64> {
416 let (n_points, n_dims) = points.dim();
417
418 if n_points < 10 {
419 return Ok(0.5); }
421
422 let sample_size = n_points.min(20);
424 let mut real_distances = Vec::new();
425 let mut random_distances = Vec::new();
426
427 for i in 0..sample_size {
429 let mut min_dist = f64::INFINITY;
430 for j in 0..n_points {
431 if i != j {
432 let dist: f64 = points
433 .row(i)
434 .iter()
435 .zip(points.row(j).iter())
436 .map(|(&a, &b)| (a - b).powi(2))
437 .sum::<f64>()
438 .sqrt();
439 min_dist = min_dist.min(dist);
440 }
441 }
442 real_distances.push(min_dist);
443 }
444
445 let bounds = self.get_data_bounds(points);
447 for _ in 0..sample_size {
448 let random_point: Array1<f64> = Array1::from_shape_fn(n_dims, |i| {
449 scirs2_core::random::random::<f64>() * (bounds[i].1 - bounds[i].0) + bounds[i].0
450 });
451
452 let mut min_dist = f64::INFINITY;
453 for j in 0..n_points {
454 let dist: f64 = random_point
455 .iter()
456 .zip(points.row(j).iter())
457 .map(|(&a, &b)| (a - b).powi(2))
458 .sum::<f64>()
459 .sqrt();
460 min_dist = min_dist.min(dist);
461 }
462 random_distances.push(min_dist);
463 }
464
465 let sum_random: f64 = random_distances.iter().sum();
467 let sum_real: f64 = real_distances.iter().sum();
468 let hopkins = sum_random / (sum_random + sum_real);
469
470 Ok(hopkins)
471 }
472
473 fn get_data_bounds(&self, points: &ArrayView2<'_, f64>) -> Vec<(f64, f64)> {
475 let (_, n_dims) = points.dim();
476 let mut bounds = Vec::new();
477
478 for dim in 0..n_dims {
479 let column = points.column(dim);
480 let min_val = column.fold(f64::INFINITY, |a, &b| a.min(b));
481 let max_val = column.fold(f64::NEG_INFINITY, |a, &b| a.max(b));
482 bounds.push((min_val, max_val));
483 }
484
485 bounds
486 }
487
488 fn forward_pass(&self, input: &Array1<f64>) -> SpatialResult<Array1<f64>> {
490 let mut current = input.clone();
491
492 for layer in &self.layers {
493 let linear_output = layer.weights.dot(¤t) + &layer.biases;
495
496 current = linear_output.mapv(|x| layer.activation.apply(x));
498 }
499
500 Ok(current)
501 }
502
503 fn decode_clustering_parameters(
505 &self,
506 output: &Array1<f64>,
507 ) -> SpatialResult<ClusteringParameters> {
508 if output.len() < 8 {
509 return Err(SpatialError::InvalidInput(
510 "Insufficient network output for parameter decoding".to_string(),
511 ));
512 }
513
514 Ok(ClusteringParameters {
515 num_clusters: ((output[0] * 20.0) as usize).clamp(1, 20), max_iterations: ((output[1] * 500.0) as usize).clamp(10, 500), tolerance: output[2] * 1e-3, init_method: if output[3] > 0.5 {
519 InitMethod::KMeansPlusPlus
520 } else {
521 InitMethod::Random
522 },
523 distance_metric: NeuralSpatialOptimizer::decode_distance_metric(output[4]),
524 regularization: output[5] * 0.1, early_stopping: output[6] > 0.5,
526 adaptive_parameters: output[7] > 0.5,
527 })
528 }
529
530 fn decode_distance_metric(value: f64) -> DistanceMetric {
532 match (value * 4.0) as usize {
533 0 => DistanceMetric::Euclidean,
534 1 => DistanceMetric::Manhattan,
535 2 => DistanceMetric::Cosine,
536 3 => DistanceMetric::Minkowski(2.0),
537 _ => DistanceMetric::Euclidean,
538 }
539 }
540
541 fn evaluate_clustering_quality(
543 &self,
544 points: &ArrayView2<'_, f64>,
545 params: &ClusteringParameters,
546 ) -> SpatialResult<Option<f64>> {
547 let clustering_result = self.run_clustering(points, params)?;
549
550 let silhouette_score = self.calculate_silhouette_score(points, &clustering_result)?;
552 let inertia = NeuralSpatialOptimizer::calculate_inertia(points, &clustering_result)?;
553 let calinski_harabasz =
554 self.calculate_calinski_harabasz_score(points, &clustering_result)?;
555
556 let qualityscore =
558 0.5 * silhouette_score + 0.3 * (1.0 / (1.0 + inertia)) + 0.2 * calinski_harabasz;
559
560 Ok(Some(qualityscore))
561 }
562
563 fn run_clustering(
565 &self,
566 points: &ArrayView2<'_, f64>,
567 params: &ClusteringParameters,
568 ) -> SpatialResult<ClusteringResult> {
569 let (n_points, n_dims) = points.dim();
571 let mut centroids = Array2::zeros((params.num_clusters, n_dims));
572 let mut assignments = Array1::zeros(n_points);
573
574 match params.init_method {
576 InitMethod::Random => {
577 for i in 0..params.num_clusters {
578 for j in 0..n_dims {
579 centroids[[i, j]] = scirs2_core::random::random::<f64>();
580 }
581 }
582 }
583 InitMethod::KMeansPlusPlus => {
584 let mut rng = scirs2_core::random::rng();
586 let mut selected = Vec::new();
587 for _ in 0..params.num_clusters {
588 let idx = rng.gen_range(0..n_points);
589 selected.push(idx);
590 }
591
592 for (i, &idx) in selected.iter().enumerate() {
593 centroids.row_mut(i).assign(&points.row(idx));
594 }
595 }
596 }
597
598 for _ in 0..params.max_iterations {
600 let mut changed = false;
601
602 for i in 0..n_points {
604 let mut best_cluster = 0;
605 let mut best_distance = f64::INFINITY;
606
607 for j in 0..params.num_clusters {
608 let distance = NeuralSpatialOptimizer::calculate_distance(
609 &points.row(i).to_owned(),
610 ¢roids.row(j).to_owned(),
611 ¶ms.distance_metric,
612 );
613
614 if distance < best_distance {
615 best_distance = distance;
616 best_cluster = j;
617 }
618 }
619
620 if assignments[i] != best_cluster {
621 assignments[i] = best_cluster;
622 changed = true;
623 }
624 }
625
626 if !changed {
627 break;
628 }
629
630 let mut cluster_counts = vec![0; params.num_clusters];
632 let mut new_centroids = Array2::zeros((params.num_clusters, n_dims));
633
634 for i in 0..n_points {
635 let cluster = assignments[i];
636 cluster_counts[cluster] += 1;
637 for j in 0..n_dims {
638 new_centroids[[cluster, j]] += points[[i, j]];
639 }
640 }
641
642 for i in 0..params.num_clusters {
643 if cluster_counts[i] > 0 {
644 for j in 0..n_dims {
645 new_centroids[[i, j]] /= cluster_counts[i] as f64;
646 }
647 }
648 }
649
650 centroids = new_centroids;
651 }
652
653 let inertia = self.calculate_inertia_direct(points, ¢roids, &assignments)?;
654
655 Ok(ClusteringResult {
656 centroids,
657 assignments,
658 inertia,
659 })
660 }
661
662 fn calculate_distance(a: &Array1<f64>, b: &Array1<f64>, metric: &DistanceMetric) -> f64 {
664 match metric {
665 DistanceMetric::Euclidean => a
666 .iter()
667 .zip(b.iter())
668 .map(|(&x, &y)| (x - y).powi(2))
669 .sum::<f64>()
670 .sqrt(),
671 DistanceMetric::Manhattan => a.iter().zip(b.iter()).map(|(&x, &y)| (x - y).abs()).sum(),
672 DistanceMetric::Cosine => {
673 let dot_product: f64 = a.iter().zip(b.iter()).map(|(&x, &y)| x * y).sum();
674 let norm_a = a.iter().map(|&x| x.powi(2)).sum::<f64>().sqrt();
675 let norm_b = b.iter().map(|&x| x.powi(2)).sum::<f64>().sqrt();
676 1.0 - (dot_product / (norm_a * norm_b))
677 }
678 DistanceMetric::Minkowski(p) => a
679 .iter()
680 .zip(b.iter())
681 .map(|(&x, &y)| (x - y).abs().powf(*p))
682 .sum::<f64>()
683 .powf(1.0 / p),
684 }
685 }
686
687 fn calculate_silhouette_score(
689 &self,
690 points: &ArrayView2<'_, f64>,
691 result: &ClusteringResult,
692 ) -> SpatialResult<f64> {
693 let (n_points, n_dims) = points.dim();
694 let mut silhouette_scores = Vec::new();
695
696 for i in 0..n_points {
697 let cluster_i = result.assignments[i];
698
699 let mut intra_cluster_distances = Vec::new();
701 for j in 0..n_points {
702 if i != j && result.assignments[j] == cluster_i {
703 let dist = NeuralSpatialOptimizer::calculate_distance(
704 &points.row(i).to_owned(),
705 &points.row(j).to_owned(),
706 &DistanceMetric::Euclidean,
707 );
708 intra_cluster_distances.push(dist);
709 }
710 }
711
712 let a = if intra_cluster_distances.is_empty() {
713 0.0
714 } else {
715 intra_cluster_distances.iter().sum::<f64>() / intra_cluster_distances.len() as f64
716 };
717
718 let mut min_inter_cluster_distance = f64::INFINITY;
720 for cluster in 0..result.centroids.nrows() {
721 if cluster != cluster_i {
722 let mut inter_cluster_distances = Vec::new();
723 for j in 0..n_points {
724 if result.assignments[j] == cluster {
725 let dist = NeuralSpatialOptimizer::calculate_distance(
726 &points.row(i).to_owned(),
727 &points.row(j).to_owned(),
728 &DistanceMetric::Euclidean,
729 );
730 inter_cluster_distances.push(dist);
731 }
732 }
733
734 if !inter_cluster_distances.is_empty() {
735 let avg_dist = inter_cluster_distances.iter().sum::<f64>()
736 / inter_cluster_distances.len() as f64;
737 min_inter_cluster_distance = min_inter_cluster_distance.min(avg_dist);
738 }
739 }
740 }
741
742 let b = min_inter_cluster_distance;
743
744 let silhouette = if a < b {
745 1.0 - (a / b)
746 } else if a > b {
747 (b / a) - 1.0
748 } else {
749 0.0
750 };
751
752 silhouette_scores.push(silhouette);
753 }
754
755 Ok(silhouette_scores.iter().sum::<f64>() / silhouette_scores.len() as f64)
756 }
757
758 fn calculate_inertia(
760 self_points: &ArrayView2<'_, f64>,
761 result: &ClusteringResult,
762 ) -> SpatialResult<f64> {
763 Ok(result.inertia)
764 }
765
766 fn calculate_inertia_direct(
768 &self,
769 points: &ArrayView2<'_, f64>,
770 centroids: &Array2<f64>,
771 assignments: &Array1<usize>,
772 ) -> SpatialResult<f64> {
773 let (n_points, n_dims) = points.dim();
774 let mut inertia = 0.0;
775
776 for i in 0..n_points {
777 let cluster = assignments[i];
778 let distance = NeuralSpatialOptimizer::calculate_distance(
779 &points.row(i).to_owned(),
780 ¢roids.row(cluster).to_owned(),
781 &DistanceMetric::Euclidean,
782 );
783 inertia += distance.powi(2);
784 }
785
786 Ok(inertia)
787 }
788
789 fn calculate_calinski_harabasz_score(
791 &self,
792 points: &ArrayView2<'_, f64>,
793 result: &ClusteringResult,
794 ) -> SpatialResult<f64> {
795 let (n_points, n_dims) = points.dim();
796 let k = result.centroids.nrows();
797
798 if k <= 1 || n_points <= k {
799 return Ok(0.0);
800 }
801
802 let overall_centroid: Array1<f64> = points.mean_axis(Axis(0)).unwrap();
804
805 let mut between_ss = 0.0;
807 for i in 0..k {
808 let cluster_size = result.assignments.iter().filter(|&&x| x == i).count() as f64;
809 if cluster_size > 0.0 {
810 let distance_to_overall = NeuralSpatialOptimizer::calculate_distance(
811 &result.centroids.row(i).to_owned(),
812 &overall_centroid,
813 &DistanceMetric::Euclidean,
814 );
815 between_ss += cluster_size * distance_to_overall.powi(2);
816 }
817 }
818
819 let within_ss = result.inertia;
821
822 let ch_score = (between_ss / (k - 1) as f64) / (within_ss / (n_points - k) as f64);
824 Ok(ch_score)
825 }
826
827 fn update_network(&mut self, _input: &Array1<f64>, qualityscore: f64) -> SpatialResult<()> {
829 let target = Array1::from(vec![qualityscore; 8]); self.experience_buffer.push_back((_input.clone(), target));
832
833 if self.experience_buffer.len() > 1000 {
835 self.experience_buffer.pop_front();
836 }
837
838 if self.experience_buffer.len() >= 32 {
840 self.train_network_batch()?;
841 }
842
843 Ok(())
844 }
845
846 fn train_network_batch(&mut self) -> SpatialResult<()> {
848 let batch_size = 32.min(self.experience_buffer.len());
849
850 for _ in 0..batch_size {
851 if let Some((input, target)) = self.experience_buffer.pop_front() {
852 self.train_single_example(&input, &target)?;
853 }
854 }
855
856 self.training_iterations += 1;
857
858 if self.adaptive_learning && self.training_iterations.is_multiple_of(100) {
860 self.learning_rate *= 0.95; }
862
863 Ok(())
864 }
865
866 fn train_single_example(
868 &mut self,
869 input: &Array1<f64>,
870 target: &Array1<f64>,
871 ) -> SpatialResult<()> {
872 let mut activations = vec![input.clone()];
874 let mut current = input.clone();
875
876 for layer in &self.layers {
877 let linear_output = layer.weights.dot(¤t) + &layer.biases;
878 current = linear_output.mapv(|x| layer.activation.apply(x));
879 activations.push(current.clone());
880 }
881
882 let output = &activations[activations.len() - 1];
884 let loss: f64 = target
885 .iter()
886 .zip(output.iter())
887 .map(|(&t, &o)| (t - o).powi(2))
888 .sum::<f64>()
889 / target.len() as f64;
890
891 self.loss_history.push(loss);
892
893 let mut delta = output - target;
895
896 for (i, layer) in self.layers.iter_mut().enumerate().rev() {
897 let layer_input = &activations[i];
898 let _layer_output = &activations[i + 1];
899
900 let weight_gradients = delta
902 .clone()
903 .insert_axis(Axis(1))
904 .dot(&layer_input.clone().insert_axis(Axis(0)));
905 let bias_gradients = delta.clone();
906
907 layer.weights = &layer.weights - self.learning_rate * &weight_gradients;
909 layer.biases = &layer.biases - self.learning_rate * &bias_gradients;
910
911 if i > 0 {
913 let prev_layer_output = &activations[i];
914 delta = layer.weights.t().dot(&delta);
915
916 for (j, &output_val) in prev_layer_output.iter().enumerate() {
918 delta[j] *= layer.activation.derivative(output_val);
919 }
920 }
921 }
922
923 Ok(())
924 }
925}
926
927#[derive(Debug, Clone)]
929pub struct ClusteringParameters {
930 pub num_clusters: usize,
931 pub max_iterations: usize,
932 pub tolerance: f64,
933 pub init_method: InitMethod,
934 pub distance_metric: DistanceMetric,
935 pub regularization: f64,
936 pub early_stopping: bool,
937 pub adaptive_parameters: bool,
938}
939
940#[derive(Debug, Clone, Copy, PartialEq)]
942pub enum InitMethod {
943 Random,
944 KMeansPlusPlus,
945}
946
947#[derive(Debug, Clone, PartialEq)]
949pub enum DistanceMetric {
950 Euclidean,
951 Manhattan,
952 Cosine,
953 Minkowski(f64),
954}
955
956#[derive(Debug, Clone)]
958pub struct ClusteringResult {
959 pub centroids: Array2<f64>,
960 pub assignments: Array1<usize>,
961 pub inertia: f64,
962}
963
964#[allow(dead_code)]
966#[derive(Debug, Clone)]
967pub struct ReinforcementLearningSelector {
968 q_table: HashMap<StateAction, f64>,
970 epsilon: f64,
972 learning_rate: f64,
974 gamma: f64,
976 experience_buffer: VecDeque<Experience>,
978 target_q_table: Option<HashMap<StateAction, f64>>,
980 episodes: usize,
982}
983
984#[derive(Debug, Clone, Hash, PartialEq, Eq)]
986pub struct StateAction {
987 pub state: DataState,
988 pub action: SpatialAlgorithm,
989}
990
991#[derive(Debug, Clone, Hash, PartialEq, Eq)]
993pub struct DataState {
994 pub size_category: SizeCategory,
995 pub dimensionality_category: DimensionalityCategory,
996 pub density_category: DensityCategory,
997 pub clustering_tendency_category: ClusteringTendencyCategory,
998}
999
1000#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1002pub enum SizeCategory {
1003 Small, Medium, Large, }
1007
1008#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1010pub enum DimensionalityCategory {
1011 Low, Medium, High, }
1015
1016#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1018pub enum DensityCategory {
1019 Low,
1020 Medium,
1021 High,
1022}
1023
1024#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1026pub enum ClusteringTendencyCategory {
1027 Random,
1028 Structured,
1029 HighlyStructured,
1030}
1031
1032#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1034pub enum SpatialAlgorithm {
1035 KMeans,
1036 DBScan,
1037 HierarchicalClustering,
1038 GaussianMixture,
1039 SpectralClustering,
1040 KDTree,
1041 BallTree,
1042}
1043
1044#[derive(Debug, Clone)]
1046pub struct Experience {
1047 pub state: DataState,
1048 pub action: SpatialAlgorithm,
1049 pub reward: f64,
1050 pub next_state: DataState,
1051 pub done: bool,
1052}
1053
1054impl Default for ReinforcementLearningSelector {
1055 fn default() -> Self {
1056 Self::new()
1057 }
1058}
1059
1060impl ReinforcementLearningSelector {
1061 pub fn new() -> Self {
1063 Self {
1064 q_table: HashMap::new(),
1065 epsilon: 0.1,
1066 learning_rate: 0.1,
1067 gamma: 0.9,
1068 experience_buffer: VecDeque::new(),
1069 target_q_table: None,
1070 episodes: 0,
1071 }
1072 }
1073
1074 pub fn with_epsilon_greedy(&mut self, epsilon: f64) -> &mut Self {
1076 self.epsilon = epsilon;
1077 self
1078 }
1079
1080 pub fn with_experience_replay(&mut self, enabled: bool) -> &mut Self {
1082 if enabled {
1083 self.experience_buffer = VecDeque::new();
1084 }
1085 self
1086 }
1087
1088 pub fn with_target_network(&mut self, enabled: bool) -> &mut Self {
1090 if enabled {
1091 self.target_q_table = Some(HashMap::new());
1092 }
1093 self
1094 }
1095
1096 pub fn select_best_algorithm(
1098 &mut self,
1099 points: &ArrayView2<'_, f64>,
1100 ) -> SpatialResult<SpatialAlgorithm> {
1101 let state = self.analyze_data_state(points)?;
1102
1103 if scirs2_core::random::random::<f64>() < self.epsilon {
1105 Ok(self.random_algorithm())
1107 } else {
1108 Ok(self.best_algorithm_for_state(&state))
1110 }
1111 }
1112
1113 fn analyze_data_state(&self, points: &ArrayView2<'_, f64>) -> SpatialResult<DataState> {
1115 let (n_points, n_dims) = points.dim();
1116
1117 let size_category = match n_points {
1118 0..=999 => SizeCategory::Small,
1119 1000..=9999 => SizeCategory::Medium,
1120 _ => SizeCategory::Large,
1121 };
1122
1123 let dimensionality_category = match n_dims {
1124 0..=4 => DimensionalityCategory::Low,
1125 5..=20 => DimensionalityCategory::Medium,
1126 _ => DimensionalityCategory::High,
1127 };
1128
1129 let density = self.estimate_density(points)?;
1131 let density_category = if density < 0.3 {
1132 DensityCategory::Low
1133 } else if density < 0.7 {
1134 DensityCategory::Medium
1135 } else {
1136 DensityCategory::High
1137 };
1138
1139 let hopkins = self.estimate_hopkins_statistic(points)?;
1141 let clustering_tendency_category = if hopkins < 0.3 {
1142 ClusteringTendencyCategory::HighlyStructured
1143 } else if hopkins < 0.7 {
1144 ClusteringTendencyCategory::Structured
1145 } else {
1146 ClusteringTendencyCategory::Random
1147 };
1148
1149 Ok(DataState {
1150 size_category,
1151 dimensionality_category,
1152 density_category,
1153 clustering_tendency_category,
1154 })
1155 }
1156
1157 fn estimate_density(&self, points: &ArrayView2<'_, f64>) -> SpatialResult<f64> {
1159 let (n_points, n_dims) = points.dim();
1160
1161 if n_points < 2 {
1162 return Ok(0.0);
1163 }
1164
1165 let sample_size = n_points.min(100);
1166 let mut total_inverse_distance = 0.0;
1167 let mut count = 0;
1168
1169 for i in 0..sample_size {
1170 let mut nearest_distance = f64::INFINITY;
1171
1172 for j in 0..n_points {
1173 if i != j {
1174 let dist: f64 = points
1175 .row(i)
1176 .iter()
1177 .zip(points.row(j).iter())
1178 .map(|(&a, &b)| (a - b).powi(2))
1179 .sum::<f64>()
1180 .sqrt();
1181
1182 if dist < nearest_distance {
1183 nearest_distance = dist;
1184 }
1185 }
1186 }
1187
1188 if nearest_distance > 0.0 && nearest_distance.is_finite() {
1189 total_inverse_distance += 1.0 / nearest_distance;
1190 count += 1;
1191 }
1192 }
1193
1194 Ok(if count > 0 {
1195 total_inverse_distance / count as f64 / 10.0
1196 } else {
1197 0.0
1198 })
1199 }
1200
1201 fn estimate_hopkins_statistic(&self, points: &ArrayView2<'_, f64>) -> SpatialResult<f64> {
1203 let (n_points, n_dims) = points.dim();
1204
1205 if n_points < 10 {
1206 return Ok(0.5);
1207 }
1208
1209 let sample_size = n_points.min(20);
1210 let mut real_distances = Vec::new();
1211 let mut random_distances = Vec::new();
1212
1213 for i in 0..sample_size {
1215 let mut min_dist = f64::INFINITY;
1216 for j in 0..n_points {
1217 if i != j {
1218 let dist: f64 = points
1219 .row(i)
1220 .iter()
1221 .zip(points.row(j).iter())
1222 .map(|(&a, &b)| (a - b).powi(2))
1223 .sum::<f64>()
1224 .sqrt();
1225 min_dist = min_dist.min(dist);
1226 }
1227 }
1228 real_distances.push(min_dist);
1229 }
1230
1231 let bounds = self.get_data_bounds(points);
1233 for _ in 0..sample_size {
1234 let random_point: Array1<f64> = Array1::from_shape_fn(n_dims, |i| {
1235 scirs2_core::random::random::<f64>() * (bounds[i].1 - bounds[i].0) + bounds[i].0
1236 });
1237
1238 let mut min_dist = f64::INFINITY;
1239 for j in 0..n_points {
1240 let dist: f64 = random_point
1241 .iter()
1242 .zip(points.row(j).iter())
1243 .map(|(&a, &b)| (a - b).powi(2))
1244 .sum::<f64>()
1245 .sqrt();
1246 min_dist = min_dist.min(dist);
1247 }
1248 random_distances.push(min_dist);
1249 }
1250
1251 let sum_random: f64 = random_distances.iter().sum();
1252 let sum_real: f64 = real_distances.iter().sum();
1253 let hopkins = sum_random / (sum_random + sum_real);
1254
1255 Ok(hopkins)
1256 }
1257
1258 fn get_data_bounds(&self, points: &ArrayView2<'_, f64>) -> Vec<(f64, f64)> {
1260 let (_, n_dims) = points.dim();
1261 let mut bounds = Vec::new();
1262
1263 for dim in 0..n_dims {
1264 let column = points.column(dim);
1265 let min_val = column.fold(f64::INFINITY, |a, &b| a.min(b));
1266 let max_val = column.fold(f64::NEG_INFINITY, |a, &b| a.max(b));
1267 bounds.push((min_val, max_val));
1268 }
1269
1270 bounds
1271 }
1272
1273 fn random_algorithm(&self) -> SpatialAlgorithm {
1275 let algorithms = [
1276 SpatialAlgorithm::KMeans,
1277 SpatialAlgorithm::DBScan,
1278 SpatialAlgorithm::HierarchicalClustering,
1279 SpatialAlgorithm::GaussianMixture,
1280 SpatialAlgorithm::SpectralClustering,
1281 SpatialAlgorithm::KDTree,
1282 SpatialAlgorithm::BallTree,
1283 ];
1284
1285 let index = (scirs2_core::random::random::<f64>() * algorithms.len() as f64) as usize;
1286 algorithms[index.min(algorithms.len() - 1)].clone()
1287 }
1288
1289 fn best_algorithm_for_state(&self, state: &DataState) -> SpatialAlgorithm {
1291 let algorithms = [
1292 SpatialAlgorithm::KMeans,
1293 SpatialAlgorithm::DBScan,
1294 SpatialAlgorithm::HierarchicalClustering,
1295 SpatialAlgorithm::GaussianMixture,
1296 SpatialAlgorithm::SpectralClustering,
1297 SpatialAlgorithm::KDTree,
1298 SpatialAlgorithm::BallTree,
1299 ];
1300
1301 let mut best_algorithm = SpatialAlgorithm::KMeans;
1302 let mut best_q_value = f64::NEG_INFINITY;
1303
1304 for algorithm in &algorithms {
1305 let state_action = StateAction {
1306 state: state.clone(),
1307 action: algorithm.clone(),
1308 };
1309
1310 let q_value = self.q_table.get(&state_action).unwrap_or(&0.0);
1311 if *q_value > best_q_value {
1312 best_q_value = *q_value;
1313 best_algorithm = algorithm.clone();
1314 }
1315 }
1316
1317 best_algorithm
1318 }
1319
1320 pub fn update_q_values(&mut self, experience: Experience) -> SpatialResult<()> {
1322 let state_action = StateAction {
1323 state: experience.state.clone(),
1324 action: experience.action.clone(),
1325 };
1326
1327 let current_q = *self.q_table.get(&state_action).unwrap_or(&0.0);
1328
1329 let max_next_q = if experience.done {
1331 0.0
1332 } else {
1333 self.max_q_value_for_state(&experience.next_state)
1334 };
1335
1336 let new_q = current_q
1338 + self.learning_rate * (experience.reward + self.gamma * max_next_q - current_q);
1339
1340 self.q_table.insert(state_action, new_q);
1341
1342 self.experience_buffer.push_back(experience);
1344 if self.experience_buffer.len() > 10000 {
1345 self.experience_buffer.pop_front();
1346 }
1347
1348 if self.experience_buffer.len() >= 32 {
1350 self.replay_experience()?;
1351 }
1352
1353 Ok(())
1354 }
1355
1356 fn max_q_value_for_state(&self, state: &DataState) -> f64 {
1358 let algorithms = [
1359 SpatialAlgorithm::KMeans,
1360 SpatialAlgorithm::DBScan,
1361 SpatialAlgorithm::HierarchicalClustering,
1362 SpatialAlgorithm::GaussianMixture,
1363 SpatialAlgorithm::SpectralClustering,
1364 SpatialAlgorithm::KDTree,
1365 SpatialAlgorithm::BallTree,
1366 ];
1367
1368 algorithms
1369 .iter()
1370 .map(|algorithm| {
1371 let state_action = StateAction {
1372 state: state.clone(),
1373 action: algorithm.clone(),
1374 };
1375 *self.q_table.get(&state_action).unwrap_or(&0.0)
1376 })
1377 .fold(f64::NEG_INFINITY, |a, b| a.max(b))
1378 }
1379
1380 fn replay_experience(&mut self) -> SpatialResult<()> {
1382 let batch_size = 32.min(self.experience_buffer.len());
1383
1384 for _ in 0..batch_size {
1385 if let Some(experience) = self.experience_buffer.pop_front() {
1386 let state_action = StateAction {
1387 state: experience.state.clone(),
1388 action: experience.action.clone(),
1389 };
1390
1391 let current_q = *self.q_table.get(&state_action).unwrap_or(&0.0);
1392 let max_next_q = if experience.done {
1393 0.0
1394 } else {
1395 self.max_q_value_for_state(&experience.next_state)
1396 };
1397
1398 let new_q = current_q
1399 + self.learning_rate
1400 * (experience.reward + self.gamma * max_next_q - current_q);
1401
1402 self.q_table.insert(state_action, new_q);
1403 }
1404 }
1405
1406 Ok(())
1407 }
1408}
1409
1410#[cfg(test)]
1411mod tests {
1412 use super::*;
1413 use scirs2_core::ndarray::array;
1414
1415 #[test]
1416 fn test_activation_functions() {
1417 assert_eq!(ActivationFunction::ReLU.apply(1.0), 1.0);
1418 assert_eq!(ActivationFunction::ReLU.apply(-1.0), 0.0);
1419
1420 let sigmoid_result = ActivationFunction::Sigmoid.apply(0.0);
1421 assert!((sigmoid_result - 0.5).abs() < 1e-10);
1422 }
1423
1424 #[test]
1425 fn test_neural_spatial_optimizer_creation() {
1426 let mut optimizer =
1427 NeuralSpatialOptimizer::new().with_network_architecture([10, 64, 32, 8]);
1428 optimizer.with_learning_rate(0.001);
1429 optimizer.with_adaptive_learning(true);
1430
1431 assert_eq!(optimizer.layers.len(), 3);
1432 assert_eq!(optimizer.learning_rate, 0.001);
1433 assert!(optimizer.adaptive_learning);
1434 }
1435
1436 #[test]
1437 fn test_clustering_parameters() {
1438 let params = ClusteringParameters {
1439 num_clusters: 3,
1440 max_iterations: 100,
1441 tolerance: 1e-6,
1442 init_method: InitMethod::KMeansPlusPlus,
1443 distance_metric: DistanceMetric::Euclidean,
1444 regularization: 0.01,
1445 early_stopping: true,
1446 adaptive_parameters: false,
1447 };
1448
1449 assert_eq!(params.num_clusters, 3);
1450 assert_eq!(params.init_method, InitMethod::KMeansPlusPlus);
1451 }
1452
1453 #[test]
1454 fn test_reinforcement_learning_selector() {
1455 let mut selector = ReinforcementLearningSelector::new();
1456 selector.with_epsilon_greedy(0.1);
1457 selector.with_experience_replay(true);
1458
1459 let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
1460 let algorithm = selector.select_best_algorithm(&points.view());
1461
1462 assert!(algorithm.is_ok());
1463 }
1464
1465 #[test]
1466 fn test_data_state_analysis() {
1467 let selector = ReinforcementLearningSelector::new();
1468 let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]];
1469
1470 let state = selector.analyze_data_state(&points.view());
1471 assert!(state.is_ok());
1472
1473 let data_state = state.unwrap();
1474 assert_eq!(data_state.size_category, SizeCategory::Small);
1475 assert_eq!(
1476 data_state.dimensionality_category,
1477 DimensionalityCategory::Low
1478 );
1479 }
1480
1481 #[test]
1482 fn test_neural_optimizer_feature_extraction() {
1483 let optimizer = NeuralSpatialOptimizer::new();
1484 let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
1485
1486 let features = optimizer.extract_spatial_features(&points.view());
1487 assert!(features.is_ok());
1488
1489 let feature_vector = features.unwrap();
1490 assert!(!feature_vector.is_empty());
1491 }
1492}