1use crate::*;
7use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis};
9use sklears_core::error::Result as SklResult;
10use sklears_core::prelude::{SklearsError, Transform};
11
12#[derive(Debug, Clone)]
40pub struct RadialBasisFunctions {
41 n_centers: usize,
42 rbf_type: RBFType,
43 centers: Option<Array2<Float>>,
44 shape_parameter: Float,
45 normalize: bool,
46}
47
48#[derive(Debug, Clone, Copy)]
49pub enum RBFType {
50 Gaussian,
52 Multiquadric,
54 InverseMultiquadric,
56 ThinPlateSpline,
58 Linear,
60 Cubic,
62 Quintic,
64}
65
66#[allow(non_snake_case)] impl RadialBasisFunctions {
68 pub fn new() -> Self {
70 Self {
71 n_centers: 10,
72 rbf_type: RBFType::Gaussian,
73 centers: None,
74 shape_parameter: 1.0,
75 normalize: false,
76 }
77 }
78
79 pub fn n_centers(mut self, n_centers: usize) -> Self {
81 self.n_centers = n_centers;
82 self
83 }
84
85 pub fn rbf_type(mut self, rbf_type: RBFType) -> Self {
87 self.rbf_type = rbf_type;
88 self
89 }
90
91 pub fn centers(mut self, centers: Array2<Float>) -> Self {
93 self.centers = Some(centers);
94 self
95 }
96
97 pub fn shape_parameter(mut self, shape_parameter: Float) -> Self {
99 self.shape_parameter = shape_parameter;
100 self
101 }
102
103 pub fn normalize(mut self, normalize: bool) -> Self {
105 self.normalize = normalize;
106 self
107 }
108
109 pub fn transform(&self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
111 let (n_samples, _n_features) = X.dim();
112
113 let centers = match &self.centers {
115 Some(centers) => centers.clone(),
116 None => self.generate_centers(X)?,
117 };
118
119 let n_centers = centers.nrows();
120 let mut rbf_features = Array2::zeros((n_samples, n_centers));
121
122 for (sample_idx, sample) in X.axis_iter(Axis(0)).enumerate() {
124 for (center_idx, center) in centers.axis_iter(Axis(0)).enumerate() {
125 let distance = self.compute_distance(&sample, ¢er);
126 let rbf_value = self.evaluate_rbf(distance);
127 rbf_features[[sample_idx, center_idx]] = rbf_value;
128 }
129 }
130
131 if self.normalize {
133 self.normalize_features(&mut rbf_features);
134 }
135
136 Ok(rbf_features)
137 }
138
139 fn generate_centers(&self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
140 let (n_samples, n_features) = X.dim();
141
142 if self.n_centers > n_samples {
143 return Err(SklearsError::InvalidInput(
144 "Number of centers cannot exceed number of samples".to_string(),
145 ));
146 }
147
148 let mut centers = Array2::zeros((self.n_centers, n_features));
150
151 if n_samples > 0 {
153 for j in 0..n_features {
154 centers[[0, j]] = X[[0, j]];
155 }
156 }
157
158 for center_idx in 1..self.n_centers {
160 let mut best_sample_idx = 0;
161 let mut max_min_distance = 0.0;
162
163 for sample_idx in 0..n_samples {
164 let sample = X.row(sample_idx);
165 let mut min_distance = Float::INFINITY;
166
167 for existing_center_idx in 0..center_idx {
169 let center = centers.row(existing_center_idx);
170 let distance = self.compute_distance(&sample, ¢er);
171 min_distance = min_distance.min(distance);
172 }
173
174 if min_distance > max_min_distance {
176 max_min_distance = min_distance;
177 best_sample_idx = sample_idx;
178 }
179 }
180
181 for j in 0..n_features {
183 centers[[center_idx, j]] = X[[best_sample_idx, j]];
184 }
185 }
186
187 Ok(centers)
188 }
189
190 fn compute_distance(&self, point1: &ArrayView1<Float>, point2: &ArrayView1<Float>) -> Float {
191 let mut sum_squared = 0.0;
193 for (x1, x2) in point1.iter().zip(point2.iter()) {
194 sum_squared += (x1 - x2).powi(2);
195 }
196 sum_squared.sqrt()
197 }
198
199 fn evaluate_rbf(&self, distance: Float) -> Float {
200 let r = distance;
201 let epsilon = self.shape_parameter;
202
203 match self.rbf_type {
204 RBFType::Gaussian => (-epsilon * r * r).exp(),
205 RBFType::Multiquadric => (1.0 + epsilon * r * r).sqrt(),
206 RBFType::InverseMultiquadric => 1.0 / (1.0 + epsilon * r * r).sqrt(),
207 RBFType::ThinPlateSpline => {
208 if r < 1e-10 {
209 0.0
210 } else {
211 r * r * r.ln()
212 }
213 }
214 RBFType::Linear => r,
215 RBFType::Cubic => r.powi(3),
216 RBFType::Quintic => r.powi(5),
217 }
218 }
219
220 fn normalize_features(&self, features: &mut Array2<Float>) {
221 let (n_samples, n_features) = features.dim();
222
223 for j in 0..n_features {
224 let mut column_sum = 0.0;
225 for i in 0..n_samples {
226 column_sum += features[[i, j]];
227 }
228
229 if column_sum > 1e-10 {
230 for i in 0..n_samples {
231 features[[i, j]] /= column_sum;
232 }
233 }
234 }
235 }
236}
237
238impl Default for RadialBasisFunctions {
239 fn default() -> Self {
240 Self::new()
241 }
242}
243
244#[allow(non_snake_case)] impl Transform<ArrayView2<'_, Float>, Array2<Float>> for RadialBasisFunctions {
246 fn transform(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
247 self.transform(X)
248 }
249}
250
251#[derive(Debug, Clone)]
284pub struct RBFSampler {
285 gamma: Float,
286 n_components: usize,
287 random_state: Option<u64>,
288 random_weights: Option<Array2<Float>>,
289 random_offset: Option<Array1<Float>>,
290}
291
292#[allow(non_snake_case)] impl RBFSampler {
294 pub fn new() -> Self {
296 Self {
297 gamma: 1.0,
298 n_components: 100,
299 random_state: None,
300 random_weights: None,
301 random_offset: None,
302 }
303 }
304
305 pub fn gamma(mut self, gamma: Float) -> Self {
307 self.gamma = gamma;
308 self
309 }
310
311 pub fn n_components(mut self, n_components: usize) -> Self {
313 self.n_components = n_components;
314 self
315 }
316
317 pub fn random_state(mut self, random_state: Option<u64>) -> Self {
319 self.random_state = random_state;
320 self
321 }
322
323 pub fn fit(&mut self, X: &ArrayView2<Float>) -> SklResult<&mut Self> {
325 let (_, n_features) = X.dim();
326
327 let variance = 2.0 * self.gamma;
329 let mut random_weights = Array2::zeros((self.n_components, n_features));
330 let mut random_offset = Array1::zeros(self.n_components);
331
332 let mut seed = self.random_state.unwrap_or(42);
334
335 for i in 0..self.n_components {
336 for j in 0..n_features {
337 let u1 = (seed % 1000) as Float / 1000.0;
339 seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
340 let u2 = (seed % 1000) as Float / 1000.0;
341 seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
342
343 let z = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
344 random_weights[[i, j]] = z * variance.sqrt();
345 }
346
347 let u = (seed % 1000) as Float / 1000.0;
349 seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
350 random_offset[i] = 2.0 * std::f64::consts::PI * u;
351 }
352
353 self.random_weights = Some(random_weights);
354 self.random_offset = Some(random_offset);
355
356 Ok(self)
357 }
358
359 pub fn transform(&self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
361 let weights = self.random_weights.as_ref().ok_or_else(|| {
362 SklearsError::InvalidInput("RBFSampler must be fitted before transform".to_string())
363 })?;
364 let offset = self.random_offset.as_ref().ok_or_else(|| {
365 SklearsError::InvalidInput("RBFSampler must be fitted before transform".to_string())
366 })?;
367
368 let (n_samples, n_features) = X.dim();
369 if weights.ncols() != n_features {
370 return Err(SklearsError::InvalidInput(
371 "Feature dimension mismatch".to_string(),
372 ));
373 }
374
375 let mut features = Array2::zeros((n_samples, self.n_components));
376 let normalization = (2.0 / self.n_components as Float).sqrt();
377
378 for i in 0..n_samples {
379 let sample = X.row(i);
380 for j in 0..self.n_components {
381 let weight_row = weights.row(j);
382 let projection = sample.dot(&weight_row) + offset[j];
383 features[[i, j]] = normalization * projection.cos();
384 }
385 }
386
387 Ok(features)
388 }
389
390 pub fn fit_transform(&mut self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
392 self.fit(X)?;
393 self.transform(X)
394 }
395}
396
397impl Default for RBFSampler {
398 fn default() -> Self {
399 Self::new()
400 }
401}
402
403#[derive(Debug, Clone)]
439pub struct Nystroem {
440 kernel: NystromKernel,
441 n_components: usize,
442 gamma: Float,
443 degree: usize,
444 coef0: Float,
445 random_state: Option<u64>,
446 landmarks: Option<Array2<Float>>,
447 normalization_matrix: Option<Array2<Float>>,
448}
449
450#[derive(Debug, Clone, Copy)]
451pub enum NystromKernel {
452 RBF,
454 Polynomial,
456 Linear,
458 Sigmoid,
460}
461
462#[allow(non_snake_case)] impl Nystroem {
464 pub fn new() -> Self {
466 Self {
467 kernel: NystromKernel::RBF,
468 n_components: 100,
469 gamma: 1.0,
470 degree: 3,
471 coef0: 1.0,
472 random_state: None,
473 landmarks: None,
474 normalization_matrix: None,
475 }
476 }
477
478 pub fn kernel(mut self, kernel: NystromKernel) -> Self {
480 self.kernel = kernel;
481 self
482 }
483
484 pub fn n_components(mut self, n_components: usize) -> Self {
486 self.n_components = n_components;
487 self
488 }
489
490 pub fn gamma(mut self, gamma: Float) -> Self {
492 self.gamma = gamma;
493 self
494 }
495
496 pub fn degree(mut self, degree: usize) -> Self {
498 self.degree = degree;
499 self
500 }
501
502 pub fn coef0(mut self, coef0: Float) -> Self {
504 self.coef0 = coef0;
505 self
506 }
507
508 pub fn random_state(mut self, random_state: Option<u64>) -> Self {
510 self.random_state = random_state;
511 self
512 }
513
514 #[allow(non_snake_case)]
516 pub fn fit(&mut self, X: &ArrayView2<Float>) -> SklResult<&mut Self> {
517 let (n_samples, n_features) = X.dim();
518 let n_landmarks = self.n_components.min(n_samples);
519
520 let landmark_indices = self.select_landmarks(n_samples, n_landmarks)?;
522 let mut landmarks = Array2::zeros((n_landmarks, n_features));
523
524 for (i, &idx) in landmark_indices.iter().enumerate() {
525 landmarks.row_mut(i).assign(&X.row(idx));
526 }
527
528 let W = self.compute_kernel_matrix(&landmarks.view(), &landmarks.view())?;
530
531 let W_inv = self.compute_regularized_inverse(&W)?;
533
534 self.landmarks = Some(landmarks);
535 self.normalization_matrix = Some(W_inv);
536
537 Ok(self)
538 }
539
540 pub fn transform(&self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
542 let landmarks = self.landmarks.as_ref().ok_or_else(|| {
543 SklearsError::InvalidInput("Nystroem must be fitted before transform".to_string())
544 })?;
545 let W_inv = self.normalization_matrix.as_ref().ok_or_else(|| {
546 SklearsError::InvalidInput("Nystroem must be fitted before transform".to_string())
547 })?;
548
549 let C = self.compute_kernel_matrix(X, &landmarks.view())?;
551
552 let features = C.dot(W_inv);
554
555 Ok(features)
556 }
557
558 pub fn fit_transform(&mut self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
560 self.fit(X)?;
561 self.transform(X)
562 }
563
564 fn select_landmarks(&self, n_samples: usize, n_landmarks: usize) -> SklResult<Vec<usize>> {
565 let mut landmarks = Vec::new();
566 let mut seed = self.random_state.unwrap_or(42);
567
568 for _ in 0..n_landmarks {
570 let idx = (seed % n_samples as u64) as usize;
571 landmarks.push(idx);
572 seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
573 }
574
575 Ok(landmarks)
576 }
577
578 fn compute_kernel_matrix(
579 &self,
580 X: &ArrayView2<Float>,
581 Y: &ArrayView2<Float>,
582 ) -> SklResult<Array2<Float>> {
583 let (n_x, _) = X.dim();
584 let (n_y, _) = Y.dim();
585 let mut K = Array2::zeros((n_x, n_y));
586
587 for i in 0..n_x {
588 for j in 0..n_y {
589 let x_i = X.row(i);
590 let y_j = Y.row(j);
591 K[[i, j]] = self.kernel_function(&x_i, &y_j);
592 }
593 }
594
595 Ok(K)
596 }
597
598 fn kernel_function(&self, x: &ArrayView1<Float>, y: &ArrayView1<Float>) -> Float {
599 match self.kernel {
600 NystromKernel::RBF => {
601 let mut squared_distance = 0.0;
602 for (xi, yi) in x.iter().zip(y.iter()) {
603 squared_distance += (xi - yi).powi(2);
604 }
605 (-self.gamma * squared_distance).exp()
606 }
607 NystromKernel::Linear => x.dot(y),
608 NystromKernel::Polynomial => {
609 let dot_product = x.dot(y);
610 (self.gamma * dot_product + self.coef0).powf(self.degree as Float)
611 }
612 NystromKernel::Sigmoid => {
613 let dot_product = x.dot(y);
614 (self.gamma * dot_product + self.coef0).tanh()
615 }
616 }
617 }
618
619 fn compute_regularized_inverse(&self, matrix: &Array2<Float>) -> SklResult<Array2<Float>> {
620 let n = matrix.nrows();
621 if n != matrix.ncols() {
622 return Err(SklearsError::InvalidInput(
623 "Matrix must be square for inversion".to_string(),
624 ));
625 }
626
627 let regularization = 1e-12;
629 let mut A = matrix.clone();
630 for i in 0..n {
631 A[[i, i]] += regularization;
632 }
633
634 self.approximate_matrix_sqrt(&A)
637 }
638
639 fn approximate_matrix_sqrt(&self, matrix: &Array2<Float>) -> SklResult<Array2<Float>> {
640 let n = matrix.nrows();
641
642 let max_components = n.min(self.n_components);
645 let mut result = Array2::zeros((n, max_components));
646
647 for comp in 0..max_components {
648 let mut v = Array1::ones(n);
649
650 let norm = v.iter().map(|x| x * x).sum::<Float>().sqrt();
652 if norm > 0.0 {
653 v /= norm;
654 }
655
656 for _iter in 0..20 {
658 let mut Av = Array1::zeros(n);
659 for i in 0..n {
660 for j in 0..n {
661 Av[i] += matrix[[i, j]] * v[j];
662 }
663 }
664
665 for prev_comp in 0..comp {
667 let prev_vec = result.column(prev_comp);
668 let projection = Av.dot(&prev_vec);
669 for k in 0..n {
670 Av[k] -= projection * prev_vec[k];
671 }
672 }
673
674 let norm = Av.iter().map(|x| x * x).sum::<Float>().sqrt();
675 if norm < 1e-10 {
676 break;
677 }
678 v = Av / norm;
679 }
680
681 let mut Av = Array1::zeros(n);
683 for i in 0..n {
684 for j in 0..n {
685 Av[i] += matrix[[i, j]] * v[j];
686 }
687 }
688 let eigenvalue = v.dot(&Av).max(1e-10);
689
690 let sqrt_eigenvalue = eigenvalue.sqrt();
692 for i in 0..n {
693 result[[i, comp]] = sqrt_eigenvalue * v[i];
694 }
695 }
696
697 Ok(result)
698 }
699}
700
701impl Default for Nystroem {
702 fn default() -> Self {
703 Self::new()
704 }
705}
706
707#[derive(Debug, Clone)]
733pub struct AdditiveChi2Sampler {
734 sample_steps: usize,
735 sample_interval: Float,
736}
737
738impl AdditiveChi2Sampler {
739 pub fn new() -> Self {
741 Self {
742 sample_steps: 2,
743 sample_interval: 1.0,
744 }
745 }
746
747 pub fn sample_steps(mut self, sample_steps: usize) -> Self {
749 self.sample_steps = sample_steps;
750 self
751 }
752
753 pub fn sample_interval(mut self, sample_interval: Float) -> Self {
755 self.sample_interval = sample_interval;
756 self
757 }
758
759 #[allow(non_snake_case)]
761 pub fn transform(&self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
762 let (n_samples, n_features) = X.dim();
763 let n_components = 2 * n_features * self.sample_steps;
764 let mut features = Array2::zeros((n_samples, n_components));
765
766 for (sample_idx, sample) in X.axis_iter(Axis(0)).enumerate() {
767 let mut feature_idx = 0;
768
769 for &x_i in sample.iter() {
770 for step in 0..self.sample_steps {
771 let omega = (step + 1) as Float * self.sample_interval;
772
773 if x_i > 0.0 {
774 let sqrt_x = x_i.sqrt();
775 let cos_val = (2.0 * omega * sqrt_x).cos();
776 let sin_val = (2.0 * omega * sqrt_x).sin();
777
778 features[[sample_idx, feature_idx]] =
779 (2.0 / std::f64::consts::PI).sqrt() * cos_val;
780 features[[sample_idx, feature_idx + 1]] =
781 (2.0 / std::f64::consts::PI).sqrt() * sin_val;
782 }
783
784 feature_idx += 2;
785 }
786 }
787 }
788
789 Ok(features)
790 }
791
792 pub fn get_n_output_features(&self, n_input_features: usize) -> usize {
794 2 * n_input_features * self.sample_steps
795 }
796}
797
798impl Default for AdditiveChi2Sampler {
799 fn default() -> Self {
800 Self::new()
801 }
802}
803
804#[allow(non_snake_case)] impl Transform<ArrayView2<'_, Float>, Array2<Float>> for AdditiveChi2Sampler {
806 fn transform(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
807 self.transform(X)
808 }
809}