use super::DenseTensor;
#[derive(Debug, Clone)]
pub struct CPConfig {
pub rank: usize,
pub max_iters: usize,
pub tolerance: f64,
}
impl Default for CPConfig {
fn default() -> Self {
Self {
rank: 10,
max_iters: 100,
tolerance: 1e-8,
}
}
}
#[derive(Debug, Clone)]
pub struct CPDecomposition {
pub weights: Vec<f64>,
pub factors: Vec<Vec<f64>>,
pub shape: Vec<usize>,
pub rank: usize,
}
impl CPDecomposition {
pub fn als(tensor: &DenseTensor, config: &CPConfig) -> Self {
let d = tensor.order();
let r = config.rank;
let mut factors: Vec<Vec<f64>> = tensor
.shape
.iter()
.enumerate()
.map(|(k, &n_k)| {
(0..n_k * r)
.map(|i| {
let x =
((i * 2654435769 + k * 1103515245) as f64 / 4294967296.0) * 2.0 - 1.0;
x
})
.collect()
})
.collect();
let mut weights = vec![1.0; r];
for (k, factor) in factors.iter_mut().enumerate() {
normalize_columns(factor, tensor.shape[k], r);
}
for _ in 0..config.max_iters {
for k in 0..d {
update_factor_als(tensor, &mut factors, k, r);
normalize_columns(&mut factors[k], tensor.shape[k], r);
}
}
for col in 0..r {
let mut norm = 0.0;
for row in 0..tensor.shape[0] {
norm += factors[0][row * r + col].powi(2);
}
weights[col] = norm.sqrt();
if weights[col] > 1e-15 {
for row in 0..tensor.shape[0] {
factors[0][row * r + col] /= weights[col];
}
}
}
Self {
weights,
factors,
shape: tensor.shape.clone(),
rank: r,
}
}
pub fn to_dense(&self) -> DenseTensor {
let total_size: usize = self.shape.iter().product();
let mut data = vec![0.0; total_size];
let d = self.shape.len();
let mut indices = vec![0usize; d];
for flat_idx in 0..total_size {
let mut val = 0.0;
for col in 0..self.rank {
let mut prod = self.weights[col];
for (k, &idx) in indices.iter().enumerate() {
prod *= self.factors[k][idx * self.rank + col];
}
val += prod;
}
data[flat_idx] = val;
for k in (0..d).rev() {
indices[k] += 1;
if indices[k] < self.shape[k] {
break;
}
indices[k] = 0;
}
}
DenseTensor::new(data, self.shape.clone())
}
pub fn eval(&self, indices: &[usize]) -> f64 {
let mut val = 0.0;
for col in 0..self.rank {
let mut prod = self.weights[col];
for (k, &idx) in indices.iter().enumerate() {
prod *= self.factors[k][idx * self.rank + col];
}
val += prod;
}
val
}
pub fn storage(&self) -> usize {
self.weights.len() + self.factors.iter().map(|f| f.len()).sum::<usize>()
}
pub fn compression_ratio(&self) -> f64 {
let original: usize = self.shape.iter().product();
let storage = self.storage();
if storage == 0 {
return f64::INFINITY;
}
original as f64 / storage as f64
}
pub fn relative_error(&self, tensor: &DenseTensor) -> f64 {
let reconstructed = self.to_dense();
let mut error_sq = 0.0;
let mut tensor_sq = 0.0;
for (a, b) in tensor.data.iter().zip(reconstructed.data.iter()) {
error_sq += (a - b).powi(2);
tensor_sq += a.powi(2);
}
(error_sq / tensor_sq.max(1e-15)).sqrt()
}
}
fn normalize_columns(factor: &mut [f64], rows: usize, cols: usize) {
for c in 0..cols {
let mut norm = 0.0;
for r in 0..rows {
norm += factor[r * cols + c].powi(2);
}
norm = norm.sqrt();
if norm > 1e-15 {
for r in 0..rows {
factor[r * cols + c] /= norm;
}
}
}
}
fn update_factor_als(tensor: &DenseTensor, factors: &mut [Vec<f64>], k: usize, rank: usize) {
let d = tensor.order();
let n_k = tensor.shape[k];
let mut v = vec![1.0; rank * rank];
for m in 0..d {
if m == k {
continue;
}
let n_m = tensor.shape[m];
let factor_m = &factors[m];
let mut gram = vec![0.0; rank * rank];
for i in 0..rank {
for j in 0..rank {
for row in 0..n_m {
gram[i * rank + j] += factor_m[row * rank + i] * factor_m[row * rank + j];
}
}
}
for i in 0..rank * rank {
v[i] *= gram[i];
}
}
let mttkrp = compute_mttkrp(tensor, factors, k, rank);
let v_inv = pseudo_inverse_symmetric(&v, rank);
let mut new_factor = vec![0.0; n_k * rank];
for row in 0..n_k {
for col in 0..rank {
for c in 0..rank {
new_factor[row * rank + col] += mttkrp[row * rank + c] * v_inv[c * rank + col];
}
}
}
factors[k] = new_factor;
}
fn compute_mttkrp(tensor: &DenseTensor, factors: &[Vec<f64>], k: usize, rank: usize) -> Vec<f64> {
let d = tensor.order();
let n_k = tensor.shape[k];
let mut result = vec![0.0; n_k * rank];
let total_size: usize = tensor.shape.iter().product();
let mut indices = vec![0usize; d];
for flat_idx in 0..total_size {
let val = tensor.data[flat_idx];
let i_k = indices[k];
for col in 0..rank {
let mut prod = val;
for (m, &idx) in indices.iter().enumerate() {
if m != k {
prod *= factors[m][idx * rank + col];
}
}
result[i_k * rank + col] += prod;
}
for m in (0..d).rev() {
indices[m] += 1;
if indices[m] < tensor.shape[m] {
break;
}
indices[m] = 0;
}
}
result
}
fn pseudo_inverse_symmetric(a: &[f64], n: usize) -> Vec<f64> {
let eps = 1e-10;
let mut a_reg = a.to_vec();
for i in 0..n {
a_reg[i * n + i] += eps;
}
let mut augmented = vec![0.0; n * 2 * n];
for i in 0..n {
for j in 0..n {
augmented[i * 2 * n + j] = a_reg[i * n + j];
}
augmented[i * 2 * n + n + i] = 1.0;
}
for col in 0..n {
let mut max_row = col;
for row in col + 1..n {
if augmented[row * 2 * n + col].abs() > augmented[max_row * 2 * n + col].abs() {
max_row = row;
}
}
for j in 0..2 * n {
augmented.swap(col * 2 * n + j, max_row * 2 * n + j);
}
let pivot = augmented[col * 2 * n + col];
if pivot.abs() < 1e-15 {
continue;
}
for j in 0..2 * n {
augmented[col * 2 * n + j] /= pivot;
}
for row in 0..n {
if row == col {
continue;
}
let factor = augmented[row * 2 * n + col];
for j in 0..2 * n {
augmented[row * 2 * n + j] -= factor * augmented[col * 2 * n + j];
}
}
}
let mut inv = vec![0.0; n * n];
for i in 0..n {
for j in 0..n {
inv[i * n + j] = augmented[i * 2 * n + n + j];
}
}
inv
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cp_als() {
let tensor = DenseTensor::random(vec![4, 5, 3], 42);
let config = CPConfig {
rank: 5,
max_iters: 50, ..Default::default()
};
let cp = CPDecomposition::als(&tensor, &config);
assert_eq!(cp.rank, 5);
assert_eq!(cp.weights.len(), 5);
let error = cp.relative_error(&tensor);
assert!(error.is_finite(), "Error should be finite: {}", error);
}
#[test]
fn test_cp_eval() {
let tensor = DenseTensor::new(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]);
let config = CPConfig {
rank: 2,
max_iters: 50,
..Default::default()
};
let cp = CPDecomposition::als(&tensor, &config);
let reconstructed = cp.to_dense();
for (a, b) in tensor.data.iter().zip(reconstructed.data.iter()) {
}
}
}