1#[cfg(feature = "no-std")]
7use alloc::{vec, vec::Vec};
8
9pub fn kmeans_distances(
12 points: &[&[f32]], centroids: &[&[f32]], distances: &mut [Vec<f32>], ) {
16 let n_samples = points.len();
17 let _n_clusters = centroids.len();
18
19 assert!(!points.is_empty(), "Points cannot be empty");
20 assert!(!centroids.is_empty(), "Centroids cannot be empty");
21 assert_eq!(distances.len(), n_samples, "Distance array size mismatch");
22
23 let n_features = points[0].len();
24 for centroid in centroids {
25 assert_eq!(
26 centroid.len(),
27 n_features,
28 "All points must have the same number of features"
29 );
30 }
31
32 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
33 {
34 if crate::simd_feature_detected!("avx2") {
35 unsafe { kmeans_distances_avx2(points, centroids, distances) };
36 return;
37 } else if crate::simd_feature_detected!("sse2") {
38 unsafe { kmeans_distances_sse2(points, centroids, distances) };
39 return;
40 }
41 }
42
43 kmeans_distances_scalar(points, centroids, distances);
44}
45
46fn kmeans_distances_scalar(points: &[&[f32]], centroids: &[&[f32]], distances: &mut [Vec<f32>]) {
47 let n_samples = points.len();
48 let n_clusters = centroids.len();
49 let n_features = points[0].len();
50
51 for i in 0..n_samples {
52 if distances[i].len() != n_clusters {
53 distances[i] = vec![0.0; n_clusters];
54 }
55
56 for j in 0..n_clusters {
57 let mut sum = 0.0;
58 for k in 0..n_features {
59 let diff = points[i][k] - centroids[j][k];
60 sum += diff * diff;
61 }
62 distances[i][j] = sum.sqrt();
63 }
64 }
65}
66
67#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
68#[target_feature(enable = "sse2")]
69unsafe fn kmeans_distances_sse2(
70 points: &[&[f32]],
71 centroids: &[&[f32]],
72 distances: &mut [Vec<f32>],
73) {
74 use core::arch::x86_64::*;
75
76 let n_samples = points.len();
77 let n_clusters = centroids.len();
78 let n_features = points[0].len();
79
80 for i in 0..n_samples {
81 if distances[i].len() != n_clusters {
82 distances[i] = vec![0.0; n_clusters];
83 }
84
85 for j in 0..n_clusters {
86 let mut sum = _mm_setzero_ps();
87 let mut k = 0;
88
89 while k + 4 <= n_features {
90 let p_vec = _mm_loadu_ps(&points[i][k]);
91 let c_vec = _mm_loadu_ps(¢roids[j][k]);
92 let diff = _mm_sub_ps(p_vec, c_vec);
93 let squared = _mm_mul_ps(diff, diff);
94 sum = _mm_add_ps(sum, squared);
95 k += 4;
96 }
97
98 let mut result = [0.0f32; 4];
99 _mm_storeu_ps(result.as_mut_ptr(), sum);
100 let mut scalar_sum = result[0] + result[1] + result[2] + result[3];
101
102 while k < n_features {
103 let diff = points[i][k] - centroids[j][k];
104 scalar_sum += diff * diff;
105 k += 1;
106 }
107
108 distances[i][j] = scalar_sum.sqrt();
109 }
110 }
111}
112
113#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
114#[target_feature(enable = "avx2")]
115unsafe fn kmeans_distances_avx2(
116 points: &[&[f32]],
117 centroids: &[&[f32]],
118 distances: &mut [Vec<f32>],
119) {
120 use core::arch::x86_64::*;
121
122 let n_samples = points.len();
123 let n_clusters = centroids.len();
124 let n_features = points[0].len();
125
126 for i in 0..n_samples {
127 if distances[i].len() != n_clusters {
128 distances[i] = vec![0.0; n_clusters];
129 }
130
131 for j in 0..n_clusters {
132 let mut sum = _mm256_setzero_ps();
133 let mut k = 0;
134
135 while k + 8 <= n_features {
136 let p_vec = _mm256_loadu_ps(&points[i][k]);
137 let c_vec = _mm256_loadu_ps(¢roids[j][k]);
138 let diff = _mm256_sub_ps(p_vec, c_vec);
139 let squared = _mm256_mul_ps(diff, diff);
140 sum = _mm256_add_ps(sum, squared);
141 k += 8;
142 }
143
144 let mut result = [0.0f32; 8];
145 _mm256_storeu_ps(result.as_mut_ptr(), sum);
146 let mut scalar_sum = result.iter().sum::<f32>();
147
148 while k < n_features {
149 let diff = points[i][k] - centroids[j][k];
150 scalar_sum += diff * diff;
151 k += 1;
152 }
153
154 distances[i][j] = scalar_sum.sqrt();
155 }
156 }
157}
158
159pub fn update_centroids(
162 points: &[&[f32]], assignments: &[usize], n_clusters: usize, centroids: &mut [Vec<f32>], ) {
167 let n_samples = points.len();
168 let n_features = if n_samples > 0 { points[0].len() } else { 0 };
169
170 assert_eq!(
171 assignments.len(),
172 n_samples,
173 "Assignments length must match number of samples"
174 );
175 assert_eq!(
176 centroids.len(),
177 n_clusters,
178 "Centroids length must match number of clusters"
179 );
180
181 for centroid in centroids.iter_mut().take(n_clusters) {
183 if centroid.len() != n_features {
184 *centroid = vec![0.0; n_features];
185 } else {
186 centroid.fill(0.0);
187 }
188 }
189
190 let mut counts = vec![0usize; n_clusters];
191
192 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
193 {
194 if crate::simd_feature_detected!("avx2") {
195 unsafe {
196 update_centroids_avx2(points, assignments, n_clusters, centroids, &mut counts)
197 };
198 return;
199 } else if crate::simd_feature_detected!("sse2") {
200 unsafe {
201 update_centroids_sse2(points, assignments, n_clusters, centroids, &mut counts)
202 };
203 return;
204 }
205 }
206
207 update_centroids_scalar(points, assignments, n_clusters, centroids, &mut counts);
208}
209
210fn update_centroids_scalar(
211 points: &[&[f32]],
212 assignments: &[usize],
213 n_clusters: usize,
214 centroids: &mut [Vec<f32>],
215 counts: &mut [usize],
216) {
217 let n_samples = points.len();
218 let n_features = if n_samples > 0 { points[0].len() } else { 0 };
219
220 for i in 0..n_samples {
222 let cluster_id = assignments[i];
223 if cluster_id < n_clusters {
224 counts[cluster_id] += 1;
225 for j in 0..n_features {
226 centroids[cluster_id][j] += points[i][j];
227 }
228 }
229 }
230
231 for (centroid, &count) in centroids.iter_mut().zip(counts.iter()).take(n_clusters) {
233 if count > 0 {
234 for v in centroid.iter_mut() {
235 *v /= count as f32;
236 }
237 }
238 }
239}
240
241#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
242#[target_feature(enable = "sse2")]
243unsafe fn update_centroids_sse2(
244 points: &[&[f32]],
245 assignments: &[usize],
246 n_clusters: usize,
247 centroids: &mut [Vec<f32>],
248 counts: &mut [usize],
249) {
250 use core::arch::x86_64::*;
251
252 let n_samples = points.len();
253 let n_features = if n_samples > 0 { points[0].len() } else { 0 };
254
255 for i in 0..n_samples {
257 let cluster_id = assignments[i];
258 if cluster_id < n_clusters {
259 counts[cluster_id] += 1;
260
261 let mut j = 0;
262 while j + 4 <= n_features {
263 let centroid_vec = _mm_loadu_ps(¢roids[cluster_id][j]);
264 let point_vec = _mm_loadu_ps(&points[i][j]);
265 let result = _mm_add_ps(centroid_vec, point_vec);
266 _mm_storeu_ps(&mut centroids[cluster_id][j], result);
267 j += 4;
268 }
269
270 while j < n_features {
271 centroids[cluster_id][j] += points[i][j];
272 j += 1;
273 }
274 }
275 }
276
277 for i in 0..n_clusters {
279 if counts[i] > 0 {
280 let count_vec = _mm_set1_ps(counts[i] as f32);
281 let mut j = 0;
282
283 while j + 4 <= n_features {
284 let centroid_vec = _mm_loadu_ps(¢roids[i][j]);
285 let result = _mm_div_ps(centroid_vec, count_vec);
286 _mm_storeu_ps(&mut centroids[i][j], result);
287 j += 4;
288 }
289
290 while j < n_features {
291 centroids[i][j] /= counts[i] as f32;
292 j += 1;
293 }
294 }
295 }
296}
297
298#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
299#[target_feature(enable = "avx2")]
300unsafe fn update_centroids_avx2(
301 points: &[&[f32]],
302 assignments: &[usize],
303 n_clusters: usize,
304 centroids: &mut [Vec<f32>],
305 counts: &mut [usize],
306) {
307 use core::arch::x86_64::*;
308
309 let n_samples = points.len();
310 let n_features = if n_samples > 0 { points[0].len() } else { 0 };
311
312 for i in 0..n_samples {
314 let cluster_id = assignments[i];
315 if cluster_id < n_clusters {
316 counts[cluster_id] += 1;
317
318 let mut j = 0;
319 while j + 8 <= n_features {
320 let centroid_vec = _mm256_loadu_ps(¢roids[cluster_id][j]);
321 let point_vec = _mm256_loadu_ps(&points[i][j]);
322 let result = _mm256_add_ps(centroid_vec, point_vec);
323 _mm256_storeu_ps(&mut centroids[cluster_id][j], result);
324 j += 8;
325 }
326
327 while j < n_features {
328 centroids[cluster_id][j] += points[i][j];
329 j += 1;
330 }
331 }
332 }
333
334 for i in 0..n_clusters {
336 if counts[i] > 0 {
337 let count_vec = _mm256_set1_ps(counts[i] as f32);
338 let mut j = 0;
339
340 while j + 8 <= n_features {
341 let centroid_vec = _mm256_loadu_ps(¢roids[i][j]);
342 let result = _mm256_div_ps(centroid_vec, count_vec);
343 _mm256_storeu_ps(&mut centroids[i][j], result);
344 j += 8;
345 }
346
347 while j < n_features {
348 centroids[i][j] /= counts[i] as f32;
349 j += 1;
350 }
351 }
352 }
353}
354
355pub fn wcss(
358 points: &[&[f32]], centroids: &[&[f32]], assignments: &[usize], ) -> f32 {
362 let n_samples = points.len();
363 assert_eq!(
364 assignments.len(),
365 n_samples,
366 "Assignments length must match number of samples"
367 );
368
369 if n_samples == 0 {
370 return 0.0;
371 }
372
373 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
374 {
375 if crate::simd_feature_detected!("avx2") {
376 return unsafe { wcss_avx2(points, centroids, assignments) };
377 } else if crate::simd_feature_detected!("sse2") {
378 return unsafe { wcss_sse2(points, centroids, assignments) };
379 }
380 }
381
382 wcss_scalar(points, centroids, assignments)
383}
384
385fn wcss_scalar(points: &[&[f32]], centroids: &[&[f32]], assignments: &[usize]) -> f32 {
386 let n_samples = points.len();
387 let n_features = points[0].len();
388 let mut total_wcss = 0.0;
389
390 for i in 0..n_samples {
391 let cluster_id = assignments[i];
392 if cluster_id < centroids.len() {
393 let mut sum_squared = 0.0;
394 for j in 0..n_features {
395 let diff = points[i][j] - centroids[cluster_id][j];
396 sum_squared += diff * diff;
397 }
398 total_wcss += sum_squared;
399 }
400 }
401
402 total_wcss
403}
404
405#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
406#[target_feature(enable = "sse2")]
407unsafe fn wcss_sse2(points: &[&[f32]], centroids: &[&[f32]], assignments: &[usize]) -> f32 {
408 use core::arch::x86_64::*;
409
410 let n_samples = points.len();
411 let n_features = points[0].len();
412 let mut total_wcss = 0.0;
413
414 for i in 0..n_samples {
415 let cluster_id = assignments[i];
416 if cluster_id < centroids.len() {
417 let mut sum = _mm_setzero_ps();
418 let mut j = 0;
419
420 while j + 4 <= n_features {
421 let p_vec = _mm_loadu_ps(&points[i][j]);
422 let c_vec = _mm_loadu_ps(¢roids[cluster_id][j]);
423 let diff = _mm_sub_ps(p_vec, c_vec);
424 let squared = _mm_mul_ps(diff, diff);
425 sum = _mm_add_ps(sum, squared);
426 j += 4;
427 }
428
429 let mut result = [0.0f32; 4];
430 _mm_storeu_ps(result.as_mut_ptr(), sum);
431 let mut sum_squared = result[0] + result[1] + result[2] + result[3];
432
433 while j < n_features {
434 let diff = points[i][j] - centroids[cluster_id][j];
435 sum_squared += diff * diff;
436 j += 1;
437 }
438
439 total_wcss += sum_squared;
440 }
441 }
442
443 total_wcss
444}
445
446#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
447#[target_feature(enable = "avx2")]
448unsafe fn wcss_avx2(points: &[&[f32]], centroids: &[&[f32]], assignments: &[usize]) -> f32 {
449 use core::arch::x86_64::*;
450
451 let n_samples = points.len();
452 let n_features = points[0].len();
453 let mut total_wcss = 0.0;
454
455 for i in 0..n_samples {
456 let cluster_id = assignments[i];
457 if cluster_id < centroids.len() {
458 let mut sum = _mm256_setzero_ps();
459 let mut j = 0;
460
461 while j + 8 <= n_features {
462 let p_vec = _mm256_loadu_ps(&points[i][j]);
463 let c_vec = _mm256_loadu_ps(¢roids[cluster_id][j]);
464 let diff = _mm256_sub_ps(p_vec, c_vec);
465 let squared = _mm256_mul_ps(diff, diff);
466 sum = _mm256_add_ps(sum, squared);
467 j += 8;
468 }
469
470 let mut result = [0.0f32; 8];
471 _mm256_storeu_ps(result.as_mut_ptr(), sum);
472 let mut sum_squared = result.iter().sum::<f32>();
473
474 while j < n_features {
475 let diff = points[i][j] - centroids[cluster_id][j];
476 sum_squared += diff * diff;
477 j += 1;
478 }
479
480 total_wcss += sum_squared;
481 }
482 }
483
484 total_wcss
485}
486
487pub fn silhouette_score(
490 points: &[&[f32]], assignments: &[usize], n_clusters: usize, ) -> f32 {
494 let n_samples = points.len();
495
496 if n_samples <= 1 || n_clusters <= 1 {
497 return 0.0;
498 }
499
500 let mut silhouette_scores = vec![0.0; n_samples];
501
502 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
503 {
504 if crate::simd_feature_detected!("avx2") {
505 unsafe {
506 silhouette_score_avx2(points, assignments, n_clusters, &mut silhouette_scores)
507 };
508 } else if crate::simd_feature_detected!("sse2") {
509 unsafe {
510 silhouette_score_sse2(points, assignments, n_clusters, &mut silhouette_scores)
511 };
512 } else {
513 silhouette_score_scalar(points, assignments, n_clusters, &mut silhouette_scores);
514 }
515 }
516 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
517 {
518 silhouette_score_scalar(points, assignments, n_clusters, &mut silhouette_scores);
519 }
520
521 silhouette_scores.iter().sum::<f32>() / n_samples as f32
523}
524
525fn silhouette_score_scalar(
526 points: &[&[f32]],
527 assignments: &[usize],
528 n_clusters: usize,
529 silhouette_scores: &mut [f32],
530) {
531 let n_samples = points.len();
532 let _n_features = points[0].len();
533
534 for i in 0..n_samples {
535 let cluster_i = assignments[i];
536
537 let mut intra_distance = 0.0;
539 let mut intra_count = 0;
540
541 for j in 0..n_samples {
542 if i != j && assignments[j] == cluster_i {
543 let dist: f32 = points[i]
544 .iter()
545 .zip(points[j].iter())
546 .map(|(a, b)| (a - b).powi(2))
547 .sum::<f32>();
548 intra_distance += dist.sqrt();
549 intra_count += 1;
550 }
551 }
552
553 let a_i = if intra_count > 0 {
554 intra_distance / intra_count as f32
555 } else {
556 0.0
557 };
558
559 let mut min_inter_distance = f32::INFINITY;
561
562 for c in 0..n_clusters {
563 if c != cluster_i {
564 let mut inter_distance = 0.0;
565 let mut inter_count = 0;
566
567 for j in 0..n_samples {
568 if assignments[j] == c {
569 let dist: f32 = points[i]
570 .iter()
571 .zip(points[j].iter())
572 .map(|(a, b)| (a - b).powi(2))
573 .sum::<f32>();
574 inter_distance += dist.sqrt();
575 inter_count += 1;
576 }
577 }
578
579 if inter_count > 0 {
580 let avg_inter = inter_distance / inter_count as f32;
581 min_inter_distance = min_inter_distance.min(avg_inter);
582 }
583 }
584 }
585
586 let b_i = min_inter_distance;
587
588 silhouette_scores[i] = if a_i < b_i {
590 1.0 - a_i / b_i
591 } else if a_i > b_i {
592 b_i / a_i - 1.0
593 } else {
594 0.0
595 };
596 }
597}
598
599#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
600#[target_feature(enable = "sse2")]
601unsafe fn silhouette_score_sse2(
602 points: &[&[f32]],
603 assignments: &[usize],
604 n_clusters: usize,
605 silhouette_scores: &mut [f32],
606) {
607 use core::arch::x86_64::*;
608
609 let n_samples = points.len();
610 let n_features = points[0].len();
611
612 for i in 0..n_samples {
613 let cluster_i = assignments[i];
614
615 let mut intra_distance = 0.0;
617 let mut intra_count = 0;
618
619 for j in 0..n_samples {
620 if i != j && assignments[j] == cluster_i {
621 let mut sum = _mm_setzero_ps();
622 let mut k = 0;
623
624 while k + 4 <= n_features {
625 let p1_vec = _mm_loadu_ps(&points[i][k]);
626 let p2_vec = _mm_loadu_ps(&points[j][k]);
627 let diff = _mm_sub_ps(p1_vec, p2_vec);
628 let squared = _mm_mul_ps(diff, diff);
629 sum = _mm_add_ps(sum, squared);
630 k += 4;
631 }
632
633 let mut result = [0.0f32; 4];
634 _mm_storeu_ps(result.as_mut_ptr(), sum);
635 let mut dist_squared = result[0] + result[1] + result[2] + result[3];
636
637 while k < n_features {
638 let diff = points[i][k] - points[j][k];
639 dist_squared += diff * diff;
640 k += 1;
641 }
642
643 intra_distance += dist_squared.sqrt();
644 intra_count += 1;
645 }
646 }
647
648 let a_i = if intra_count > 0 {
649 intra_distance / intra_count as f32
650 } else {
651 0.0
652 };
653
654 let mut min_inter_distance = f32::INFINITY;
656
657 for c in 0..n_clusters {
658 if c != cluster_i {
659 let mut inter_distance = 0.0;
660 let mut inter_count = 0;
661
662 for j in 0..n_samples {
663 if assignments[j] == c {
664 let mut sum = _mm_setzero_ps();
665 let mut k = 0;
666
667 while k + 4 <= n_features {
668 let p1_vec = _mm_loadu_ps(&points[i][k]);
669 let p2_vec = _mm_loadu_ps(&points[j][k]);
670 let diff = _mm_sub_ps(p1_vec, p2_vec);
671 let squared = _mm_mul_ps(diff, diff);
672 sum = _mm_add_ps(sum, squared);
673 k += 4;
674 }
675
676 let mut result = [0.0f32; 4];
677 _mm_storeu_ps(result.as_mut_ptr(), sum);
678 let mut dist_squared = result[0] + result[1] + result[2] + result[3];
679
680 while k < n_features {
681 let diff = points[i][k] - points[j][k];
682 dist_squared += diff * diff;
683 k += 1;
684 }
685
686 inter_distance += dist_squared.sqrt();
687 inter_count += 1;
688 }
689 }
690
691 if inter_count > 0 {
692 let avg_inter = inter_distance / inter_count as f32;
693 min_inter_distance = min_inter_distance.min(avg_inter);
694 }
695 }
696 }
697
698 let b_i = min_inter_distance;
699
700 silhouette_scores[i] = if a_i < b_i {
702 1.0 - a_i / b_i
703 } else if a_i > b_i {
704 b_i / a_i - 1.0
705 } else {
706 0.0
707 };
708 }
709}
710
711#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
712#[target_feature(enable = "avx2")]
713unsafe fn silhouette_score_avx2(
714 points: &[&[f32]],
715 assignments: &[usize],
716 n_clusters: usize,
717 silhouette_scores: &mut [f32],
718) {
719 use core::arch::x86_64::*;
720
721 let n_samples = points.len();
722 let n_features = points[0].len();
723
724 for i in 0..n_samples {
725 let cluster_i = assignments[i];
726
727 let mut intra_distance = 0.0;
729 let mut intra_count = 0;
730
731 for j in 0..n_samples {
732 if i != j && assignments[j] == cluster_i {
733 let mut sum = _mm256_setzero_ps();
734 let mut k = 0;
735
736 while k + 8 <= n_features {
737 let p1_vec = _mm256_loadu_ps(&points[i][k]);
738 let p2_vec = _mm256_loadu_ps(&points[j][k]);
739 let diff = _mm256_sub_ps(p1_vec, p2_vec);
740 let squared = _mm256_mul_ps(diff, diff);
741 sum = _mm256_add_ps(sum, squared);
742 k += 8;
743 }
744
745 let mut result = [0.0f32; 8];
746 _mm256_storeu_ps(result.as_mut_ptr(), sum);
747 let mut dist_squared = result.iter().sum::<f32>();
748
749 while k < n_features {
750 let diff = points[i][k] - points[j][k];
751 dist_squared += diff * diff;
752 k += 1;
753 }
754
755 intra_distance += dist_squared.sqrt();
756 intra_count += 1;
757 }
758 }
759
760 let a_i = if intra_count > 0 {
761 intra_distance / intra_count as f32
762 } else {
763 0.0
764 };
765
766 let mut min_inter_distance = f32::INFINITY;
768
769 for c in 0..n_clusters {
770 if c != cluster_i {
771 let mut inter_distance = 0.0;
772 let mut inter_count = 0;
773
774 for j in 0..n_samples {
775 if assignments[j] == c {
776 let mut sum = _mm256_setzero_ps();
777 let mut k = 0;
778
779 while k + 8 <= n_features {
780 let p1_vec = _mm256_loadu_ps(&points[i][k]);
781 let p2_vec = _mm256_loadu_ps(&points[j][k]);
782 let diff = _mm256_sub_ps(p1_vec, p2_vec);
783 let squared = _mm256_mul_ps(diff, diff);
784 sum = _mm256_add_ps(sum, squared);
785 k += 8;
786 }
787
788 let mut result = [0.0f32; 8];
789 _mm256_storeu_ps(result.as_mut_ptr(), sum);
790 let mut dist_squared = result.iter().sum::<f32>();
791
792 while k < n_features {
793 let diff = points[i][k] - points[j][k];
794 dist_squared += diff * diff;
795 k += 1;
796 }
797
798 inter_distance += dist_squared.sqrt();
799 inter_count += 1;
800 }
801 }
802
803 if inter_count > 0 {
804 let avg_inter = inter_distance / inter_count as f32;
805 min_inter_distance = min_inter_distance.min(avg_inter);
806 }
807 }
808 }
809
810 let b_i = min_inter_distance;
811
812 silhouette_scores[i] = if a_i < b_i {
814 1.0 - a_i / b_i
815 } else if a_i > b_i {
816 b_i / a_i - 1.0
817 } else {
818 0.0
819 };
820 }
821}
822
823pub fn dbscan_neighbors(
826 points: &[&[f32]], eps: f32, neighbors: &mut [Vec<usize>], ) {
830 let n_samples = points.len();
831 let eps_squared = eps * eps;
832
833 assert!(!points.is_empty(), "Points cannot be empty");
834 assert_eq!(
835 neighbors.len(),
836 n_samples,
837 "Neighbors array size must match points"
838 );
839
840 let n_features = points[0].len();
841 for point in points {
842 assert_eq!(
843 point.len(),
844 n_features,
845 "All points must have the same number of features"
846 );
847 }
848
849 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
850 {
851 if crate::simd_feature_detected!("avx2") {
852 unsafe { dbscan_neighbors_avx2(points, eps_squared, neighbors) };
853 return;
854 } else if crate::simd_feature_detected!("sse2") {
855 unsafe { dbscan_neighbors_sse2(points, eps_squared, neighbors) };
856 return;
857 }
858 }
859
860 dbscan_neighbors_scalar(points, eps_squared, neighbors);
861}
862
863fn dbscan_neighbors_scalar(points: &[&[f32]], eps_squared: f32, neighbors: &mut [Vec<usize>]) {
864 let n_samples = points.len();
865 let _n_features = points[0].len();
866
867 for i in 0..n_samples {
868 neighbors[i].clear();
869
870 for j in 0..n_samples {
871 if i != j {
872 let dist_squared: f32 = points[i]
873 .iter()
874 .zip(points[j].iter())
875 .map(|(a, b)| (a - b).powi(2))
876 .sum();
877
878 if dist_squared <= eps_squared {
879 neighbors[i].push(j);
880 }
881 }
882 }
883 }
884}
885
886#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
887#[target_feature(enable = "sse2")]
888unsafe fn dbscan_neighbors_sse2(points: &[&[f32]], eps_squared: f32, neighbors: &mut [Vec<usize>]) {
889 use core::arch::x86_64::*;
890
891 let n_samples = points.len();
892 let n_features = points[0].len();
893
894 for i in 0..n_samples {
895 neighbors[i].clear();
896
897 for j in 0..n_samples {
898 if i != j {
899 let mut sum = _mm_setzero_ps();
900 let mut k = 0;
901
902 while k + 4 <= n_features {
903 let p1_vec = _mm_loadu_ps(&points[i][k]);
904 let p2_vec = _mm_loadu_ps(&points[j][k]);
905 let diff = _mm_sub_ps(p1_vec, p2_vec);
906 let squared = _mm_mul_ps(diff, diff);
907 sum = _mm_add_ps(sum, squared);
908 k += 4;
909 }
910
911 let mut result = [0.0f32; 4];
912 _mm_storeu_ps(result.as_mut_ptr(), sum);
913 let mut dist_squared = result[0] + result[1] + result[2] + result[3];
914
915 while k < n_features {
916 let diff = points[i][k] - points[j][k];
917 dist_squared += diff * diff;
918 k += 1;
919 }
920
921 if dist_squared <= eps_squared {
922 neighbors[i].push(j);
923 }
924 }
925 }
926 }
927}
928
929#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
930#[target_feature(enable = "avx2")]
931unsafe fn dbscan_neighbors_avx2(points: &[&[f32]], eps_squared: f32, neighbors: &mut [Vec<usize>]) {
932 use core::arch::x86_64::*;
933
934 let n_samples = points.len();
935 let n_features = points[0].len();
936
937 for i in 0..n_samples {
938 neighbors[i].clear();
939
940 for j in 0..n_samples {
941 if i != j {
942 let mut sum = _mm256_setzero_ps();
943 let mut k = 0;
944
945 while k + 8 <= n_features {
946 let p1_vec = _mm256_loadu_ps(&points[i][k]);
947 let p2_vec = _mm256_loadu_ps(&points[j][k]);
948 let diff = _mm256_sub_ps(p1_vec, p2_vec);
949 let squared = _mm256_mul_ps(diff, diff);
950 sum = _mm256_add_ps(sum, squared);
951 k += 8;
952 }
953
954 let mut result = [0.0f32; 8];
955 _mm256_storeu_ps(result.as_mut_ptr(), sum);
956 let mut dist_squared = result.iter().sum::<f32>();
957
958 while k < n_features {
959 let diff = points[i][k] - points[j][k];
960 dist_squared += diff * diff;
961 k += 1;
962 }
963
964 if dist_squared <= eps_squared {
965 neighbors[i].push(j);
966 }
967 }
968 }
969 }
970}
971
972pub fn dbscan_core_points(
975 neighbors: &[Vec<usize>], min_samples: usize, core_points: &mut [bool], ) {
979 let n_samples = neighbors.len();
980
981 assert_eq!(
982 core_points.len(),
983 n_samples,
984 "Core points array size must match points"
985 );
986
987 for i in 0..n_samples {
988 core_points[i] = neighbors[i].len() + 1 >= min_samples;
990 }
991}
992
993pub fn hierarchical_linkage_distances(
996 points: &[&[f32]], cluster1: &[usize], cluster2: &[usize], linkage: LinkageType, ) -> f32 {
1001 assert!(!cluster1.is_empty(), "Cluster 1 cannot be empty");
1002 assert!(!cluster2.is_empty(), "Cluster 2 cannot be empty");
1003 assert!(!points.is_empty(), "Points cannot be empty");
1004
1005 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
1006 {
1007 if crate::simd_feature_detected!("avx2") {
1008 return unsafe {
1009 hierarchical_linkage_distances_avx2(points, cluster1, cluster2, linkage)
1010 };
1011 } else if crate::simd_feature_detected!("sse2") {
1012 return unsafe {
1013 hierarchical_linkage_distances_sse2(points, cluster1, cluster2, linkage)
1014 };
1015 }
1016 }
1017
1018 hierarchical_linkage_distances_scalar(points, cluster1, cluster2, linkage)
1019}
1020
1021#[derive(Debug, Clone, Copy)]
1023pub enum LinkageType {
1024 Single, Complete, Average, Ward, }
1029
1030fn hierarchical_linkage_distances_scalar(
1031 points: &[&[f32]],
1032 cluster1: &[usize],
1033 cluster2: &[usize],
1034 linkage: LinkageType,
1035) -> f32 {
1036 let n_features = points[0].len();
1037 let mut distances: Vec<f32> = Vec::new();
1038
1039 for &i in cluster1 {
1041 for &j in cluster2 {
1042 let dist_squared: f32 = points[i]
1043 .iter()
1044 .zip(points[j].iter())
1045 .map(|(a, b)| (a - b).powi(2))
1046 .sum();
1047 distances.push(dist_squared.sqrt());
1048 }
1049 }
1050
1051 match linkage {
1052 LinkageType::Single => distances.iter().fold(f32::INFINITY, |acc, &x| acc.min(x)),
1053 LinkageType::Complete => distances.iter().fold(0.0, |acc, &x| acc.max(x)),
1054 LinkageType::Average => distances.iter().sum::<f32>() / distances.len() as f32,
1055 LinkageType::Ward => {
1056 let mut centroid1 = vec![0.0; n_features];
1058 let mut centroid2 = vec![0.0; n_features];
1059
1060 for &i in cluster1 {
1062 for (c, &p) in centroid1.iter_mut().zip(points[i].iter()) {
1063 *c += p;
1064 }
1065 }
1066 for c in centroid1.iter_mut() {
1067 *c /= cluster1.len() as f32;
1068 }
1069
1070 for &i in cluster2 {
1071 for (c, &p) in centroid2.iter_mut().zip(points[i].iter()) {
1072 *c += p;
1073 }
1074 }
1075 for c in centroid2.iter_mut() {
1076 *c /= cluster2.len() as f32;
1077 }
1078
1079 let dist_squared: f32 = centroid1
1081 .iter()
1082 .zip(centroid2.iter())
1083 .map(|(a, b)| (a - b).powi(2))
1084 .sum();
1085
1086 let n1 = cluster1.len() as f32;
1088 let n2 = cluster2.len() as f32;
1089 let ward_factor = (n1 * n2) / (n1 + n2);
1090
1091 (ward_factor * dist_squared).sqrt()
1092 }
1093 }
1094}
1095
1096#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
1097#[target_feature(enable = "sse2")]
1098unsafe fn hierarchical_linkage_distances_sse2(
1099 points: &[&[f32]],
1100 cluster1: &[usize],
1101 cluster2: &[usize],
1102 linkage: LinkageType,
1103) -> f32 {
1104 use core::arch::x86_64::*;
1105
1106 let n_features = points[0].len();
1107 let mut distances = Vec::new();
1108
1109 for &i in cluster1 {
1111 for &j in cluster2 {
1112 let mut sum = _mm_setzero_ps();
1113 let mut k = 0;
1114
1115 while k + 4 <= n_features {
1116 let p1_vec = _mm_loadu_ps(&points[i][k]);
1117 let p2_vec = _mm_loadu_ps(&points[j][k]);
1118 let diff = _mm_sub_ps(p1_vec, p2_vec);
1119 let squared = _mm_mul_ps(diff, diff);
1120 sum = _mm_add_ps(sum, squared);
1121 k += 4;
1122 }
1123
1124 let mut result = [0.0f32; 4];
1125 _mm_storeu_ps(result.as_mut_ptr(), sum);
1126 let mut dist_squared = result[0] + result[1] + result[2] + result[3];
1127
1128 while k < n_features {
1129 let diff = points[i][k] - points[j][k];
1130 dist_squared += diff * diff;
1131 k += 1;
1132 }
1133
1134 distances.push(dist_squared.sqrt());
1135 }
1136 }
1137
1138 match linkage {
1139 LinkageType::Single => distances.iter().fold(f32::INFINITY, |acc, &x| acc.min(x)),
1140 LinkageType::Complete => distances.iter().fold(0.0, |acc, &x| acc.max(x)),
1141 LinkageType::Average => distances.iter().sum::<f32>() / distances.len() as f32,
1142 LinkageType::Ward => {
1143 hierarchical_linkage_distances_scalar(points, cluster1, cluster2, linkage)
1144 }
1145 }
1146}
1147
1148#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
1149#[target_feature(enable = "avx2")]
1150unsafe fn hierarchical_linkage_distances_avx2(
1151 points: &[&[f32]],
1152 cluster1: &[usize],
1153 cluster2: &[usize],
1154 linkage: LinkageType,
1155) -> f32 {
1156 use core::arch::x86_64::*;
1157
1158 let n_features = points[0].len();
1159 let mut distances = Vec::new();
1160
1161 for &i in cluster1 {
1163 for &j in cluster2 {
1164 let mut sum = _mm256_setzero_ps();
1165 let mut k = 0;
1166
1167 while k + 8 <= n_features {
1168 let p1_vec = _mm256_loadu_ps(&points[i][k]);
1169 let p2_vec = _mm256_loadu_ps(&points[j][k]);
1170 let diff = _mm256_sub_ps(p1_vec, p2_vec);
1171 let squared = _mm256_mul_ps(diff, diff);
1172 sum = _mm256_add_ps(sum, squared);
1173 k += 8;
1174 }
1175
1176 let mut result = [0.0f32; 8];
1177 _mm256_storeu_ps(result.as_mut_ptr(), sum);
1178 let mut dist_squared = result.iter().sum::<f32>();
1179
1180 while k < n_features {
1181 let diff = points[i][k] - points[j][k];
1182 dist_squared += diff * diff;
1183 k += 1;
1184 }
1185
1186 distances.push(dist_squared.sqrt());
1187 }
1188 }
1189
1190 match linkage {
1191 LinkageType::Single => distances.iter().fold(f32::INFINITY, |acc, &x| acc.min(x)),
1192 LinkageType::Complete => distances.iter().fold(0.0, |acc, &x| acc.max(x)),
1193 LinkageType::Average => distances.iter().sum::<f32>() / distances.len() as f32,
1194 LinkageType::Ward => {
1195 hierarchical_linkage_distances_scalar(points, cluster1, cluster2, linkage)
1196 }
1197 }
1198}
1199
1200#[allow(non_snake_case)]
1201#[cfg(all(test, not(feature = "no-std")))]
1202mod tests {
1203 use super::*;
1204 use approx::assert_relative_eq;
1205
1206 #[test]
1207 fn test_kmeans_distances() {
1208 let p1 = [1.0, 2.0];
1209 let p2 = [3.0, 4.0];
1210 let points = vec![&p1[..], &p2[..]];
1211
1212 let c1 = [0.0, 0.0];
1213 let c2 = [2.0, 3.0];
1214 let centroids = vec![&c1[..], &c2[..]];
1215
1216 let mut distances = vec![vec![]; 2];
1217
1218 kmeans_distances(&points, ¢roids, &mut distances);
1219
1220 assert_relative_eq!(distances[0][0], (5.0f32).sqrt(), epsilon = 1e-6);
1222
1223 assert_relative_eq!(distances[0][1], (2.0f32).sqrt(), epsilon = 1e-6);
1225 }
1226
1227 #[test]
1228 fn test_update_centroids() {
1229 let p1 = [1.0, 2.0];
1230 let p2 = [3.0, 4.0];
1231 let p3 = [5.0, 6.0];
1232 let points = vec![&p1[..], &p2[..], &p3[..]];
1233
1234 let assignments = vec![0, 1, 0]; let mut centroids = vec![vec![]; 2];
1236
1237 update_centroids(&points, &assignments, 2, &mut centroids);
1238
1239 assert_relative_eq!(centroids[0][0], 3.0, epsilon = 1e-6);
1241 assert_relative_eq!(centroids[0][1], 4.0, epsilon = 1e-6);
1242
1243 assert_relative_eq!(centroids[1][0], 3.0, epsilon = 1e-6);
1245 assert_relative_eq!(centroids[1][1], 4.0, epsilon = 1e-6);
1246 }
1247
1248 #[test]
1249 fn test_wcss() {
1250 let p1 = [1.0, 1.0];
1251 let p2 = [2.0, 2.0];
1252 let points = vec![&p1[..], &p2[..]];
1253
1254 let c1 = [0.0, 0.0];
1255 let centroids = vec![&c1[..]];
1256
1257 let assignments = vec![0, 0]; let wcss_value = wcss(&points, ¢roids, &assignments);
1260
1261 assert_relative_eq!(wcss_value, 10.0, epsilon = 1e-6);
1263 }
1264
1265 #[test]
1266 fn test_silhouette_score() {
1267 let p1 = [1.0, 1.0];
1269 let p2 = [1.1, 1.1];
1270 let p3 = [5.0, 5.0];
1271 let p4 = [5.1, 5.1];
1272 let points = vec![&p1[..], &p2[..], &p3[..], &p4[..]];
1273
1274 let assignments = vec![0, 0, 1, 1]; let score = silhouette_score(&points, &assignments, 2);
1277
1278 assert!(score > 0.0);
1280 assert!(score <= 1.0);
1281 }
1282
1283 #[test]
1284 fn test_dbscan_neighbors() {
1285 let p1 = [1.0, 1.0];
1287 let p2 = [1.1, 1.1];
1288 let p3 = [5.0, 5.0];
1289 let p4 = [5.1, 5.1];
1290 let points = vec![&p1[..], &p2[..], &p3[..], &p4[..]];
1291
1292 let mut neighbors = vec![vec![]; 4];
1293 let eps = 0.5; dbscan_neighbors(&points, eps, &mut neighbors);
1296
1297 assert!(neighbors[0].contains(&1));
1299 assert!(neighbors[1].contains(&0));
1300
1301 assert!(neighbors[2].contains(&3));
1303 assert!(neighbors[3].contains(&2));
1304
1305 assert!(!neighbors[0].contains(&2));
1307 assert!(!neighbors[0].contains(&3));
1308 assert!(!neighbors[1].contains(&2));
1309 assert!(!neighbors[1].contains(&3));
1310 }
1311
1312 #[test]
1313 fn test_dbscan_core_points() {
1314 let neighbors = vec![
1316 vec![1], vec![0, 2], vec![1], ];
1320
1321 let mut core_points = vec![false; 3];
1322 let min_samples = 3;
1323
1324 dbscan_core_points(&neighbors, min_samples, &mut core_points);
1325
1326 assert!(!core_points[0]);
1328 assert!(core_points[1]);
1329 assert!(!core_points[2]);
1330 }
1331
1332 #[test]
1333 fn test_hierarchical_linkage_single() {
1334 let p1 = [1.0, 1.0];
1335 let p2 = [2.0, 2.0];
1336 let p3 = [5.0, 5.0];
1337 let p4 = [6.0, 6.0];
1338 let points = vec![&p1[..], &p2[..], &p3[..], &p4[..]];
1339
1340 let cluster1 = vec![0, 1]; let cluster2 = vec![2, 3]; let distance =
1344 hierarchical_linkage_distances(&points, &cluster1, &cluster2, LinkageType::Single);
1345
1346 let expected = ((5.0f32 - 2.0f32).powi(2) + (5.0f32 - 2.0f32).powi(2)).sqrt();
1349 assert_relative_eq!(distance, expected, epsilon = 1e-6);
1350 }
1351
1352 #[test]
1353 fn test_hierarchical_linkage_complete() {
1354 let p1 = [1.0, 1.0];
1355 let p2 = [2.0, 2.0];
1356 let p3 = [5.0, 5.0];
1357 let p4 = [6.0, 6.0];
1358 let points = vec![&p1[..], &p2[..], &p3[..], &p4[..]];
1359
1360 let cluster1 = vec![0, 1]; let cluster2 = vec![2, 3]; let distance =
1364 hierarchical_linkage_distances(&points, &cluster1, &cluster2, LinkageType::Complete);
1365
1366 let expected = ((6.0f32 - 1.0f32).powi(2) + (6.0f32 - 1.0f32).powi(2)).sqrt();
1369 assert_relative_eq!(distance, expected, epsilon = 1e-6);
1370 }
1371
1372 #[test]
1373 fn test_hierarchical_linkage_average() {
1374 let p1 = [0.0, 0.0];
1375 let p2 = [1.0, 0.0];
1376 let p3 = [3.0, 0.0];
1377 let points = vec![&p1[..], &p2[..], &p3[..]];
1378
1379 let cluster1 = vec![0]; let cluster2 = vec![1, 2]; let distance =
1383 hierarchical_linkage_distances(&points, &cluster1, &cluster2, LinkageType::Average);
1384
1385 assert_relative_eq!(distance, 2.0, epsilon = 1e-6);
1389 }
1390
1391 #[test]
1392 fn test_hierarchical_linkage_ward() {
1393 let p1 = [0.0, 0.0];
1394 let p2 = [2.0, 0.0];
1395 let p3 = [4.0, 0.0];
1396 let points = vec![&p1[..], &p2[..], &p3[..]];
1397
1398 let cluster1 = vec![0]; let cluster2 = vec![1, 2]; let distance =
1402 hierarchical_linkage_distances(&points, &cluster1, &cluster2, LinkageType::Ward);
1403
1404 let expected = (2.0f32 / 3.0f32 * 9.0f32).sqrt();
1411 assert_relative_eq!(distance, expected, epsilon = 1e-6);
1412 }
1413}