Skip to main content

sklears_feature_selection/genetic_optimization/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::types::*;
6use scirs2_core::ndarray::{Array1, Array2, ArrayBase, Axis, Dim, OwnedRepr};
7use scirs2_core::random::rngs::StdRng;
8use scirs2_core::random::RngExt;
9use sklears_core::{
10    error::Result as SklResult,
11    traits::{Fit, Score},
12};
13
14// ndarray 0.17 3-param form to avoid trait-solver normalization failure in where bounds
15type Mat2f64 = ArrayBase<OwnedRepr<f64>, Dim<[usize; 2]>, f64>;
16type Vec1f64 = ArrayBase<OwnedRepr<f64>, Dim<[usize; 1]>, f64>;
17type Vec1i32 = ArrayBase<OwnedRepr<i32>, Dim<[usize; 1]>, i32>;
18type Vec1usize = ArrayBase<OwnedRepr<usize>, Dim<[usize; 1]>, usize>;
19/// Generate a random chromosome (binary vector representing feature selection)
20pub(crate) fn generate_random_chromosome(
21    n_features: usize,
22    min_features: usize,
23    max_features: usize,
24    rng: &mut StdRng,
25) -> Vec<bool> {
26    let mut chromosome = vec![false; n_features];
27    let n_selected = rng.random_range(min_features..max_features.min(n_features + 1));
28    let mut selected_indices: Vec<usize> = (0..n_features).collect();
29    for _ in 0..n_selected {
30        let idx = rng.random_range(0..selected_indices.len());
31        let feature_idx = selected_indices.swap_remove(idx);
32        chromosome[feature_idx] = true;
33    }
34    chromosome
35}
36/// Tournament selection
37pub(crate) fn tournament_selection<'a>(
38    population: &'a [Vec<bool>],
39    fitness_scores: &[f64],
40    tournament_size: usize,
41    rng: &mut StdRng,
42) -> &'a Vec<bool> {
43    let mut best_idx = rng.random_range(0..population.len());
44    let mut best_fitness = fitness_scores[best_idx];
45    for _ in 1..tournament_size {
46        let idx = rng.random_range(0..population.len());
47        if fitness_scores[idx] > best_fitness {
48            best_idx = idx;
49            best_fitness = fitness_scores[idx];
50        }
51    }
52    &population[best_idx]
53}
54/// Uniform crossover
55pub(crate) fn uniform_crossover(
56    parent1: &[bool],
57    parent2: &[bool],
58    rng: &mut StdRng,
59) -> (Vec<bool>, Vec<bool>) {
60    let mut child1 = Vec::with_capacity(parent1.len());
61    let mut child2 = Vec::with_capacity(parent1.len());
62    for i in 0..parent1.len() {
63        if rng.random::<bool>() {
64            child1.push(parent1[i]);
65            child2.push(parent2[i]);
66        } else {
67            child1.push(parent2[i]);
68            child2.push(parent1[i]);
69        }
70    }
71    (child1, child2)
72}
73/// Flip mutation
74pub(crate) fn flip_mutation(
75    chromosome: &mut [bool],
76    min_features: usize,
77    max_features: usize,
78    rng: &mut StdRng,
79) {
80    let flip_idx = rng.random_range(0..chromosome.len());
81    chromosome[flip_idx] = !chromosome[flip_idx];
82    let new_count = chromosome.iter().filter(|&&x| x).count();
83    if new_count < min_features {
84        let need_to_add = min_features - new_count;
85        let false_indices: Vec<usize> = chromosome
86            .iter()
87            .enumerate()
88            .filter_map(|(i, &val)| if !val { Some(i) } else { None })
89            .collect();
90        for _ in 0..need_to_add {
91            if !false_indices.is_empty() {
92                let idx = rng.random_range(0..false_indices.len());
93                chromosome[false_indices[idx]] = true;
94            }
95        }
96    } else if new_count > max_features {
97        let need_to_remove = new_count - max_features;
98        let true_indices: Vec<usize> = chromosome
99            .iter()
100            .enumerate()
101            .filter_map(|(i, &val)| if val { Some(i) } else { None })
102            .collect();
103        for _ in 0..need_to_remove {
104            if !true_indices.is_empty() {
105                let idx = rng.random_range(0..true_indices.len());
106                chromosome[true_indices[idx]] = false;
107            }
108        }
109    }
110}
111/// Cross-validate score for a given feature subset (f64 target)
112pub(crate) fn cross_validate_score_f64<E>(
113    estimator: &E,
114    x: &Mat2f64,
115    y: &Vec1f64,
116    cv_folds: usize,
117) -> SklResult<f64>
118where
119    E: Clone + Fit<Mat2f64, Vec1f64>,
120    E::Fitted: Score<Mat2f64, Vec1f64>,
121    <E::Fitted as Score<Mat2f64, Vec1f64>>::Float: Into<f64>,
122{
123    let n_samples = x.nrows();
124    let cv = KFold::new(cv_folds).shuffle(true);
125    let mut scores = Vec::new();
126    for (train_idx, test_idx) in cv.split(n_samples) {
127        let x_train = x.select(Axis(0), &train_idx);
128        let y_train = Array1::from_iter(train_idx.iter().map(|&i| y[i]));
129        let x_test = x.select(Axis(0), &test_idx);
130        let y_test = Array1::from_iter(test_idx.iter().map(|&i| y[i]));
131        let fitted = estimator.clone().fit(&x_train, &y_train)?;
132        let score = fitted.score(&x_test, &y_test)?;
133        scores.push(score);
134    }
135    let mut sum = 0.0;
136    for score in &scores {
137        sum += (*score).into();
138    }
139    Ok(sum / scores.len() as f64)
140}
141
142/// Cross-validate score for a given feature subset (i32 target, for classification)
143pub(crate) fn cross_validate_score_i32<E>(
144    estimator: &E,
145    x: &Mat2f64,
146    y: &Vec1i32,
147    cv_folds: usize,
148) -> SklResult<f64>
149where
150    E: Clone + Fit<Mat2f64, Vec1i32>,
151    E::Fitted: Score<Mat2f64, Vec1i32>,
152    <E::Fitted as Score<Mat2f64, Vec1i32>>::Float: Into<f64>,
153{
154    let n_samples = x.nrows();
155    let cv = KFold::new(cv_folds).shuffle(true);
156    let mut scores = Vec::new();
157    for (train_idx, test_idx) in cv.split(n_samples) {
158        let x_train = x.select(Axis(0), &train_idx);
159        let y_train = Array1::from_iter(train_idx.iter().map(|&i| y[i]));
160        let x_test = x.select(Axis(0), &test_idx);
161        let y_test = Array1::from_iter(test_idx.iter().map(|&i| y[i]));
162        let fitted = estimator.clone().fit(&x_train, &y_train)?;
163        let score = fitted.score(&x_test, &y_test)?;
164        scores.push(score);
165    }
166    let mut sum = 0.0;
167    for score in &scores {
168        sum += (*score).into();
169    }
170    Ok(sum / scores.len() as f64)
171}
172
173/// Cross-validate score for a given feature subset (usize target)
174pub(crate) fn cross_validate_score_usize<E>(
175    estimator: &E,
176    x: &Mat2f64,
177    y: &Vec1usize,
178    cv_folds: usize,
179) -> SklResult<f64>
180where
181    E: Clone + Fit<Mat2f64, Vec1usize>,
182    E::Fitted: Score<Mat2f64, Vec1usize>,
183    <E::Fitted as Score<Mat2f64, Vec1usize>>::Float: Into<f64>,
184{
185    let n_samples = x.nrows();
186    let cv = KFold::new(cv_folds).shuffle(true);
187    let mut scores = Vec::new();
188    for (train_idx, test_idx) in cv.split(n_samples) {
189        let x_train = x.select(Axis(0), &train_idx);
190        let y_train = Array1::from_iter(train_idx.iter().map(|&i| y[i]));
191        let x_test = x.select(Axis(0), &test_idx);
192        let y_test = Array1::from_iter(test_idx.iter().map(|&i| y[i]));
193        let fitted = estimator.clone().fit(&x_train, &y_train)?;
194        let score = fitted.score(&x_test, &y_test)?;
195        scores.push(score);
196    }
197    let mut sum = 0.0;
198    for score in &scores {
199        sum += (*score).into();
200    }
201    Ok(sum / scores.len() as f64)
202}
203/// Trait for objective functions in multi-objective optimization
204pub trait ObjectiveFunction: Send + Sync + std::fmt::Debug {
205    /// Evaluate the objective for a given feature subset
206    /// Returns higher values for better solutions (maximization)
207    fn evaluate(
208        &self,
209        x: &Array2<f64>,
210        y_classif: Option<&Array1<i32>>,
211        y_regression: Option<&Array1<f64>>,
212        feature_mask: &[bool],
213    ) -> SklResult<f64>;
214    /// Get the name of this objective
215    fn name(&self) -> &str;
216    /// Whether this is a minimization objective (will be converted to maximization)
217    fn is_minimization(&self) -> bool {
218        false
219    }
220    /// fn
221    fn clone_box(&self) -> Box<dyn ObjectiveFunction>;
222}
223impl Clone for Box<dyn ObjectiveFunction> {
224    fn clone(&self) -> Self {
225        self.clone_box()
226    }
227}
228/// Helper function to compute Pearson correlation between two arrays
229pub(crate) fn compute_pearson_correlation(x: &Array1<f64>, y: &Array1<f64>) -> f64 {
230    let n = x.len() as f64;
231    if n < 2.0 {
232        return 0.0;
233    }
234    let mean_x = x.mean().unwrap_or(0.0);
235    let mean_y = y.mean().unwrap_or(0.0);
236    let mut numerator = 0.0;
237    let mut sum_sq_x = 0.0;
238    let mut sum_sq_y = 0.0;
239    for (&xi, &yi) in x.iter().zip(y.iter()) {
240        let dx = xi - mean_x;
241        let dy = yi - mean_y;
242        numerator += dx * dy;
243        sum_sq_x += dx * dx;
244        sum_sq_y += dy * dy;
245    }
246    let denominator = (sum_sq_x * sum_sq_y).sqrt();
247    if denominator == 0.0 {
248        0.0
249    } else {
250        numerator / denominator
251    }
252}