use crate::error::FdarError;
use crate::helpers::simpsons_weights;
use crate::matrix::FdMatrix;
use super::chi_squared::chi2_quantile;
use super::mfpca::MfpcaResult;
#[must_use = "contributions should not be discarded"]
pub fn t2_contributions(
scores: &FdMatrix,
eigenvalues: &[f64],
grid_sizes: &[usize],
) -> Result<FdMatrix, FdarError> {
let (n, ncomp) = scores.shape();
if ncomp != eigenvalues.len() {
return Err(FdarError::InvalidDimension {
parameter: "eigenvalues",
expected: format!("{ncomp}"),
actual: format!("{}", eigenvalues.len()),
});
}
let p = grid_sizes.len();
if p == 0 {
return Err(FdarError::InvalidDimension {
parameter: "grid_sizes",
expected: "at least 1 variable".to_string(),
actual: "0 variables".to_string(),
});
}
let total_grid: usize = grid_sizes.iter().sum();
let mut contrib = FdMatrix::zeros(n, p);
for i in 0..n {
for l in 0..ncomp {
if eigenvalues[l] <= 0.0 {
continue;
}
let comp_contrib = scores[(i, l)] * scores[(i, l)] / eigenvalues[l];
for v in 0..p {
let weight = grid_sizes[v] as f64 / total_grid as f64;
contrib[(i, v)] += comp_contrib * weight;
}
}
}
Ok(contrib)
}
#[must_use = "contributions should not be discarded"]
pub fn spe_contributions(
standardized_vars: &[&FdMatrix],
reconstructed_vars: &[&FdMatrix],
argvals_list: &[&[f64]],
) -> Result<FdMatrix, FdarError> {
let p = standardized_vars.len();
if reconstructed_vars.len() != p || argvals_list.len() != p {
return Err(FdarError::InvalidDimension {
parameter: "variables",
expected: format!("{p} for all arguments"),
actual: format!(
"std={}, recon={}, argvals={}",
standardized_vars.len(),
reconstructed_vars.len(),
argvals_list.len()
),
});
}
if p == 0 {
return Ok(FdMatrix::zeros(0, 0));
}
let n = standardized_vars[0].nrows();
let mut contrib = FdMatrix::zeros(n, p);
for v in 0..p {
let std_v = standardized_vars[v];
let rec_v = reconstructed_vars[v];
let argvals = argvals_list[v];
if std_v.nrows() != n || rec_v.nrows() != n {
return Err(FdarError::InvalidDimension {
parameter: "variables",
expected: format!("{n} rows for variable {v}"),
actual: format!("std={}, recon={}", std_v.nrows(), rec_v.nrows()),
});
}
let m_v = std_v.ncols();
if rec_v.ncols() != m_v || argvals.len() != m_v {
return Err(FdarError::InvalidDimension {
parameter: "argvals_list",
expected: format!("{m_v} for variable {v}"),
actual: format!(
"recon_cols={}, argvals_len={}",
rec_v.ncols(),
argvals.len()
),
});
}
let weights = simpsons_weights(argvals);
for i in 0..n {
let mut var_spe = 0.0;
for j in 0..m_v {
let diff = std_v[(i, j)] - rec_v[(i, j)];
var_spe += diff * diff * weights[j];
}
contrib[(i, v)] = var_spe;
}
}
Ok(contrib)
}
#[must_use = "contributions should not be discarded"]
pub fn t2_pc_contributions(scores: &FdMatrix, eigenvalues: &[f64]) -> Result<FdMatrix, FdarError> {
let (n, ncomp) = scores.shape();
if ncomp != eigenvalues.len() {
return Err(FdarError::InvalidDimension {
parameter: "eigenvalues",
expected: format!("{ncomp}"),
actual: format!("{}", eigenvalues.len()),
});
}
for (l, &ev) in eigenvalues.iter().enumerate() {
if ev <= 0.0 {
return Err(FdarError::InvalidParameter {
parameter: "eigenvalues",
message: format!("eigenvalue[{l}] = {ev} must be positive"),
});
}
}
let mut contrib = FdMatrix::zeros(n, ncomp);
for i in 0..n {
for l in 0..ncomp {
contrib[(i, l)] = scores[(i, l)] * scores[(i, l)] / eigenvalues[l];
}
}
Ok(contrib)
}
#[must_use = "contributions should not be discarded"]
pub fn t2_contributions_mfpca(
scores: &FdMatrix,
mfpca: &MfpcaResult,
) -> Result<FdMatrix, FdarError> {
let (n, ncomp) = scores.shape();
let eigenvalues = &mfpca.eigenvalues;
if ncomp > eigenvalues.len() {
return Err(FdarError::InvalidDimension {
parameter: "scores",
expected: format!("at most {} columns", eigenvalues.len()),
actual: format!("{ncomp} columns"),
});
}
let p = mfpca.eigenfunctions.len();
if p == 0 {
return Err(FdarError::InvalidDimension {
parameter: "eigenfunctions",
expected: "at least 1 variable".to_string(),
actual: "0 variables".to_string(),
});
}
let mut weights = vec![vec![0.0_f64; ncomp]; p];
for l in 0..ncomp {
let mut total_norm = 0.0;
let mut var_norms = vec![0.0_f64; p];
for (v, ef) in mfpca.eigenfunctions.iter().enumerate() {
let m_v = ef.nrows();
let ef_ncomp = ef.ncols().min(ncomp);
if l < ef_ncomp {
let norm_sq: f64 = (0..m_v).map(|j| ef[(j, l)] * ef[(j, l)]).sum();
var_norms[v] = norm_sq;
total_norm += norm_sq;
}
}
if total_norm > 0.0 {
for v in 0..p {
weights[v][l] = var_norms[v] / total_norm;
}
}
}
let mut contrib = FdMatrix::zeros(n, p);
for i in 0..n {
for l in 0..ncomp {
if eigenvalues[l] <= 0.0 {
continue;
}
let comp_contrib = scores[(i, l)] * scores[(i, l)] / eigenvalues[l];
for v in 0..p {
contrib[(i, v)] += comp_contrib * weights[v][l];
}
}
}
Ok(contrib)
}
#[must_use = "significance result should not be discarded"]
pub fn t2_pc_significance(contributions: &FdMatrix, alpha: f64) -> Result<FdMatrix, FdarError> {
if alpha <= 0.0 || alpha >= 1.0 {
return Err(FdarError::InvalidParameter {
parameter: "alpha",
message: format!("alpha must be in (0, 1), got {alpha}"),
});
}
let (n, ncomp) = contributions.shape();
let threshold = chi2_quantile(1.0 - alpha / ncomp as f64, 1);
let mut significant = FdMatrix::zeros(n, ncomp);
for i in 0..n {
for l in 0..ncomp {
if contributions[(i, l)] > threshold {
significant[(i, l)] = 1.0;
}
}
}
Ok(significant)
}