use ndarray::{Array1, Array2};
pub fn equilibrate_gram(g: &Array2<f64>) -> (Array2<f64>, Array1<f64>) {
let p = g.nrows();
let scale: Array1<f64> = Array1::from_shape_fn(p, |j| {
let d = g[[j, j]];
if d > 0.0 { d.sqrt() } else { 1.0 }
});
let mut c = Array2::<f64>::zeros((p, p));
for i in 0..p {
for j in 0..p {
c[[i, j]] = g[[i, j]] / (scale[i] * scale[j]);
}
}
(c, scale)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RankDecision {
Certified {
rank: usize,
sigma_r: f64,
sigma_next: f64,
spectrum_len: usize,
margin_low: f64,
margin_high: f64,
tol: f64,
gap: f64,
},
Ambiguous {
rank_floor: usize,
rank_ceil: usize,
sigma_in_band: f64,
tol: f64,
gap: f64,
},
}
pub fn certified_rank(singular_values: &[f64], tol: f64, gap: f64) -> RankDecision {
let mut sv: Vec<f64> = singular_values.to_vec();
sv.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
let n = sv.len();
let high = tol * (1.0 + gap);
let low = tol / (1.0 + gap);
if let Some(&sigma_in_band) = sv.iter().find(|&&s| s > low && s < high) {
let rank_floor = sv.iter().filter(|&&s| s >= high).count();
let rank_ceil = sv.iter().filter(|&&s| s > low).count();
return RankDecision::Ambiguous {
rank_floor,
rank_ceil,
sigma_in_band,
tol,
gap,
};
}
let rank = sv.iter().filter(|&&s| s >= high).count();
let sigma_r = if rank == 0 {
f64::INFINITY
} else {
sv[rank - 1]
};
let sigma_next = if rank < n { sv[rank] } else { 0.0 };
let margin_high = if rank == 0 {
f64::INFINITY
} else {
sigma_r / high
};
let margin_low = if sigma_next == 0.0 {
f64::INFINITY
} else {
low / sigma_next
};
RankDecision::Certified {
rank,
sigma_r,
sigma_next,
spectrum_len: n,
margin_low,
margin_high,
tol,
gap,
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RankTransport {
Transported {
rank: usize,
radius: f64,
excursion: f64,
slack: f64,
},
GapExhausted {
rank: usize,
radius: f64,
excursion: f64,
},
NoCertificate,
}
pub fn rank_transport_radius(decision: &RankDecision) -> Option<f64> {
match *decision {
RankDecision::Certified {
rank,
sigma_r,
sigma_next,
spectrum_len,
tol,
gap,
..
} => {
let high = tol * (1.0 + gap);
let low = tol / (1.0 + gap);
let kept_slack = if sigma_r.is_finite() {
sigma_r - high
} else {
f64::INFINITY
};
let dropped_slack = if spectrum_len > 0 && rank == spectrum_len {
f64::INFINITY
} else {
low - sigma_next
};
let radius = kept_slack.min(dropped_slack);
(radius.is_finite() && radius >= 0.0).then_some(radius)
}
RankDecision::Ambiguous { .. } => None,
}
}
pub fn transport_certified_rank(decision: &RankDecision, excursion: f64) -> RankTransport {
let Some(radius) = rank_transport_radius(decision) else {
return RankTransport::NoCertificate;
};
if !(excursion.is_finite() && excursion >= 0.0) {
return RankTransport::NoCertificate;
}
let RankDecision::Certified { rank, .. } = *decision else {
return RankTransport::NoCertificate;
};
if excursion <= radius {
RankTransport::Transported {
rank,
radius,
excursion,
slack: radius - excursion,
}
} else {
RankTransport::GapExhausted {
rank,
radius,
excursion,
}
}
}
pub fn spectral_excursion_lower_bound(reference: &[f64], current: &[f64]) -> f64 {
let mut a: Vec<f64> = reference.to_vec();
let mut b: Vec<f64> = current.to_vec();
a.sort_by(|x, y| y.partial_cmp(x).unwrap_or(std::cmp::Ordering::Equal));
b.sort_by(|x, y| y.partial_cmp(x).unwrap_or(std::cmp::Ordering::Equal));
let n = a.len().max(b.len());
let mut worst = 0.0_f64;
for i in 0..n {
let sa = a.get(i).copied().unwrap_or(0.0);
let sb = b.get(i).copied().unwrap_or(0.0);
worst = worst.max((sa - sb).abs());
}
worst
}
pub fn projector_error_bar(gap: f64, backward_error: f64) -> f64 {
if !(gap.is_finite() && backward_error.is_finite()) {
return f64::INFINITY;
}
let separation = gap - backward_error;
if !(separation > 0.0) {
return f64::INFINITY;
}
backward_error / separation
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DecrementEnclosure {
pub lower: f64,
pub upper: f64,
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct ShadowSum {
pub sum: f64,
pub abs_sum: f64,
pub count: usize,
}
impl ShadowSum {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, x: f64) {
self.sum += x;
self.abs_sum += x.abs();
self.count += 1;
}
pub fn merge(&mut self, other: &ShadowSum) {
self.sum += other.sum;
self.abs_sum += other.abs_sum;
self.count += other.count;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::faer_ndarray::{FaerEigh, fast_ata};
use faer::Side;
use ndarray::{Array1, Array2};
const U: f64 = f64::EPSILON / 2.0;
fn eigenvalues(m: &Array2<f64>) -> Vec<f64> {
let (evals, _) = m.eigh(Side::Lower).expect("eigh");
evals.to_vec()
}
#[test]
fn projector_error_bar_dominates_the_closed_form_rotation_and_stays_tight_2448() {
let gap = 1.0_f64;
let (a, b) = (2.0_f64, 2.0 - gap);
let rotation = |e: f64| -> f64 {
let leading = |off: f64| -> Array2<f64> {
let m = ndarray::arr2(&[[a, off], [off, b]]);
let (evals, evecs) = m.eigh(Side::Lower).expect("2x2 eigh");
let top = evals
.iter()
.enumerate()
.max_by(|(_, x), (_, y)| x.total_cmp(y))
.map(|(i, _)| i)
.expect("non-empty spectrum");
let u = evecs.column(top);
let mut p = Array2::<f64>::zeros((2, 2));
for i in 0..2 {
for j in 0..2 {
p[[i, j]] = u[i] * u[j];
}
}
p
};
let diff = &leading(e) - &leading(0.0);
let dsym = 0.5 * (&diff + &diff.t());
let (evals, _) = dsym.eigh(Side::Lower).expect("difference eigh");
evals.iter().fold(0.0_f64, |acc, &v| acc.max(v.abs()))
};
for &e in &[1e-12_f64, 1e-8, 1e-4, 1e-2, 0.1, 0.4, 0.9] {
let measured = rotation(e);
let exact = (0.5 * (2.0 * e / gap).atan()).sin();
assert!(
(measured - exact).abs() <= 1e-12 + 1e-9 * exact,
"the 2×2 oracle disagrees with the measured projector distance at \
e={e}: measured={measured}, closed form={exact} — the oracle, not \
the bound, is what is wrong here"
);
let bar = projector_error_bar(gap, e);
assert!(
bar >= measured,
"the bar must DOMINATE the true rotation at e={e} (gap={gap}): \
bar={bar}, measured={measured}"
);
if e <= 1e-2 {
assert!(
bar <= 1.05 * measured.max(f64::MIN_POSITIVE),
"the bar must stay tight for e ≪ δ, else it would refuse \
decidable subspace claims: e={e}, bar={bar}, measured={measured}"
);
}
}
for &(g, e) in &[(1.0_f64, 1.0_f64), (1.0, 2.0), (0.0, 1e-30), (-1.0, 1e-30)] {
assert!(
projector_error_bar(g, e).is_infinite(),
"gap={g} with ‖E‖={e} determines no subspace; the bar must refuse"
);
}
assert!(projector_error_bar(f64::NAN, 1.0).is_infinite());
assert!(projector_error_bar(1.0, f64::NAN).is_infinite());
assert!(projector_error_bar(f64::INFINITY, f64::INFINITY).is_infinite());
let lambda_max = 1.0_f64;
let spectrum = [lambda_max, 1.3e-2, 4.4e-4, 5.1e-6, 3.6e-8, 1.2e-10, 3.0e-13];
let rank_tol = 1.0e-10 * lambda_max;
let backward_error = 8.0 * (spectrum.len() as f64) * f64::EPSILON * lambda_max;
let decision = certified_rank(&spectrum, rank_tol, backward_error / rank_tol);
let RankDecision::Certified { rank, .. } = decision else {
panic!("the integer is decidable here: {decision:?}");
};
assert_eq!(rank, 6, "six eigenvalues clear the cutoff with margin");
let bar = projector_error_bar(spectrum[rank - 1] - spectrum[rank], backward_error);
assert!(
bar > 1e-6,
"the rank-6 eigenspace of a smoothly decaying spectrum is NOT resolved \
even though the rank is certified; got bar={bar:e}, which would mean \
the two currencies had collapsed into one"
);
}
#[test]
fn equilibration_certifies_full_rank_where_raw_gram_would_kill_eleven_columns() {
let n = 50usize;
let p = 12usize;
let stiff = 2.4e6_f64;
let mut x = Array2::<f64>::zeros((n, p));
for j in 0..p {
x[[j, j]] = if j == 0 { stiff } else { 1.0 };
}
let g = fast_ata(&x);
let raw_evals = eigenvalues(&g);
let raw_lambda_max = raw_evals.iter().cloned().fold(0.0_f64, f64::max);
let raw_tol = raw_lambda_max * 64.0 * (n as f64) * f64::EPSILON;
let raw_rank = raw_evals.iter().filter(|&&e| e > raw_tol).count();
assert_eq!(raw_rank, 1, "raw size-scaled cutoff must kill 11 columns");
let (g_eq, _) = equilibrate_gram(&g);
let eq_evals = eigenvalues(&g_eq);
for &e in &eq_evals {
assert!((e - 1.0).abs() < 1e-9, "equilibrated spectrum must be ~1");
}
let eq_lambda_max = eq_evals.iter().cloned().fold(0.0_f64, f64::max);
let nk = (n.max(p)) as f64;
let eq_tol = eq_lambda_max * 64.0 * nk * f64::EPSILON;
match certified_rank(&eq_evals, eq_tol, 1.0) {
RankDecision::Certified {
rank, margin_high, ..
} => {
assert_eq!(rank, 12, "equilibrated Gram is full rank");
assert!(
margin_high > 1e10,
"kept side must clear the band by a huge factor, got {margin_high}"
);
}
other => panic!("expected Certified full rank, got {other:?}"),
}
}
#[test]
fn a_full_rank_certificate_transports_on_its_kept_side_alone() {
let full = certified_rank(&[10.0_f64, 9.0], 1.0, 1.0);
let RankDecision::Certified {
rank, sigma_next, ..
} = full
else {
panic!("expected a Certified reference, got {full:?}");
};
assert_eq!(rank, 2, "both values clear high = 2");
assert_eq!(sigma_next, 0.0, "the dropped slot holds the sentinel");
let radius = rank_transport_radius(&full).expect("certified ⇒ a radius");
assert!(
(radius - 7.0).abs() < 1e-12,
"ε* is the kept slack σ_n − high = 9 − 2 = 7; reading the sentinel as a dropped value would cap it at low = 0.5. got {radius}"
);
match certified_rank(&[10.0 - radius, 9.0 - radius], 1.0, 1.0) {
RankDecision::Certified { rank: moved, .. } => assert_eq!(
moved, 2,
"a perturbation of exactly ε* must not move the certified rank"
),
other => panic!("expected the rank to transport at ε*, got {other:?}"),
}
let past = radius + 1e-9;
assert!(
matches!(
certified_rank(&[10.0 - past, 9.0 - past], 1.0, 1.0),
RankDecision::Ambiguous { .. }
),
"a hair past ε* must push σ_n strictly inside the band"
);
let with_zero = certified_rank(&[10.0_f64, 9.0, 0.0], 1.0, 1.0);
let RankDecision::Certified {
rank, sigma_next, ..
} = with_zero
else {
panic!("expected a Certified reference, got {with_zero:?}");
};
assert_eq!(rank, 2, "the exact zero is dropped");
assert_eq!(
sigma_next, 0.0,
"indistinguishable from the full-rank sentinel BY VALUE"
);
let zero_radius = rank_transport_radius(&with_zero).expect("certified ⇒ a radius");
assert!(
(zero_radius - 0.5).abs() < 1e-12,
"a real zero can be lifted to ε by a perturbation of norm ε and must stay ≤ low, so ε* = 0.5 here; got {zero_radius}"
);
assert!(
matches!(
certified_rank(&[10.0_f64, 9.0, zero_radius + 1e-9], 1.0, 1.0),
RankDecision::Ambiguous { .. }
),
"past ε* the formerly-zero value enters the open band"
);
}
#[test]
fn a_full_rank_epsilon_scaled_certificate_can_transport_at_all() {
let spectrum = [1.0_f64, 0.94, 0.71, 0.55, 0.38];
let rows_plus_penalties = 150.0_f64;
let sigma_max = spectrum[0];
let tol = 100.0 * f64::EPSILON * rows_plus_penalties * sigma_max.max(1.0);
let gap = 1.0_f64;
let decision = certified_rank(&spectrum, tol, gap);
let RankDecision::Certified { rank, .. } = decision else {
panic!("an O(1) spectrum at a roundoff cutoff is decidable: {decision:?}");
};
assert_eq!(rank, spectrum.len(), "every value clears a roundoff cutoff");
let radius = rank_transport_radius(&decision).expect("certified ⇒ a radius");
let sentinel_reading = tol / (1.0 + gap);
assert!(
radius > 0.37,
"ε* must be the kept slack σ_n − high ≈ 0.38, an O(1) margin; got {radius:e}"
);
assert!(
radius > 1.0e10 * sentinel_reading,
"the sentinel reading is the roundoff-scale {sentinel_reading:e}; the true \
margin is {radius:e}, more than ten orders larger. If these were within \
ten orders of each other the defect this pins would not be reachable."
);
let realistic_excursion = 1.0e-3_f64;
assert!(
matches!(
transport_certified_rank(&decision, realistic_excursion),
RankTransport::Transported { .. }
),
"a 1e-3 excursion sits well inside an O(1) margin and must transport"
);
assert!(
realistic_excursion > sentinel_reading,
"under the sentinel reading the same excursion exhausts the radius, which \
is why the transported branch was unreachable at full rank"
);
}
#[test]
fn transport_radius_is_the_sharp_threshold_of_a_certified_rank() {
let sv = [10.0_f64, 3.0, 0.1, 0.05];
let reference = certified_rank(&sv, 1.0, 1.0);
let RankDecision::Certified { rank, .. } = reference else {
panic!("expected a Certified reference, got {reference:?}");
};
assert_eq!(rank, 2);
let radius = rank_transport_radius(&reference).expect("certified ⇒ a radius");
assert!(
(radius - 0.4).abs() < 1e-12,
"ε* = min(σ_r − high, low − σ_next) = min(1, 0.4); got {radius}"
);
let inside = [10.0 + radius, 3.0 - radius, 0.1 + radius, 0.05 + radius];
match certified_rank(&inside, 1.0, 1.0) {
RankDecision::Certified { rank: moved, .. } => assert_eq!(
moved, rank,
"a perturbation of exactly ε* must not move the certified rank"
),
other => panic!("expected the rank to transport at ε*, got {other:?}"),
}
let outside = [10.0, 3.0, 0.1 + radius + 1e-9, 0.05];
assert!(
matches!(
certified_rank(&outside, 1.0, 1.0),
RankDecision::Ambiguous { .. }
),
"ε* must be sharp: a perturbation past it breaks the certificate"
);
}
#[test]
fn transport_verdict_prices_the_excursion_against_the_radius() {
let sv = [10.0_f64, 3.0, 0.1, 0.05];
let reference = certified_rank(&sv, 1.0, 1.0);
let radius = rank_transport_radius(&reference).expect("certified ⇒ a radius");
match transport_certified_rank(&reference, 0.25) {
RankTransport::Transported {
rank,
slack,
radius: r,
..
} => {
assert_eq!(rank, 2);
assert!((r - radius).abs() < 1e-12);
assert!(
(slack - (radius - 0.25)).abs() < 1e-12,
"slack must be the unspent radius, got {slack}"
);
}
other => panic!("0.25 < ε* must transport, got {other:?}"),
}
match transport_certified_rank(&reference, radius * 2.0) {
RankTransport::GapExhausted { rank, .. } => assert_eq!(
rank, 2,
"GapExhausted still names the rank it can no longer imply"
),
other => panic!("an excursion past ε* must exhaust the gap, got {other:?}"),
}
assert_eq!(
transport_certified_rank(&reference, f64::NAN),
RankTransport::NoCertificate,
"an unbounded excursion certifies nothing"
);
}
#[test]
fn ambiguous_reference_has_no_transport_certificate() {
let ambiguous = certified_rank(&[10.0_f64, 3.0, 1.0, 0.2], 1.0, 1.0);
assert!(matches!(ambiguous, RankDecision::Ambiguous { .. }));
assert_eq!(rank_transport_radius(&ambiguous), None);
assert_eq!(
transport_certified_rank(&ambiguous, 0.0),
RankTransport::NoCertificate
);
}
#[test]
fn certified_rank_transports_along_a_lipschitz_operator_path() {
let mut a0 = Array2::<f64>::zeros((4, 4));
for (i, &s) in [6.0_f64, 5.0, 0.02, 0.01].iter().enumerate() {
a0[[i, i]] = s;
}
let reference = certified_rank(&eigenvalues(&a0), 1.0, 1.0);
let RankDecision::Certified { rank, .. } = reference else {
panic!("expected a Certified reference, got {reference:?}");
};
assert_eq!(rank, 2);
let radius = rank_transport_radius(&reference).expect("certified ⇒ a radius");
let v = Array1::from(vec![0.5_f64, -0.5, 0.5, -0.5]);
let w = Array1::from(vec![0.5_f64, 0.5, -0.5, -0.5]);
let mut e = Array2::<f64>::zeros((4, 4));
for i in 0..4 {
for j in 0..4 {
e[[i, j]] = v[i] * v[j] - w[i] * w[j];
}
}
let e_norm = eigenvalues(&e)
.into_iter()
.fold(0.0_f64, |acc, l| acc.max(l.abs()));
assert!(e_norm > 0.0);
e.mapv_inplace(|x| x / e_norm);
for step in 0..=8usize {
let s = radius * (step as f64) / 8.0;
let a_s = &a0 + &(e.clone() * s);
let sv: Vec<f64> = eigenvalues(&a_s).into_iter().map(f64::abs).collect();
match certified_rank(&sv, 1.0, 1.0) {
RankDecision::Certified { rank: moved, .. } => assert_eq!(
moved, rank,
"path sample s={s} inside ε*={radius} must keep rank {rank}"
),
other => panic!("path sample s={s} inside ε*={radius} lost its rank: {other:?}"),
}
assert!(
matches!(
transport_certified_rank(&reference, s),
RankTransport::Transported { .. }
),
"the transport verdict must agree with the realized path at s={s}"
);
}
}
#[test]
fn spectral_excursion_is_a_weyl_lower_bound_on_the_operator_norm() {
let mut a0 = Array2::<f64>::zeros((4, 4));
for (i, &s) in [6.0_f64, 5.0, 0.02, 0.01].iter().enumerate() {
a0[[i, i]] = s;
}
let mut delta = Array2::<f64>::zeros((4, 4));
delta[[0, 3]] = 0.4;
delta[[3, 0]] = 0.4;
delta[[1, 2]] = -0.3;
delta[[2, 1]] = -0.3;
let a1 = &a0 + δ
let sv0: Vec<f64> = eigenvalues(&a0).into_iter().map(f64::abs).collect();
let sv1: Vec<f64> = eigenvalues(&a1).into_iter().map(f64::abs).collect();
let measured = spectral_excursion_lower_bound(&sv0, &sv1);
let true_norm = eigenvalues(&delta)
.into_iter()
.fold(0.0_f64, |acc, l| acc.max(l.abs()));
assert!(
measured <= true_norm + 8.0 * U * true_norm.max(1.0),
"Weyl: max|Δσ| = {measured} must not exceed ‖ΔA‖₂ = {true_norm}"
);
assert!(
measured > 0.0,
"the monitor must actually see this perturbation"
);
assert!(
(spectral_excursion_lower_bound(&[3.0, 0.25], &[3.0]) - 0.25).abs() < 1e-15,
"a dropped trailing singular value is compared against 0"
);
}
#[test]
fn spectrum_inside_two_sided_band_is_ambiguous() {
let sv = [10.0_f64, 3.0, 1.0, 0.2];
match certified_rank(&sv, 1.0, 1.0) {
RankDecision::Ambiguous {
rank_floor,
rank_ceil,
sigma_in_band,
..
} => {
assert_eq!(rank_floor, 2, "#{{σ ≥ 2}} = 2");
assert_eq!(rank_ceil, 3, "#{{σ > 0.5}} = 3");
assert_eq!(sigma_in_band, 1.0);
}
other => panic!("expected Ambiguous, got {other:?}"),
}
}
#[test]
fn shadow_sum_merge_is_additive() {
let mut a = ShadowSum::new();
let mut b = ShadowSum::new();
a.push(1.0);
a.push(-2.0);
b.push(3.0);
a.merge(&b);
assert_eq!(a.count, 3);
assert_eq!(a.sum, 2.0);
assert_eq!(a.abs_sum, 6.0);
}
}