use crate::{Float, SklResult, SklearsError};
use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis};
use sklears_core::prelude::Transform;
#[derive(Debug, Clone)]
pub struct RBFSampler {
gamma: Float,
n_components: usize,
random_state: Option<u64>,
random_weights: Option<Array2<Float>>,
random_offset: Option<Array1<Float>>,
}
#[allow(non_snake_case)] impl RBFSampler {
pub fn new() -> Self {
Self {
gamma: 1.0,
n_components: 100,
random_state: None,
random_weights: None,
random_offset: None,
}
}
pub fn gamma(mut self, gamma: Float) -> Self {
self.gamma = gamma;
self
}
pub fn n_components(mut self, n_components: usize) -> Self {
self.n_components = n_components;
self
}
pub fn random_state(mut self, random_state: Option<u64>) -> Self {
self.random_state = random_state;
self
}
pub fn fit(&mut self, X: &ArrayView2<Float>) -> SklResult<&mut Self> {
let (_, n_features) = X.dim();
let variance = 2.0 * self.gamma;
let mut random_weights = Array2::zeros((self.n_components, n_features));
let mut random_offset = Array1::zeros(self.n_components);
let mut seed = self.random_state.unwrap_or(42);
for i in 0..self.n_components {
for j in 0..n_features {
let u1 = (seed % 1000) as Float / 1000.0;
seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
let u2 = (seed % 1000) as Float / 1000.0;
seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
let z = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
random_weights[[i, j]] = z * variance.sqrt();
}
let u = (seed % 1000) as Float / 1000.0;
seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
random_offset[i] = 2.0 * std::f64::consts::PI * u;
}
self.random_weights = Some(random_weights);
self.random_offset = Some(random_offset);
Ok(self)
}
pub fn transform(&self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
let weights = self.random_weights.as_ref().ok_or_else(|| {
SklearsError::InvalidInput("RBFSampler must be fitted before transform".to_string())
})?;
let offset = self.random_offset.as_ref().ok_or_else(|| {
SklearsError::InvalidInput("RBFSampler must be fitted before transform".to_string())
})?;
let (n_samples, n_features) = X.dim();
if weights.ncols() != n_features {
return Err(SklearsError::InvalidInput(
"Feature dimension mismatch".to_string(),
));
}
let mut features = Array2::zeros((n_samples, self.n_components));
let normalization = (2.0 / self.n_components as Float).sqrt();
for i in 0..n_samples {
let sample = X.row(i);
for j in 0..self.n_components {
let weight_row = weights.row(j);
let projection = sample.dot(&weight_row) + offset[j];
features[[i, j]] = normalization * projection.cos();
}
}
Ok(features)
}
pub fn fit_transform(&mut self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
self.fit(X)?;
self.transform(X)
}
}
impl Default for RBFSampler {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy)]
pub enum NystromKernel {
RBF,
Polynomial,
Linear,
Sigmoid,
}
#[derive(Debug, Clone)]
pub struct Nystroem {
kernel: NystromKernel,
n_components: usize,
gamma: Float,
degree: usize,
coef0: Float,
random_state: Option<u64>,
landmarks: Option<Array2<Float>>,
normalization_matrix: Option<Array2<Float>>,
}
#[allow(non_snake_case)] impl Nystroem {
pub fn new() -> Self {
Self {
kernel: NystromKernel::RBF,
n_components: 100,
gamma: 1.0,
degree: 3,
coef0: 1.0,
random_state: None,
landmarks: None,
normalization_matrix: None,
}
}
pub fn kernel(mut self, kernel: NystromKernel) -> Self {
self.kernel = kernel;
self
}
pub fn n_components(mut self, n_components: usize) -> Self {
self.n_components = n_components;
self
}
pub fn gamma(mut self, gamma: Float) -> Self {
self.gamma = gamma;
self
}
pub fn degree(mut self, degree: usize) -> Self {
self.degree = degree;
self
}
pub fn coef0(mut self, coef0: Float) -> Self {
self.coef0 = coef0;
self
}
pub fn random_state(mut self, random_state: Option<u64>) -> Self {
self.random_state = random_state;
self
}
#[allow(non_snake_case)]
pub fn fit(&mut self, X: &ArrayView2<Float>) -> SklResult<&mut Self> {
let (n_samples, n_features) = X.dim();
let n_landmarks = self.n_components.min(n_samples);
let landmark_indices = self.select_landmarks(n_samples, n_landmarks)?;
let mut landmarks = Array2::zeros((n_landmarks, n_features));
for (i, &idx) in landmark_indices.iter().enumerate() {
landmarks.row_mut(i).assign(&X.row(idx));
}
let W = self.compute_kernel_matrix(&landmarks.view(), &landmarks.view())?;
let W_inv = self.compute_regularized_inverse(&W)?;
self.landmarks = Some(landmarks);
self.normalization_matrix = Some(W_inv);
Ok(self)
}
pub fn transform(&self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
let landmarks = self.landmarks.as_ref().ok_or_else(|| {
SklearsError::InvalidInput("Nystroem must be fitted before transform".to_string())
})?;
let W_inv = self.normalization_matrix.as_ref().ok_or_else(|| {
SklearsError::InvalidInput("Nystroem must be fitted before transform".to_string())
})?;
let C = self.compute_kernel_matrix(X, &landmarks.view())?;
let features = C.dot(W_inv);
Ok(features)
}
pub fn fit_transform(&mut self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
self.fit(X)?;
self.transform(X)
}
fn select_landmarks(&self, n_samples: usize, n_landmarks: usize) -> SklResult<Vec<usize>> {
let mut landmarks = Vec::new();
let mut seed = self.random_state.unwrap_or(42);
for _ in 0..n_landmarks {
let idx = (seed % n_samples as u64) as usize;
landmarks.push(idx);
seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
}
Ok(landmarks)
}
fn compute_kernel_matrix(
&self,
X: &ArrayView2<Float>,
Y: &ArrayView2<Float>,
) -> SklResult<Array2<Float>> {
let (n_x, _) = X.dim();
let (n_y, _) = Y.dim();
let mut K = Array2::zeros((n_x, n_y));
for i in 0..n_x {
for j in 0..n_y {
let x_i = X.row(i);
let y_j = Y.row(j);
K[[i, j]] = self.kernel_function(&x_i, &y_j);
}
}
Ok(K)
}
fn kernel_function(&self, x: &ArrayView1<Float>, y: &ArrayView1<Float>) -> Float {
match self.kernel {
NystromKernel::RBF => {
let mut squared_distance = 0.0;
for (xi, yi) in x.iter().zip(y.iter()) {
squared_distance += (xi - yi).powi(2);
}
(-self.gamma * squared_distance).exp()
}
NystromKernel::Linear => x.dot(y),
NystromKernel::Polynomial => {
let dot_product = x.dot(y);
(self.gamma * dot_product + self.coef0).powf(self.degree as Float)
}
NystromKernel::Sigmoid => {
let dot_product = x.dot(y);
(self.gamma * dot_product + self.coef0).tanh()
}
}
}
fn compute_regularized_inverse(&self, matrix: &Array2<Float>) -> SklResult<Array2<Float>> {
let n = matrix.nrows();
if n != matrix.ncols() {
return Err(SklearsError::InvalidInput(
"Matrix must be square for inversion".to_string(),
));
}
let regularization = 1e-12;
let mut A = matrix.clone();
for i in 0..n {
A[[i, i]] += regularization;
}
self.approximate_matrix_sqrt(&A)
}
fn approximate_matrix_sqrt(&self, matrix: &Array2<Float>) -> SklResult<Array2<Float>> {
let n = matrix.nrows();
let max_components = n.min(self.n_components);
let mut result = Array2::zeros((n, max_components));
for comp in 0..max_components {
let mut v = Array1::ones(n);
let norm = v.iter().map(|x| x * x).sum::<Float>().sqrt();
if norm > 0.0 {
v /= norm;
}
for _iter in 0..20 {
let mut Av = Array1::zeros(n);
for i in 0..n {
for j in 0..n {
Av[i] += matrix[[i, j]] * v[j];
}
}
for prev_comp in 0..comp {
let prev_vec = result.column(prev_comp);
let projection = Av.dot(&prev_vec);
for k in 0..n {
Av[k] -= projection * prev_vec[k];
}
}
let norm = Av.iter().map(|x| x * x).sum::<Float>().sqrt();
if norm < 1e-10 {
break;
}
v = Av / norm;
}
let mut Av = Array1::zeros(n);
for i in 0..n {
for j in 0..n {
Av[i] += matrix[[i, j]] * v[j];
}
}
let eigenvalue = v.dot(&Av).max(1e-10);
let sqrt_eigenvalue = eigenvalue.sqrt();
for i in 0..n {
result[[i, comp]] = sqrt_eigenvalue * v[i];
}
}
Ok(result)
}
}
impl Default for Nystroem {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct AdditiveChi2Sampler {
sample_steps: usize,
sample_interval: Float,
}
impl AdditiveChi2Sampler {
pub fn new() -> Self {
Self {
sample_steps: 2,
sample_interval: 1.0,
}
}
pub fn sample_steps(mut self, sample_steps: usize) -> Self {
self.sample_steps = sample_steps;
self
}
pub fn sample_interval(mut self, sample_interval: Float) -> Self {
self.sample_interval = sample_interval;
self
}
#[allow(non_snake_case)]
pub fn transform(&self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
let (n_samples, n_features) = X.dim();
let n_components = 2 * n_features * self.sample_steps;
let mut features = Array2::zeros((n_samples, n_components));
for (sample_idx, sample) in X.axis_iter(Axis(0)).enumerate() {
let mut feature_idx = 0;
for &x_i in sample.iter() {
for step in 0..self.sample_steps {
let omega = (step + 1) as Float * self.sample_interval;
if x_i > 0.0 {
let sqrt_x = x_i.sqrt();
let cos_val = (2.0 * omega * sqrt_x).cos();
let sin_val = (2.0 * omega * sqrt_x).sin();
features[[sample_idx, feature_idx]] =
(2.0 / std::f64::consts::PI).sqrt() * cos_val;
features[[sample_idx, feature_idx + 1]] =
(2.0 / std::f64::consts::PI).sqrt() * sin_val;
}
feature_idx += 2;
}
}
}
Ok(features)
}
pub fn get_n_output_features(&self, n_input_features: usize) -> usize {
2 * n_input_features * self.sample_steps
}
}
impl Default for AdditiveChi2Sampler {
fn default() -> Self {
Self::new()
}
}
#[allow(non_snake_case)] impl Transform<ArrayView2<'_, Float>, Array2<Float>> for AdditiveChi2Sampler {
fn transform(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
self.transform(X)
}
}