#![cfg(test)]
use super::*;
use crate::manifold::construction::ThetaAdjointDhChannel;
use ndarray::{Array1, Array2};
pub(crate) fn threshold_gate_tiny_fixture(
straddle: bool,
) -> (SaeManifoldTerm, Array2<f64>, SaeManifoldRho) {
let n = 10usize;
let p = 3usize;
let k_atoms = 2usize;
let m = 3usize;
let tau = 1.0_f64;
let threshold = 0.0_f64;
let evaluator = Arc::new(
PeriodicHarmonicEvaluator::new(m)
.expect("m=3 is odd and positive, the basis width this evaluator requires"),
);
let mut logits = Array2::<f64>::zeros((n, k_atoms));
let mut coords = vec![Array2::<f64>::zeros((n, 1)), Array2::<f64>::zeros((n, 1))];
let weights = [
[
[0.10, -0.05, 0.03],
[0.35, -0.20, 0.12],
[-0.16, 0.18, 0.08],
],
[
[-0.08, 0.04, 0.06],
[0.22, 0.10, -0.18],
[0.11, -0.24, 0.15],
],
];
let mut target = Array2::<f64>::zeros((n, p));
for row in 0..n {
let phase = (row as f64 + 0.35) / n as f64;
coords[0][[row, 0]] = phase;
coords[1][[row, 0]] = (phase + 0.21).fract();
if straddle {
logits[[row, 0]] = if row % 2 == 0 { -0.8 } else { 0.9 };
logits[[row, 1]] = if row % 2 == 0 { 0.7 } else { -0.5 };
} else {
logits[[row, 0]] = -0.8;
logits[[row, 1]] = -0.5;
}
for atom in 0..k_atoms {
let gate = 1.0 / (1.0 + (-(logits[[row, atom]] - threshold) / tau).exp());
let theta = std::f64::consts::TAU * coords[atom][[row, 0]];
let basis = [1.0, theta.sin(), theta.cos()];
for out_col in 0..p {
for basis_col in 0..m {
target[[row, out_col]] +=
gate * basis[basis_col] * weights[atom][basis_col][out_col];
}
}
}
}
let mut atoms = Vec::with_capacity(k_atoms);
for atom in 0..k_atoms {
let (phi, jet) = evaluator
.evaluate(coords[atom].view())
.expect("fixture coords are finite and inside the unit-period circle chart");
let decoder = Array2::from_shape_fn((m, p), |(basis_col, out_col)| {
weights[atom][basis_col][out_col]
});
atoms.push(
SaeManifoldAtom::new_with_provided_function_gram(
format!("tgate_{atom}"),
SaeAtomBasisKind::Periodic,
1,
phi,
jet,
decoder,
Array2::<f64>::eye(m),
)
.expect("decoder is (m, p) and phi/jet come from the same evaluator")
.with_basis_second_jet(evaluator.clone()),
);
}
let mode = AssignmentMode::threshold_gate(tau, threshold);
let assignment = SaeAssignment::from_blocks_with_mode_and_manifolds(
logits,
coords,
vec![LatentManifold::Circle { period: 1.0 }; k_atoms],
mode,
)
.expect("k_atoms coord blocks and k_atoms manifolds match the logits' k_atoms columns");
let term = SaeManifoldTerm::new(atoms, assignment)
.expect("the assignment was built with exactly one block per atom");
let rho = SaeManifoldRho::new(
-1.0,
-1.0,
vec![Array1::from_vec(vec![-1.0]), Array1::from_vec(vec![-1.0])],
)
.for_assignment(mode);
(term, target, rho)
}
fn frozen_cache(
term: &SaeManifoldTerm,
target: &Array2<f64>,
rho: &SaeManifoldRho,
) -> (SaeManifoldLoss, ArrowFactorCache) {
let mut t = term.clone();
let (_value, loss, cache) = t
.penalized_quasi_laplace_criterion_with_cache(
target.view(),
rho,
None,
0,
0.4,
1.0e-6,
1.0e-6,
)
.expect("threshold-gate fixed-theta cache");
(loss, cache)
}
fn deflated_direction_count(term: &SaeManifoldTerm, cache: &ArrowFactorCache) -> usize {
(0..term.n_obs())
.map(|row| cache.deflated_row_directions[row].len())
.sum()
}
fn logit_slots(term: &SaeManifoldTerm, cache: &ArrowFactorCache) -> Vec<(usize, usize, usize)> {
let mut out = Vec::new();
let dim = term.assignment.assignment_coord_dim();
for row in 0..term.n_obs() {
let base = cache.row_offsets[row];
for atom in 0..dim.min(cache.row_dims[row]) {
out.push((row, atom, base + atom));
}
}
out
}
#[test]
fn threshold_gate_sparse_operator_is_the_installed_exact_a_derivative_2500() {
for straddle in [false, true] {
let (term, target, rho) = threshold_gate_tiny_fixture(straddle);
let (_loss, cache) = frozen_cache(&term, &target, &rho);
let sparse = rho
.sparse_flat_index()
.expect("a ThresholdGate rho must carry a sparse log-strength coordinate");
let operators = term
.penalty_curvature_operators_by_flat(&rho, &cache)
.expect("#2500: the ThresholdGate sparse curvature operator must be modelled");
let block = operators
.get(&sparse)
.expect("#2500: the sparse coordinate must own a curvature operator");
let deltas = term
.exact_stationarity_penalty_derivative_delta_by_flat(&rho, &cache)
.expect("exact-minus-majorizer delta map");
assert!(
!deltas.contains_key(&sparse),
"#2500: ΔC carries no threshold-gate sparse part, so ∂A/∂ρ_sparse is the \
operator alone; a delta appearing here means this gate is comparing the \
wrong object"
);
let deflated = deflated_direction_count(&term, &cache);
if straddle {
assert!(
deflated > 0,
"#2500: the straddling arm must actually exercise the spectral-deflation \
stratum (the raw operator is exact without it, so a zero here would make \
this arm a duplicate of the other)"
);
} else {
assert_eq!(
deflated, 0,
"#2500: the below-threshold arm must be deflation-free, so it isolates the \
operator from the deflation map"
);
}
let dense_a = |r: &SaeManifoldRho| -> (Array2<f64>, usize) {
let (_l, c) = frozen_cache(&term, &target, r);
let a = term
.materialize_exact_hessian_dense(r, target.view(), &c)
.expect("dense exact A");
let count = deflated_direction_count(&term, &c);
(a, count)
};
let base = rho.to_flat();
let h = 1.0e-5;
let mut plus_flat = base.clone();
plus_flat[sparse] += h;
let mut minus_flat = base.clone();
minus_flat[sparse] -= h;
let (a_plus, deflated_plus) = dense_a(&rho.from_flat(plus_flat.view()).unwrap());
let (a_minus, deflated_minus) = dense_a(&rho.from_flat(minus_flat.view()).unwrap());
assert!(
deflated_plus == deflated && deflated_minus == deflated,
"#2500: the ±h endpoints must sit on the SAME discrete deflation stratum \
(base={deflated}, +h={deflated_plus}, -h={deflated_minus})"
);
let dim = a_plus.nrows();
let mut worst = 0.0_f64;
let mut worst_label = String::new();
for i in 0..dim {
for j in 0..dim {
let fd = (a_plus[[i, j]] - a_minus[[i, j]]) / (2.0 * h);
let analytic = block[[i, j]];
let err = (fd - analytic).abs();
let tol = 1.0e-7 + 1.0e-5 * analytic.abs();
if err / tol > worst {
worst = err / tol;
worst_label =
format!("[{i},{j}] analytic={analytic:.12e} fd={fd:.12e} tol={tol:.3e}");
}
}
}
assert!(
worst <= 1.0,
"#2500 (straddle={straddle}, {deflated} deflated directions): the sparse \
curvature operator must equal dA/drho_sparse of the INSTALLED exact Hessian; \
worst normalized error {worst:.3} at {worst_label}"
);
}
}
#[test]
fn threshold_gate_dense_exact_a_sparse_logdet_trace_matches_finite_difference_2500() {
for straddle in [false, true] {
let (term, target, rho) = threshold_gate_tiny_fixture(straddle);
let (loss, cache) = frozen_cache(&term, &target, &rho);
let sparse = rho.sparse_flat_index().expect("sparse coordinate");
let trace = term
.dense_exact_a_logdet_channels(target.view(), &rho, &loss, &cache)
.expect("#2500: the dense exact-A logdet channels must assemble")
.logdet_trace;
assert!(
trace[sparse].abs() > 1.0e-3,
"#2500: the sparse logdet trace must be materially live on this fixture, else \
the FD comparison is a zero-vs-zero gate: {}",
trace[sparse]
);
let log_dets = |r: &SaeManifoldRho| -> (f64, f64) {
let (_l, c) = frozen_cache(&term, &target, r);
term.exact_observed_information_log_dets(r, target.view(), &c)
.expect("production exact-A log dets")
};
let base = rho.to_flat();
let h = 1.0e-5;
let mut plus_flat = base.clone();
plus_flat[sparse] += h;
let mut minus_flat = base.clone();
minus_flat[sparse] -= h;
let (joint_plus, tt_plus) = log_dets(&rho.from_flat(plus_flat.view()).unwrap());
let (joint_minus, tt_minus) = log_dets(&rho.from_flat(minus_flat.view()).unwrap());
let fd = 0.5 * ((joint_plus - joint_minus) - (tt_plus - tt_minus)) / (2.0 * h);
let err = (trace[sparse] - fd).abs();
let tol = 1.0e-6 + 1.0e-5 * fd.abs();
assert!(
err <= tol,
"#2500 (straddle={straddle}): the sparse logdet trace must differentiate the \
ranked value; analytic={:.9e} fd={fd:.9e} err={err:.3e} tol={tol:.3e}",
trace[sparse]
);
}
}
#[test]
fn deflation_map_applies_to_every_row_local_curvature_coordinate_2500() {
let (term, target, rho) = threshold_gate_tiny_fixture(true);
let (_loss, cache) = frozen_cache(&term, &target, &rho);
assert!(
deflated_direction_count(&term, &cache) > 0,
"#2500: this gate needs the deflating stratum"
);
let operators = term
.penalty_curvature_operators_by_flat(&rho, &cache)
.expect("operator map");
let deltas = term
.exact_stationarity_penalty_derivative_delta_by_flat(&rho, &cache)
.expect("delta map");
let base = rho.to_flat();
let h = 1.0e-5;
let total_t = cache.delta_t_len();
let coord = rho.ard_flat_index(0, 0);
let block = operators
.get(&coord)
.expect("#2500: the ARD coordinate must own a curvature operator");
let expected = match deltas.get(&coord) {
Some(delta) => block + delta,
None => block.clone(),
};
let dense_a = |flat: &Array1<f64>| -> Array2<f64> {
let r = rho.from_flat(flat.view()).unwrap();
let (_l, c) = frozen_cache(&term, &target, &r);
term.materialize_exact_hessian_dense(&r, target.view(), &c)
.expect("dense exact A")
};
let mut plus_flat = base.clone();
plus_flat[coord] += h;
let mut minus_flat = base.clone();
minus_flat[coord] -= h;
let a_plus = dense_a(&plus_flat);
let a_minus = dense_a(&minus_flat);
let mut worst = 0.0_f64;
let mut label = String::new();
for i in 0..total_t {
for j in 0..total_t {
let fd = (a_plus[[i, j]] - a_minus[[i, j]]) / (2.0 * h);
let analytic = expected[[i, j]];
let tol = 1.0e-6 + 1.0e-4 * analytic.abs();
let ratio = (fd - analytic).abs() / tol;
if ratio > worst {
worst = ratio;
label = format!("[{i},{j}] analytic={analytic:.9e} fd={fd:.9e}");
}
}
}
assert!(
worst <= 1.0,
"#2500: the ARD curvature operator must equal dA/drho on the t-block of a \
deflating fixture; worst normalized error {worst:.3} at {label}"
);
}
#[test]
fn threshold_gate_outer_solve_is_not_aborted_by_an_unmodelled_sparse_operator_2500() {
use gam_solve::rho_optimizer::OuterProblem;
use gam_solve::seeding::SeedConfig;
let (term, target, rho) = threshold_gate_tiny_fixture(true);
let init_flat = rho.to_flat();
let n_params = init_flat.len();
let mut objective =
SaeManifoldOuterObjective::new(term, target, None, rho, 8, 0.04, 1.0e-6, 1.0e-6);
let result = OuterProblem::new(n_params)
.with_initial_rho(init_flat)
.with_seed_config(SeedConfig {
max_seeds: 1,
seed_budget: 1,
..Default::default()
})
.run(&mut objective, "SAE manifold");
if let Err(err) = &result {
let msg = err.to_string();
for marker in [
"operator this map does not model",
"majorizer operator this channel does not yet model",
"explicit second derivative this channel does not yet model",
] {
assert!(
!msg.contains(marker),
"#2500: a ThresholdGate outer solve must not abort on an unmodelled sparse \
log-strength operator ({marker}); got: {msg}"
);
}
}
}
#[test]
fn dense_theta_adjoint_is_not_interchangeable_for_a_threshold_gate_2500() {
for straddle in [false, true] {
let (term, target, rho) = threshold_gate_tiny_fixture(straddle);
let (_loss, cache) = frozen_cache(&term, &target, &rho);
let solver = crate::manifold::arrow_solver::DeflatedArrowSolver::plain(&cache);
let production = term
.logdet_theta_adjoint(&rho, &cache, &solver)
.expect("production theta adjoint");
let g = term
.materialize_joint_inverse(&cache, &solver)
.expect("joint inverse");
let dense = term
.logdet_theta_adjoint_dense(
&rho,
&cache,
&g,
ThetaAdjointDhChannel::All,
false,
false,
None,
)
.expect("dense theta adjoint");
let worst = production
.t
.iter()
.zip(dense.t.iter())
.filter(|(p, _)| p.abs() > 1.0e-3)
.map(|(p, d)| (p - d).abs() / p.abs())
.fold(0.0_f64, f64::max);
assert!(
worst > 1.0,
"#2500 (straddle={straddle}): refusing the exact-A override for this family is \
only justified if the two θ-adjoints genuinely disagree; worst per-entry \
relative gap = {worst:.3}"
);
}
}
#[test]
fn threshold_gate_coordinate_block_theta_adjoint_matches_finite_difference_2500() {
for straddle in [false, true] {
let (term, target, rho) = threshold_gate_tiny_fixture(straddle);
let (_loss, cache) = frozen_cache(&term, &target, &rho);
let coord = term
.coordinate_block_logdet_theta_adjoint(
&rho,
&cache,
crate::manifold::EvidenceOperator::Majorizer,
None,
)
.expect("coordinate-block theta adjoint");
let base_deflated = deflated_direction_count(&term, &cache);
let block_logdet = |t: &SaeManifoldTerm| -> (f64, usize) {
let (_l, c) = frozen_cache(t, &target, &rho);
let mut acc = 0.0_f64;
for row in 0..t.n_obs() {
let factor = c.undamped_factor(row);
for d in 0..c.row_dims[row] {
acc += 2.0 * factor[[d, d]].ln();
}
}
(acc, deflated_direction_count(t, &c))
};
let h = 1.0e-6;
let mut worst = 0.0_f64;
let mut label = String::new();
let mut checked = 0usize;
for (row, atom, slot) in logit_slots(&term, &cache) {
let mut plus = term.clone();
plus.assignment.logits[[row, atom]] += h;
let mut minus = term.clone();
minus.assignment.logits[[row, atom]] -= h;
let (lp, dp) = block_logdet(&plus);
let (lm, dm) = block_logdet(&minus);
if dp != base_deflated || dm != base_deflated {
continue;
}
let fd = (lp - lm) / (2.0 * h);
let analytic = coord.t[slot];
let tol = 1.0e-6 + 1.0e-4 * analytic.abs().max(fd.abs());
let ratio = (analytic - fd).abs() / tol;
if ratio > worst {
worst = ratio;
label = format!(
"row {row} atom {atom}: analytic={analytic:.9e} fd={fd:.9e} tol={tol:.3e}"
);
}
checked += 1;
}
assert!(
checked >= 4,
"#2500 (straddle={straddle}): the FD gate must reach at least four logits on a \
fixed deflation stratum; checked={checked}"
);
assert!(
worst <= 1.0,
"#2500 (straddle={straddle}): the coordinate-block theta-adjoint must \
differentiate the per-row log|H_tt| it is defined as; worst normalized error \
{worst:.3} at {label}"
);
}
}
#[test]
fn dense_exact_a_matches_finite_difference_of_the_kkt_gradient_2330() {
let (term, target, rho) = threshold_gate_tiny_fixture(false);
let (_loss, cache) = frozen_cache(&term, &target, &rho);
assert_eq!(
deflated_direction_count(&term, &cache),
0,
"#2330 FD gate: the below-threshold arm must be deflation-free, or the +/-h \
endpoints can straddle a discrete deflation change and the central \
difference stops being a difference of one smooth branch"
);
let a = term
.materialize_exact_hessian_dense(&rho, target.view(), &cache)
.expect("dense exact A at the frozen fixture mode");
let total_t = cache.delta_t_len();
let k = cache.k;
let dim = total_t + k;
assert_eq!(
a.nrows(),
dim,
"#2330 FD gate: dense A must be (total_t + k) square before it can be compared \
against the (gt, gb) gradient layout"
);
let gradient = |t: &mut SaeManifoldTerm| -> Array1<f64> {
let sys = t
.assemble_arrow_schur(target.view(), &rho, None)
.expect("arrow-Schur assembly at the finite-difference endpoint");
let mut g = Array1::<f64>::zeros(dim);
let mut offset = 0usize;
for row in &sys.rows {
for (axis, &value) in row.gt.iter().enumerate() {
g[offset + axis] = value;
}
offset += row.gt.len();
}
assert_eq!(
offset, total_t,
"#2330 FD gate: concatenated per-row gt width must equal cache.delta_t_len(), \
or the gradient and A are not in the same coordinates"
);
assert_eq!(
sys.gb.len(),
k,
"#2330 FD gate: border gradient width must equal cache.k"
);
for (axis, &value) in sys.gb.iter().enumerate() {
g[total_t + axis] = value;
}
g
};
let h = 1.0e-5_f64;
for (label, weight_t, weight_beta) in [
("coordinate-weighted", 1.0_f64, 0.25_f64),
("border-weighted", 0.25_f64, 1.0_f64),
] {
let mut v_t = Array1::<f64>::zeros(total_t);
let mut v_beta = Array1::<f64>::zeros(k);
let mut v = Array1::<f64>::zeros(dim);
for idx in 0..dim {
let phase = 0.7 + 0.31 * (idx as f64);
let raw = phase.sin() + 0.5 * (2.0 * phase).cos();
let value = raw * if idx < total_t { weight_t } else { weight_beta };
v[idx] = value;
}
let norm = v.dot(&v).sqrt();
assert!(
norm.is_finite() && norm > 0.0,
"#2330 FD gate ({label}): probe direction must be finite and nonzero"
);
v.mapv_inplace(|value| value / norm);
for idx in 0..total_t {
v_t[idx] = v[idx];
}
for idx in 0..k {
v_beta[idx] = v[total_t + idx];
}
let analytic = a.dot(&v);
let endpoint = |sign: f64| -> Array1<f64> {
let mut moved = term.clone();
let signed_t = v_t.mapv(|value| sign * value);
let signed_beta = v_beta.mapv(|value| sign * value);
moved
.apply_newton_step(signed_t.view(), signed_beta.view(), h)
.expect("finite-difference endpoint step");
gradient(&mut moved)
};
let g_plus = endpoint(1.0);
let g_minus = endpoint(-1.0);
let fd = (&g_plus - &g_minus).mapv(|delta| delta / (2.0 * h));
let analytic_norm = analytic.dot(&analytic).sqrt();
let fd_norm = fd.dot(&fd).sqrt();
assert!(
analytic_norm > 1.0e-6,
"#2330 FD gate ({label}): |A.v|={analytic_norm:.6e} is too small for this \
comparison to be a gate rather than a zero-vs-zero tautology"
);
let mut worst = 0.0_f64;
let mut worst_idx = 0usize;
for idx in 0..dim {
let scale = analytic[idx].abs().max(fd[idx].abs()).max(1.0e-8);
let relative = (analytic[idx] - fd[idx]).abs() / scale;
if relative > worst {
worst = relative;
worst_idx = idx;
}
}
let directional = analytic.dot(&v);
let directional_fd = fd.dot(&v);
assert!(
worst <= 1.0e-4,
"#2330: the dense exact A is NOT the second derivative of the penalized \
objective whose gradient the inner solve drives to zero. Worst component \
relative error {worst:.6e} at index {worst_idx} (A.v={:.9e}, FD={:.9e}); \
|A.v|={analytic_norm:.6e} |FD|={fd_norm:.6e}; v'Av={directional:.9e} vs \
v'(dg/dtheta)v={directional_fd:.9e} (ratio {:.6e}). If this fires, the \
IndefiniteObservedInformation refusal is measuring the wrong operator, the \
converged-but-indefinite verdict is an artefact of the refusal site rather \
than a saddle upstream, and the negative-curvature escape is the WRONG fix. \
Probe: {label}, h={h:.3e}",
analytic[worst_idx],
fd[worst_idx],
directional_fd / directional,
);
}
}