use super::tests::*;
use super::*;
use gam_solve::arrow_schur::ArrowSchurError;
use ndarray::array;
fn schur_non_pd_seed_refusal_message() -> String {
format!(
"SaeManifoldTerm::run_joint_fit_arrow_schur: {}",
ArrowSchurError::SchurFactorFailed {
reason: "non-PD pivot -2.5e-09 at index 2 (matrix is not positive definite)"
.to_string(),
}
)
}
#[test]
pub(crate) fn non_pd_schur_seed_refusal_is_recoverable_1782() {
let msg = schur_non_pd_seed_refusal_message();
assert!(
msg.contains("Schur complement Cholesky failed") && msg.contains("not positive definite"),
"fixture message must carry both non-PD Schur markers: {msg}"
);
assert!(
SaeManifoldOuterObjective::is_recoverable_value_probe_refusal(&msg),
"the non-PD reduced-Schur seed refusal must be recoverable (#1782); got fatal for: {msg}"
);
let genuine_defect = format!(
"SaeManifoldTerm::run_joint_fit_arrow_schur: {}",
ArrowSchurError::SchurFactorFailed {
reason: "non-finite entry at linear index 3".to_string(),
}
);
assert!(
genuine_defect.contains("Schur complement Cholesky failed"),
"fixture must exercise the Schur-failure surface: {genuine_defect}"
);
assert!(
!SaeManifoldOuterObjective::is_recoverable_value_probe_refusal(&genuine_defect),
"a non-PD-marker-free Schur factorization defect must stay FATAL (#1782 must not \
weaken validation into accepting genuine defects): {genuine_defect}"
);
}
#[test]
pub(crate) fn planted_circle_multi_atom_jumprelu_clears_startup_validation_1782() {
use gam_solve::rho_optimizer::OuterProblem;
use gam_solve::seeding::SeedConfig;
let n = 40usize;
let k_atoms = 3usize;
let z = planted_circle_embedded(n, 6, 0.03);
let p = z.ncols();
let coords = Array2::<f64>::from_shape_fn((n, 1), |(row, _)| (row as f64 / n as f64) - 0.5);
let evaluator = Arc::new(EuclideanPatchEvaluator::new(1, 1).unwrap());
let (phi0, jet0) = evaluator.evaluate(coords.view()).unwrap();
let m = phi0.ncols();
let mut atoms = Vec::with_capacity(k_atoms);
let mut coord_blocks = Vec::with_capacity(k_atoms);
let mut manifolds = Vec::with_capacity(k_atoms);
for atom_idx in 0..k_atoms {
let mut decoder = Array2::<f64>::zeros((m, p));
for col in 0..p {
decoder[[m - 1, col]] = 0.1 + 0.02 * ((atom_idx + col) % 3) as f64;
}
atoms.push(
SaeManifoldAtom::new(
format!("euclid{atom_idx}"),
SaeAtomBasisKind::EuclideanPatch,
1,
phi0.clone(),
jet0.clone(),
decoder,
Array2::<f64>::eye(m),
)
.unwrap()
.with_basis_evaluator(evaluator.clone()),
);
coord_blocks.push(coords.clone());
manifolds.push(LatentManifold::Euclidean);
}
let assignment_mode = AssignmentMode::jumprelu(1.0, 0.0);
let assignment = SaeAssignment::from_blocks_with_mode_and_manifolds(
Array2::<f64>::from_elem((n, k_atoms), 0.5),
coord_blocks,
manifolds,
assignment_mode,
)
.unwrap();
let term = SaeManifoldTerm::new(atoms, assignment).unwrap();
let init_rho = SaeManifoldRho::new(
0.02_f64.ln(),
1.0_f64.ln(),
vec![array![0.0]; k_atoms],
)
.seed_scaled_by_dispersion_for_assignment(1.0, assignment_mode)
.unwrap();
let init_rho_flat = init_rho.to_flat();
let n_params = init_rho_flat.len();
let mut objective =
SaeManifoldOuterObjective::new(term, z.clone(), None, init_rho, 8, 0.04, 1.0e-6, 1.0e-6);
let result = OuterProblem::new(n_params)
.with_initial_rho(init_rho_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();
assert!(
!msg.contains("no candidate seeds passed outer startup validation"),
"#1782: K>1 jumprelu euclidean fit must not abort with an emptied seed cascade; got: {msg}"
);
}
result.expect("#1782: multi-atom jumprelu fit must run to a result, not a seed-cascade abort");
}