use faer::{Mat, RowRef, mat::AsMatRef};
pub fn aca_partial_pivoting<F>(
num_rows: usize,
num_columns: usize,
matrix_subset_generator: F,
epsilon: &f64,
) -> (Mat<f64>, Mat<f64>)
where
F: Fn(&usize, &usize, &usize, &usize) -> Mat<f64>,
{
let mut unused_rows = vec![1; num_rows];
let mut unused_columns = vec![1; num_columns];
let max_iterations = std::cmp::min(num_rows, num_columns);
let tolerance = epsilon.powi(2);
let mut u = Mat::<f64>::zeros(num_rows, max_iterations);
let mut v = Mat::<f64>::zeros(num_columns, max_iterations);
let mut residual_norm = 0.0;
let mut i = 0 as usize;
let mut j: usize;
let mut sum_k = 0.0;
let mut k = 0;
for _iteration in 0..max_iterations {
let mut v_column_i = matrix_subset_generator(&i, &(i + 1), &0, &num_columns);
unused_rows[i] = 0;
if k > 0 {
v_column_i -= u.submatrix(i, 0, 1, k) * v.submatrix(0, 0, num_columns, k).transpose();
}
j = argmax_masked(&v_column_i.row(0), &unused_columns);
let pivot = 1.0 / v_column_i.row(0)[j];
v_column_i *= pivot;
let mut u_column_j = matrix_subset_generator(&0, &num_rows, &j, &(j + 1));
unused_columns[j] = 0;
if k > 0 {
u_column_j -=
(v.submatrix(j, 0, 1, k) * u.submatrix(0, 0, num_rows, k).transpose()).transpose();
}
i = argmax_masked(&u_column_j.col(0).transpose(), &unused_rows);
if k > 0 {
if k == 1 {
let part_1 = u.col(0).transpose() * &u_column_j.col(0);
let part_2 = v.col(0).transpose() * &v_column_i.row(0).transpose();
sum_k = part_1 * part_2;
} else {
let part1 = u.submatrix(0, 0, num_rows, k).transpose() * &u_column_j;
let part2 = v.submatrix(0, 0, num_columns, k).transpose() * &v_column_i.transpose();
let part3 = &part1.transpose() * &part2;
sum_k = part3.as_mat_ref().sum();
}
}
let norm_u_v_2 = (u_column_j.col(0).transpose() * u_column_j.col(0))
* (v_column_i.row(0) * v_column_i.row(0).transpose());
residual_norm += norm_u_v_2 + 2.0 * sum_k;
u.col_mut(k).copy_from(&u_column_j.col(0));
v.col_mut(k).copy_from(&v_column_i.row(0).transpose());
k += 1;
if norm_u_v_2 <= tolerance * residual_norm {
break;
}
}
(u.subcols(0, k).to_owned(), v.subcols(0, k).to_owned())
}
fn argmax_masked(data: &RowRef<f64>, mask: &[u8]) -> usize {
let mut max_index = 0;
let mut max_value = 0.0;
for (idx, &value) in data.iter().enumerate() {
let weighted_value = value.abs() * mask[idx] as f64;
if weighted_value > max_value {
max_value = weighted_value;
max_index = idx;
}
}
max_index
}
pub fn recompress_aca(u_aca: &Mat<f64>, v_aca: &Mat<f64>, epsilon: &f64) -> (Mat<f64>, Mat<f64>) {
let u_qr = u_aca.qr();
let qu = u_qr.compute_thin_Q(); let ru = u_qr.thin_R();
let v_qr = v_aca.qr();
let qv = v_qr.compute_thin_Q(); let rv = v_qr.thin_R();
let ur_vrt = &ru * &rv.transpose();
let svd = ur_vrt.svd().unwrap();
let ur = svd.U(); let sr = svd.S(); let vrt = svd.V().transpose();
let sigma_vec: Vec<f64> = sr.column_vector().iter().cloned().collect();
let new_rank = calculate_singular_values_cutoff(sigma_vec, &epsilon);
let u = qu * (ur.subcols(0, new_rank) * sr.column_vector().subrows(0, new_rank).as_diagonal());
let vt = vrt.subrows(0, new_rank) * qv.transpose();
(u, vt)
}
pub fn calculate_singular_values_cutoff(sigma: Vec<f64>, epsilon: &f64) -> usize {
let cumulative_sum_sqr = inverse_cumulative_sum_of_squares(&sigma);
let eps_qr = cumulative_sum_sqr[0] * epsilon * epsilon;
let cutoff = cumulative_sum_sqr
.iter()
.position(|&x| x < eps_qr)
.unwrap_or(cumulative_sum_sqr.len());
cutoff
}
fn inverse_cumulative_sum_of_squares(sigma: &Vec<f64>) -> Vec<f64> {
let cumulative_sum_squared: Vec<f64> = sigma
.iter()
.rev()
.scan(0.0, |acc, &x| {
*acc += x * x;
Some(*acc)
})
.collect();
cumulative_sum_squared.into_iter().rev().collect()
}