use super::*;
use gam_terms::analytic_penalties::{AnalyticPenalty, SoftmaxAssignmentSparsityPenalty};
use ndarray::Array1;
fn probabilities(logits: &[f64], inv_tau: f64) -> Vec<f64> {
let maximum = logits.iter().copied().fold(f64::NEG_INFINITY, f64::max);
let mut values: Vec<f64> = logits
.iter()
.map(|value| ((value - maximum) * inv_tau).exp())
.collect();
let total: f64 = values.iter().sum();
for value in &mut values {
*value /= total;
}
values
}
#[test]
fn active_softmax_dense_entropy_hessian_entry_matches_dense_block_1410() {
let k = 48;
let temperature = 1.3_f64;
let scale = 0.9_f64;
let penalty = SoftmaxAssignmentSparsityPenalty::new(k, temperature);
let rho = Array1::from_elem(1, (scale * temperature * temperature).ln());
let mut largest_reference = 0.0_f64;
let mut largest_error = 0.0_f64;
for fixture in 0..3 {
let logits = Array1::from_shape_fn(k, |axis| match fixture {
0 => (0.17 * axis as f64).sin(),
1 => 0.01,
_ if axis + 1 == k => -800.0 * temperature,
_ => 3.0 * (0.31 * axis as f64).sin(),
});
let a = probabilities(logits.as_slice().unwrap(), 1.0 / temperature);
let mean = softmax_majorizer_log_mean(&a);
for column in 0..k {
let mut direction = Array1::zeros(k);
direction[column] = 1.0;
let reference = penalty.hvp(logits.view(), rho.view(), direction.view());
for atom in 0..k {
let actual = softmax_dense_entropy_hessian_entry(&a, atom, column, mean, scale);
let error = (actual - reference[atom]).abs();
assert!(actual.is_finite() && reference[atom].is_finite());
assert!(
error <= 2e-13 * (1.0 + reference[atom].abs()),
"fixture={fixture} atom={atom} column={column}: leaf={actual} HVP={}",
reference[atom]
);
largest_reference = largest_reference.max(reference[atom].abs());
largest_error = largest_error.max(error);
}
}
}
assert!(
largest_reference > 1e-3,
"a zero Hessian cannot satisfy the oracle"
);
eprintln!(
"#1410 active Hessian versus HVP: max_error={largest_error:.6e} max_reference={largest_reference:.6e}"
);
}
#[test]
fn active_softmax_majorizer_logit_derivative_matches_dense_1410() {
let k = 40;
let temperature = 0.7_f64;
let scale = 1.1_f64;
let penalty = SoftmaxAssignmentSparsityPenalty::new(k, temperature);
let mut largest_reference = 0.0_f64;
let mut largest_error = 0.0_f64;
for fixture in 0..3 {
let logits: Vec<f64> = (0..k)
.map(|axis| match fixture {
0 => (0.23 * axis as f64).sin(),
1 => 0.02,
_ if axis + 1 == k => -800.0 * temperature,
_ => 2.0 * (0.37 * axis as f64).sin(),
})
.collect();
let a = probabilities(&logits, 1.0 / temperature);
let mean = softmax_majorizer_log_mean(&a);
for w in [0, k / 2, k - 1] {
let mut plus = logits.clone();
let mut minus = logits.clone();
plus[w] += 1e-6;
minus[w] -= 1e-6;
let dp = penalty.row_psd_majorizer(&plus, scale);
let dm = penalty.row_psd_majorizer(&minus, scale);
for atom in 0..k {
let actual = active_softmax_majorizer_logit_derivative_entry(
&a,
atom,
w,
mean,
scale,
1.0 / temperature,
);
let reference = (dp[[atom, atom]] - dm[[atom, atom]]) / (plus[w] - minus[w]);
assert!(actual.is_finite() && reference.is_finite());
let error = (actual - reference).abs();
assert!(
error <= 1e-6 * (1.0 + reference.abs()),
"fixture={fixture} atom={atom} logit={w}: leaf={actual} public FD={reference}"
);
largest_reference = largest_reference.max(reference.abs());
largest_error = largest_error.max(error);
}
}
}
assert!(
largest_reference > 1e-3,
"a zero derivative cannot satisfy the oracle"
);
eprintln!(
"#1410 active adjoint versus public radius FD: max_error={largest_error:.6e} max_reference={largest_reference:.6e}"
);
}
fn isolated_crossing() -> ([f64; 3], f64, f64) {
let entry = |z0: f64| {
let a = probabilities(&[z0, 0.0, -0.7], 1.0);
softmax_dense_entropy_hessian_entry(&a, 0, 1, softmax_majorizer_log_mean(&a), 1.3)
};
let (mut lo, mut hi) = (-8.0_f64, 0.0_f64);
assert!(entry(lo) < 0.0 && entry(hi) > 0.0);
loop {
let mid = lo + 0.5 * (hi - lo);
if mid <= lo || mid >= hi {
break;
}
let value = entry(mid);
if value == 0.0 {
lo = mid;
hi = mid;
break;
} else if value < 0.0 {
lo = mid;
} else {
hi = mid;
}
}
let row = [lo + 0.5 * (hi - lo), 0.0, -0.7];
let a = probabilities(&row, 1.0);
let mean = softmax_majorizer_log_mean(&a);
let norm = (0..3)
.map(|j| softmax_dense_entropy_hessian_entry(&a, 0, j, mean, 1.3).powi(2))
.sum::<f64>()
.sqrt();
let step = f64::EPSILON.cbrt() * (1.0 + row[0].abs());
let slope = ((entry(row[0] + step) - entry(row[0] - step)) / (2.0 * step)).abs();
let epsilon = SoftmaxAssignmentSparsityPenalty::soft_abs_temperature(3) * norm;
let band = epsilon / slope;
assert!(norm > 1e-4 && slope > 1e-4 && band > 1e-13 && band.is_finite());
(row, band, slope)
}
#[test]
fn gershgorin_majorizer_logit_derivative_matches_fd_1419() {
let logits = [0.3_f64, -0.6, 0.9, 0.2];
let inv_tau = 1.0 / 0.8;
let scale = 1.1 * inv_tau * inv_tau;
let a = probabilities(&logits, inv_tau);
let mean = softmax_majorizer_log_mean(&a);
let step = 1e-6;
let mut largest_reference = 0.0_f64;
let mut largest_error = 0.0_f64;
for w in 0..4 {
let mut plus = logits;
let mut minus = logits;
plus[w] += step;
minus[w] -= step;
let ap = probabilities(&plus, inv_tau);
let am = probabilities(&minus, inv_tau);
let mp = softmax_majorizer_log_mean(&ap);
let mm = softmax_majorizer_log_mean(&am);
for kk in 0..4 {
let actual =
active_softmax_majorizer_logit_derivative_entry(&a, kk, w, mean, scale, inv_tau);
let reference = (active_softmax_gershgorin_majorizer_entry(&ap, kk, mp, scale)
- active_softmax_gershgorin_majorizer_entry(&am, kk, mm, scale))
/ (plus[w] - minus[w]);
largest_reference = largest_reference.max(reference.abs());
largest_error = largest_error.max((actual - reference).abs());
assert!(
(actual - reference).abs() < 1e-6,
"atom={kk} logit={w}: analytic={actual} FD={reference}"
);
}
}
assert!(
largest_reference > 1e-3,
"a zero derivative must fail this oracle"
);
eprintln!(
"#1419 adjoint FD max_error={largest_error:.6e} max_reference={largest_reference:.6e}"
);
}
#[test]
fn smooth_gershgorin_adjoint_is_continuous_across_a_zero_crossing_2339() {
let (row, band, slope) = isolated_crossing();
let adjoint = |logits: &[f64; 3]| {
let a = probabilities(logits, 1.0);
active_softmax_majorizer_logit_derivative_entry(
&a,
0,
0,
softmax_majorizer_log_mean(&a),
1.3,
1.0,
)
};
let hard_adjoint = |logits: &[f64; 3]| {
let a = probabilities(logits, 1.0);
let mean = softmax_majorizer_log_mean(&a);
let step = f64::EPSILON.cbrt() * (1.0 + logits[0].abs());
let mut plus = *logits;
let mut minus = *logits;
plus[0] += step;
minus[0] -= step;
let ap = probabilities(&plus, 1.0);
let am = probabilities(&minus, 1.0);
let mp = softmax_majorizer_log_mean(&ap);
let mm = softmax_majorizer_log_mean(&am);
(0..3)
.map(|j| {
let value = softmax_dense_entropy_hessian_entry(&a, 0, j, mean, 1.3);
let derivative = (softmax_dense_entropy_hessian_entry(&ap, 0, j, mp, 1.3)
- softmax_dense_entropy_hessian_entry(&am, 0, j, mm, 1.3))
/ (plus[0] - minus[0]);
if value == 0.0 {
0.0
} else {
value.signum() * derivative
}
})
.sum::<f64>()
};
let mut smooth_jumps = Vec::new();
for divisor in [100.0, 1000.0] {
let mut plus = row;
let mut minus = row;
plus[0] += band / divisor;
minus[0] -= band / divisor;
assert!(plus[0] > minus[0], "the seam probe must survive rounding");
let smooth = (adjoint(&plus) - adjoint(&minus)).abs();
let hard = (hard_adjoint(&plus) - hard_adjoint(&minus)).abs();
assert!(
hard >= slope,
"counterfactual hard radius must retain its jump"
);
assert!(smooth <= 0.05 * hard, "smooth={smooth} hard={hard}");
smooth_jumps.push(smooth);
eprintln!(
"#2339 crossing divisor={divisor} smooth_jump={smooth:.6e} hard_jump={hard:.6e} band={band:.6e}"
);
}
assert!(smooth_jumps[1] <= 0.25 * smooth_jumps[0]);
}
#[test]
fn smooth_gershgorin_adjoint_matches_fd_inside_the_smoothing_band_2339() {
let (row, band, slope) = isolated_crossing();
let a = probabilities(&row, 1.0);
let mean = softmax_majorizer_log_mean(&a);
let step = band / 100.0;
let mut largest_error = 0.0_f64;
assert!(step > 1e-15 && slope > 1e-4);
for w in 0..3 {
let mut plus = row;
let mut minus = row;
plus[w] += step;
minus[w] -= step;
assert!(plus[w] > minus[w]);
let ap = probabilities(&plus, 1.0);
let am = probabilities(&minus, 1.0);
let mp = softmax_majorizer_log_mean(&ap);
let mm = softmax_majorizer_log_mean(&am);
for kk in 0..3 {
let analytic =
active_softmax_majorizer_logit_derivative_entry(&a, kk, w, mean, 1.3, 1.0);
let reference = (active_softmax_gershgorin_majorizer_entry(&ap, kk, mp, 1.3)
- active_softmax_gershgorin_majorizer_entry(&am, kk, mm, 1.3))
/ (plus[w] - minus[w]);
largest_error = largest_error.max((analytic - reference).abs());
assert!(
(analytic - reference).abs() < 1e-3,
"atom={kk} logit={w}: analytic={analytic} FD={reference}"
);
}
}
eprintln!(
"#2339 in-band FD max_error={largest_error:.6e} step={step:.6e} crossing_slope={slope:.6e}"
);
}
#[test]
fn smooth_gershgorin_adjoint_is_degree_one_homogeneous_in_scale_2339() {
let a = probabilities(&[0.3, -0.6, 0.9, 0.2, -1.4], 1.0 / 0.9);
let mean = softmax_majorizer_log_mean(&a);
let mut largest = 0.0_f64;
for kk in 0..5 {
for w in 0..5 {
let base =
active_softmax_majorizer_logit_derivative_entry(&a, kk, w, mean, 0.625, 1.0 / 0.9);
largest = largest.max(base.abs());
for factor in [0.5, 2.0, 4.0] {
let scaled = active_softmax_majorizer_logit_derivative_entry(
&a,
kk,
w,
mean,
factor * 0.625,
1.0 / 0.9,
);
assert_eq!(scaled, factor * base, "atom={kk} logit={w} factor={factor}");
}
}
}
assert!(largest > 1e-3);
}
#[test]
fn smooth_gershgorin_adjoint_is_exactly_zero_on_an_underflowed_atom_2339() {
let a = probabilities(&[0.0, -800.0, -1.0], 1.0);
let mean = softmax_majorizer_log_mean(&a);
assert_eq!(a[1], 0.0);
assert!(active_softmax_gershgorin_majorizer_entry(&a, 0, mean, 2.0) > 0.01);
let mut largest_live = 0.0_f64;
for w in 0..3 {
assert_eq!(
active_softmax_majorizer_logit_derivative_entry(&a, 1, w, mean, 2.0, 1.0),
0.0
);
for kk in [0, 2] {
let value = active_softmax_majorizer_logit_derivative_entry(&a, kk, w, mean, 2.0, 1.0);
assert!(value.is_finite());
largest_live = largest_live.max(value.abs());
}
}
assert!(
largest_live > 1e-3,
"live atoms must exercise a nonzero derivative"
);
}