1use scirs2_core::ndarray::{Array2, ArrayView1, ArrayView2, Axis};
9use sklears_core::{
11 error::Result as SklResult,
12 prelude::{SklearsError, Transform},
13 types::Float,
14};
15
16#[derive(Debug, Clone)]
43pub struct PolynomialFeatures {
44 degree: usize,
45 interaction_only: bool,
46 include_bias: bool,
47}
48
49#[allow(non_snake_case)] impl PolynomialFeatures {
51 pub fn new() -> Self {
53 Self {
54 degree: 2,
55 interaction_only: false,
56 include_bias: true,
57 }
58 }
59
60 pub fn degree(mut self, degree: usize) -> Self {
62 self.degree = degree;
63 self
64 }
65
66 pub fn interaction_only(mut self, interaction_only: bool) -> Self {
68 self.interaction_only = interaction_only;
69 self
70 }
71
72 pub fn include_bias(mut self, include_bias: bool) -> Self {
74 self.include_bias = include_bias;
75 self
76 }
77
78 pub fn transform(&self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
80 let (n_samples, n_features) = X.dim();
81
82 if self.degree == 0 && !self.include_bias {
83 return Err(SklearsError::InvalidInput(
84 "At least one of degree > 0 or include_bias=true must be specified".to_string(),
85 ));
86 }
87
88 let combinations = self.generate_combinations(n_features)?;
90 let n_output_features = combinations.len();
91
92 let mut result = Array2::<Float>::zeros((n_samples, n_output_features));
93
94 for (sample_idx, sample) in X.axis_iter(Axis(0)).enumerate() {
95 for (feat_idx, combination) in combinations.iter().enumerate() {
96 let mut value = 1.0;
97
98 if combination.is_empty() {
99 value = 1.0;
101 } else {
102 for &feature_idx in combination {
103 value *= sample[feature_idx];
104 }
105 }
106
107 result[[sample_idx, feat_idx]] = value;
108 }
109 }
110
111 Ok(result)
112 }
113
114 pub fn get_feature_names(&self, input_features: Option<&[String]>) -> SklResult<Vec<String>> {
116 let n_features = match input_features {
117 Some(names) => names.len(),
118 None => {
119 return Err(SklearsError::InvalidInput(
120 "Input feature names must be provided".to_string(),
121 ))
122 }
123 };
124
125 let combinations = self.generate_combinations(n_features)?;
126 let mut feature_names = Vec::new();
127
128 for combination in combinations {
129 if combination.is_empty() {
130 feature_names.push("1".to_string());
132 } else if combination.len() == 1 {
133 feature_names.push(
135 input_features.expect("operation should succeed")[combination[0]].clone(),
136 );
137 } else {
138 let mut name = String::new();
140 for (i, &feature_idx) in combination.iter().enumerate() {
141 if i > 0 {
142 name.push(' ');
143 }
144 name.push_str(&input_features.expect("operation should succeed")[feature_idx]);
145 }
146 feature_names.push(name);
147 }
148 }
149
150 Ok(feature_names)
151 }
152
153 pub fn get_n_output_features(&self, n_input_features: usize) -> SklResult<usize> {
155 let combinations = self.generate_combinations(n_input_features)?;
156 Ok(combinations.len())
157 }
158
159 fn generate_combinations(&self, n_features: usize) -> SklResult<Vec<Vec<usize>>> {
160 let mut combinations = Vec::new();
161
162 if self.include_bias {
164 combinations.push(Vec::new());
165 }
166
167 for degree in 1..=self.degree {
169 let degree_combinations = if self.interaction_only {
170 self.generate_interaction_combinations(n_features, degree)?
171 } else {
172 self.generate_degree_combinations(n_features, degree)?
173 };
174 combinations.extend(degree_combinations);
175 }
176
177 Ok(combinations)
178 }
179
180 fn generate_interaction_combinations(
181 &self,
182 n_features: usize,
183 degree: usize,
184 ) -> SklResult<Vec<Vec<usize>>> {
185 let mut combinations = Vec::new();
186 let mut current = vec![0; degree];
187
188 self.generate_interaction_recursive(
189 &mut combinations,
190 &mut current,
191 n_features,
192 degree,
193 0,
194 0,
195 );
196
197 Ok(combinations)
198 }
199
200 fn generate_interaction_recursive(
201 &self,
202 combinations: &mut Vec<Vec<usize>>,
203 current: &mut [usize],
204 n_features: usize,
205 degree: usize,
206 pos: usize,
207 start: usize,
208 ) {
209 if pos == degree {
210 let mut unique_features = current.to_vec();
212 unique_features.sort_unstable();
213 unique_features.dedup();
214
215 if unique_features.len() == degree {
216 combinations.push(current.to_vec());
217 }
218 return;
219 }
220
221 for i in start..n_features {
222 current[pos] = i;
223 self.generate_interaction_recursive(
224 combinations,
225 current,
226 n_features,
227 degree,
228 pos + 1,
229 i + 1,
230 );
231 }
232 }
233
234 fn generate_degree_combinations(
235 &self,
236 n_features: usize,
237 degree: usize,
238 ) -> SklResult<Vec<Vec<usize>>> {
239 let mut combinations = Vec::new();
240 let mut current = vec![0; degree];
241
242 self.generate_degree_recursive(&mut combinations, &mut current, n_features, degree, 0, 0);
243
244 Ok(combinations)
245 }
246
247 fn generate_degree_recursive(
248 &self,
249 combinations: &mut Vec<Vec<usize>>,
250 current: &mut [usize],
251 n_features: usize,
252 degree: usize,
253 pos: usize,
254 start: usize,
255 ) {
256 if pos == degree {
257 combinations.push(current.to_vec());
258 return;
259 }
260
261 for i in start..n_features {
262 current[pos] = i;
263 self.generate_degree_recursive(combinations, current, n_features, degree, pos + 1, i);
264 }
265 }
266}
267
268impl Default for PolynomialFeatures {
269 fn default() -> Self {
270 Self::new()
271 }
272}
273
274#[allow(non_snake_case)] impl Transform<ArrayView2<'_, Float>, Array2<Float>> for PolynomialFeatures {
276 fn transform(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
277 self.transform(X)
278 }
279}
280
281#[derive(Debug, Clone)]
310pub struct SplineBasisFunctions {
311 n_splines: usize,
312 degree: usize,
313 spline_type: SplineType,
314 knots: Option<Vec<Float>>,
315 extrapolation: ExtrapolationMode,
316}
317
318#[derive(Debug, Clone, Copy)]
319pub enum SplineType {
320 BSpline,
322 NaturalCubic,
324 TruncatedPower,
326}
327
328#[derive(Debug, Clone, Copy)]
329pub enum ExtrapolationMode {
330 Constant,
332 Linear,
334 Zero,
336 Error,
338}
339
340#[allow(non_snake_case)] impl SplineBasisFunctions {
342 pub fn new() -> Self {
344 Self {
345 n_splines: 5,
346 degree: 3,
347 spline_type: SplineType::BSpline,
348 knots: None,
349 extrapolation: ExtrapolationMode::Constant,
350 }
351 }
352
353 pub fn n_splines(mut self, n_splines: usize) -> Self {
355 self.n_splines = n_splines;
356 self
357 }
358
359 pub fn degree(mut self, degree: usize) -> Self {
361 self.degree = degree;
362 self
363 }
364
365 pub fn spline_type(mut self, spline_type: SplineType) -> Self {
367 self.spline_type = spline_type;
368 self
369 }
370
371 pub fn knots(mut self, knots: Vec<Float>) -> Self {
373 self.knots = Some(knots);
374 self
375 }
376
377 pub fn extrapolation(mut self, extrapolation: ExtrapolationMode) -> Self {
379 self.extrapolation = extrapolation;
380 self
381 }
382
383 pub fn transform(&self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
385 if X.ncols() != 1 {
386 return Err(SklearsError::InvalidInput(
387 "SplineBasisFunctions currently supports only 1D input".to_string(),
388 ));
389 }
390
391 let x_values: Vec<Float> = X.column(0).to_vec();
392 let x_min = x_values.iter().cloned().fold(Float::INFINITY, Float::min);
393 let x_max = x_values
394 .iter()
395 .cloned()
396 .fold(Float::NEG_INFINITY, Float::max);
397
398 let knots = match &self.knots {
400 Some(knots) => knots.clone(),
401 None => self.generate_uniform_knots(x_min, x_max)?,
402 };
403
404 match self.spline_type {
405 SplineType::BSpline => self.compute_bspline_basis(&x_values, &knots),
406 SplineType::NaturalCubic => self.compute_natural_cubic_basis(&x_values, &knots),
407 SplineType::TruncatedPower => self.compute_truncated_power_basis(&x_values, &knots),
408 }
409 }
410
411 fn generate_uniform_knots(&self, x_min: Float, x_max: Float) -> SklResult<Vec<Float>> {
412 if x_min >= x_max {
413 return Err(SklearsError::InvalidInput(
414 "Invalid range for knot generation".to_string(),
415 ));
416 }
417
418 let mut knots = Vec::new();
419
420 match self.spline_type {
421 SplineType::BSpline => {
422 let n_interior_knots = self.n_splines - self.degree - 1;
424 let _total_knots = n_interior_knots + 2 * (self.degree + 1);
425
426 let step = (x_max - x_min) / (n_interior_knots + 1) as Float;
427
428 for _ in 0..=self.degree {
430 knots.push(x_min);
431 }
432
433 for i in 1..=n_interior_knots {
435 knots.push(x_min + i as Float * step);
436 }
437
438 for _ in 0..=self.degree {
440 knots.push(x_max);
441 }
442 }
443 SplineType::NaturalCubic | SplineType::TruncatedPower => {
444 let step = (x_max - x_min) / (self.n_splines - 1) as Float;
446 for i in 0..self.n_splines {
447 knots.push(x_min + i as Float * step);
448 }
449 }
450 }
451
452 Ok(knots)
453 }
454
455 fn compute_bspline_basis(
456 &self,
457 x_values: &[Float],
458 knots: &[Float],
459 ) -> SklResult<Array2<Float>> {
460 let n_samples = x_values.len();
461 let n_basis = self.n_splines;
462 let mut basis_matrix = Array2::zeros((n_samples, n_basis));
463
464 for (sample_idx, &x) in x_values.iter().enumerate() {
465 let basis_values = self.evaluate_bspline_basis(x, knots)?;
466
467 for (basis_idx, &value) in basis_values.iter().enumerate() {
468 if basis_idx < n_basis {
469 basis_matrix[[sample_idx, basis_idx]] = value;
470 }
471 }
472 }
473
474 Ok(basis_matrix)
475 }
476
477 fn evaluate_bspline_basis(&self, x: Float, knots: &[Float]) -> SklResult<Vec<Float>> {
478 let n_knots = knots.len();
479 let degree = self.degree;
480 let n_basis = n_knots - degree - 1;
481
482 if n_basis == 0 {
483 return Ok(vec![]);
484 }
485
486 let mut basis = vec![0.0; n_basis];
487
488 let (x_clamped, is_extrapolated, extrapolation_factor) = match self.extrapolation {
490 ExtrapolationMode::Constant => (x.max(knots[0]).min(knots[n_knots - 1]), false, 0.0),
491 ExtrapolationMode::Zero => {
492 if x < knots[0] || x > knots[n_knots - 1] {
493 return Ok(basis); }
495 (x, false, 0.0)
496 }
497 ExtrapolationMode::Error => {
498 if x < knots[0] || x > knots[n_knots - 1] {
499 return Err(SklearsError::InvalidInput(
500 "Value outside knot range".to_string(),
501 ));
502 }
503 (x, false, 0.0)
504 }
505 ExtrapolationMode::Linear => {
506 if x < knots[0] {
507 let boundary = knots[0];
509 let next_knot = if n_knots > 1 {
510 knots[1]
511 } else {
512 knots[0] + 1.0
513 };
514 let slope = 1.0 / (next_knot - boundary);
515 let factor = (x - boundary) * slope;
516 (boundary, true, factor)
517 } else if x > knots[n_knots - 1] {
518 let boundary = knots[n_knots - 1];
520 let prev_knot = if n_knots > 1 {
521 knots[n_knots - 2]
522 } else {
523 knots[n_knots - 1] - 1.0
524 };
525 let slope = 1.0 / (boundary - prev_knot);
526 let factor = (x - boundary) * slope;
527 (boundary, true, factor)
528 } else {
529 (x, false, 0.0)
530 }
531 }
532 };
533
534 for i in 0..n_basis {
537 if i + degree + 1 < n_knots && x_clamped >= knots[i] && x_clamped < knots[i + 1] {
538 basis[i] = 1.0;
539 }
540 }
541
542 if (x_clamped - knots[n_knots - 1]).abs() < 1e-10 && n_basis > 0 {
544 basis[n_basis - 1] = 1.0;
545 }
546
547 for d in 1..=degree {
549 let mut new_basis = vec![0.0; n_basis];
550
551 for i in 0..n_basis {
552 if i + d < n_knots && (knots[i + d] - knots[i]).abs() > 1e-10 {
553 let alpha = (x_clamped - knots[i]) / (knots[i + d] - knots[i]);
554 new_basis[i] += alpha * basis[i];
555 }
556
557 if i > 0 && i + d < n_knots && (knots[i + d] - knots[i]).abs() > 1e-10 {
558 let alpha = (knots[i + d] - x_clamped) / (knots[i + d] - knots[i]);
559 new_basis[i] += alpha * basis[i - 1];
560 }
561 }
562
563 basis = new_basis;
564 }
565
566 if is_extrapolated && extrapolation_factor != 0.0 {
568 for value in basis.iter_mut() {
571 *value *= 1.0 + extrapolation_factor.abs().min(10.0); }
573 }
574
575 Ok(basis)
576 }
577
578 fn compute_natural_cubic_basis(
579 &self,
580 x_values: &[Float],
581 knots: &[Float],
582 ) -> SklResult<Array2<Float>> {
583 let n_samples = x_values.len();
584 let n_knots = knots.len();
585 let n_basis = n_knots;
586 let mut basis_matrix = Array2::zeros((n_samples, n_basis));
587
588 if n_knots < 4 {
589 return Err(SklearsError::InvalidInput(
590 "Natural cubic splines require at least 4 knots".to_string(),
591 ));
592 }
593
594 for (sample_idx, &x) in x_values.iter().enumerate() {
595 basis_matrix[[sample_idx, 0]] = 1.0;
597 basis_matrix[[sample_idx, 1]] = x;
598
599 for k in 2..n_basis {
601 if k - 2 < n_knots - 2 {
602 let knot = knots[k - 2];
603 let _x_min = knots[0];
604 let x_max = knots[n_knots - 1];
605
606 let d_k = self.natural_cubic_basis_function(x, knot, x_max);
607 let d_n = self.natural_cubic_basis_function(x, x_max, x_max);
608
609 basis_matrix[[sample_idx, k]] = d_k - d_n;
610 }
611 }
612 }
613
614 Ok(basis_matrix)
615 }
616
617 fn natural_cubic_basis_function(&self, x: Float, knot: Float, _x_max: Float) -> Float {
618 if x <= knot {
619 0.0
620 } else {
621 let diff = x - knot;
622 diff.powi(3) / 6.0
623 }
624 }
625
626 fn compute_truncated_power_basis(
627 &self,
628 x_values: &[Float],
629 knots: &[Float],
630 ) -> SklResult<Array2<Float>> {
631 let n_samples = x_values.len();
632 let n_basis = self.n_splines;
633 let mut basis_matrix = Array2::zeros((n_samples, n_basis));
634
635 for (sample_idx, &x) in x_values.iter().enumerate() {
636 for d in 0..=self.degree.min(n_basis - 1) {
638 basis_matrix[[sample_idx, d]] = x.powi(d as i32);
639 }
640
641 for (basis_idx, &knot) in (self.degree + 1..).zip(knots.iter()) {
643 if basis_idx >= n_basis {
644 break;
645 }
646
647 if x > knot {
648 basis_matrix[[sample_idx, basis_idx]] = (x - knot).powi(self.degree as i32);
649 }
650 }
651 }
652
653 Ok(basis_matrix)
654 }
655}
656
657impl Default for SplineBasisFunctions {
658 fn default() -> Self {
659 Self::new()
660 }
661}
662
663#[allow(non_snake_case)] impl Transform<ArrayView2<'_, Float>, Array2<Float>> for SplineBasisFunctions {
665 fn transform(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
666 self.transform(X)
667 }
668}
669
670#[derive(Debug, Clone)]
698pub struct RadialBasisFunctions {
699 n_centers: usize,
700 rbf_type: RBFType,
701 centers: Option<Array2<Float>>,
702 shape_parameter: Float,
703 normalize: bool,
704}
705
706#[derive(Debug, Clone, Copy)]
707pub enum RBFType {
708 Gaussian,
710 Multiquadric,
712 InverseMultiquadric,
714 ThinPlateSpline,
716 Linear,
718 Cubic,
720 Quintic,
722}
723
724#[allow(non_snake_case)] impl RadialBasisFunctions {
726 pub fn new() -> Self {
728 Self {
729 n_centers: 10,
730 rbf_type: RBFType::Gaussian,
731 centers: None,
732 shape_parameter: 1.0,
733 normalize: false,
734 }
735 }
736
737 pub fn n_centers(mut self, n_centers: usize) -> Self {
739 self.n_centers = n_centers;
740 self
741 }
742
743 pub fn rbf_type(mut self, rbf_type: RBFType) -> Self {
745 self.rbf_type = rbf_type;
746 self
747 }
748
749 pub fn centers(mut self, centers: Array2<Float>) -> Self {
751 self.centers = Some(centers);
752 self
753 }
754
755 pub fn shape_parameter(mut self, shape_parameter: Float) -> Self {
757 self.shape_parameter = shape_parameter;
758 self
759 }
760
761 pub fn normalize(mut self, normalize: bool) -> Self {
763 self.normalize = normalize;
764 self
765 }
766
767 pub fn transform(&self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
769 let (n_samples, _n_features) = X.dim();
770
771 let centers = match &self.centers {
773 Some(centers) => centers.clone(),
774 None => self.generate_centers(X)?,
775 };
776
777 let n_centers = centers.nrows();
778 let mut rbf_features = Array2::zeros((n_samples, n_centers));
779
780 for (sample_idx, sample) in X.axis_iter(Axis(0)).enumerate() {
782 for (center_idx, center) in centers.axis_iter(Axis(0)).enumerate() {
783 let distance = self.compute_distance(&sample, ¢er);
784 let rbf_value = self.evaluate_rbf(distance);
785 rbf_features[[sample_idx, center_idx]] = rbf_value;
786 }
787 }
788
789 if self.normalize {
791 self.normalize_features(&mut rbf_features);
792 }
793
794 Ok(rbf_features)
795 }
796
797 fn generate_centers(&self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
798 let (n_samples, n_features) = X.dim();
799
800 if self.n_centers > n_samples {
801 return Err(SklearsError::InvalidInput(
802 "Number of centers cannot exceed number of samples".to_string(),
803 ));
804 }
805
806 let mut centers = Array2::zeros((self.n_centers, n_features));
808
809 if n_samples > 0 {
811 for j in 0..n_features {
812 centers[[0, j]] = X[[0, j]];
813 }
814 }
815
816 for center_idx in 1..self.n_centers {
818 let mut best_sample_idx = 0;
819 let mut max_min_distance = 0.0;
820
821 for sample_idx in 0..n_samples {
822 let sample = X.row(sample_idx);
823 let mut min_distance = Float::INFINITY;
824
825 for existing_center_idx in 0..center_idx {
827 let center = centers.row(existing_center_idx);
828 let distance = self.compute_distance(&sample, ¢er);
829 min_distance = min_distance.min(distance);
830 }
831
832 if min_distance > max_min_distance {
834 max_min_distance = min_distance;
835 best_sample_idx = sample_idx;
836 }
837 }
838
839 for j in 0..n_features {
841 centers[[center_idx, j]] = X[[best_sample_idx, j]];
842 }
843 }
844
845 Ok(centers)
846 }
847
848 fn compute_distance(&self, point1: &ArrayView1<Float>, point2: &ArrayView1<Float>) -> Float {
849 let mut sum_squared = 0.0;
851 for (x1, x2) in point1.iter().zip(point2.iter()) {
852 sum_squared += (x1 - x2).powi(2);
853 }
854 sum_squared.sqrt()
855 }
856
857 fn evaluate_rbf(&self, distance: Float) -> Float {
858 let r = distance;
859 let epsilon = self.shape_parameter;
860
861 match self.rbf_type {
862 RBFType::Gaussian => (-epsilon * r * r).exp(),
863 RBFType::Multiquadric => (1.0 + epsilon * r * r).sqrt(),
864 RBFType::InverseMultiquadric => 1.0 / (1.0 + epsilon * r * r).sqrt(),
865 RBFType::ThinPlateSpline => {
866 if r < 1e-10 {
867 0.0
868 } else {
869 r * r * r.ln()
870 }
871 }
872 RBFType::Linear => r,
873 RBFType::Cubic => r.powi(3),
874 RBFType::Quintic => r.powi(5),
875 }
876 }
877
878 fn normalize_features(&self, features: &mut Array2<Float>) {
879 let (n_samples, n_features) = features.dim();
880
881 for j in 0..n_features {
882 let mut column_sum = 0.0;
883 for i in 0..n_samples {
884 column_sum += features[[i, j]];
885 }
886
887 if column_sum > 1e-10 {
888 for i in 0..n_samples {
889 features[[i, j]] /= column_sum;
890 }
891 }
892 }
893 }
894}
895
896impl Default for RadialBasisFunctions {
897 fn default() -> Self {
898 Self::new()
899 }
900}
901
902#[allow(non_snake_case)] impl Transform<ArrayView2<'_, Float>, Array2<Float>> for RadialBasisFunctions {
904 fn transform(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
905 self.transform(X)
906 }
907}