use crate::*;
use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis};
use sklears_core::error::Result as SklResult;
use sklears_core::prelude::{SklearsError, Transform};
#[derive(Debug, Clone)]
pub struct RadialBasisFunctions {
n_centers: usize,
rbf_type: RBFType,
centers: Option<Array2<Float>>,
shape_parameter: Float,
normalize: bool,
}
#[derive(Debug, Clone, Copy)]
pub enum RBFType {
Gaussian,
Multiquadric,
InverseMultiquadric,
ThinPlateSpline,
Linear,
Cubic,
Quintic,
}
#[allow(non_snake_case)] impl RadialBasisFunctions {
pub fn new() -> Self {
Self {
n_centers: 10,
rbf_type: RBFType::Gaussian,
centers: None,
shape_parameter: 1.0,
normalize: false,
}
}
pub fn n_centers(mut self, n_centers: usize) -> Self {
self.n_centers = n_centers;
self
}
pub fn rbf_type(mut self, rbf_type: RBFType) -> Self {
self.rbf_type = rbf_type;
self
}
pub fn centers(mut self, centers: Array2<Float>) -> Self {
self.centers = Some(centers);
self
}
pub fn shape_parameter(mut self, shape_parameter: Float) -> Self {
self.shape_parameter = shape_parameter;
self
}
pub fn normalize(mut self, normalize: bool) -> Self {
self.normalize = normalize;
self
}
pub fn transform(&self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
let (n_samples, _n_features) = X.dim();
let centers = match &self.centers {
Some(centers) => centers.clone(),
None => self.generate_centers(X)?,
};
let n_centers = centers.nrows();
let mut rbf_features = Array2::zeros((n_samples, n_centers));
for (sample_idx, sample) in X.axis_iter(Axis(0)).enumerate() {
for (center_idx, center) in centers.axis_iter(Axis(0)).enumerate() {
let distance = self.compute_distance(&sample, ¢er);
let rbf_value = self.evaluate_rbf(distance);
rbf_features[[sample_idx, center_idx]] = rbf_value;
}
}
if self.normalize {
self.normalize_features(&mut rbf_features);
}
Ok(rbf_features)
}
fn generate_centers(&self, X: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
let (n_samples, n_features) = X.dim();
if self.n_centers > n_samples {
return Err(SklearsError::InvalidInput(
"Number of centers cannot exceed number of samples".to_string(),
));
}
let mut centers = Array2::zeros((self.n_centers, n_features));
if n_samples > 0 {
for j in 0..n_features {
centers[[0, j]] = X[[0, j]];
}
}
for center_idx in 1..self.n_centers {
let mut best_sample_idx = 0;
let mut max_min_distance = 0.0;
for sample_idx in 0..n_samples {
let sample = X.row(sample_idx);
let mut min_distance = Float::INFINITY;
for existing_center_idx in 0..center_idx {
let center = centers.row(existing_center_idx);
let distance = self.compute_distance(&sample, ¢er);
min_distance = min_distance.min(distance);
}
if min_distance > max_min_distance {
max_min_distance = min_distance;
best_sample_idx = sample_idx;
}
}
for j in 0..n_features {
centers[[center_idx, j]] = X[[best_sample_idx, j]];
}
}
Ok(centers)
}
fn compute_distance(&self, point1: &ArrayView1<Float>, point2: &ArrayView1<Float>) -> Float {
let mut sum_squared = 0.0;
for (x1, x2) in point1.iter().zip(point2.iter()) {
sum_squared += (x1 - x2).powi(2);
}
sum_squared.sqrt()
}
fn evaluate_rbf(&self, distance: Float) -> Float {
let r = distance;
let epsilon = self.shape_parameter;
match self.rbf_type {
RBFType::Gaussian => (-epsilon * r * r).exp(),
RBFType::Multiquadric => (1.0 + epsilon * r * r).sqrt(),
RBFType::InverseMultiquadric => 1.0 / (1.0 + epsilon * r * r).sqrt(),
RBFType::ThinPlateSpline => {
if r < 1e-10 {
0.0
} else {
r * r * r.ln()
}
}
RBFType::Linear => r,
RBFType::Cubic => r.powi(3),
RBFType::Quintic => r.powi(5),
}
}
fn normalize_features(&self, features: &mut Array2<Float>) {
let (n_samples, n_features) = features.dim();
for j in 0..n_features {
let mut column_sum = 0.0;
for i in 0..n_samples {
column_sum += features[[i, j]];
}
if column_sum > 1e-10 {
for i in 0..n_samples {
features[[i, j]] /= column_sum;
}
}
}
}
}
impl Default for RadialBasisFunctions {
fn default() -> Self {
Self::new()
}
}
#[allow(non_snake_case)] impl Transform<ArrayView2<'_, Float>, Array2<Float>> for RadialBasisFunctions {
fn transform(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
self.transform(X)
}
}
#[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)]
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>>,
}
#[derive(Debug, Clone, Copy)]
pub enum NystromKernel {
RBF,
Polynomial,
Linear,
Sigmoid,
}
#[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)
}
}