use crate::matrix::traits::RandomizedAlgs;
use nalgebra::DMatrix;
use rand::{rngs::SmallRng, RngExt, SeedableRng};
type Mat = DMatrix<f32>;
pub const INIT_SCALE: f32 = 10.0;
const JITTER_FRAC: f32 = 1e-4;
pub fn pc_scores(data: &Mat, rank: usize, skip: usize) -> anyhow::Result<Mat> {
let n = data.nrows();
let want = (rank + skip).min(n).min(data.ncols());
if rank == 0 || want <= skip {
return Err(anyhow::anyhow!(
"no components left: {n} × {} data, rank={rank}, skip={skip}",
data.ncols()
));
}
let (u, s, _v) = data.rsvd(want)?;
let keep = u.ncols().saturating_sub(skip).min(rank);
if keep == 0 {
return Err(anyhow::anyhow!(
"randomized SVD returned {} component(s), skip={skip}",
u.ncols()
));
}
let mut out = Mat::zeros(n, keep);
for c in 0..keep {
out.set_column(c, &(u.column(skip + c) * s[skip + c]));
}
Ok(out)
}
#[must_use]
pub fn init_2d_from_scores(scores: &Mat, seed: u64) -> Vec<f32> {
let n = scores.nrows();
if scores.ncols() < 2 {
return random_init_2d(n, seed);
}
let max_abs = scores
.columns(0, 2)
.iter()
.fold(0.0_f32, |m, &x| m.max(x.abs()));
if max_abs <= 0.0 || !max_abs.is_finite() {
return random_init_2d(n, seed);
}
let scale = INIT_SCALE / max_abs;
let mut rng = SmallRng::seed_from_u64(seed ^ 0x5151_5151_5151_5151);
let jitter = INIT_SCALE * JITTER_FRAC;
let mut init = Vec::with_capacity(n * 2);
for i in 0..n {
for c in 0..2 {
init.push(scores[(i, c)] * scale + rng.random_range(-jitter..jitter));
}
}
init
}
#[must_use]
pub fn random_init_2d(n: usize, seed: u64) -> Vec<f32> {
let mut rng = SmallRng::seed_from_u64(seed);
(0..n * 2)
.map(|_| rng.random_range(-INIT_SCALE..INIT_SCALE))
.collect()
}
#[must_use]
pub fn pc_layout_init(data: &Mat, rank: usize, skip: usize, seed: u64) -> (Option<Mat>, Vec<f32>) {
match pc_scores(data, rank, skip) {
Ok(scores) if scores.ncols() >= 2 => {
let init = init_2d_from_scores(&scores, seed);
(Some(scores), init)
}
Ok(_) | Err(_) => (None, random_init_2d(data.nrows(), seed)),
}
}
#[cfg(test)]
mod tests;