use model_selection_rs::evaluate::validation_curve;
use model_selection_rs::scoring::MeanSquaredError;
use model_selection_rs::splitters::KFold;
use ndarray::{Array1, Array2};
fn poly_fit(
degree: &usize,
x: &Array2<f64>,
y: &Array1<f64>,
) -> impl Fn(&Array2<f64>) -> Array1<f64> {
let d = *degree;
let xs: Vec<f64> = x.column(0).to_vec();
let ys: Vec<f64> = y.to_vec();
let coeffs = fit_poly(&xs, &ys, d);
move |xq: &Array2<f64>| xq.column(0).mapv(|v| eval_poly(&coeffs, v))
}
fn eval_poly(c: &[f64], x: f64) -> f64 {
c.iter()
.enumerate()
.map(|(i, ci)| ci * x.powi(i as i32))
.sum()
}
fn fit_poly(xs: &[f64], ys: &[f64], degree: usize) -> Vec<f64> {
let (n, m) = (xs.len(), degree + 1);
let vand: Vec<Vec<f64>> = xs
.iter()
.map(|&v| (0..m).map(|p| v.powi(p as i32)).collect())
.collect();
let mut ata = vec![vec![0.0; m]; m];
let mut atb = vec![0.0; m];
for i in 0..n {
for a in 0..m {
atb[a] += vand[i][a] * ys[i];
for b in 0..m {
ata[a][b] += vand[i][a] * vand[i][b];
}
}
}
solve(ata, atb)
}
#[allow(clippy::needless_range_loop)] fn solve(mut a: Vec<Vec<f64>>, mut b: Vec<f64>) -> Vec<f64> {
let n = b.len();
for col in 0..n {
let piv = (col..n)
.max_by(|&r1, &r2| a[r1][col].abs().partial_cmp(&a[r2][col].abs()).unwrap())
.unwrap();
a.swap(col, piv);
b.swap(col, piv);
let d = a[col][col];
if d.abs() < 1e-12 {
continue;
}
for row in (col + 1)..n {
let f = a[row][col] / d;
for k in col..n {
a[row][k] -= f * a[col][k];
}
b[row] -= f * b[col];
}
}
let mut sol = vec![0.0; n];
for row in (0..n).rev() {
let mut s = b[row];
for k in (row + 1)..n {
s -= a[row][k] * sol[k];
}
sol[row] = if a[row][row].abs() < 1e-12 {
0.0
} else {
s / a[row][row]
};
}
sol
}
fn main() {
let x = Array2::from_shape_fn((60, 1), |(i, _)| (i as f64) / 10.0 - 3.0);
let y = x
.column(0)
.mapv(|v| v * v - 0.5 * v + 1.0 + (v * 5.0).sin() * 0.2);
let kf = KFold::new(5).unwrap().with_shuffle(0);
let degrees = [1usize, 2, 3, 5, 8, 12];
let vc = validation_curve(&kf, &x, &y, poly_fit, &MeanSquaredError, °rees).unwrap();
println!("Validation curve over polynomial degree (MSE, lower is better):\n");
println!(" {:>6} {:>12} {:>12}", "degree", "train MSE", "val MSE");
let train = vc.mean_train_scores();
let val = vc.mean_val_scores();
for i in 0..degrees.len() {
println!(" {:>6} {:>12.4} {:>12.4}", degrees[i], train[i], val[i]);
}
println!("\nTraining error falls monotonically with degree, but validation error");
println!("bottoms out near the true degree (2) and then climbs as the model");
println!("starts fitting noise — the U-shaped over/under-fit signature.");
}