pub mod frame_curvature;
pub use frame_curvature::{
FrameColumnLayout, OutputBlockRootAccumulator, ResidualGaugeCurvature, StreamedFrameCurvature,
StreamedLambdaMax, TriangularRootAccumulator, streamed_lambda_max,
};
use crate::chart_canonicalization::CanonicalChartTopology;
use crate::inference::layer_transport::TransportLadderReport;
use crate::inference::riesz::{RieszInput, SmoothFunctional, debias_with_dense_hessian};
use faer::Side;
use gam_linalg::faer_ndarray::{FaerCholesky, FaerEigh, FaerSvd, default_rrqr_rank_alpha};
use gam_math::score_opt::{
AffineRemlProfile, ScoreOptimumLocation, certified_exp_representative, certified_ln_positive,
};
use gam_problem::{MetricProvenance, RowMetric};
use gam_terms::inference::structure_evidence::StructureCertificate;
use ndarray::{Array1, Array2, Array3, Array4, ArrayView1, ArrayView2, s};
#[derive(Debug, Clone)]
pub struct MechanismSparsityJacobian {
pub weight: f64,
pub epsilon: f64,
}
impl MechanismSparsityJacobian {
pub fn new(weight: f64, epsilon: f64) -> Result<Self, String> {
if !(weight.is_finite() && weight > 0.0) {
return Err(format!(
"MechanismSparsityJacobian: weight must be finite and >0, got {weight}"
));
}
if !(epsilon.is_finite() && epsilon > 0.0) {
return Err(format!(
"MechanismSparsityJacobian: epsilon must be finite and >0, got {epsilon}"
));
}
Ok(Self { weight, epsilon })
}
pub fn value_and_grad(&self, w: ArrayView2<f64>) -> (f64, Array2<f64>) {
let (d, k) = w.dim();
let eps2 = self.epsilon * self.epsilon;
let mut grad = Array2::<f64>::zeros((d, k));
let mut value = 0.0;
for col in 0..k {
let mut sq = 0.0;
for row in 0..d {
sq += w[[row, col]] * w[[row, col]];
}
let denom = (sq + eps2).sqrt();
value += denom - self.epsilon;
let factor = self.weight / denom;
for row in 0..d {
grad[[row, col]] = factor * w[[row, col]];
}
}
(self.weight * value, grad)
}
pub fn hessian_diag(&self, w: ArrayView2<f64>) -> Array2<f64> {
let (d, k) = w.dim();
let eps2 = self.epsilon * self.epsilon;
let mut out = Array2::<f64>::zeros((d, k));
for col in 0..k {
let mut sq = 0.0;
for row in 0..d {
sq += w[[row, col]] * w[[row, col]];
}
let denom = (sq + eps2).sqrt();
let inv = 1.0 / denom;
let inv3 = inv * inv * inv;
for row in 0..d {
out[[row, col]] = self.weight * (inv - w[[row, col]] * w[[row, col]] * inv3);
}
}
out
}
}
#[derive(Debug, Clone)]
pub struct ConditionalPriorIvae {
pub mean: Array2<f64>,
pub scale: Array2<f64>,
pub weight: f64,
}
impl ConditionalPriorIvae {
pub fn new(mean: Array2<f64>, scale: Array2<f64>, weight: f64) -> Result<Self, String> {
if mean.dim() != scale.dim() {
return Err(format!(
"ConditionalPriorIvae: mean shape {:?} != scale shape {:?}",
mean.dim(),
scale.dim()
));
}
if !(weight.is_finite() && weight > 0.0) {
return Err(format!(
"ConditionalPriorIvae: weight must be finite and >0, got {weight}"
));
}
for &v in scale.iter() {
if !(v.is_finite() && v > 0.0) {
return Err(format!(
"ConditionalPriorIvae: every scale must be finite and >0, got {v}"
));
}
}
for &v in mean.iter() {
if !v.is_finite() {
return Err("ConditionalPriorIvae: mean contains non-finite entry".to_string());
}
}
let (n_rows, latent_dim) = mean.dim();
let needed_rows = 2 * latent_dim + 1;
if n_rows < needed_rows {
return Err(format!(
"ConditionalPriorIvae: Khemakhem (arXiv:2107.10098) Theorem 1 \
precondition violated: need at least 2k+1 = {needed_rows} distinct \
auxiliary states for latent_dim k = {latent_dim}, got n_rows = {n_rows}"
));
}
let natural = {
let mut s = Array2::<f64>::zeros((n_rows, 2 * latent_dim));
for r in 0..n_rows {
for c in 0..latent_dim {
let variance = scale[[r, c]] * scale[[r, c]];
s[[r, c]] = mean[[r, c]] / variance;
s[[r, latent_dim + c]] = -0.5 / variance;
}
}
s
};
let mut differences = Array2::<f64>::zeros((n_rows - 1, 2 * latent_dim));
for r in 1..n_rows {
for c in 0..2 * latent_dim {
differences[[r - 1, c]] = natural[[r, c]] - natural[[0, c]];
}
}
let (_u, sv, _vt) = differences.svd(false, false).map_err(|e| {
format!("ConditionalPriorIvae: SVD of natural-parameter differences failed: {e}")
})?;
let max_sv = sv.iter().cloned().fold(0.0_f64, f64::max);
let tol = max_sv * (n_rows.max(2 * latent_dim) as f64) * f64::EPSILON;
let numerical_rank = sv.iter().filter(|&&s| s > tol).count();
let required = 2 * latent_dim;
if numerical_rank < required {
return Err(format!(
"ConditionalPriorIvae: Khemakhem (arXiv:2107.10098) Theorem 1 \
precondition violated: baseline differences of Gaussian natural \
parameters [μ/σ² ‖ −1/(2σ²)] have \
numerical rank {numerical_rank} < 2·latent_dim = {required} \
(tolerance {tol:.3e}); the family `p(t|u)` does not span a \
2k-dimensional set of natural parameters"
));
}
Ok(Self {
mean,
scale,
weight,
})
}
pub fn value_and_grad(&self, t: ArrayView2<f64>) -> (f64, Array2<f64>) {
assert_eq!(
t.dim(),
self.mean.dim(),
"ConditionalPriorIvae: t/mean shape mismatch"
);
let (n, d) = t.dim();
let log_2pi = (2.0 * std::f64::consts::PI).ln();
let mut grad = Array2::<f64>::zeros((n, d));
let mut value = 0.0;
for row in 0..n {
for col in 0..d {
let mu = self.mean[[row, col]];
let sigma = self.scale[[row, col]];
let z = (t[[row, col]] - mu) / sigma;
value += 0.5 * (z * z + 2.0 * sigma.ln() + log_2pi);
grad[[row, col]] = self.weight * z / sigma;
}
}
(self.weight * value, grad)
}
pub fn value(&self, t: ArrayView2<f64>) -> f64 {
self.value_and_grad(t).0
}
}
pub fn derive_ivae_aux_scale(
aux: ArrayView2<f64>,
log_amplitude: f64,
frequency_scale: f64,
) -> Array2<f64> {
let (n_rows, n_cols) = aux.dim();
let mut out = Array2::<f64>::zeros((n_rows, n_cols));
let n = n_rows as f64;
for col in 0..n_cols {
let mut mean = 0.0;
for row in 0..n_rows {
mean += aux[[row, col]];
}
mean /= n;
let mut var = 0.0;
for row in 0..n_rows {
let centered = aux[[row, col]] - mean;
var += centered * centered;
}
let std = (var / n).sqrt();
let safe_std = if std > 0.0 { std } else { 1.0 };
let freq = frequency_scale * (col + 1) as f64;
for row in 0..n_rows {
let z = (aux[[row, col]] - mean) / safe_std;
let log_sigma = log_amplitude * (freq * z).tanh();
out[[row, col]] = log_sigma.exp();
}
}
out
}
pub fn identifiable_factor_log_evidence(
residual_sum_squares: f64,
penalty: f64,
n_obs: usize,
) -> Result<f64, String> {
if n_obs == 0 {
return Err("identifiable_factor_log_evidence: n_obs must be > 0".to_string());
}
if !(residual_sum_squares.is_finite() && residual_sum_squares > 0.0) {
return Err(format!(
"identifiable_factor_log_evidence: residual_sum_squares must be finite and \
positive; got {residual_sum_squares}"
));
}
if !penalty.is_finite() {
return Err(format!(
"identifiable_factor_log_evidence: penalty must be finite; got {penalty}"
));
}
let observations = n_obs as f64;
Ok(-0.5 * observations * (residual_sum_squares / observations).ln() - 0.5 * penalty)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RidgeRemlWeight {
Interior { lambda: f64, score: f64 },
FullShrinkage { score: f64 },
}
impl RidgeRemlWeight {
pub fn score(&self) -> f64 {
match self {
RidgeRemlWeight::Interior { score, .. } => *score,
RidgeRemlWeight::FullShrinkage { score } => *score,
}
}
}
pub fn ridge_reml_select_weight(
eigvals: &[f64],
signal_energy: &[f64],
aux_norm_sq: f64,
n_obs: usize,
n_responses: usize,
) -> Result<RidgeRemlWeight, String> {
if eigvals.len() != signal_energy.len() {
return Err(format!(
"ridge_reml_select_weight: eigvals len {} != signal_energy len {}",
eigvals.len(),
signal_energy.len()
));
}
if !(aux_norm_sq.is_finite() && aux_norm_sq > 0.0) {
return Err(format!(
"ridge_reml_select_weight: aux_norm_sq must be finite positive, got {aux_norm_sq}"
));
}
if n_obs == 0 {
return Err("ridge_reml_select_weight: n_obs must be > 0".to_string());
}
if n_responses == 0 {
return Err("ridge_reml_select_weight: n_responses must be > 0".to_string());
}
for (&g, &m) in eigvals.iter().zip(signal_energy) {
if !g.is_finite() || !m.is_finite() || m < 0.0 {
return Err(format!(
"ridge_reml_select_weight: non-finite or invalid (γ={g}, m={m}) pair"
));
}
}
let response_multiplicity = n_responses as f64;
let scalar_observations = (n_obs as f64) * response_multiplicity;
let gamma_max = eigvals.iter().cloned().fold(0.0_f64, f64::max);
let spectral_scale = eigvals
.iter()
.map(|value| value.abs())
.fold(0.0_f64, f64::max);
let rank_resolution = f64::EPSILON * eigvals.len().max(1) as f64 * spectral_scale;
for (&g, &m) in eigvals.iter().zip(signal_energy) {
if g < -rank_resolution {
return Err(format!(
"ridge_reml_select_weight: Gram spectrum is materially negative ({g})"
));
}
if g <= rank_resolution && m > rank_resolution * aux_norm_sq {
return Err(format!(
"ridge_reml_select_weight: numerical Gram-null direction carries signal \
energy {m}; sufficient statistics are inconsistent"
));
}
}
let pairs: Vec<(f64, f64)> = eigvals
.iter()
.zip(signal_energy)
.filter(|&(&g, _)| g > rank_resolution)
.map(|(&g, &m)| (g / gamma_max, m / gamma_max))
.collect();
let boundary_score_enclosure = certified_ln_positive(aux_norm_sq)
.ok_or_else(|| {
"ridge_reml_select_weight: could not enclose the full-shrinkage response log"
.to_string()
})?
.sub(certified_ln_positive(scalar_observations).ok_or_else(|| {
"ridge_reml_select_weight: could not enclose the observation-count log".to_string()
})?)
.scale(scalar_observations);
let boundary_score = boundary_score_enclosure.lo
+ 0.5 * (boundary_score_enclosure.hi - boundary_score_enclosure.lo);
if pairs.is_empty() {
return Ok(RidgeRemlWeight::FullShrinkage {
score: boundary_score,
});
}
let repeated_modes = pairs.len().saturating_mul(n_responses);
let mut gram_modes = Vec::with_capacity(repeated_modes);
let penalty_modes = vec![1.0_f64; repeated_modes];
let mut projected_rhs_squared = Vec::with_capacity(repeated_modes);
for _ in 0..n_responses {
for &(g, m) in &pairs {
gram_modes.push(g);
projected_rhs_squared.push(m / response_multiplicity);
}
}
let response_energy = [aux_norm_sq];
let profile = AffineRemlProfile::new(
&gram_modes,
&penalty_modes,
&projected_rhs_squared,
&response_energy,
scalar_observations,
repeated_modes,
0.0,
)
.map_err(|error| format!("ridge_reml_select_weight: {error}"))?;
let rho_lo = certified_ln_positive(f64::MIN_POSITIVE)
.ok_or_else(|| {
"ridge_reml_select_weight: could not enclose the finite-domain lower bound".to_string()
})?
.lo;
let rho_hi = certified_ln_positive(f64::MAX / 2.0)
.ok_or_else(|| {
"ridge_reml_select_weight: could not enclose the finite-domain upper bound".to_string()
})?
.hi;
let rho_tolerance = f64::EPSILON.sqrt();
let search = profile
.maximize_value_ordered(rho_lo, rho_hi, rho_tolerance)
.map_err(|error| format!("ridge_reml_select_weight: {error}"))?;
let optimum = search.optimum;
let score = -2.0 * optimum.value;
let finite_criterion = search.value_certificate.maximum.scale(-2.0);
if finite_criterion.hi >= boundary_score_enclosure.lo {
return Ok(RidgeRemlWeight::FullShrinkage {
score: boundary_score,
});
}
if search.value_certificate.maximum_excess > search.value_certificate.comparison_resolution {
return Err(format!(
"ridge_reml_select_weight: finite REML candidates are not globally ordered \
(maximum excess {}, comparison resolution {})",
search.value_certificate.maximum_excess, search.value_certificate.comparison_resolution
));
}
if search.location == ScoreOptimumLocation::LowerBoundary {
return Err(
"ridge_reml_select_weight: REML is unbounded at the λ → 0 interpolation \
boundary; no converged Gaussian evidence fit exists"
.to_string(),
);
}
let ScoreOptimumLocation::Stationary(index) = search.location else {
return Err(format!(
"ridge_reml_select_weight: finite REML optimum is value-resolved but not an \
isolated stationary point ({:?})",
search.location
));
};
let stationary = search.stationary_points.get(index).ok_or_else(|| {
"ridge_reml_select_weight: optimizer returned an invalid stationary index".to_string()
})?;
let kkt = profile
.enclose(stationary.bracket.lo, stationary.bracket.hi)
.map_err(|error| format!("ridge_reml_select_weight: {error}"))?;
if !(kkt.derivative.contains_zero() && kkt.curvature.hi < 0.0) {
return Err(format!(
"ridge_reml_select_weight: exact-real interior maximum KKT certificate failed \
on {:?}: {kkt:?}",
stationary.bracket
));
}
let relative_lambda = certified_exp_representative(optimum.x).ok_or_else(|| {
"ridge_reml_select_weight: could not construct the certified finite REML representative"
.to_string()
})?;
let lambda = gamma_max * relative_lambda;
if !(lambda.is_finite() && lambda > 0.0) {
return Err(format!(
"ridge_reml_select_weight: selected finite REML weight is not representable \
after restoring the Gram scale ({gamma_max} * {relative_lambda})"
));
}
Ok(RidgeRemlWeight::Interior { lambda, score })
}
pub fn thin_svd_scores(x: ArrayView2<f64>, k: usize) -> Result<Array2<f64>, String> {
let (n, p) = x.dim();
if k == 0 {
return Ok(Array2::<f64>::zeros((n, 0)));
}
if k > n.min(p) {
return Err(format!(
"thin_svd_scores: requested {k} components but min(n={n}, p={p}) limits to {}",
n.min(p)
));
}
let mut mean_row = Array1::<f64>::zeros(p);
for row in 0..n {
for col in 0..p {
mean_row[col] += x[[row, col]];
}
}
if n > 0 {
let inv_n = 1.0 / (n as f64);
for col in 0..p {
mean_row[col] *= inv_n;
}
}
let mut xc = Array2::<f64>::zeros((n, p));
for row in 0..n {
for col in 0..p {
xc[[row, col]] = x[[row, col]] - mean_row[col];
}
}
let (u_opt, sigma, _vt_opt) = xc
.svd(true, false)
.map_err(|e| format!("thin_svd_scores: SVD failed: {e}"))?;
let u = u_opt.ok_or_else(|| "thin_svd_scores: SVD did not return U".to_string())?;
let mut out = Array2::<f64>::zeros((n, k));
for row in 0..n {
for col in 0..k {
out[[row, col]] = u[[row, col]] * sigma[col];
}
}
Ok(out)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PartialSupervisionSupMethod {
Procrustes,
Anchor,
SoftL2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PartialSupervisionFreeConstraint {
OrthogonalToSup,
None,
}
#[derive(Debug, Clone)]
pub struct PartialSupervisionResult {
pub t_supervised: Array2<f64>,
pub t_free: Array2<f64>,
pub alignment_score: f64,
pub selected_weight: Option<f64>,
pub map_r: Option<Array2<f64>>,
pub map_a: Option<Array2<f64>>,
pub map_b: Option<Array1<f64>>,
}
pub fn partial_supervision_solve(
t_sup: ArrayView2<f64>,
aux: ArrayView2<f64>,
t_free: ArrayView2<f64>,
method: PartialSupervisionSupMethod,
anchor_idx: &[usize],
free_constraint: PartialSupervisionFreeConstraint,
) -> Result<PartialSupervisionResult, String> {
let (n, d_sup) = t_sup.dim();
if aux.dim() != (n, d_sup) {
return Err(format!(
"partial_supervision_solve: aux shape {:?} must equal t_sup shape ({}, {})",
aux.dim(),
n,
d_sup
));
}
if t_free.nrows() != n {
return Err(format!(
"partial_supervision_solve: t_free has {} rows, expected {}",
t_free.nrows(),
n
));
}
let aux_norm_sq: f64 = aux.iter().map(|x| x * x).sum();
if !(aux_norm_sq.is_finite() && aux_norm_sq > 0.0) {
return Err(
"partial_supervision_solve: aux has zero or non-finite Frobenius norm".to_string(),
);
}
let mut t_sup_aligned = Array2::<f64>::zeros((n, d_sup));
let mut map_r: Option<Array2<f64>> = None;
let mut map_a: Option<Array2<f64>> = None;
let mut map_b: Option<Array1<f64>> = None;
let mut selected_weight: Option<f64> = None;
match method {
PartialSupervisionSupMethod::Procrustes => {
let m = t_sup.t().dot(&aux);
let (u_opt, _sigma, vt_opt) = m
.svd(true, true)
.map_err(|e| format!("partial_supervision_solve: Procrustes SVD failed: {e}"))?;
let u = u_opt
.ok_or_else(|| "partial_supervision_solve: SVD did not return U".to_string())?;
let vt = vt_opt
.ok_or_else(|| "partial_supervision_solve: SVD did not return Vᵀ".to_string())?;
let r = u.dot(&vt);
t_sup_aligned = t_sup.dot(&r);
map_r = Some(r);
}
PartialSupervisionSupMethod::Anchor => {
if anchor_idx.is_empty() {
return Err(
"partial_supervision_solve: anchor method requires anchor_idx with at \
least one row"
.to_string(),
);
}
for &idx in anchor_idx {
if idx >= n {
return Err(format!(
"partial_supervision_solve: anchor index {idx} out of bounds (n={n})"
));
}
}
let m_rows = anchor_idx.len();
let mut design = Array2::<f64>::zeros((m_rows, d_sup + 1));
let mut targets = Array2::<f64>::zeros((m_rows, d_sup));
for (row_out, &row_in) in anchor_idx.iter().enumerate() {
for c in 0..d_sup {
design[[row_out, c]] = t_sup[[row_in, c]];
targets[[row_out, c]] = aux[[row_in, c]];
}
design[[row_out, d_sup]] = 1.0;
}
let (u_opt, sigma, vt_opt) = design
.svd(true, true)
.map_err(|e| format!("partial_supervision_solve: Anchor SVD failed: {e}"))?;
let u = u_opt
.ok_or_else(|| "partial_supervision_solve: anchor SVD lacked U".to_string())?;
let vt = vt_opt
.ok_or_else(|| "partial_supervision_solve: anchor SVD lacked Vᵀ".to_string())?;
let leading = sigma.iter().cloned().fold(0.0_f64, f64::max);
let cutoff = leading * f64::EPSILON * (m_rows.max(d_sup + 1) as f64);
let rank = sigma.len();
let ut_targets = u.t().dot(&targets);
let mut scaled = Array2::<f64>::zeros((rank, d_sup));
for r in 0..rank {
let s = sigma[r];
if s > cutoff {
let inv = 1.0 / s;
for c in 0..d_sup {
scaled[[r, c]] = inv * ut_targets[[r, c]];
}
}
}
let coef = vt.t().dot(&scaled);
let a = coef.slice(s![..d_sup, ..]).to_owned();
let b_vec = coef.slice(s![d_sup, ..]).to_owned();
for row in 0..n {
for c in 0..d_sup {
let mut acc = b_vec[c];
for k in 0..d_sup {
acc += t_sup[[row, k]] * a[[k, c]];
}
t_sup_aligned[[row, c]] = acc;
}
}
map_a = Some(a);
map_b = Some(b_vec);
}
PartialSupervisionSupMethod::SoftL2 => {
let g = t_sup.t().dot(&t_sup);
let (eigvals, eigvecs) = g
.eigh(Side::Lower)
.map_err(|e| format!("partial_supervision_solve: eigh on Gram failed: {e}"))?;
let rhs = t_sup.t().dot(&aux);
let ut_aux = eigvecs.t().dot(&rhs);
let m_row: Array1<f64> = Array1::from_vec(
(0..d_sup)
.map(|r| (0..d_sup).map(|c| ut_aux[[r, c]] * ut_aux[[r, c]]).sum())
.collect(),
);
let selection = ridge_reml_select_weight(
eigvals.as_slice().ok_or_else(|| {
"partial_supervision_solve: eigenspectrum is not contiguous".to_string()
})?,
m_row.as_slice().ok_or_else(|| {
"partial_supervision_solve: signal energies are not contiguous".to_string()
})?,
aux_norm_sq,
n,
d_sup,
)?;
match selection {
RidgeRemlWeight::Interior { lambda, .. } => {
let denom: Array1<f64> = eigvals.mapv(|v| v + lambda);
let mut a_eig = Array2::<f64>::zeros((d_sup, d_sup));
for r in 0..d_sup {
for c in 0..d_sup {
a_eig[[r, c]] = ut_aux[[r, c]] / denom[r];
}
}
let best_a = eigvecs.dot(&a_eig);
t_sup_aligned = t_sup.dot(&best_a);
map_a = Some(best_a);
selected_weight = Some(lambda);
}
RidgeRemlWeight::FullShrinkage { .. } => {
map_a = Some(Array2::<f64>::zeros((d_sup, d_sup)));
selected_weight = Some(f64::INFINITY);
}
}
}
}
let mut sq_resid = 0.0_f64;
for row in 0..n {
for c in 0..d_sup {
let r = t_sup_aligned[[row, c]] - aux[[row, c]];
sq_resid += r * r;
}
}
let alignment_score = 1.0 - sq_resid / aux_norm_sq;
let t_free_out = match free_constraint {
PartialSupervisionFreeConstraint::None => t_free.to_owned(),
PartialSupervisionFreeConstraint::OrthogonalToSup => {
if t_sup_aligned.ncols() == 0 || t_free.ncols() == 0 {
t_free.to_owned()
} else {
let (u_opt, singular, _vt) = t_sup_aligned
.svd(true, false)
.map_err(|e| format!("partial_supervision_solve: SVD on T_sup failed: {e}"))?;
let u = u_opt.ok_or_else(|| {
"partial_supervision_solve: SVD did not return supervised left vectors"
.to_string()
})?;
let sigma_max = singular.iter().copied().fold(0.0_f64, f64::max);
let tol = sigma_max
* t_sup_aligned.nrows().max(t_sup_aligned.ncols()) as f64
* f64::EPSILON;
let rank = singular.iter().filter(|&&value| value > tol).count();
if rank == 0 {
return Ok(PartialSupervisionResult {
t_supervised: t_sup_aligned,
t_free: t_free.to_owned(),
alignment_score,
selected_weight,
map_r,
map_a,
map_b,
});
}
let q = u.slice(s![.., 0..rank]);
let qt_free = q.t().dot(&t_free);
let proj = q.dot(&qt_free);
let mut out = t_free.to_owned();
out -= &proj;
out
}
}
};
Ok(PartialSupervisionResult {
t_supervised: t_sup_aligned,
t_free: t_free_out,
alignment_score,
selected_weight,
map_r,
map_a,
map_b,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AtomTopology {
Circle,
Sphere,
ProjectivePlane,
Torus { latent_dim: usize },
KleinBottle,
EuclideanPatch { latent_dim: usize },
}
impl AtomTopology {
fn latent_dim(&self) -> usize {
match self {
AtomTopology::Circle => 1,
AtomTopology::Sphere => 2,
AtomTopology::ProjectivePlane => 2,
AtomTopology::Torus { latent_dim } => *latent_dim,
AtomTopology::KleinBottle => 2,
AtomTopology::EuclideanPatch { latent_dim } => *latent_dim,
}
}
}
#[derive(Debug, Clone)]
pub struct FittedAtom {
pub name: String,
pub topology: AtomTopology,
pub frame: Array2<f64>,
pub ard_variances: Option<Array1<f64>>,
pub lowering_error: f64,
pub chart_canonicalized: bool,
pub inner_fit: Option<AtomInnerFit>,
}
#[derive(Debug, Clone)]
pub struct AtomInnerFit {
pub design: Array2<f64>,
pub derivative_design: Array2<f64>,
pub beta: Array1<f64>,
pub penalty: Array2<f64>,
pub penalized_hessian: Array2<f64>,
pub row_scores: Array2<f64>,
pub weights: Array1<f64>,
pub dispersion: f64,
pub peak_design_row: Array1<f64>,
pub mode_design_row: Array1<f64>,
}
#[derive(Debug, Clone)]
pub struct AtomFunctionalReport {
pub peak_contrast: Option<AtomFunctionalEstimate>,
pub average_value: Option<AtomFunctionalEstimate>,
pub decoder_variation_norm: Option<AtomFunctionalEstimate>,
}
#[derive(Debug, Clone, Copy)]
pub struct AtomFunctionalEstimate {
pub theta_plugin: f64,
pub theta_onestep: f64,
pub penalty_bias: f64,
}
#[derive(Debug, Clone)]
pub struct AtomSmoothSignificance {
pub log_e_nonconstant: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct AtomInferenceReport {
pub atom_index: usize,
pub atom_name: String,
pub functionals: Option<AtomFunctionalReport>,
pub smooth_significance: Option<AtomSmoothSignificance>,
}
pub struct FittedSaeManifold {
pub atoms: Vec<FittedAtom>,
pub jacobian_rows: Vec<Vec<f64>>,
pub isometry_penalty_root: Array2<f64>,
pub metric: RowMetric,
}
impl FittedSaeManifold {
pub fn param_dim(&self) -> usize {
self.atoms.iter().map(|a| a.frame.len()).sum()
}
fn atom_offset(&self, k: usize) -> usize {
self.atoms[..k].iter().map(|a| a.frame.len()).sum()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GeneratorFamily {
IsomAtom,
EqualArdRotation,
FrameRotation,
AtomPermutation,
ChartReparameterization,
}
impl GeneratorFamily {
fn label(self) -> &'static str {
match self {
GeneratorFamily::IsomAtom => "Isom(M_k)",
GeneratorFamily::EqualArdRotation => "equal-ARD rotation",
GeneratorFamily::FrameRotation => "frame rotation O(output_dim)",
GeneratorFamily::AtomPermutation => "Sym(F) atom permutation",
GeneratorFamily::ChartReparameterization => "Diff(M_k) chart reparameterization",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VerdictProvenance {
CurvatureTest,
PinnedByCanonicalization,
}
pub const GENERATOR_FLAT_ENERGY_TOL: f64 = 1.0e-3;
#[derive(Debug, Clone)]
pub struct GeneratorVerdict {
pub family: GeneratorFamily,
pub description: String,
pub unpinned: bool,
pub generator_norm: f64,
pub pinned_energy_fraction: f64,
pub lowering_error_scale: f64,
pub provenance: VerdictProvenance,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FrameInnerRotationGauge {
pub per_atom_ranks: Vec<usize>,
pub dim: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PinningRankSupport {
ParameterSpace,
GeneratorSpan,
}
impl PinningRankSupport {
pub fn label(&self) -> &'static str {
match self {
Self::ParameterSpace => "parameter_space",
Self::GeneratorSpan => "generator_span",
}
}
}
#[derive(Debug, Clone)]
pub struct ResidualGaugeReport {
pub metric_provenance: MetricProvenance,
pub generators: Vec<GeneratorVerdict>,
pub pinning_rank: usize,
pub pinning_rank_support: PinningRankSupport,
pub residual_gauge_dim: usize,
pub diffeomorphism_unpinned: bool,
pub sym_f_trivial_under_output_fisher: Option<bool>,
pub frame_inner_rotation: Option<FrameInnerRotationGauge>,
pub summary: String,
}
impl ResidualGaugeReport {
pub fn group_signature(&self) -> String {
let base = group_signature_of(&self.generators, self.diffeomorphism_unpinned);
match &self.frame_inner_rotation {
Some(gauge) if gauge.dim > 0 => format!(
"{base} ⊕ frame-inner ∏O(r_k)×{} [dim {}, canonical-fixed]",
gauge.per_atom_ranks.len(),
gauge.dim
),
_ => base,
}
}
}
fn group_signature_of(generators: &[GeneratorVerdict], diffeomorphism_unpinned: bool) -> String {
let mut counts: std::collections::BTreeMap<&'static str, usize> =
std::collections::BTreeMap::new();
for g in generators {
if g.unpinned {
*counts.entry(g.family.label()).or_insert(0) += 1;
}
}
let body = if counts.is_empty() {
"{e} [fully pinned: rigid up to nothing]".to_string()
} else {
counts
.iter()
.map(|(name, mult)| format!("{name}×{mult}"))
.collect::<Vec<_>>()
.join(" ⊕ ")
};
if diffeomorphism_unpinned {
format!("Diff(M) ⊇ {{ {body} }} [diffeomorphism-unpinned: isometry pin inactive]")
} else {
body
}
}
fn atom_isometry_generators(atom: &FittedAtom) -> Vec<(Array1<f64>, String)> {
let (p, d) = atom.frame.dim();
if d != atom.topology.latent_dim() {
return Vec::new();
}
let mut out: Vec<(Array1<f64>, String)> = Vec::new();
match &atom.topology {
AtomTopology::Circle => {
}
AtomTopology::Sphere | AtomTopology::EuclideanPatch { .. } | AtomTopology::Torus { .. } => {
for a in 0..d {
for b in (a + 1)..d {
let mut g = Array1::<f64>::zeros(p * d);
for i in 0..p {
g[i * d + a] = -atom.frame[[i, b]];
g[i * d + b] = atom.frame[[i, a]];
}
out.push((
g,
format!(
"{}: {} rotation axes ({a},{b})",
atom.name,
match &atom.topology {
AtomTopology::Sphere => "S² so(3)",
AtomTopology::Torus { .. } => "Tᵈ frame",
_ => "patch so(d)",
}
),
));
}
}
}
AtomTopology::ProjectivePlane | AtomTopology::KleinBottle => {}
}
out
}
fn equal_ard_rotation_generators(atom: &FittedAtom) -> Vec<(Array1<f64>, String)> {
let mut out: Vec<(Array1<f64>, String)> = Vec::new();
let (p, d) = atom.frame.dim();
let Some(ard) = atom.ard_variances.as_ref() else {
return out;
};
if ard.len() != d {
return out;
}
const ARD_EQUAL_REL_TOL: f64 = 1.0e-9;
for a in 0..d {
for b in (a + 1)..d {
let va = ard[a];
let vb = ard[b];
let scale = va.abs().max(vb.abs()).max(f64::MIN_POSITIVE);
if (va - vb).abs() <= ARD_EQUAL_REL_TOL * scale {
let mut g = Array1::<f64>::zeros(p * d);
for i in 0..p {
g[i * d + a] = -atom.frame[[i, b]];
g[i * d + b] = atom.frame[[i, a]];
}
out.push((
g,
format!("{}: equal-ARD rotation axes ({a},{b})", atom.name),
));
}
}
}
out
}
fn frame_rotation_generators(model: &FittedSaeManifold) -> Vec<(Array1<f64>, String)> {
let mut out: Vec<(Array1<f64>, String)> = Vec::new();
let p = model
.atoms
.iter()
.map(|a| a.frame.nrows())
.max()
.unwrap_or(0);
let param_dim = model.param_dim();
if p == 0 || param_dim == 0 {
return out;
}
let n_cols: usize = model.atoms.iter().map(|a| a.frame.ncols()).sum();
if n_cols == 0 {
return out;
}
let mut cols = Array2::<f64>::zeros((p, n_cols));
let mut col = 0usize;
for atom in &model.atoms {
let (ap, ad) = atom.frame.dim();
for c in 0..ad {
for i in 0..ap {
cols[[i, col]] = atom.frame[[i, c]];
}
col += 1;
}
}
let (u_opt, sv, _vt) = match cols.svd(true, false) {
Ok(t) => t,
Err(_) => return out,
};
let u = match u_opt {
Some(u) => u,
None => return out,
};
let smax = sv.iter().cloned().fold(0.0_f64, f64::max);
if !(smax > 0.0) {
return out;
}
let tol = smax * f64::EPSILON * (p.max(n_cols) as f64);
let r = sv.iter().filter(|&&s| s > tol).count().min(u.ncols());
if r < 2 {
return out;
}
for a in 0..r {
for b in (a + 1)..r {
let mut g = Array1::<f64>::zeros(param_dim);
for (k, atom) in model.atoms.iter().enumerate() {
let (ap, ad) = atom.frame.dim();
let base = model.atom_offset(k);
for c in 0..ad {
let mut proj_a = 0.0_f64;
let mut proj_b = 0.0_f64;
for i in 0..ap {
proj_a += u[[i, a]] * atom.frame[[i, c]];
proj_b += u[[i, b]] * atom.frame[[i, c]];
}
if proj_a == 0.0 && proj_b == 0.0 {
continue;
}
for i in 0..ap {
g[base + i * ad + c] = u[[i, a]] * proj_b - u[[i, b]] * proj_a;
}
}
}
out.push((
g,
format!("output-frame rotation within-span axes ({a},{b})"),
));
}
}
out
}
fn embed_local_generator(offset: usize, local: &Array1<f64>, param_dim: usize) -> Array1<f64> {
let mut g = Array1::<f64>::zeros(param_dim);
g.slice_mut(s![offset..offset + local.len()]).assign(local);
g
}
fn atom_permutation_generators(
model: &FittedSaeManifold,
) -> Vec<(Array1<f64>, String, usize, usize)> {
let mut out: Vec<(Array1<f64>, String, usize, usize)> = Vec::new();
let param_dim = model.param_dim();
for ka in 0..model.atoms.len() {
for kb in (ka + 1)..model.atoms.len() {
let a = &model.atoms[ka];
let b = &model.atoms[kb];
if a.topology != b.topology || a.frame.dim() != b.frame.dim() {
continue;
}
let (ap, ad) = a.frame.dim();
let base_a = model.atom_offset(ka);
let base_b = model.atom_offset(kb);
let mut g = Array1::<f64>::zeros(param_dim);
for i in 0..ap {
for c in 0..ad {
let diff = b.frame[[i, c]] - a.frame[[i, c]];
g[base_a + i * ad + c] = diff;
g[base_b + i * ad + c] = -diff;
}
}
out.push((g, format!("atom-exchange {} ↔ {}", a.name, b.name), ka, kb));
}
}
out
}
#[derive(Debug, Clone)]
pub struct AtomParameterView {
pub basis_values: Array2<f64>,
pub basis_jacobian: Array3<f64>,
pub decoder: Array2<f64>,
pub coords: Array2<f64>,
pub activations: Array1<f64>,
pub basis_second_jet: Option<Array4<f64>>,
}
pub struct OrbitPenaltyOperator {
pub apply: Box<dyn Fn(ArrayView2<f64>, ArrayView2<f64>) -> Array1<f64> + Send + Sync>,
pub stiffness_sq: f64,
}
pub fn isometry_orbit_penalty_operator(
view: &AtomParameterView,
weight: f64,
) -> Option<OrbitPenaltyOperator> {
let second = view.basis_second_jet.as_ref()?.clone();
let (n, m) = view.basis_values.dim();
let d = view.coords.ncols();
let p = view.decoder.ncols();
if second.dim() != (n, m, d, d) || view.basis_jacobian.dim() != (n, m, d) {
return None;
}
if !(weight.is_finite() && weight > 0.0) {
return None;
}
let sqrt_w = weight.sqrt();
let jac = view.basis_jacobian.clone();
let decoder = view.decoder.clone();
let mut j_base = Array3::<f64>::zeros((n, p, d));
for row in 0..n {
for i in 0..p {
for c in 0..d {
let mut acc = 0.0;
for mm in 0..m {
acc += jac[[row, mm, c]] * decoder[[mm, i]];
}
j_base[[row, i, c]] = acc;
}
}
}
let mut max_curv_sq = 0.0_f64;
for row in 0..n {
let mut hn = vec![0.0_f64; p * d * d];
for i in 0..p {
for c in 0..d {
for e in 0..d {
let mut acc = 0.0;
for mm in 0..m {
acc += second[[row, mm, c, e]] * decoder[[mm, i]];
}
hn[(i * d + c) * d + e] = acc;
}
}
}
for e in 0..d {
let mut g_e = Array2::<f64>::zeros((d, d));
for a in 0..d {
for b in 0..d {
let mut g = 0.0;
for i in 0..p {
g += hn[(i * d + a) * d + e] * j_base[[row, i, b]];
g += j_base[[row, i, a]] * hn[(i * d + b) * d + e];
}
g_e[[a, b]] = g;
}
}
max_curv_sq = max_curv_sq.max(symmetric_spectral_norm_sq(g_e.view()));
}
}
let stiffness_sq = (weight * max_curv_sq).max(f64::MIN_POSITIVE);
let apply = move |delta_b: ArrayView2<f64>, delta_t: ArrayView2<f64>| -> Array1<f64> {
let mut image = Array1::<f64>::zeros(n * d * d);
let valid_b = delta_b.dim() == (m, p);
let valid_t = delta_t.dim() == (n, d);
if !valid_t {
return image;
}
for row in 0..n {
let mut dj = vec![0.0_f64; p * d];
for i in 0..p {
for c in 0..d {
let mut acc = 0.0;
if valid_b {
for mm in 0..m {
acc += jac[[row, mm, c]] * delta_b[[mm, i]];
}
}
for e in 0..d {
let dte = delta_t[[row, e]];
if dte == 0.0 {
continue;
}
for mm in 0..m {
acc += second[[row, mm, c, e]] * dte * decoder[[mm, i]];
}
}
dj[i * d + c] = acc;
}
}
for a in 0..d {
for b in 0..d {
let mut dg = 0.0;
for i in 0..p {
dg += dj[i * d + a] * j_base[[row, i, b]];
dg += j_base[[row, i, a]] * dj[i * d + b];
}
image[(row * d + a) * d + b] = sqrt_w * dg;
}
}
}
image
};
Some(OrbitPenaltyOperator {
apply: Box::new(apply),
stiffness_sq,
})
}
fn symmetric_spectral_norm_sq(g: ArrayView2<'_, f64>) -> f64 {
let d = g.nrows();
if d == 0 {
return 0.0;
}
match g.to_owned().eigh(Side::Lower) {
Ok((evals, _)) => {
let s = evals.iter().fold(0.0_f64, |mx, &v| mx.max(v.abs()));
s * s
}
Err(_) => {
let mut max_col_sq = 0.0_f64;
for b in 0..g.ncols() {
let mut col_sq = 0.0_f64;
for a in 0..d {
col_sq += g[[a, b]] * g[[a, b]];
}
max_col_sq = max_col_sq.max(col_sq);
}
max_col_sq
}
}
}
fn exact_orbit_fields(
atom: &FittedAtom,
view: &AtomParameterView,
) -> Result<Vec<(GeneratorFamily, Array2<f64>, String)>, String> {
let n = view.coords.nrows();
let d = view.coords.ncols();
let mut out: Vec<(GeneratorFamily, Array2<f64>, String)> = Vec::new();
let rotation_field = |a: usize, b: usize| -> Array2<f64> {
let mut dt = Array2::<f64>::zeros((n, d));
for row in 0..n {
dt[[row, a]] = -view.coords[[row, b]];
dt[[row, b]] = view.coords[[row, a]];
}
dt
};
match &atom.topology {
AtomTopology::Circle => {
out.push((
GeneratorFamily::IsomAtom,
Array2::<f64>::ones((n, 1)),
format!("{}: S¹ U(1) phase shift [exact orbit]", atom.name),
));
}
AtomTopology::Torus { .. } => {
for ax in 0..d {
let mut dt = Array2::<f64>::zeros((n, d));
dt.column_mut(ax).fill(1.0);
out.push((
GeneratorFamily::IsomAtom,
dt,
format!("{}: Tᵈ circle shift axis {ax} [exact orbit]", atom.name),
));
}
}
AtomTopology::KleinBottle => {
let mut dt = Array2::<f64>::zeros((n, d));
dt.column_mut(0).fill(1.0);
out.push((
GeneratorFamily::IsomAtom,
dt,
format!("{}: Klein S1 theta translation [exact orbit]", atom.name),
));
}
AtomTopology::Sphere | AtomTopology::ProjectivePlane => {
if d != 3 {
return Err(format!(
"exact_orbit_fields({}): the spherical cover is the ambient unit vector and needs three coordinates; got {d}",
atom.name
));
}
let mut rotation_x = Array2::<f64>::zeros((n, 3));
let mut rotation_y = Array2::<f64>::zeros((n, 3));
let mut rotation_z = Array2::<f64>::zeros((n, 3));
for row in 0..n {
let u = [
view.coords[[row, 0]],
view.coords[[row, 1]],
view.coords[[row, 2]],
];
let generators = [[0.0, -u[2], u[1]], [u[2], 0.0, -u[0]], [-u[1], u[0], 0.0]];
for axis in 0..3 {
rotation_x[[row, axis]] = generators[0][axis];
rotation_y[[row, axis]] = generators[1][axis];
rotation_z[[row, axis]] = generators[2][axis];
}
}
let label = if matches!(atom.topology, AtomTopology::Sphere) {
"S2"
} else {
"RP2"
};
for (axis, field) in [("x", rotation_x), ("y", rotation_y), ("z", rotation_z)] {
out.push((
GeneratorFamily::IsomAtom,
field,
format!(
"{}: {label} SO(3) rotation about {axis} [exact ambient orbit]",
atom.name
),
));
}
}
AtomTopology::EuclideanPatch { .. } => {
for a in 0..d {
for b in (a + 1)..d {
out.push((
GeneratorFamily::IsomAtom,
rotation_field(a, b),
format!(
"{}: patch so(d) rotation axes ({a},{b}) [exact orbit]",
atom.name
),
));
}
}
}
}
if !matches!(
atom.topology,
AtomTopology::Circle
| AtomTopology::Sphere
| AtomTopology::ProjectivePlane
| AtomTopology::KleinBottle
) {
if let Some(ard) = atom.ard_variances.as_ref() {
if ard.len() == d {
const ARD_EQUAL_REL_TOL: f64 = 1.0e-9;
for a in 0..d {
for b in (a + 1)..d {
let scale = ard[a].abs().max(ard[b].abs()).max(f64::MIN_POSITIVE);
if (ard[a] - ard[b]).abs() <= ARD_EQUAL_REL_TOL * scale {
out.push((
GeneratorFamily::EqualArdRotation,
rotation_field(a, b),
format!(
"{}: equal-ARD rotation axes ({a},{b}) [exact orbit]",
atom.name
),
));
}
}
}
}
}
}
Ok(out)
}
fn exact_orbit_verdicts(
atom: &FittedAtom,
view: &AtomParameterView,
penalty: Option<&OrbitPenaltyOperator>,
) -> Result<Vec<GeneratorVerdict>, String> {
let (n, m) = view.basis_values.dim();
let d = view.coords.ncols();
let p = view.decoder.ncols();
if view.basis_jacobian.dim() != (n, m, d) {
return Err(format!(
"exact_orbit_verdicts({}): basis_jacobian shape {:?} must be ({n}, {m}, {d})",
atom.name,
view.basis_jacobian.dim()
));
}
if view.decoder.nrows() != m {
return Err(format!(
"exact_orbit_verdicts({}): decoder has {} rows but basis has {m} columns",
atom.name,
view.decoder.nrows()
));
}
if view.coords.nrows() != n || view.activations.len() != n {
return Err(format!(
"exact_orbit_verdicts({}): coords/activations rows must match basis rows {n}",
atom.name
));
}
let fields = exact_orbit_fields(atom, view)?;
if fields.is_empty() {
return Ok(Vec::new());
}
let mut design = Array2::<f64>::zeros((n, m));
for row in 0..n {
let a = view.activations[row];
for c in 0..m {
design[[row, c]] = a * view.basis_values[[row, c]];
}
}
let (u_opt, sigma, vt_opt) = design
.svd(true, true)
.map_err(|e| format!("exact_orbit_verdicts({}): SVD of D failed: {e}", atom.name))?;
let u_svd =
u_opt.ok_or_else(|| format!("exact_orbit_verdicts({}): SVD lacked U", atom.name))?;
let vt = vt_opt.ok_or_else(|| format!("exact_orbit_verdicts({}): SVD lacked Vᵀ", atom.name))?;
let smax = sigma.iter().cloned().fold(0.0_f64, f64::max);
let cutoff = smax * f64::EPSILON * (n.max(m) as f64);
let mut out: Vec<GeneratorVerdict> = Vec::with_capacity(fields.len());
for (family, dt, description) in fields {
let mut u_mot = Array2::<f64>::zeros((n, p));
for row in 0..n {
let a = view.activations[row];
if !(a != 0.0) {
continue;
}
for ax in 0..d {
let step = dt[[row, ax]];
if step == 0.0 {
continue;
}
for bm in 0..m {
let dphi = view.basis_jacobian[[row, bm, ax]];
if dphi == 0.0 {
continue;
}
let w = a * step * dphi;
for j in 0..p {
u_mot[[row, j]] += w * view.decoder[[bm, j]];
}
}
}
}
let raw: f64 = u_mot.iter().map(|v| v * v).sum();
if raw <= f64::MIN_POSITIVE {
out.push(GeneratorVerdict {
family,
description,
unpinned: false,
generator_norm: 0.0,
pinned_energy_fraction: 1.0,
lowering_error_scale: 0.0,
provenance: VerdictProvenance::CurvatureTest,
});
continue;
}
let coeffs = u_svd.t().dot(&u_mot);
let mut kept_sq = 0.0_f64;
let mut scaled = Array2::<f64>::zeros((sigma.len(), p));
for r in 0..sigma.len() {
if sigma[r] > cutoff {
let inv = 1.0 / sigma[r];
for j in 0..p {
kept_sq += coeffs[[r, j]] * coeffs[[r, j]];
scaled[[r, j]] = -inv * coeffs[[r, j]];
}
}
}
let resid_sq = (raw - kept_sq).max(0.0);
let data_fraction = (resid_sq / raw).clamp(0.0, 1.0);
let penalty_fraction = match penalty {
Some(op) if op.stiffness_sq > f64::MIN_POSITIVE => {
let delta_b = vt.t().dot(&scaled); let image = (op.apply)(delta_b.view(), dt.view());
let motion_sq: f64 = dt.iter().map(|v| v * v).sum();
if motion_sq > f64::MIN_POSITIVE {
let cost: f64 = image.iter().map(|v| v * v).sum();
(cost / (op.stiffness_sq * motion_sq)).clamp(0.0, 1.0)
} else {
0.0
}
}
_ => 0.0,
};
let pinned_energy_fraction = data_fraction.max(penalty_fraction);
out.push(GeneratorVerdict {
family,
description,
unpinned: pinned_energy_fraction <= GENERATOR_FLAT_ENERGY_TOL,
generator_norm: raw.sqrt(),
pinned_energy_fraction,
lowering_error_scale: 0.0,
provenance: VerdictProvenance::CurvatureTest,
});
}
Ok(out)
}
enum CurvatureReduction {
Gram {
pinning_rank: usize,
sigma_max_sq: f64,
gram: Array2<f64>,
},
OutputBlockRoots {
pinning_rank: usize,
sigma_max_sq: f64,
roots: Array3<f64>,
dense_rows: Array2<f64>,
layout: FrameColumnLayout,
},
DualRoot {
pinning_rank: usize,
sigma_max_sq: f64,
root: Array2<f64>,
},
}
fn curvature_rank_tolerance(sigma_max: f64, root_rows: usize, param_dim: usize) -> f64 {
default_rrqr_rank_alpha()
* f64::EPSILON
* (root_rows.max(param_dim).max(1) as f64)
* sigma_max.max(1.0)
}
pub(crate) fn root_spectral_rank(
singular_values: &[f64],
root_rows: usize,
param_dim: usize,
) -> (f64, usize) {
let sigma_max = singular_values
.iter()
.cloned()
.fold(0.0_f64, f64::max)
.max(0.0);
let rank_tol = curvature_rank_tolerance(sigma_max, root_rows, param_dim);
let pinning_rank = singular_values
.iter()
.filter(|&&sigma| sigma > rank_tol)
.count();
(sigma_max * sigma_max, pinning_rank)
}
fn gram_spectral_rank(spectrum: &[f64], root_rows: usize, param_dim: usize) -> (f64, usize) {
let sigma_max_sq = spectrum.iter().cloned().fold(0.0_f64, f64::max).max(0.0);
let rank_tol = curvature_rank_tolerance(sigma_max_sq.sqrt(), root_rows, param_dim);
let resolution_floor = f64::EPSILON * (param_dim.max(1) as f64) * sigma_max_sq;
let lambda_tol = (rank_tol * rank_tol).max(resolution_floor);
let pinning_rank = spectrum
.iter()
.filter(|&&lambda| lambda.max(0.0) > lambda_tol)
.count();
(sigma_max_sq, pinning_rank)
}
impl CurvatureReduction {
fn from_curvature(
curvature: ResidualGaugeCurvature,
model: &FittedSaeManifold,
) -> Result<Self, String> {
let param_dim = model.param_dim();
if curvature.param_dim() != param_dim {
return Err(format!(
"residual_gauge: curvature is over {} parameters but param_dim = {param_dim}",
curvature.param_dim()
));
}
if let ResidualGaugeCurvature::OutputBlockRoots { layout, .. } = &curvature {
let expected = FrameColumnLayout::for_frames(model.atoms.iter().map(|a| &a.frame))
.ok_or_else(|| {
"residual_gauge: a block-structured curvature needs one shared output \
dimension across the fitted frames"
.to_string()
})?;
if *layout != expected {
return Err(
"residual_gauge: the curvature's frame-column layout is not the fitted \
model's"
.to_string(),
);
}
}
if !curvature.is_finite() {
return Err(
"residual_gauge: streamed curvature contains a non-finite entry; the fitted \
decoder Jacobian or the row metric is not finite"
.to_string(),
);
}
let root_rows = curvature.root_rows();
match curvature {
ResidualGaugeCurvature::OutputBlockRoots {
roots,
dense_rows,
layout,
..
} => Self::from_output_block_roots(roots, dense_rows, layout, root_rows),
ResidualGaugeCurvature::DualRoot { root, .. } => {
Self::from_dual_root(root, root_rows, param_dim)
}
ResidualGaugeCurvature::DenseGram { gram, .. } => {
Self::from_gram(gram, root_rows, param_dim)
}
}
}
fn from_output_block_roots(
roots: Array3<f64>,
dense_rows: Array2<f64>,
layout: FrameColumnLayout,
root_rows: usize,
) -> Result<Self, String> {
let p = layout.output_dim();
let d = layout.block_dim();
if roots.dim() != (p, d, d) {
return Err(format!(
"residual_gauge: curvature block roots have shape {:?} but the frame layout is \
({p}, {d}, {d})",
roots.dim()
));
}
if dense_rows.nrows() > 0 && dense_rows.ncols() != layout.param_dim() {
return Err(format!(
"residual_gauge: curvature dense rows have {} columns but param_dim = {}",
dense_rows.ncols(),
layout.param_dim()
));
}
if layout.param_dim() == 0 || root_rows == 0 {
return Ok(Self::OutputBlockRoots {
pinning_rank: 0,
sigma_max_sq: 0.0,
roots,
dense_rows,
layout,
});
}
if dense_rows.nrows() == 0 {
let mut singular_values: Vec<f64> = Vec::with_capacity(p * d);
for i in 0..p {
let block = roots.slice(s![i, .., ..]);
if block.iter().all(|v| *v == 0.0) {
continue;
}
if d == 1 {
singular_values.push(block[[0, 0]].abs());
continue;
}
let (_u, sv, _vt) = block.to_owned().svd(false, false).map_err(|e| {
format!("residual_gauge: SVD of curvature block root {i} failed: {e}")
})?;
singular_values.extend(sv.iter().copied());
}
let (sigma_max_sq, pinning_rank) =
root_spectral_rank(&singular_values, root_rows, layout.param_dim());
return Ok(Self::OutputBlockRoots {
pinning_rank,
sigma_max_sq,
roots,
dense_rows,
layout,
});
}
let spectrum = frame_curvature::BlockPlusRowsSpectrum::new(&roots, &dense_rows, &layout)?;
let sigma_max_sq = spectrum.lambda_max()?;
let rank_tol = curvature_rank_tolerance(sigma_max_sq.sqrt(), root_rows, layout.param_dim());
let pinning_rank = spectrum.count_above(rank_tol * rank_tol)?;
Ok(Self::OutputBlockRoots {
pinning_rank,
sigma_max_sq,
roots,
dense_rows,
layout,
})
}
fn from_dual_root(
root: Array2<f64>,
root_rows: usize,
param_dim: usize,
) -> Result<Self, String> {
if root.ncols() != param_dim {
return Err(format!(
"residual_gauge: curvature root has {} columns but param_dim = {param_dim}",
root.ncols()
));
}
if param_dim == 0 || root_rows == 0 || root.nrows() == 0 {
return Ok(Self::DualRoot {
pinning_rank: 0,
sigma_max_sq: 0.0,
root,
});
}
let (_u, sv, _vt) = root
.svd(false, false)
.map_err(|e| format!("residual_gauge: SVD of the curvature root failed: {e}"))?;
let singular_values: Vec<f64> = sv.iter().copied().collect();
let (sigma_max_sq, pinning_rank) =
root_spectral_rank(&singular_values, root_rows, param_dim);
Ok(Self::DualRoot {
pinning_rank,
sigma_max_sq,
root,
})
}
fn from_gram(gram: Array2<f64>, root_rows: usize, param_dim: usize) -> Result<Self, String> {
if gram.nrows() != param_dim || gram.ncols() != param_dim {
return Err(format!(
"residual_gauge: curvature gram has shape ({}, {}) but param_dim = {param_dim}",
gram.nrows(),
gram.ncols()
));
}
if param_dim == 0 || root_rows == 0 {
return Ok(Self::Gram {
pinning_rank: 0,
sigma_max_sq: 0.0,
gram,
});
}
let (evals, _) = gram.eigh(Side::Lower).map_err(|e| {
format!("residual_gauge: eigendecomposition of curvature gram failed: {e}")
})?;
let spectrum: Vec<f64> = evals.iter().copied().collect();
let (sigma_max_sq, pinning_rank) = gram_spectral_rank(&spectrum, root_rows, param_dim);
Ok(Self::Gram {
pinning_rank,
sigma_max_sq,
gram,
})
}
fn pinning_rank(&self) -> usize {
match self {
Self::Gram { pinning_rank, .. }
| Self::OutputBlockRoots { pinning_rank, .. }
| Self::DualRoot { pinning_rank, .. } => *pinning_rank,
}
}
fn sigma_max_sq(&self) -> f64 {
match self {
Self::Gram { sigma_max_sq, .. }
| Self::OutputBlockRoots { sigma_max_sq, .. }
| Self::DualRoot { sigma_max_sq, .. } => *sigma_max_sq,
}
}
fn unit_generator_energy(&self, unit: &Array1<f64>) -> f64 {
match self {
Self::DualRoot { root, .. } => {
let r_xi = root.dot(unit);
r_xi.iter().map(|c| c * c).sum::<f64>()
}
Self::Gram { gram, .. } => {
let h_xi = gram.dot(unit);
unit.dot(&h_xi).max(0.0)
}
Self::OutputBlockRoots {
roots,
dense_rows,
layout,
..
} => {
let d = layout.block_dim();
let mut xi = vec![0.0_f64; d];
let mut total = 0.0_f64;
for i in 0..layout.output_dim() {
layout.gather_output(unit.view(), i, &mut xi);
if xi.iter().all(|v| *v == 0.0) {
continue;
}
for a in 0..d {
let mut row = 0.0_f64;
for b in a..d {
row += roots[[i, a, b]] * xi[b];
}
total += row * row;
}
}
if dense_rows.nrows() > 0 {
let l_xi = dense_rows.dot(unit);
total += l_xi.iter().map(|c| c * c).sum::<f64>();
}
total.max(0.0)
}
}
}
}
enum CurvatureAccess<'a> {
Reduced(CurvatureReduction),
Streamed(&'a dyn StreamedFrameCurvature),
}
struct CurvatureMeasurement {
pinning_rank: usize,
pinning_rank_support: PinningRankSupport,
sigma_max_sq: f64,
energies: Vec<f64>,
stiffness_note: String,
}
struct EnumeratedGenerator {
family: GeneratorFamily,
description: String,
lowering_error_scale: f64,
norm: f64,
unit: Option<Array1<f64>>,
}
impl EnumeratedGenerator {
fn from_tangent(
family: GeneratorFamily,
tangent: Array1<f64>,
description: String,
lowering_error_scale: f64,
) -> Self {
let norm = tangent.iter().map(|v| v * v).sum::<f64>().sqrt();
let unit = if norm <= f64::MIN_POSITIVE {
None
} else {
let mut unit = tangent;
unit.mapv_inplace(|v| v / norm);
Some(unit)
};
Self {
family,
description,
lowering_error_scale,
norm,
unit,
}
}
}
fn measure_reduced(
curvature: &CurvatureReduction,
gens: &[EnumeratedGenerator],
) -> CurvatureMeasurement {
let sigma_max_sq = curvature.sigma_max_sq();
let energies = gens
.iter()
.map(|generator| match &generator.unit {
Some(unit) if sigma_max_sq > f64::MIN_POSITIVE => {
curvature.unit_generator_energy(unit)
}
_ => 0.0,
})
.collect();
CurvatureMeasurement {
pinning_rank: curvature.pinning_rank(),
pinning_rank_support: PinningRankSupport::ParameterSpace,
sigma_max_sq,
energies,
stiffness_note: String::new(),
}
}
fn measure_streamed(
operator: &dyn StreamedFrameCurvature,
gens: &[EnumeratedGenerator],
) -> Result<CurvatureMeasurement, String> {
let lambda = streamed_lambda_max(operator)?;
let sigma_max_sq = lambda.lambda_max;
let present: Vec<usize> = gens
.iter()
.enumerate()
.filter_map(|(index, generator)| generator.unit.as_ref().map(|_| index))
.collect();
let directions: Vec<ArrayView1<'_, f64>> = present
.iter()
.map(|&index| gens[index].unit.as_ref().expect("filtered to Some").view())
.collect();
let mut energies = vec![0.0_f64; gens.len()];
let mut pinning_rank = 0usize;
if !directions.is_empty() {
let factor = operator.project_root(&directions)?;
if factor.nrows() != directions.len() || factor.ncols() != directions.len() {
return Err(format!(
"residual_gauge: streamed generator factor is {:?}, expected a square factor \
over the {} enumerated directions",
factor.dim(),
directions.len()
));
}
if factor.iter().any(|v| !v.is_finite()) {
return Err(
"residual_gauge: streamed generator factor contains a non-finite entry; the \
fitted decoder Jacobian or the row metric is not finite"
.to_string(),
);
}
for (column, &index) in present.iter().enumerate() {
energies[index] = factor
.column(column)
.iter()
.map(|v| v * v)
.sum::<f64>()
.max(0.0);
}
let (_u, sv, _vt) = factor
.svd(false, false)
.map_err(|e| format!("residual_gauge: SVD of the streamed generator factor: {e}"))?;
let singular_values: Vec<f64> = sv.iter().copied().collect();
let (_projected_sigma_max_sq, rank) =
root_spectral_rank(&singular_values, operator.root_rows(), directions.len());
pinning_rank = rank;
}
if !(sigma_max_sq > f64::MIN_POSITIVE) {
energies.iter_mut().for_each(|e| *e = 0.0);
}
Ok(CurvatureMeasurement {
pinning_rank,
pinning_rank_support: PinningRankSupport::GeneratorSpan,
sigma_max_sq,
energies,
stiffness_note: format!(
"; stiffness scale streamed (Krylov relative residual {:.1e}, tr(H) = {:.6e})",
lambda.relative_residual, lambda.trace
),
})
}
pub fn residual_gauge_exact_from_curvature(
model: &FittedSaeManifold,
views: &[Option<AtomParameterView>],
penalty_ops: &[Option<OrbitPenaltyOperator>],
curvature: ResidualGaugeCurvature,
) -> Result<ResidualGaugeReport, String> {
let curvature = CurvatureReduction::from_curvature(curvature, model)?;
let exact = residual_gauge_exact_inputs(model, views, penalty_ops)?;
residual_gauge_inner(model, Some(exact), CurvatureAccess::Reduced(curvature))
}
pub fn residual_gauge_exact_from_streamed(
model: &FittedSaeManifold,
views: &[Option<AtomParameterView>],
penalty_ops: &[Option<OrbitPenaltyOperator>],
operator: &dyn StreamedFrameCurvature,
) -> Result<ResidualGaugeReport, String> {
let param_dim = model.param_dim();
if operator.param_dim() != param_dim {
return Err(format!(
"residual_gauge: streamed curvature is over {} parameters but param_dim = \
{param_dim}",
operator.param_dim()
));
}
let exact = residual_gauge_exact_inputs(model, views, penalty_ops)?;
residual_gauge_inner(model, Some(exact), CurvatureAccess::Streamed(operator))
}
fn residual_gauge_exact_inputs(
model: &FittedSaeManifold,
views: &[Option<AtomParameterView>],
penalty_ops: &[Option<OrbitPenaltyOperator>],
) -> Result<(Vec<bool>, Vec<GeneratorVerdict>), String> {
if views.len() != model.atoms.len() || penalty_ops.len() != model.atoms.len() {
return Err(format!(
"residual_gauge_exact: views ({}) and penalty_ops ({}) must align with atoms ({})",
views.len(),
penalty_ops.len(),
model.atoms.len()
));
}
let mut mask = vec![false; model.atoms.len()];
let mut exact_verdicts: Vec<GeneratorVerdict> = Vec::new();
for (k, (atom, view)) in model.atoms.iter().zip(views.iter()).enumerate() {
let Some(view) = view else { continue };
exact_verdicts.extend(exact_orbit_verdicts(atom, view, penalty_ops[k].as_ref())?);
mask[k] = true;
}
Ok((mask, exact_verdicts))
}
fn enumerate_generators(
model: &FittedSaeManifold,
exact_mask: Option<&[bool]>,
) -> Vec<EnumeratedGenerator> {
let param_dim = model.param_dim();
let scale_of = |k: usize| -> f64 { model.atoms[k].lowering_error.clamp(0.0, 1.0) };
let global_scale = (0..model.atoms.len()).map(scale_of).fold(0.0_f64, f64::max);
let mut gens: Vec<EnumeratedGenerator> = Vec::new();
for (k, atom) in model.atoms.iter().enumerate() {
if exact_mask.is_some_and(|mask| mask[k]) {
continue;
}
let base = model.atom_offset(k);
for (g, desc) in atom_isometry_generators(atom) {
gens.push(EnumeratedGenerator::from_tangent(
GeneratorFamily::IsomAtom,
embed_local_generator(base, &g, param_dim),
desc,
scale_of(k),
));
}
for (g, desc) in equal_ard_rotation_generators(atom) {
gens.push(EnumeratedGenerator::from_tangent(
GeneratorFamily::EqualArdRotation,
embed_local_generator(base, &g, param_dim),
desc,
scale_of(k),
));
}
}
for (g, desc) in frame_rotation_generators(model) {
gens.push(EnumeratedGenerator::from_tangent(
GeneratorFamily::FrameRotation,
g,
desc,
global_scale,
));
}
for (g, desc, ka, kb) in atom_permutation_generators(model) {
gens.push(EnumeratedGenerator::from_tangent(
GeneratorFamily::AtomPermutation,
g,
desc,
scale_of(ka).max(scale_of(kb)),
));
}
gens
}
fn residual_gauge_inner(
model: &FittedSaeManifold,
exact: Option<(Vec<bool>, Vec<GeneratorVerdict>)>,
access: CurvatureAccess<'_>,
) -> Result<ResidualGaugeReport, String> {
let metric_provenance = model.metric.provenance();
let (exact_mask, exact_verdicts) = match exact {
Some((mask, verdicts)) => (Some(mask), verdicts),
None => (None, Vec::new()),
};
let gens = enumerate_generators(model, exact_mask.as_deref());
let measurement = match access {
CurvatureAccess::Streamed(operator) => measure_streamed(operator, &gens)?,
CurvatureAccess::Reduced(curvature) => measure_reduced(&curvature, &gens),
};
let pinning_rank = measurement.pinning_rank;
let pinning_rank_support = measurement.pinning_rank_support;
let sigma_max_sq = measurement.sigma_max_sq;
let diffeomorphism_unpinned = model.isometry_penalty_root.nrows() == 0;
let mut verdicts: Vec<GeneratorVerdict> = Vec::with_capacity(gens.len());
for (index, generator) in gens.iter().enumerate() {
let family = generator.family;
let description = &generator.description;
let lowering_error_scale = generator.lowering_error_scale;
if generator.unit.is_none() {
verdicts.push(GeneratorVerdict {
family,
description: description.clone(),
unpinned: false,
generator_norm: 0.0,
pinned_energy_fraction: 1.0,
lowering_error_scale,
provenance: VerdictProvenance::CurvatureTest,
});
continue;
}
let pinned_energy_fraction = if sigma_max_sq <= f64::MIN_POSITIVE {
0.0
} else {
(measurement.energies[index] / sigma_max_sq).clamp(0.0, 1.0)
};
let tolerance = GENERATOR_FLAT_ENERGY_TOL.max(lowering_error_scale);
let unpinned = pinned_energy_fraction <= tolerance;
verdicts.push(GeneratorVerdict {
family,
description: description.clone(),
unpinned,
generator_norm: generator.norm,
pinned_energy_fraction,
lowering_error_scale,
provenance: VerdictProvenance::CurvatureTest,
});
}
verdicts.extend(exact_verdicts);
let mut canonicalized_charts = 0usize;
let mut canonicalized_torus_charts = 0usize;
let mut canonicalized_patch_charts = 0usize;
let mut canonicalized_sphere_charts = 0usize;
for atom in &model.atoms {
if !atom.chart_canonicalized {
continue;
}
let (pinned_to, residual_group) = match &atom.topology {
AtomTopology::Circle | AtomTopology::Torus { latent_dim: 1 } => {
canonicalized_charts += 1;
("arc length", "O(2) on S¹ (rotation + reflection)")
}
AtomTopology::EuclideanPatch { latent_dim: 1 } => {
canonicalized_charts += 1;
(
"arc length",
"reflection + translation of the unit interval",
)
}
AtomTopology::Torus { latent_dim: 2 } => {
canonicalized_torus_charts += 1;
(
"the isometry-flow canonical chart",
"Isom(T², flat) = U(1)² ⋊ D₄ (axis translations + axis swap/reflections)",
)
}
AtomTopology::KleinBottle => {
canonicalized_torus_charts += 1;
(
"the Klein quotient gauge chart",
"Isom0(K, flat) = U(1)_theta with residual deck Z2",
)
}
AtomTopology::EuclideanPatch { latent_dim: 2 } => {
canonicalized_patch_charts += 1;
(
"the flat-reference isometry-flow canonical chart",
"Isom(ℝ², flat) = O(2) ⋉ ℝ² (rotation + reflection + translation)",
)
}
AtomTopology::Sphere => {
canonicalized_sphere_charts += 1;
(
"the round-sphere conformal-boost isometry-flow canonical chart",
"Isom(S², round) = O(3) (rotations + reflection)",
)
}
_ => continue,
};
verdicts.push(GeneratorVerdict {
family: GeneratorFamily::ChartReparameterization,
description: format!(
"{}: chart pinned to {pinned_to} by post-fit canonicalization; \
residual chart freedom = {residual_group}",
atom.name
),
unpinned: false,
generator_norm: 0.0,
pinned_energy_fraction: 1.0,
lowering_error_scale: 0.0,
provenance: VerdictProvenance::PinnedByCanonicalization,
});
}
let residual_gauge_dim = verdicts.iter().filter(|v| v.unpinned).count();
let sym_f_trivial_under_output_fisher = if matches!(
metric_provenance,
MetricProvenance::OutputFisher { .. } | MetricProvenance::OutputFisherDownstream { .. }
) {
let any_perm_unpinned = verdicts
.iter()
.any(|v| v.family == GeneratorFamily::AtomPermutation && v.unpinned);
Some(!any_perm_unpinned)
} else {
None
};
let summary = format!(
"residual gauge certificate (computed in metric {metric_provenance:?}): \
pinning rank {pinning_rank} over the {}, {residual_gauge_dim} unpinned residual gauge \
generator(s) of {} enumerated; group = {}{}{}{}",
pinning_rank_support.label(),
verdicts.len(),
group_signature_of(&verdicts, diffeomorphism_unpinned),
match sym_f_trivial_under_output_fisher {
Some(true) => "; Sym(F) trivially pinned under OutputFisher",
Some(false) => "; ⚠ Sym(F) NON-trivial under OutputFisher (certificate violation)",
None => "",
},
if diffeomorphism_unpinned {
"; ⚠ isometry pin inactive"
} else {
""
},
measurement.stiffness_note,
);
let summary = if canonicalized_charts > 0 {
format!(
"{summary}; {canonicalized_charts} chart(s) pinned to arc length by post-fit \
canonicalization (residual chart freedom = finite isometry group)"
)
} else {
summary
};
let summary = if canonicalized_torus_charts > 0 {
format!(
"{summary}; {canonicalized_torus_charts} torus chart(s) pinned to the \
isometry-flow canonical chart by post-fit canonicalization (residual chart \
freedom = Isom(T², flat))"
)
} else {
summary
};
let summary = if canonicalized_patch_charts > 0 {
format!(
"{summary}; {canonicalized_patch_charts} free/patch chart(s) pinned to the \
flat-reference isometry-flow canonical chart by post-fit canonicalization \
(residual chart freedom = Isom(ℝ², flat) = O(2) ⋉ ℝ²)"
)
} else {
summary
};
let summary = if canonicalized_sphere_charts > 0 {
format!(
"{summary}; {canonicalized_sphere_charts} sphere chart(s) pinned to the \
round-sphere conformal-boost isometry-flow canonical chart by post-fit \
canonicalization (residual chart freedom = Isom(S², round) = O(3))"
)
} else {
summary
};
Ok(ResidualGaugeReport {
metric_provenance,
generators: verdicts,
pinning_rank,
pinning_rank_support,
residual_gauge_dim,
diffeomorphism_unpinned,
sym_f_trivial_under_output_fisher,
frame_inner_rotation: None,
summary,
})
}
#[derive(Debug, Clone)]
pub struct DictionaryReport {
pub gauge: ResidualGaugeReport,
pub structure: StructureCertificate,
pub transport_ladders: Vec<AtomTransportLadderReport>,
pub atom_inference: Vec<AtomInferenceReport>,
}
#[derive(Debug, Clone)]
pub struct AtomTransportLadderInput {
pub atom_index: usize,
pub layers: Vec<usize>,
pub coords: Vec<Array1<f64>>,
pub topologies: Vec<CanonicalChartTopology>,
}
#[derive(Debug, Clone)]
pub struct AtomTransportLadderReport {
pub atom_index: usize,
pub atom_name: String,
pub report: TransportLadderReport,
}
fn atom_functional_report(fit: &AtomInnerFit) -> AtomFunctionalReport {
let penalty_beta = fit.penalty.dot(&fit.beta);
let debias = |functional_gradient: Array1<f64>| -> Option<AtomFunctionalEstimate> {
let input = RieszInput {
beta: fit.beta.view(),
functional_gradient: functional_gradient.view(),
row_scores: fit.row_scores.view(),
penalty_beta: penalty_beta.view(),
leverage: None,
};
debias_with_dense_hessian(&input, fit.penalized_hessian.view())
.ok()
.map(|r| AtomFunctionalEstimate {
theta_plugin: r.theta_plugin,
theta_onestep: r.theta_onestep,
penalty_bias: r.penalty_bias,
})
};
let peak_contrast = SmoothFunctional::Contrast {
design_row_a: fit.peak_design_row.view(),
design_row_b: fit.mode_design_row.view(),
}
.gradient()
.ok()
.and_then(debias);
let average_value = SmoothFunctional::AverageValue {
value_design: fit.design.view(),
weights: Some(fit.weights.view()),
}
.gradient()
.ok()
.and_then(debias);
let decoder_variation_norm = SmoothFunctional::AverageDerivative {
derivative_design: fit.derivative_design.view(),
weights: Some(fit.weights.view()),
}
.gradient()
.ok()
.and_then(debias);
AtomFunctionalReport {
peak_contrast,
average_value,
decoder_variation_norm,
}
}
fn atom_smooth_significance(fit: &AtomInnerFit) -> Option<AtomSmoothSignificance> {
let m = fit.design.ncols();
if m <= 1 || fit.beta.len() != m {
return None;
}
let n = fit.design.nrows();
if n == 0 || fit.weights.len() != n || fit.row_scores.nrows() != n {
return None;
}
let phi = if fit.dispersion.is_finite() && fit.dispersion > 0.0 {
fit.dispersion
} else {
return None;
};
let mut z = Array1::<f64>::zeros(n);
for i in 0..n {
let mu_hat = fit.design.row(i).dot(&fit.beta);
let w_i = fit.weights[i];
let phi_row = fit.design.row(i);
let phi_norm_sq = phi_row.dot(&phi_row);
let r_i = if w_i > 0.0 && phi_norm_sq > 0.0 {
let s_dot_phi = fit.row_scores.row(i).dot(&phi_row);
-phi * s_dot_phi / (w_i * phi_norm_sq)
} else {
0.0
};
z[i] = mu_hat + r_i;
}
let est: Vec<usize> = (0..n).filter(|i| i % 2 == 0).collect();
let eval: Vec<usize> = (0..n).filter(|i| i % 2 == 1).collect();
if est.is_empty() || eval.is_empty() {
return None;
}
let mut a_gram = fit.penalty.clone();
let mut b = Array1::<f64>::zeros(m);
for &i in &est {
let w_i = fit.weights[i];
if !(w_i > 0.0) {
continue;
}
let row = fit.design.row(i);
for r in 0..m {
let xr = row[r];
if xr == 0.0 {
continue;
}
b[r] += w_i * xr * z[i];
for c in 0..m {
a_gram[[r, c]] += w_i * xr * row[c];
}
}
}
let beta_alt = a_gram.cholesky(Side::Lower).ok()?.solvevec(&b);
let mut eval_mass = 0.0_f64;
let mut eval_wz = 0.0_f64;
for &i in &eval {
let w_i = fit.weights[i];
eval_mass += w_i;
eval_wz += w_i * z[i];
}
if !(eval_mass > 0.0) {
return None;
}
let null_mean = eval_wz / eval_mass;
let mut sse_alt = 0.0_f64;
let mut sse_null = 0.0_f64;
for &i in &eval {
let w_i = fit.weights[i];
let mu_alt = fit.design.row(i).dot(&beta_alt);
let r_alt = z[i] - mu_alt;
let r_null = z[i] - null_mean;
sse_alt += w_i * r_alt * r_alt;
sse_null += w_i * r_null * r_null;
}
let log_lik_alt = -0.5 * sse_alt / phi;
let log_lik_null_sup = -0.5 * sse_null / phi;
let log_e = gam_terms::inference::structure_evidence::split_likelihood_log_e_value(
log_lik_alt,
log_lik_null_sup,
)
.ok()?;
if !log_e.is_finite() {
return None;
}
Some(AtomSmoothSignificance {
log_e_nonconstant: Some(log_e),
})
}
pub(crate) fn atom_inference_reports(model: &FittedSaeManifold) -> Vec<AtomInferenceReport> {
model
.atoms
.iter()
.enumerate()
.map(|(atom_index, atom)| {
let (functionals, smooth_significance) = match &atom.inner_fit {
Some(fit) => (
Some(atom_functional_report(fit)),
atom_smooth_significance(fit),
),
None => (None, None),
};
AtomInferenceReport {
atom_index,
atom_name: atom.name.clone(),
functionals,
smooth_significance,
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::{Array1, array};
fn orbit_fixture(
topology: AtomTopology,
coords: Array2<f64>,
) -> (FittedAtom, AtomParameterView) {
let n = coords.nrows();
let d = coords.ncols();
(
FittedAtom {
name: "quotient".to_string(),
topology,
frame: Array2::zeros((2, d)),
ard_variances: None,
lowering_error: 0.0,
chart_canonicalized: false,
inner_fit: None,
},
AtomParameterView {
basis_values: Array2::ones((n, 1)),
basis_jacobian: Array3::zeros((n, 1, d)),
decoder: Array2::zeros((1, 2)),
coords,
activations: Array1::ones(n),
basis_second_jet: None,
},
)
}
#[test]
fn quotient_exact_orbits_have_the_correct_connected_gauge_dimensions() {
let (klein, klein_view) = orbit_fixture(
AtomTopology::KleinBottle,
Array2::from_shape_vec((2, 2), vec![0.1, 0.2, 0.4, -0.3]).unwrap(),
);
let klein_fields = exact_orbit_fields(&klein, &klein_view).unwrap();
assert_eq!(klein_fields.len(), 1);
assert!(
klein_fields[0]
.1
.column(0)
.iter()
.all(|value| *value == 1.0)
);
assert!(
klein_fields[0]
.1
.column(1)
.iter()
.all(|value| *value == 0.0)
);
let u = [0.36_f64, 0.48, 0.8];
let (rp2, rp2_view) = orbit_fixture(
AtomTopology::ProjectivePlane,
Array2::from_shape_vec((2, 3), vec![u[0], u[1], u[2], -u[0], -u[1], -u[2]]).unwrap(),
);
let rp2_fields = exact_orbit_fields(&rp2, &rp2_view).unwrap();
assert_eq!(rp2_fields.len(), 3);
for (_, field, _) in rp2_fields {
for axis in 0..3 {
assert!(
(field[[1, axis]] + field[[0, axis]]).abs() <= 1.0e-12,
"the antipodal deck must negate every ambient component of a Killing field"
);
}
}
let (sphere, sphere_view) = orbit_fixture(
AtomTopology::Sphere,
Array2::from_shape_vec((2, 3), vec![u[0], u[1], u[2], 0.0, 0.0, 1.0]).unwrap(),
);
let sphere_fields = exact_orbit_fields(&sphere, &sphere_view).unwrap();
assert_eq!(
sphere_fields.len(),
3,
"Isom(S²) = O(3) has three connected generators; the sphere used to emit zero"
);
for (_, field, _) in &sphere_fields {
for row in 0..2 {
let point = if row == 0 { u } else { [0.0, 0.0, 1.0] };
let radial: f64 = (0..3).map(|axis| field[[row, axis]] * point[axis]).sum();
assert!(
radial.abs() <= 1.0e-12,
"a Killing field of S² is tangent, so u·K(u) = 0; got {radial:.3e}"
);
}
}
}
#[test]
fn ambient_cover_has_no_pole_to_refuse() {
let (rp2, view) = orbit_fixture(
AtomTopology::ProjectivePlane,
Array2::from_shape_vec((1, 3), vec![0.0, 0.0, 1.0]).unwrap(),
);
let fields = exact_orbit_fields(&rp2, &view)
.expect("the former pole is an ordinary point on the ambient cover");
assert_eq!(fields.len(), 3);
let magnitudes: Vec<f64> = fields
.iter()
.map(|(_, field, _)| (0..3).map(|a| field[[0, a]].abs()).fold(0.0, f64::max))
.collect();
assert_eq!(
magnitudes.iter().filter(|m| **m > 1.0e-12).count(),
2,
"at a pole exactly one generator (the rotation fixing it) vanishes"
);
}
#[test]
fn atom_functional_report_recovers_known_functionals() {
use ndarray::{Array1 as A1, Array2 as A2};
let n = 40usize;
let m = 3usize;
let beta = A1::from(vec![0.5_f64, -1.0, 2.0]);
let mut design = A2::<f64>::zeros((n, m));
let mut derivative_design = A2::<f64>::zeros((n, m));
let mut weights = A1::<f64>::ones(n);
let mut t = vec![0.0_f64; n];
for i in 0..n {
let ti = i as f64 / (n - 1) as f64;
t[i] = ti;
design[[i, 0]] = 1.0;
design[[i, 1]] = ti;
design[[i, 2]] = ti * ti;
derivative_design[[i, 0]] = 0.0;
derivative_design[[i, 1]] = 1.0;
derivative_design[[i, 2]] = 2.0 * ti;
weights[i] = 1.0;
}
let dispersion = 1.0_f64;
let row_scores = A2::<f64>::zeros((n, m));
let mut penalty = A2::<f64>::zeros((m, m));
penalty[[2, 2]] = 1e-3;
let mut xtwx = A2::<f64>::zeros((m, m));
for i in 0..n {
for a in 0..m {
for b in 0..m {
xtwx[[a, b]] += weights[i] * design[[i, a]] * design[[i, b]];
}
}
}
let penalized_hessian = &xtwx + &penalty;
let mut peak_slot = 0usize;
let mut peak_val = -1.0;
for i in 0..n {
let g = design.row(i).dot(&beta).abs();
if g > peak_val {
peak_val = g;
peak_slot = i;
}
}
let peak_design_row = design.row(peak_slot).to_owned();
let mode_design_row = design.row(0).to_owned();
let fit = AtomInnerFit {
design: design.clone(),
derivative_design: derivative_design.clone(),
beta: beta.clone(),
penalty,
penalized_hessian,
row_scores,
weights: weights.clone(),
dispersion,
peak_design_row: peak_design_row.clone(),
mode_design_row: mode_design_row.clone(),
};
let report = atom_functional_report(&fit);
let av = report.average_value.expect("average value");
let expected_av: f64 = (0..n).map(|i| design.row(i).dot(&beta)).sum::<f64>() / n as f64;
assert!(
(av.theta_plugin - expected_av).abs() < 1e-9,
"average value plug-in {} vs expected {}",
av.theta_plugin,
expected_av
);
assert!(
av.theta_onestep.is_finite(),
"average-value debiased finite"
);
let ad = report
.decoder_variation_norm
.expect("decoder variation norm");
let mean_t: f64 = t.iter().sum::<f64>() / n as f64;
let expected_ad = beta[1] + 2.0 * beta[2] * mean_t;
assert!(
(ad.theta_plugin - expected_ad).abs() < 1e-9,
"decoder variation plug-in {} vs expected {}",
ad.theta_plugin,
expected_ad
);
let pc = report.peak_contrast.expect("peak contrast");
let expected_pc = peak_design_row.dot(&beta) - mode_design_row.dot(&beta);
assert!(
(pc.theta_plugin - expected_pc).abs() < 1e-9,
"peak contrast plug-in {} vs expected {}",
pc.theta_plugin,
expected_pc
);
}
#[test]
fn mechanism_sparsity_jacobian_value_matches_closed_form() {
let w = array![[3.0_f64, 0.0], [4.0, 0.0]]; let pen = MechanismSparsityJacobian::new(1.0, 1.0e-8).unwrap();
let (v, _g) = pen.value_and_grad(w.view());
assert!((v - 5.0).abs() < 1e-6, "value {v} expected ≈5");
}
#[test]
fn mechanism_sparsity_jacobian_grad_matches_finite_diff() {
let w = array![[0.5_f64, -1.2, 0.3], [1.1, 0.4, -0.7]];
let pen = MechanismSparsityJacobian::new(2.5, 1.0e-6).unwrap();
let (_, g) = pen.value_and_grad(w.view());
let h = 1.0e-5;
for i in 0..w.nrows() {
for j in 0..w.ncols() {
let mut wp = w.clone();
let mut wm = w.clone();
wp[[i, j]] += h;
wm[[i, j]] -= h;
let (vp, _) = pen.value_and_grad(wp.view());
let (vm, _) = pen.value_and_grad(wm.view());
let fd = (vp - vm) / (2.0 * h);
assert!(
(g[[i, j]] - fd).abs() < 1e-4,
"grad[{i},{j}] = {} vs fd {}",
g[[i, j]],
fd
);
}
}
}
#[test]
fn mechanism_sparsity_jacobian_rejects_bad_input() {
assert!(MechanismSparsityJacobian::new(-1.0, 1e-6).is_err());
assert!(MechanismSparsityJacobian::new(1.0, 0.0).is_err());
}
fn ivae_precondition_pair(n: usize, d: usize) -> (Array2<f64>, Array2<f64>) {
assert!(n >= 2 * d + 1, "need at least 2d+1 rows");
let mut mean = Array2::<f64>::zeros((n, d));
let mut scale = Array2::<f64>::from_elem((n, d), 1.0);
for r in 0..n {
let t = r as f64 / (n as f64 - 1.0);
for c in 0..d {
let omega = (c + 1) as f64;
mean[[r, c]] = (std::f64::consts::PI * omega * t).sin();
scale[[r, c]] = (0.4 * (std::f64::consts::PI * omega * t).cos()).exp();
}
}
(mean, scale)
}
#[test]
fn conditional_prior_ivae_zero_mean_unit_scale_matches_standard_gaussian() {
let n = 7;
let d = 3;
let (mean, scale) = ivae_precondition_pair(n, d);
let t = mean.clone();
let log_norm: f64 = scale.iter().map(|s| s.ln()).sum();
let pen = ConditionalPriorIvae::new(mean, scale, 1.0).unwrap();
let (v, g) = pen.value_and_grad(t.view());
let expected = log_norm + 0.5 * (n * d) as f64 * (2.0 * std::f64::consts::PI).ln();
assert!(
(v - expected).abs() < 1e-9,
"value {v} vs expected {expected}"
);
for &gv in g.iter() {
assert!(gv.abs() < 1e-12);
}
}
#[test]
fn derive_ivae_aux_scale_matches_numpy_formula() {
let aux = array![
[1.0_f64, 2.0, 5.0],
[2.5, -1.0, 5.0],
[-0.5, 4.0, 5.0],
[3.0, 0.5, 5.0],
];
let scale = derive_ivae_aux_scale(aux.view(), 0.4, 1.0);
let expected = array![
[0.8694483838365188_f64, 1.2655369552163311, 1.0],
[1.2831253020529474, 0.6734640022297076, 1.0],
[0.6982995444784196, 1.4877547448810657, 1.0],
[1.376498244165498, 0.7443881970243742, 1.0],
];
let mut max_abs = 0.0_f64;
for (actual, reference) in scale.iter().zip(expected.iter()) {
max_abs = max_abs.max((actual - reference).abs());
}
assert!(
max_abs < 1.0e-12,
"Rust iVAE aux-scale derivation differs from the old NumPy formula by {max_abs}"
);
}
#[test]
fn conditional_prior_ivae_grad_matches_finite_diff() {
let (mean, scale) = ivae_precondition_pair(5, 2);
let mut t = mean.clone();
for r in 0..5 {
t[[r, 0]] += 0.4;
t[[r, 1]] -= 0.3;
}
let pen = ConditionalPriorIvae::new(mean, scale, 1.7).unwrap();
let (_, g) = pen.value_and_grad(t.view());
let h = 1.0e-5;
for i in 0..t.nrows() {
for j in 0..t.ncols() {
let mut tp = t.clone();
let mut tm = t.clone();
tp[[i, j]] += h;
tm[[i, j]] -= h;
let vp = pen.value(tp.view());
let vm = pen.value(tm.view());
let fd = (vp - vm) / (2.0 * h);
assert!((g[[i, j]] - fd).abs() < 1e-5);
}
}
}
#[test]
fn conditional_prior_ivae_rejects_nonpositive_scale() {
let mean = Array2::<f64>::zeros((2, 2));
let mut scale = Array2::<f64>::ones((2, 2));
scale[[0, 0]] = -0.1;
assert!(ConditionalPriorIvae::new(mean, scale, 1.0).is_err());
}
#[test]
fn conditional_prior_ivae_accepts_when_signature_full_rank() {
let (mean, scale) = ivae_precondition_pair(7, 3);
let result = ConditionalPriorIvae::new(mean, scale, 1.0);
assert!(
result.is_ok(),
"full-rank signature should satisfy Khemakhem Theorem 1, got {:?}",
result.err(),
);
}
#[test]
fn conditional_prior_ivae_rejects_trivial_constant_prior() {
let n = 9;
let d = 3;
let mean = Array2::<f64>::from_elem((n, d), 0.25);
let scale = Array2::<f64>::from_elem((n, d), 1.5);
let err = ConditionalPriorIvae::new(mean, scale, 1.0).unwrap_err();
assert!(
err.contains("trivial unconditional") && err.contains("Khemakhem"),
"unexpected error: {err}"
);
}
#[test]
fn conditional_prior_ivae_rejects_too_few_auxiliary_states() {
let (full_mean, full_scale) = ivae_precondition_pair(7, 3);
let mean = full_mean.slice(s![..4, ..]).to_owned();
let scale = full_scale.slice(s![..4, ..]).to_owned();
let err = ConditionalPriorIvae::new(mean, scale, 1.0).unwrap_err();
assert!(
err.contains("2k+1") && err.contains("Khemakhem"),
"unexpected error: {err}"
);
}
#[test]
fn conditional_prior_ivae_rejects_rank_deficient_signature() {
let n = 9;
let d = 3;
let mut mean = Array2::<f64>::zeros((n, d));
let mut scale = Array2::<f64>::from_elem((n, d), 1.0);
for r in 0..n {
let v = ((r as f64) * 0.5).sin();
mean[[r, 0]] = v;
scale[[r, 0]] = v.exp(); }
let err = ConditionalPriorIvae::new(mean, scale, 1.0).unwrap_err();
assert!(
err.contains("numerical rank") && err.contains("Khemakhem"),
"unexpected error: {err}"
);
}
#[test]
fn identifiable_factor_evidence_scores_one_converged_fit() {
let score = identifiable_factor_log_evidence(4.0, 1.5, 8).unwrap();
let expected = -4.0 * (0.5_f64).ln() - 0.75;
assert!((score - expected).abs() < f64::EPSILON.sqrt());
}
#[test]
fn identifiable_factor_evidence_rejects_unbounded_zero_residual() {
let error = identifiable_factor_log_evidence(0.0, 1.0, 8).unwrap_err();
assert!(error.contains("positive"));
}
#[test]
fn ridge_reml_weight_matches_one_direction_stationary_solution() {
let selected = ridge_reml_select_weight(&[2.0], &[8.0], 10.0, 5, 3).unwrap();
match selected {
RidgeRemlWeight::Interior { lambda, score } => {
let tolerance = 1.2 * f64::EPSILON.sqrt().exp_m1();
assert!(
(lambda - 1.2).abs() <= tolerance,
"lambda={lambda}, off by {} against the search's location contract \
{tolerance:e}",
(lambda - 1.2).abs()
);
assert!(score.is_finite());
}
RidgeRemlWeight::FullShrinkage { .. } => {
panic!("the planted signal has an interior REML optimum")
}
}
}
#[test]
fn ridge_reml_weight_recovers_exact_full_shrinkage_boundary() {
let selected = ridge_reml_select_weight(&[2.0], &[1.0], 10.0, 5, 2).unwrap();
assert!(matches!(selected, RidgeRemlWeight::FullShrinkage { .. }));
}
#[test]
fn partial_supervision_procrustes_recovers_rotation_and_orthogonalizes_free() {
let aux = array![
[1.0_f64, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[1.0, 1.0, 0.0],
[-1.0, 1.0, 2.0],
];
let q = array![[0.0_f64, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]];
let t_sup = aux.dot(&q.t());
let t_free = array![
[1.5_f64, 0.0],
[0.0, 1.0],
[-1.0, 2.0],
[0.3, -0.7],
[2.0, 1.0],
];
let result = partial_supervision_solve(
t_sup.view(),
aux.view(),
t_free.view(),
PartialSupervisionSupMethod::Procrustes,
&[],
PartialSupervisionFreeConstraint::OrthogonalToSup,
)
.expect("procrustes solve should succeed");
for r in 0..aux.nrows() {
for c in 0..aux.ncols() {
assert!(
(result.t_supervised[[r, c]] - aux[[r, c]]).abs() < 1.0e-10,
"sup[{r},{c}] = {} vs aux {}",
result.t_supervised[[r, c]],
aux[[r, c]]
);
}
}
let cross = result.t_free.t().dot(&result.t_supervised);
let frob: f64 = cross.iter().map(|x| x * x).sum::<f64>().sqrt();
assert!(frob < 1.0e-8, "cross frobenius = {frob}");
assert!(result.alignment_score > 1.0 - 1.0e-10);
assert!(result.map_r.is_some());
}
#[test]
fn partial_supervision_anchor_pins_exact_anchors_when_full_rank() {
let aux = array![[1.0_f64, 2.0], [-1.0, 0.5], [3.0, -2.0], [0.7, 1.2],];
let t_sup = array![[0.5_f64, 1.0], [-0.5, 0.25], [1.5, -1.0], [0.35, 0.6],];
let t_free = Array2::<f64>::zeros((4, 1));
let result = partial_supervision_solve(
t_sup.view(),
aux.view(),
t_free.view(),
PartialSupervisionSupMethod::Anchor,
&[0, 1, 2],
PartialSupervisionFreeConstraint::None,
)
.expect("anchor solve should succeed");
for &row in &[0, 1, 2] {
for c in 0..2 {
assert!(
(result.t_supervised[[row, c]] - aux[[row, c]]).abs() < 1.0e-9,
"anchor row {row} col {c} not pinned: {} vs {}",
result.t_supervised[[row, c]],
aux[[row, c]]
);
}
}
assert!(result.map_a.is_some() && result.map_b.is_some());
}
#[test]
fn partial_supervision_softl2_selects_a_finite_weight() {
let aux = array![
[1.0_f64, 0.0],
[0.0, 1.0],
[1.0, 1.0],
[-1.0, 1.0],
[0.5, -0.5],
];
let t_sup = array![
[1.0_f64, 0.1],
[0.1, 1.0],
[1.0, 1.0],
[-1.0, 1.0],
[0.5, -0.5],
];
let t_free = array![[0.5_f64], [0.5], [0.5], [0.5], [0.5]];
let result = partial_supervision_solve(
t_sup.view(),
aux.view(),
t_free.view(),
PartialSupervisionSupMethod::SoftL2,
&[],
PartialSupervisionFreeConstraint::OrthogonalToSup,
)
.expect("soft_l2 solve should succeed");
let lam = result.selected_weight.unwrap();
assert!(lam.is_finite() && lam > 0.0, "lam={lam}");
assert!(result.map_a.is_some());
}
#[test]
fn partial_supervision_softl2_returns_exact_null_map_without_signal() {
let t_sup = array![[-1.0_f64], [0.0], [1.0]];
let aux = array![[1.0_f64], [1.0], [1.0]];
let t_free = Array2::<f64>::zeros((3, 0));
let result = partial_supervision_solve(
t_sup.view(),
aux.view(),
t_free.view(),
PartialSupervisionSupMethod::SoftL2,
&[],
PartialSupervisionFreeConstraint::None,
)
.expect("zero-signal soft-L2 solve should select the null boundary");
assert_eq!(result.selected_weight, Some(f64::INFINITY));
assert!(result.map_a.unwrap().iter().all(|&value| value == 0.0));
assert!(result.t_supervised.iter().all(|&value| value == 0.0));
}
#[test]
fn symmetric_spectral_norm_sq_uses_sigma_max_not_frobenius() {
use ndarray::Array2;
let g = Array2::<f64>::from_shape_vec((2, 2), vec![0.0, 1.0, 1.0, 0.0]).unwrap();
let sigma_sq = super::symmetric_spectral_norm_sq(g.view());
let frob_sq: f64 = g.iter().map(|&v| v * v).sum();
assert!(
(sigma_sq - 1.0).abs() < 1e-9,
"σ_max² must be 1, got {sigma_sq}"
);
assert!(
(frob_sq - 2.0).abs() < 1e-9,
"fixture ‖G‖_F² is 2 (the flip)"
);
assert!(
sigma_sq < frob_sq,
"σ_max² must be strictly below ‖G‖_F² here"
);
let g2 = Array2::<f64>::from_shape_vec((2, 2), vec![1.0, 0.1, 0.1, 1.0]).unwrap();
let s2 = super::symmetric_spectral_norm_sq(g2.view());
assert!((s2 - 1.21).abs() < 1e-9, "σ_max² must be 1.21, got {s2}");
let g1 = Array2::<f64>::from_shape_vec((1, 1), vec![-1.3]).unwrap();
let s1 = super::symmetric_spectral_norm_sq(g1.view());
assert!((s1 - 1.69).abs() < 1e-9, "d=1 must be g² = 1.69, got {s1}");
}
}