use scirs2_core::ndarray::{Array1, Array2, ArrayView2};
use scirs2_core::random::rngs::StdRng;
use scirs2_core::random::thread_rng;
use scirs2_core::random::RngExt;
use scirs2_core::random::SeedableRng;
use sklears_core::{
error::{Result as SklResult, SklearsError},
traits::{Estimator, Fit, Transform, Untrained},
types::Float,
};
#[derive(Debug, Clone)]
pub struct JohnsonLindenstrauss<S = Untrained> {
state: S,
n_components: usize,
eps: f64,
random_state: Option<u64>,
}
impl Default for JohnsonLindenstrauss<Untrained> {
fn default() -> Self {
Self::new()
}
}
impl JohnsonLindenstrauss<Untrained> {
pub fn new() -> Self {
Self {
state: Untrained,
n_components: 2,
eps: 0.1,
random_state: None,
}
}
pub fn n_components(mut self, n_components: usize) -> Self {
self.n_components = n_components;
self
}
pub fn eps(mut self, eps: f64) -> Self {
self.eps = eps;
self
}
pub fn random_state(mut self, random_state: u64) -> Self {
self.random_state = Some(random_state);
self
}
pub fn min_safe_components(n_samples: usize, eps: f64) -> usize {
let n = n_samples as f64;
let eps_sq = eps * eps;
let log_n = n.ln();
if n_samples <= 10 {
let simple_bound = if n_samples <= 3 && eps >= 0.1 {
(log_n / (eps_sq * 2.0)).ceil() as usize
} else {
(log_n / eps_sq).ceil() as usize
};
return simple_bound.min(n_samples - 1).max(2);
}
let denominator = eps_sq / 2.0 - eps_sq * eps / 3.0;
if denominator <= 0.0 {
return n_samples; }
let min_d = (4.0 * log_n / denominator).ceil() as usize;
min_d.min(n_samples * 10).max(1)
}
}
#[derive(Debug, Clone)]
pub struct JLTrained {
projection_matrix: Array2<f64>,
scaling_factor: f64,
}
impl Estimator for JohnsonLindenstrauss<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = Float;
fn config(&self) -> &Self::Config {
&()
}
}
impl Fit<ArrayView2<'_, Float>, ()> for JohnsonLindenstrauss<Untrained> {
type Fitted = JohnsonLindenstrauss<JLTrained>;
fn fit(self, x: &ArrayView2<'_, Float>, _y: &()) -> SklResult<Self::Fitted> {
let (n_samples, n_features) = x.dim();
if self.eps <= 0.0 || self.eps >= 1.0 {
return Err(SklearsError::InvalidInput(
"eps must be between 0 and 1".to_string(),
));
}
let min_components = Self::min_safe_components(n_samples, self.eps);
if self.n_components < min_components {
return Err(SklearsError::InvalidInput(format!(
"n_components ({}) is too small. For {} samples and eps={}, minimum is {}",
self.n_components, n_samples, self.eps, min_components
)));
}
let mut rng = if let Some(seed) = self.random_state {
StdRng::seed_from_u64(seed)
} else {
StdRng::seed_from_u64(thread_rng().random::<u64>())
};
let mut projection_matrix = Array2::zeros((n_features, self.n_components));
for elem in projection_matrix.iter_mut() {
*elem = rng.sample(scirs2_core::StandardNormal);
}
let scaling_factor = 1.0 / (self.n_components as f64).sqrt();
Ok(JohnsonLindenstrauss {
state: JLTrained {
projection_matrix,
scaling_factor,
},
n_components: self.n_components,
eps: self.eps,
random_state: self.random_state,
})
}
}
impl Transform<ArrayView2<'_, Float>, Array2<f64>> for JohnsonLindenstrauss<JLTrained> {
fn transform(&self, x: &ArrayView2<'_, Float>) -> SklResult<Array2<f64>> {
let x_f64 = x.mapv(|v| v);
let projected = x_f64.dot(&self.state.projection_matrix);
let scaled = projected * self.state.scaling_factor;
Ok(scaled)
}
}
#[derive(Debug, Clone)]
pub struct FastJohnsonLindenstrauss<S = Untrained> {
state: S,
n_components: usize,
eps: f64,
random_state: Option<u64>,
}
impl Default for FastJohnsonLindenstrauss<Untrained> {
fn default() -> Self {
Self::new()
}
}
impl FastJohnsonLindenstrauss<Untrained> {
pub fn new() -> Self {
Self {
state: Untrained,
n_components: 4,
eps: 0.1,
random_state: None,
}
}
pub fn n_components(mut self, n_components: usize) -> Self {
self.n_components = n_components;
self
}
pub fn eps(mut self, eps: f64) -> Self {
self.eps = eps;
self
}
pub fn random_state(mut self, random_state: u64) -> Self {
self.random_state = Some(random_state);
self
}
fn is_power_of_two(n: usize) -> bool {
n > 0 && (n & (n - 1)) == 0
}
#[allow(dead_code)]
fn fwht(data: &mut [f64]) {
let n = data.len();
let mut h = 1;
while h < n {
for i in (0..n).step_by(h * 2) {
for j in i..i + h {
let u = data[j];
let v = data[j + h];
data[j] = u + v;
data[j + h] = u - v;
}
}
h *= 2;
}
}
}
#[derive(Debug, Clone)]
pub struct FastJLTrained {
diagonal_matrix: Array1<f64>,
scaling_factor: f64,
padded_size: usize,
}
impl Estimator for FastJohnsonLindenstrauss<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = Float;
fn config(&self) -> &Self::Config {
&()
}
}
impl Fit<ArrayView2<'_, Float>, ()> for FastJohnsonLindenstrauss<Untrained> {
type Fitted = FastJohnsonLindenstrauss<FastJLTrained>;
fn fit(self, x: &ArrayView2<'_, Float>, _y: &()) -> SklResult<Self::Fitted> {
let (n_samples, n_features) = x.dim();
if self.eps <= 0.0 || self.eps >= 1.0 {
return Err(SklearsError::InvalidInput(
"eps must be between 0 and 1".to_string(),
));
}
if !Self::is_power_of_two(self.n_components) {
return Err(SklearsError::InvalidInput(
"n_components must be a power of 2 for fast transform".to_string(),
));
}
let min_components = JohnsonLindenstrauss::min_safe_components(n_samples, self.eps);
if self.n_components < min_components {
return Err(SklearsError::InvalidInput(format!(
"n_components ({}) is too small. For {} samples and eps={}, minimum is {}",
self.n_components, n_samples, self.eps, min_components
)));
}
let mut rng = if let Some(seed) = self.random_state {
StdRng::seed_from_u64(seed)
} else {
StdRng::seed_from_u64(thread_rng().random::<u64>())
};
let padded_size = (n_features - 1).next_power_of_two().max(self.n_components);
let mut diagonal_matrix = Array1::zeros(padded_size);
for elem in diagonal_matrix.iter_mut() {
*elem = if rng.random::<bool>() { 1.0 } else { -1.0 };
}
let scaling_factor = 1.0 / (self.n_components as f64).sqrt();
Ok(FastJohnsonLindenstrauss {
state: FastJLTrained {
diagonal_matrix,
scaling_factor,
padded_size,
},
n_components: self.n_components,
eps: self.eps,
random_state: self.random_state,
})
}
}
impl Transform<ArrayView2<'_, Float>, Array2<f64>> for FastJohnsonLindenstrauss<FastJLTrained> {
fn transform(&self, x: &ArrayView2<'_, Float>) -> SklResult<Array2<f64>> {
let (n_samples, _n_features) = x.dim();
let padded_size = self.state.padded_size;
let mut result = Array2::zeros((n_samples, self.n_components));
for (i, sample) in x.outer_iter().enumerate() {
let mut padded_sample = vec![0.0; padded_size];
for (j, &val) in sample.iter().enumerate() {
padded_sample[j] = val;
}
for (j, &diag_val) in self.state.diagonal_matrix.iter().enumerate() {
padded_sample[j] *= diag_val;
}
Self::fwht(&mut padded_sample);
for j in 0..self.n_components {
result[[i, j]] = padded_sample[j] * self.state.scaling_factor;
}
}
Ok(result)
}
}
impl FastJohnsonLindenstrauss<FastJLTrained> {
fn fwht(data: &mut [f64]) {
let n = data.len();
let mut h = 1;
while h < n {
for i in (0..n).step_by(h * 2) {
for j in i..i + h {
let u = data[j];
let v = data[j + h];
data[j] = u + v;
data[j + h] = u - v;
}
}
h *= 2;
}
}
}
#[derive(Debug, Clone)]
pub struct RandomProjection<S = Untrained> {
state: S,
n_components: usize,
density: f64,
random_state: Option<u64>,
}
impl Default for RandomProjection<Untrained> {
fn default() -> Self {
Self::new()
}
}
impl RandomProjection<Untrained> {
pub fn new() -> Self {
Self {
state: Untrained,
n_components: 2,
density: 1.0,
random_state: None,
}
}
pub fn n_components(mut self, n_components: usize) -> Self {
self.n_components = n_components;
self
}
pub fn density(mut self, density: f64) -> Self {
self.density = density;
self
}
pub fn random_state(mut self, random_state: u64) -> Self {
self.random_state = Some(random_state);
self
}
}
#[derive(Debug, Clone)]
pub struct RPTrained {
projection_matrix: Array2<f64>,
scaling_factor: f64,
}
#[derive(Debug, Clone)]
pub struct RPConfig {
pub n_components: usize,
pub density: f64,
pub random_state: Option<u64>,
}
impl Estimator for RandomProjection<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = Float;
fn config(&self) -> &Self::Config {
&()
}
}
impl Fit<ArrayView2<'_, Float>, ()> for RandomProjection<Untrained> {
type Fitted = RandomProjection<RPTrained>;
fn fit(self, x: &ArrayView2<'_, Float>, _y: &()) -> SklResult<Self::Fitted> {
let (_, n_features) = x.dim();
if self.density <= 0.0 || self.density > 1.0 {
return Err(SklearsError::InvalidInput(
"density must be between 0 and 1".to_string(),
));
}
let mut rng = if let Some(seed) = self.random_state {
StdRng::seed_from_u64(seed)
} else {
StdRng::seed_from_u64(thread_rng().random::<u64>())
};
let mut projection_matrix = Array2::zeros((n_features, self.n_components));
if self.density >= 1.0 {
for elem in projection_matrix.iter_mut() {
*elem = rng.sample(scirs2_core::StandardNormal);
}
} else {
let s = 1.0 / self.density;
let prob_positive = 1.0 / (2.0 * s);
let prob_negative = 1.0 / (2.0 * s);
for elem in projection_matrix.iter_mut() {
let rand_val: f64 = rng.random();
if rand_val < prob_positive {
*elem = s.sqrt();
} else if rand_val < prob_positive + prob_negative {
*elem = -s.sqrt();
} else {
*elem = 0.0;
}
}
}
let scaling_factor = 1.0 / (self.n_components as f64).sqrt();
Ok(RandomProjection {
state: RPTrained {
projection_matrix,
scaling_factor,
},
n_components: self.n_components,
density: self.density,
random_state: self.random_state,
})
}
}
impl Transform<ArrayView2<'_, Float>, Array2<f64>> for RandomProjection<RPTrained> {
fn transform(&self, x: &ArrayView2<'_, Float>) -> SklResult<Array2<f64>> {
let x_f64 = x.mapv(|v| v);
let projected = x_f64.dot(&self.state.projection_matrix);
let scaled = projected * self.state.scaling_factor;
Ok(scaled)
}
}
impl RandomProjection<RPTrained> {
pub fn projection_matrix(&self) -> &Array2<f64> {
&self.state.projection_matrix
}
pub fn scaling_factor(&self) -> f64 {
self.state.scaling_factor
}
pub fn n_components(&self) -> usize {
self.n_components
}
pub fn density(&self) -> f64 {
self.density
}
pub fn random_state(&self) -> Option<u64> {
self.random_state
}
pub fn from_fitted(
projection_matrix: Array2<f64>,
n_components: usize,
density: f64,
random_state: Option<u64>,
) -> SklResult<Self> {
if n_components == 0 {
return Err(SklearsError::InvalidInput(
"n_components must be greater than zero".to_string(),
));
}
if projection_matrix.ncols() != n_components {
return Err(SklearsError::InvalidInput(format!(
"projection_matrix has {} columns but n_components is {}",
projection_matrix.ncols(),
n_components
)));
}
let scaling_factor = 1.0 / (n_components as f64).sqrt();
Ok(RandomProjection {
state: RPTrained {
projection_matrix,
scaling_factor,
},
n_components,
density,
random_state,
})
}
}
#[derive(Debug, Clone)]
pub struct SparseRandomProjection<S = Untrained> {
state: S,
n_components: usize,
density: f64,
random_state: Option<u64>,
}
impl Default for SparseRandomProjection<Untrained> {
fn default() -> Self {
Self::new()
}
}
impl SparseRandomProjection<Untrained> {
pub fn new() -> Self {
Self {
state: Untrained,
n_components: 2,
density: 0.1,
random_state: None,
}
}
pub fn n_components(mut self, n_components: usize) -> Self {
self.n_components = n_components;
self
}
pub fn density(mut self, density: f64) -> Self {
self.density = density;
self
}
pub fn random_state(mut self, random_state: u64) -> Self {
self.random_state = Some(random_state);
self
}
pub fn optimal_density(n_features: usize) -> f64 {
1.0 / (n_features as f64).sqrt()
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)] pub struct SRPTrained {
projection_matrix: Array2<f64>,
scaling_factor: f64,
density: f64,
}
#[derive(Debug, Clone)]
pub struct SRPConfig {
pub n_components: usize,
pub density: f64,
pub random_state: Option<u64>,
}
impl Estimator for SparseRandomProjection<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = Float;
fn config(&self) -> &Self::Config {
&()
}
}
impl Fit<ArrayView2<'_, Float>, ()> for SparseRandomProjection<Untrained> {
type Fitted = SparseRandomProjection<SRPTrained>;
fn fit(self, x: &ArrayView2<'_, Float>, _y: &()) -> SklResult<Self::Fitted> {
let (_, n_features) = x.dim();
if self.density <= 0.0 || self.density > 1.0 {
return Err(SklearsError::InvalidInput(
"density must be between 0 and 1".to_string(),
));
}
let mut rng = if let Some(seed) = self.random_state {
StdRng::seed_from_u64(seed)
} else {
StdRng::seed_from_u64(thread_rng().random::<u64>())
};
let mut projection_matrix = Array2::zeros((n_features, self.n_components));
let s = 1.0 / self.density;
let prob_positive = 1.0 / (2.0 * s);
let prob_negative = 1.0 / (2.0 * s);
for elem in projection_matrix.iter_mut() {
let rand_val: f64 = rng.random();
if rand_val < prob_positive {
*elem = s.sqrt();
} else if rand_val < prob_positive + prob_negative {
*elem = -s.sqrt();
} else {
*elem = 0.0;
}
}
let scaling_factor = 1.0 / (self.n_components as f64).sqrt();
Ok(SparseRandomProjection {
state: SRPTrained {
projection_matrix,
scaling_factor,
density: self.density,
},
n_components: self.n_components,
density: self.density,
random_state: self.random_state,
})
}
}
impl Transform<ArrayView2<'_, Float>, Array2<f64>> for SparseRandomProjection<SRPTrained> {
fn transform(&self, x: &ArrayView2<'_, Float>) -> SklResult<Array2<f64>> {
let x_f64 = x.mapv(|v| v);
let projected = x_f64.dot(&self.state.projection_matrix);
let scaled = projected * self.state.scaling_factor;
Ok(scaled)
}
}