use super::DenseTensor;
#[derive(Debug, Clone)]
pub struct TuckerConfig {
pub ranks: Vec<usize>,
pub tolerance: f64,
pub max_iters: usize,
}
impl Default for TuckerConfig {
fn default() -> Self {
Self {
ranks: vec![],
tolerance: 1e-10,
max_iters: 20,
}
}
}
#[derive(Debug, Clone)]
pub struct TuckerDecomposition {
pub core: DenseTensor,
pub factors: Vec<Vec<f64>>,
pub shape: Vec<usize>,
pub core_shape: Vec<usize>,
}
impl TuckerDecomposition {
pub fn hosvd(tensor: &DenseTensor, config: &TuckerConfig) -> Self {
let d = tensor.order();
let mut factors = Vec::new();
let mut core_shape = Vec::new();
for k in 0..d {
let unfolding = mode_k_unfold(tensor, k);
let (n_k, cols) = (tensor.shape[k], unfolding.len() / tensor.shape[k]);
let rank = if k < config.ranks.len() {
config.ranks[k].min(n_k)
} else {
n_k
};
let u_k = compute_left_singular_vectors(&unfolding, n_k, cols, rank, config.max_iters);
factors.push(u_k);
core_shape.push(rank);
}
let core = compute_core(tensor, &factors, &core_shape);
Self {
core,
factors,
shape: tensor.shape.clone(),
core_shape,
}
}
pub fn to_dense(&self) -> DenseTensor {
let mut result = self.core.data.clone();
let mut current_shape = self.core_shape.clone();
for (k, factor) in self.factors.iter().enumerate() {
let n_k = self.shape[k];
let r_k = self.core_shape[k];
result = apply_mode_product(&result, ¤t_shape, factor, n_k, r_k, k);
current_shape[k] = n_k;
}
DenseTensor::new(result, self.shape.clone())
}
pub fn compression_ratio(&self) -> f64 {
let original: usize = self.shape.iter().product();
let core_size: usize = self.core_shape.iter().product();
let factor_size: usize = self
.factors
.iter()
.enumerate()
.map(|(k, f)| self.shape[k] * self.core_shape[k])
.sum();
original as f64 / (core_size + factor_size) as f64
}
}
fn mode_k_unfold(tensor: &DenseTensor, k: usize) -> Vec<f64> {
let d = tensor.order();
let n_k = tensor.shape[k];
let cols: usize = tensor
.shape
.iter()
.enumerate()
.filter(|&(i, _)| i != k)
.map(|(_, &s)| s)
.product();
let mut result = vec![0.0; n_k * cols];
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];
let mut col = 0;
let mut stride = 1;
for m in (0..d).rev() {
if m != k {
col += indices[m] * stride;
stride *= tensor.shape[m];
}
}
result[i_k * cols + col] = val;
for m in (0..d).rev() {
indices[m] += 1;
if indices[m] < tensor.shape[m] {
break;
}
indices[m] = 0;
}
}
result
}
fn compute_left_singular_vectors(
a: &[f64],
rows: usize,
cols: usize,
rank: usize,
max_iters: usize,
) -> Vec<f64> {
let mut u = vec![0.0; rows * rank];
for r in 0..rank {
let mut v: Vec<f64> = (0..rows)
.map(|i| {
let x = ((i * 2654435769 + r * 1103515245) as f64 / 4294967296.0) * 2.0 - 1.0;
x
})
.collect();
normalize(&mut v);
for _ in 0..max_iters {
let mut av = vec![0.0; cols];
for i in 0..rows {
for j in 0..cols {
av[j] += a[i * cols + j] * v[i];
}
}
let mut aatv = vec![0.0; rows];
for i in 0..rows {
for j in 0..cols {
aatv[i] += a[i * cols + j] * av[j];
}
}
for prev in 0..r {
let mut dot = 0.0;
for i in 0..rows {
dot += aatv[i] * u[i * rank + prev];
}
for i in 0..rows {
aatv[i] -= dot * u[i * rank + prev];
}
}
v = aatv;
normalize(&mut v);
}
for i in 0..rows {
u[i * rank + r] = v[i];
}
}
u
}
fn normalize(v: &mut [f64]) {
let norm: f64 = v.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm > 1e-15 {
for x in v.iter_mut() {
*x /= norm;
}
}
}
fn compute_core(tensor: &DenseTensor, factors: &[Vec<f64>], core_shape: &[usize]) -> DenseTensor {
let mut result = tensor.data.clone();
let mut current_shape = tensor.shape.clone();
for (k, factor) in factors.iter().enumerate() {
let n_k = tensor.shape[k];
let r_k = core_shape[k];
result = apply_mode_product_transpose(&result, ¤t_shape, factor, n_k, r_k, k);
current_shape[k] = r_k;
}
DenseTensor::new(result, core_shape.to_vec())
}
fn apply_mode_product_transpose(
data: &[f64],
shape: &[usize],
u: &[f64],
n_k: usize,
r_k: usize,
k: usize,
) -> Vec<f64> {
let d = shape.len();
let mut new_shape = shape.to_vec();
new_shape[k] = r_k;
let new_size: usize = new_shape.iter().product();
let mut result = vec![0.0; new_size];
let old_size: usize = shape.iter().product();
let mut old_indices = vec![0usize; d];
for _ in 0..old_size {
let old_idx = compute_linear_index(&old_indices, shape);
let val = data[old_idx];
let i_k = old_indices[k];
for r in 0..r_k {
let mut new_indices = old_indices.clone();
new_indices[k] = r;
let new_idx = compute_linear_index(&new_indices, &new_shape);
result[new_idx] += val * u[i_k * r_k + r];
}
for m in (0..d).rev() {
old_indices[m] += 1;
if old_indices[m] < shape[m] {
break;
}
old_indices[m] = 0;
}
}
result
}
fn apply_mode_product(
data: &[f64],
shape: &[usize],
u: &[f64],
n_k: usize,
r_k: usize,
k: usize,
) -> Vec<f64> {
let d = shape.len();
let mut new_shape = shape.to_vec();
new_shape[k] = n_k;
let new_size: usize = new_shape.iter().product();
let mut result = vec![0.0; new_size];
let old_size: usize = shape.iter().product();
let mut old_indices = vec![0usize; d];
for _ in 0..old_size {
let old_idx = compute_linear_index(&old_indices, shape);
let val = data[old_idx];
let r = old_indices[k];
for i in 0..n_k {
let mut new_indices = old_indices.clone();
new_indices[k] = i;
let new_idx = compute_linear_index(&new_indices, &new_shape);
result[new_idx] += val * u[i * r_k + r];
}
for m in (0..d).rev() {
old_indices[m] += 1;
if old_indices[m] < shape[m] {
break;
}
old_indices[m] = 0;
}
}
result
}
fn compute_linear_index(indices: &[usize], shape: &[usize]) -> usize {
let mut idx = 0;
let mut stride = 1;
for i in (0..shape.len()).rev() {
idx += indices[i] * stride;
stride *= shape[i];
}
idx
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tucker_hosvd() {
let tensor = DenseTensor::random(vec![4, 5, 3], 42);
let config = TuckerConfig {
ranks: vec![2, 3, 2],
..Default::default()
};
let tucker = TuckerDecomposition::hosvd(&tensor, &config);
assert_eq!(tucker.core_shape, vec![2, 3, 2]);
assert!(tucker.compression_ratio() > 1.0);
}
#[test]
fn test_mode_unfold() {
let tensor = DenseTensor::new(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]);
let unfold0 = mode_k_unfold(&tensor, 0);
assert_eq!(unfold0.len(), 6);
}
}