use ndarray::{s, Array1, Array2, Axis};
use std::sync::Arc;
use crate::error::{Error, Result};
use crate::gpu::operations::{GpuMatrix, GpuVector};
use crate::gpu::{get_gpu_manager, GpuError, GpuManager};
#[cfg(cuda_available)]
#[cfg(cuda_available)]
use cudarc::driver::{CudaContext as CudarcContext, CudaStream};
pub struct GpuDecomposition {
#[cfg(cuda_available)]
gpu_available: bool,
}
impl GpuDecomposition {
pub fn new(gpu_manager: &GpuManager) -> Result<Self> {
#[cfg(cuda_available)]
{
if gpu_manager.is_available() {
Ok(Self {
gpu_available: true,
})
} else {
Ok(Self {
gpu_available: false,
})
}
}
#[cfg(not(cuda_available))]
{
Ok(Self {})
}
}
pub fn qr_decomposition(&self, matrix: &GpuMatrix) -> Result<(GpuMatrix, GpuMatrix)> {
#[cfg(cuda_available)]
{
}
self.cpu_qr_decomposition(matrix)
}
pub fn svd_decomposition(
&self,
matrix: &GpuMatrix,
) -> Result<(GpuMatrix, GpuVector, GpuMatrix)> {
#[cfg(cuda_available)]
{
if self.gpu_available {
return self.cuda_svd_decomposition(matrix);
}
}
self.cpu_svd_decomposition(matrix)
}
pub fn eigen_decomposition(&self, matrix: &GpuMatrix) -> Result<(GpuVector, GpuMatrix)> {
#[cfg(cuda_available)]
{
if self.gpu_available {
return self.cuda_eigen_decomposition(matrix);
}
}
self.cpu_eigen_decomposition(matrix)
}
pub fn matrix_inverse(&self, matrix: &GpuMatrix) -> Result<GpuMatrix> {
#[cfg(cuda_available)]
{
if self.gpu_available {
return self.cuda_matrix_inverse(matrix);
}
}
self.cpu_matrix_inverse(matrix)
}
#[cfg(cuda_available)]
fn cuda_qr_decomposition(&self, matrix: &GpuMatrix) -> Result<(GpuMatrix, GpuMatrix)> {
let m = matrix.data.shape()[0];
let n = matrix.data.shape()[1];
let q = GpuMatrix {
data: Array2::eye(m),
on_gpu: false,
};
let r = GpuMatrix {
data: matrix.data.clone(),
on_gpu: false,
};
Ok((q, r))
}
#[cfg(cuda_available)]
fn cuda_svd_decomposition(
&self,
matrix: &GpuMatrix,
) -> Result<(GpuMatrix, GpuVector, GpuMatrix)> {
let m = matrix.data.shape()[0];
let n = matrix.data.shape()[1];
let min_dim = m.min(n);
let u = GpuMatrix {
data: Array2::eye(m),
on_gpu: false,
};
let s = GpuVector {
data: Array1::ones(min_dim),
on_gpu: false,
};
let vt = GpuMatrix {
data: Array2::eye(n),
on_gpu: false,
};
Ok((u, s, vt))
}
#[cfg(cuda_available)]
fn cuda_eigen_decomposition(&self, matrix: &GpuMatrix) -> Result<(GpuVector, GpuMatrix)> {
let n = matrix.data.shape()[0];
let eigenvalues = GpuVector {
data: Array1::ones(n),
on_gpu: false,
};
let eigenvectors = GpuMatrix {
data: Array2::eye(n),
on_gpu: false,
};
Ok((eigenvalues, eigenvectors))
}
#[cfg(cuda_available)]
fn cuda_matrix_inverse(&self, matrix: &GpuMatrix) -> Result<GpuMatrix> {
let n = matrix.data.shape()[0];
let inverse = GpuMatrix {
data: Array2::eye(n),
on_gpu: false,
};
Ok(inverse)
}
fn cpu_qr_decomposition(&self, matrix: &GpuMatrix) -> Result<(GpuMatrix, GpuMatrix)> {
let m = matrix.data.shape()[0];
let n = matrix.data.shape()[1];
let mut q = Array2::zeros((m, n));
let mut r = Array2::zeros((n, n));
for j in 0..n {
let mut v = matrix.data.column(j).to_owned();
for i in 0..j {
let q_i = q.column(i);
let dot_product = v.dot(&q_i);
r[(i, j)] = dot_product;
v = v - dot_product * &q_i;
}
let norm = v.dot(&v).sqrt();
r[(j, j)] = norm;
if norm > 1e-10 {
let q_j = v / norm;
q.column_mut(j).assign(&q_j);
}
}
Ok((
GpuMatrix {
data: q,
on_gpu: false,
},
GpuMatrix {
data: r,
on_gpu: false,
},
))
}
fn cpu_svd_decomposition(
&self,
matrix: &GpuMatrix,
) -> Result<(GpuMatrix, GpuVector, GpuMatrix)> {
let m = matrix.data.shape()[0];
let n = matrix.data.shape()[1];
let min_dim = m.min(n);
let u = GpuMatrix {
data: Array2::eye(m),
on_gpu: false,
};
let s = GpuVector {
data: Array1::ones(min_dim),
on_gpu: false,
};
let vt = GpuMatrix {
data: Array2::eye(n),
on_gpu: false,
};
Ok((u, s, vt))
}
fn cpu_eigen_decomposition(&self, matrix: &GpuMatrix) -> Result<(GpuVector, GpuMatrix)> {
let n = matrix.data.shape()[0];
let eigenvalues = GpuVector {
data: Array1::ones(n),
on_gpu: false,
};
let eigenvectors = GpuMatrix {
data: Array2::eye(n),
on_gpu: false,
};
Ok((eigenvalues, eigenvectors))
}
fn cpu_matrix_inverse(&self, matrix: &GpuMatrix) -> Result<GpuMatrix> {
let n = matrix.data.shape()[0];
if matrix.data.shape()[1] != n {
return Err(Error::DimensionMismatch(
"Matrix must be square for inversion".to_string(),
));
}
let mut augmented = Array2::zeros((n, 2 * n));
for i in 0..n {
for j in 0..n {
augmented[(i, j)] = matrix.data[(i, j)];
}
}
for i in 0..n {
augmented[(i, n + i)] = 1.0;
}
for i in 0..n {
let mut max_row = i;
for k in (i + 1)..n {
if augmented[(k, i)].abs() > augmented[(max_row, i)].abs() {
max_row = k;
}
}
if max_row != i {
for j in 0..(2 * n) {
let temp = augmented[(i, j)];
augmented[(i, j)] = augmented[(max_row, j)];
augmented[(max_row, j)] = temp;
}
}
if augmented[(i, i)].abs() < 1e-10 {
return Err(Error::Computation("Matrix is singular".to_string()));
}
let pivot = augmented[(i, i)];
for j in 0..(2 * n) {
augmented[(i, j)] /= pivot;
}
for k in 0..n {
if k != i {
let factor = augmented[(k, i)];
for j in 0..(2 * n) {
augmented[(k, j)] -= factor * augmented[(i, j)];
}
}
}
}
let mut inverse = Array2::zeros((n, n));
for i in 0..n {
for j in 0..n {
inverse[(i, j)] = augmented[(i, n + j)];
}
}
Ok(GpuMatrix {
data: inverse,
on_gpu: false,
})
}
}
pub struct GpuAdvancedStats;
impl GpuAdvancedStats {
pub fn pca(data: &GpuMatrix, n_components: usize) -> Result<(GpuMatrix, GpuVector, GpuMatrix)> {
let gpu_manager = get_gpu_manager()?;
let decomp = GpuDecomposition::new(&gpu_manager)?;
let mean = data
.data
.mean_axis(Axis(0))
.expect("operation should succeed");
let centered_data = &data.data - &mean.insert_axis(Axis(0));
let centered_matrix = GpuMatrix {
data: centered_data,
on_gpu: data.on_gpu,
};
let n_samples = data.data.shape()[0] as f64;
let cov_matrix = GpuMatrix {
data: centered_matrix.data.t().dot(¢ered_matrix.data) / (n_samples - 1.0),
on_gpu: false,
};
let (eigenvalues, eigenvectors) = decomp.eigen_decomposition(&cov_matrix)?;
let mut indices: Vec<usize> = (0..eigenvalues.data.len()).collect();
indices.sort_by(|&i, &j| {
eigenvalues.data[j]
.partial_cmp(&eigenvalues.data[i])
.expect("operation should succeed")
});
let n_components = n_components.min(eigenvalues.data.len());
let selected_eigenvalues = Array1::from_iter(
indices
.iter()
.take(n_components)
.map(|&i| eigenvalues.data[i]),
);
let selected_eigenvectors = {
let mut vecs = Array2::zeros((eigenvectors.data.shape()[0], n_components));
for (j, &i) in indices.iter().take(n_components).enumerate() {
vecs.column_mut(j).assign(&eigenvectors.data.column(i));
}
vecs
};
let transformed_data = centered_matrix.data.dot(&selected_eigenvectors);
Ok((
GpuMatrix {
data: transformed_data,
on_gpu: false,
},
GpuVector {
data: selected_eigenvalues,
on_gpu: false,
},
GpuMatrix {
data: selected_eigenvectors,
on_gpu: false,
},
))
}
pub fn lda(data: &GpuMatrix, labels: &Array1<i32>, n_components: usize) -> Result<GpuMatrix> {
let n_classes = labels.iter().map(|&x| x).max().unwrap_or(0) + 1;
let n_features = data.data.shape()[1];
let mut class_means = Array2::zeros((n_classes as usize, n_features));
let mut class_counts = vec![0; n_classes as usize];
for (i, &label) in labels.iter().enumerate() {
let class_idx = label as usize;
class_counts[class_idx] += 1;
for j in 0..n_features {
class_means[(class_idx, j)] += data.data[(i, j)];
}
}
for class_idx in 0..n_classes as usize {
if class_counts[class_idx] > 0 {
for j in 0..n_features {
class_means[(class_idx, j)] /= class_counts[class_idx] as f64;
}
}
}
let projection = GpuMatrix {
data: class_means
.slice(s![0..n_components.min(n_classes as usize), ..])
.to_owned(),
on_gpu: false,
};
Ok(projection)
}
pub fn correlation_with_pvalues(data: &GpuMatrix) -> Result<(GpuMatrix, GpuMatrix)> {
let n_features = data.data.shape()[1];
let n_samples = data.data.shape()[0] as f64;
let mut correlation_matrix = Array2::zeros((n_features, n_features));
let mut pvalue_matrix = Array2::zeros((n_features, n_features));
for i in 0..n_features {
for j in i..n_features {
if i == j {
correlation_matrix[(i, j)] = 1.0;
pvalue_matrix[(i, j)] = 0.0;
} else {
let x = data.data.column(i);
let y = data.data.column(j);
let mean_x = x.mean().expect("operation should succeed");
let mean_y = y.mean().expect("operation should succeed");
let numerator: f64 = x
.iter()
.zip(y.iter())
.map(|(&xi, &yi)| (xi - mean_x) * (yi - mean_y))
.sum();
let sum_sq_x: f64 = x.iter().map(|&xi| (xi - mean_x).powi(2)).sum();
let sum_sq_y: f64 = y.iter().map(|&yi| (yi - mean_y).powi(2)).sum();
let denominator = (sum_sq_x * sum_sq_y).sqrt();
let correlation = if denominator > 1e-10 {
numerator / denominator
} else {
0.0
};
let t_stat =
correlation * ((n_samples - 2.0) / (1.0 - correlation.powi(2))).sqrt();
let p_value = 2.0 * (1.0 - student_t_cdf(t_stat.abs(), n_samples - 2.0));
correlation_matrix[(i, j)] = correlation;
correlation_matrix[(j, i)] = correlation;
pvalue_matrix[(i, j)] = p_value;
pvalue_matrix[(j, i)] = p_value;
}
}
}
Ok((
GpuMatrix {
data: correlation_matrix,
on_gpu: false,
},
GpuMatrix {
data: pvalue_matrix,
on_gpu: false,
},
))
}
}
fn student_t_cdf(t: f64, df: f64) -> f64 {
if df <= 0.0 {
return 0.5;
}
let x = t / (df + t.powi(2)).sqrt();
0.5 + 0.5 * x.signum() * (1.0 - (-x.abs()).exp())
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::Array2;
#[test]
fn test_gpu_qr_decomposition() {
let gpu_manager = GpuManager::new();
let decomp = GpuDecomposition::new(&gpu_manager).expect("operation should succeed");
let matrix_data =
Array2::from_shape_vec((3, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
.expect("operation should succeed");
let matrix = GpuMatrix {
data: matrix_data,
on_gpu: false,
};
let (q, r) = decomp
.qr_decomposition(&matrix)
.expect("operation should succeed");
assert_eq!(q.data.shape(), &[3, 3]);
assert_eq!(r.data.shape(), &[3, 3]);
}
#[test]
fn test_matrix_inverse() {
let gpu_manager = GpuManager::new();
let decomp = GpuDecomposition::new(&gpu_manager).expect("operation should succeed");
let matrix_data = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
.expect("operation should succeed");
let matrix = GpuMatrix {
data: matrix_data,
on_gpu: false,
};
let inverse = decomp
.matrix_inverse(&matrix)
.expect("operation should succeed");
assert_eq!(inverse.data.shape(), &[2, 2]);
}
#[test]
fn test_pca() {
let data = Array2::from_shape_vec(
(4, 3),
vec![
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0,
],
)
.expect("operation should succeed");
let gpu_matrix = GpuMatrix {
data,
on_gpu: false,
};
let (transformed, eigenvalues, components) =
GpuAdvancedStats::pca(&gpu_matrix, 2).expect("operation should succeed");
assert_eq!(transformed.data.shape()[1], 2);
assert_eq!(eigenvalues.data.len(), 2);
assert_eq!(components.data.shape(), &[3, 2]);
}
}