use crate::cache::{LoadSource, Session as CacheSession};
use crate::estimate::EstimationError;
use crate::solver::estimate::reml::unified::BarrierConfig;
use crate::solver::priority_selection::{
PriorityBudgetStage, PriorityStageSummary, rank_indices_with_budget_cascade,
};
use crate::solver::startup_stats::{
SeedRejection, StartupStats, format_no_seeds_passed, uniform_structural_key,
};
use ::opt::{
Arc as ArcOptimizer, ArcError, Bfgs, BfgsError, Bounds, FallbackPolicy as OptFallbackPolicy,
FirstOrderObjective, FirstOrderSample, FixedPoint, FixedPointError, FixedPointObjective,
FixedPointSample, FixedPointStatus, GradientTolerance, HessianFallbackPolicy,
HessianMaterialization, HessianOperator, HessianValue, MatrixFreeTrustRegion, MaxIterations,
ObjectiveEvalError, OperatorObjective, OperatorSample, OptimizationStatus, OptimizerObserver,
SecondOrderObjective, SecondOrderSample, Solution, StepInfo, Tolerance, ZerothOrderObjective,
};
use ndarray::{Array1, Array2, ArrayView2};
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
const OPERATOR_TRUST_RESTART_RADIUS_FLOOR: f64 = 1.0e-6;
fn outer_strategy_contract_panic(message: impl Into<String>) -> ! {
std::panic::panic_any(message.into())
}
#[derive(Clone, Debug)]
pub struct OuterGradientFdComponent {
pub block: String,
pub index: usize,
pub analytic: f64,
pub fd: f64,
}
impl OuterGradientFdComponent {
pub fn abs_gap(&self) -> f64 {
(self.analytic - self.fd).abs()
}
pub fn ratio(&self) -> Option<f64> {
if self.fd.abs() > 1e-12 {
Some(self.analytic / self.fd)
} else {
None
}
}
}
#[derive(Clone, Debug)]
pub struct OuterGradientFdAudit {
pub value: f64,
pub components: Vec<OuterGradientFdComponent>,
pub hessian_eigenvalues: Vec<f64>,
}
impl OuterGradientFdAudit {
pub fn analytic_block_norms(&self) -> Vec<(String, f64)> {
let mut order: Vec<String> = Vec::new();
let mut acc: std::collections::HashMap<String, f64> = std::collections::HashMap::new();
for c in &self.components {
if !acc.contains_key(&c.block) {
order.push(c.block.clone());
}
*acc.entry(c.block.clone()).or_insert(0.0) += c.analytic * c.analytic;
}
order
.into_iter()
.map(|b| {
let v = acc.get(&b).copied().unwrap_or(0.0).sqrt();
(b, v)
})
.collect()
}
pub fn worst_component(&self) -> Option<&OuterGradientFdComponent> {
self.components.iter().max_by(|a, b| {
a.abs_gap()
.partial_cmp(&b.abs_gap())
.unwrap_or(std::cmp::Ordering::Equal)
})
}
pub fn min_abs_eigenvalue(&self) -> Option<f64> {
self.hessian_eigenvalues
.iter()
.map(|e| e.abs())
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
}
pub fn log_verdict(&self, context: &str) {
log::warn!("[OUTER-FD-AUDIT/{context}] value={:.6e}", self.value);
for (block, norm) in self.analytic_block_norms() {
log::warn!("[OUTER-FD-AUDIT/{context}] block={block} |g_analytic|={norm:.6e}");
}
for c in &self.components {
let ratio = c
.ratio()
.map(|r| format!("{r:.4}"))
.unwrap_or_else(|| "n/a".to_string());
log::warn!(
"[OUTER-FD-AUDIT/{context}] block={} i={} analytic={:.6e} fd={:.6e} gap={:.3e} ratio={}",
c.block,
c.index,
c.analytic,
c.fd,
c.abs_gap(),
ratio
);
}
if !self.hessian_eigenvalues.is_empty() {
let evs: Vec<String> = self
.hessian_eigenvalues
.iter()
.map(|e| format!("{e:.4e}"))
.collect();
log::warn!(
"[OUTER-FD-AUDIT/{context}] hessian_eigenvalues=[{}] min_abs={:.4e}",
evs.join(", "),
self.min_abs_eigenvalue().unwrap_or(f64::NAN)
);
}
match self.worst_component() {
Some(w) if w.abs_gap() > 1e-3 && w.abs_gap() > 1e-3 * w.fd.abs().max(1.0) => {
log::warn!(
"[OUTER-FD-AUDIT/{context}] VERDICT=DESYNC worst_block={} worst_i={} gap={:.3e} (analytic gradient disagrees with FD of the criterion: fix the derivative)",
w.block,
w.index,
w.abs_gap()
);
}
_ => {
let flat = self.min_abs_eigenvalue().map(|m| m < 1e-6).unwrap_or(false);
if flat {
log::warn!(
"[OUTER-FD-AUDIT/{context}] VERDICT=FLATNESS min_abs_eig={:.3e} (analytic≈FD but the outer Hessian is near-singular: weak identifiability, fix termination not the gradient)",
self.min_abs_eigenvalue().unwrap_or(f64::NAN)
);
} else {
log::warn!(
"[OUTER-FD-AUDIT/{context}] VERDICT=CLEAN analytic≈FD and outer Hessian well-conditioned at this θ"
);
}
}
}
}
}
pub fn outer_gradient_fd_audit<EvalF>(
theta0: &Array1<f64>,
h: f64,
block_for_index: impl Fn(usize) -> String,
mut eval: EvalF,
) -> Result<OuterGradientFdAudit, String>
where
EvalF: FnMut(
&Array1<f64>,
crate::solver::estimate::reml::unified::EvalMode,
) -> Result<(f64, Array1<f64>, HessianResult), String>,
{
use crate::solver::estimate::reml::unified::EvalMode;
let (value, analytic_grad, hess) = eval(theta0, EvalMode::ValueGradientHessian)?;
if analytic_grad.len() != theta0.len() {
return Err(format!(
"outer_gradient_fd_audit: analytic gradient length {} != theta length {}",
analytic_grad.len(),
theta0.len()
));
}
let mut components = Vec::with_capacity(theta0.len());
for i in 0..theta0.len() {
let mut tp = theta0.clone();
tp[i] += h;
let mut tm = theta0.clone();
tm[i] -= h;
let (vp, _, _) = eval(&tp, EvalMode::ValueOnly)?;
let (vm, _, _) = eval(&tm, EvalMode::ValueOnly)?;
let fd = (vp - vm) / (2.0 * h);
components.push(OuterGradientFdComponent {
block: block_for_index(i),
index: i,
analytic: analytic_grad[i],
fd,
});
}
let hessian_eigenvalues = match hess.materialize_dense() {
Ok(Some(mut hmat)) => {
let n = hmat.nrows();
if n == hmat.ncols() && n > 0 {
for r in 0..n {
for c in (r + 1)..n {
let avg = 0.5 * (hmat[[r, c]] + hmat[[c, r]]);
hmat[[r, c]] = avg;
hmat[[c, r]] = avg;
}
}
match crate::linalg::faer_ndarray::FaerEigh::eigh(&hmat, faer::Side::Lower) {
Ok((vals, _)) => {
let mut v: Vec<f64> = vals.to_vec();
v.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
v
}
Err(_) => Vec::new(),
}
} else {
Vec::new()
}
}
_ => Vec::new(),
};
Ok(OuterGradientFdAudit {
value,
components,
hessian_eigenvalues,
})
}
#[derive(Clone, Debug)]
struct PathDemotionRecord {
seed_idx: usize,
regime: crate::solver::continuation_path::PathRegime,
reason: String,
}
#[derive(Clone, Debug)]
pub struct InnerProgressFeedback {
pub cap: Arc<AtomicUsize>,
pub accepted_iter: Arc<AtomicUsize>,
pub last_iters: Arc<AtomicUsize>,
pub last_converged: Arc<AtomicBool>,
pub ift_residual: Arc<AtomicU64>,
pub accept_rho: Arc<AtomicU64>,
}
impl InnerProgressFeedback {
fn snapshot(&self) -> Option<InnerProgressSnapshot> {
let iters = self.last_iters.load(Ordering::Relaxed);
if iters == 0 {
None
} else {
let residual_bits = self.ift_residual.load(Ordering::Relaxed);
let r = f64::from_bits(residual_bits);
let last_ift_residual = if r.is_finite() && r >= 0.0 {
Some(r)
} else {
None
};
let accept_rho_bits = self.accept_rho.load(Ordering::Relaxed);
let ar = f64::from_bits(accept_rho_bits);
let last_accept_rho = if ar.is_finite() && ar >= 0.0 {
Some(ar)
} else {
None
};
Some(InnerProgressSnapshot {
last_iters: iters,
last_converged: self.last_converged.load(Ordering::Relaxed),
last_ift_residual,
last_accept_rho,
})
}
}
}
#[derive(Clone, Copy, Debug)]
struct InnerProgressSnapshot {
last_iters: usize,
last_converged: bool,
last_ift_residual: Option<f64>,
last_accept_rho: Option<f64>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OuterHessianMaterialization {
Unavailable,
RepeatedHvp,
BatchedHvp,
Explicit,
}
impl OuterHessianMaterialization {
fn is_available(self) -> bool {
!matches!(self, Self::Unavailable)
}
}
#[derive(Debug, Clone)]
pub enum OuterStrategyError {
OperatorShape { reason: String },
NonFiniteHessian { reason: String },
RhoBlockShape { reason: String },
}
impl std::fmt::Display for OuterStrategyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OuterStrategyError::OperatorShape { reason }
| OuterStrategyError::NonFiniteHessian { reason }
| OuterStrategyError::RhoBlockShape { reason } => f.write_str(reason),
}
}
}
impl std::error::Error for OuterStrategyError {}
impl From<OuterStrategyError> for String {
fn from(err: OuterStrategyError) -> String {
err.to_string()
}
}
pub trait OuterHessianOperator: Send + Sync {
fn dim(&self) -> usize;
fn matvec(&self, v: &Array1<f64>) -> Result<Array1<f64>, String>;
fn apply_into(&self, v: &Array1<f64>, out: &mut Array1<f64>) -> Result<(), String> {
let result = self.matvec(v)?;
if result.len() != out.len() {
return Err(format!(
"outer Hessian operator matvec produced length {} but expected {}",
result.len(),
out.len()
));
}
out.assign(&result);
Ok(())
}
fn is_cheap_to_materialize(&self) -> bool {
false
}
fn materialization_capability(&self) -> OuterHessianMaterialization {
if self.is_cheap_to_materialize() {
OuterHessianMaterialization::RepeatedHvp
} else {
OuterHessianMaterialization::Unavailable
}
}
fn mul_mat(&self, factor: ArrayView2<'_, f64>) -> Result<Array2<f64>, String> {
use rayon::iter::{IntoParallelIterator, ParallelIterator};
let dim = self.dim();
if factor.nrows() != dim {
return Err(OuterStrategyError::OperatorShape {
reason: format!(
"outer Hessian operator factor row count mismatch: got {}, expected {}",
factor.nrows(),
dim
),
}
.into());
}
let m = factor.ncols();
let cols: Result<Vec<Array1<f64>>, String> = (0..m)
.into_par_iter()
.map(|j| {
let col = factor.column(j).to_owned();
let hv = self.matvec(&col)?;
if hv.len() != dim {
return Err(OuterStrategyError::OperatorShape {
reason: format!(
"outer Hessian operator matvec length mismatch: got {}, expected {}",
hv.len(),
dim
),
}
.into());
}
Ok(hv)
})
.collect();
let cols = cols?;
let mut out = Array2::<f64>::zeros((dim, m));
for (j, hv) in cols.into_iter().enumerate() {
out.column_mut(j).assign(&hv);
}
Ok(out)
}
fn materialize_dense(&self) -> Result<Array2<f64>, String> {
let dim = self.dim();
let identity = Array2::<f64>::eye(dim);
let mut dense = self.mul_mat(identity.view())?;
if dense.nrows() != dim || dense.ncols() != dim {
return Err(OuterStrategyError::OperatorShape {
reason: format!(
"outer Hessian operator mul_mat returned {}x{}, expected {}x{}",
dense.nrows(),
dense.ncols(),
dim,
dim
),
}
.into());
}
for row in 0..dim {
for col in (row + 1)..dim {
let sym = 0.5 * (dense[[row, col]] + dense[[col, row]]);
dense[[row, col]] = sym;
dense[[col, row]] = sym;
}
}
if !dense.iter().all(|value| value.is_finite()) {
return Err(OuterStrategyError::NonFiniteHessian {
reason: "outer Hessian dense materialization produced non-finite entries"
.to_string(),
}
.into());
}
Ok(dense)
}
}
struct RhoBlockAdditiveOuterHessian {
base: Arc<dyn OuterHessianOperator>,
rho_block: Array2<f64>,
dim: usize,
}
impl OuterHessianOperator for RhoBlockAdditiveOuterHessian {
fn dim(&self) -> usize {
self.dim
}
fn matvec(&self, v: &Array1<f64>) -> Result<Array1<f64>, String> {
if v.len() != self.dim {
return Err(OuterStrategyError::OperatorShape {
reason: format!(
"outer Hessian operator input length mismatch: got {}, expected {}",
v.len(),
self.dim
),
}
.into());
}
let mut out = self.base.matvec(v)?;
let k = self.rho_block.nrows();
if k > 0 {
let rho_v = v.slice(ndarray::s![..k]).to_owned();
let rho_out = self.rho_block.dot(&rho_v);
out.slice_mut(ndarray::s![..k]).scaled_add(1.0, &rho_out);
}
Ok(out)
}
fn apply_into(&self, v: &Array1<f64>, out: &mut Array1<f64>) -> Result<(), String> {
if v.len() != self.dim {
return Err(OuterStrategyError::OperatorShape {
reason: format!(
"outer Hessian operator input length mismatch: got {}, expected {}",
v.len(),
self.dim
),
}
.into());
}
if out.len() != self.dim {
return Err(OuterStrategyError::OperatorShape {
reason: format!(
"outer Hessian apply_into output length mismatch: got {}, expected {}",
out.len(),
self.dim
),
}
.into());
}
self.base.apply_into(v, out)?;
let k = self.rho_block.nrows();
if k > 0 {
let v_top = v.slice(ndarray::s![..k]);
for i in 0..k {
out[i] += self.rho_block.row(i).dot(&v_top);
}
}
Ok(())
}
fn mul_mat(&self, factor: ArrayView2<'_, f64>) -> Result<Array2<f64>, String> {
let mut out = self.base.mul_mat(factor)?;
let k = self.rho_block.nrows();
if k > 0 {
if k > out.nrows() {
return Err(OuterStrategyError::RhoBlockShape {
reason: format!(
"rho-block Hessian update shape mismatch: rho_block is {}x{}, mul_mat output has {} rows",
self.rho_block.nrows(),
self.rho_block.ncols(),
out.nrows()
),
}
.into());
}
let factor_top = factor.slice(ndarray::s![..k, ..]);
let rho_contrib = self.rho_block.dot(&factor_top);
out.slice_mut(ndarray::s![..k, ..])
.scaled_add(1.0, &rho_contrib);
}
Ok(out)
}
fn is_cheap_to_materialize(&self) -> bool {
self.base.is_cheap_to_materialize()
}
fn materialization_capability(&self) -> OuterHessianMaterialization {
self.base.materialization_capability()
}
}
pub(crate) const OUTER_HVP_MATERIALIZE_MAX_DIM: usize = 64;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Derivative {
Analytic,
Unavailable,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DeclaredHessianForm {
Dense,
Operator {
materialization: OuterHessianMaterialization,
estimated_materialization_cost: Option<f64>,
},
Either,
Unavailable,
}
impl DeclaredHessianForm {
pub const fn is_analytic(self) -> bool {
!matches!(self, DeclaredHessianForm::Unavailable)
}
pub const fn is_operator_only(self) -> bool {
matches!(self, DeclaredHessianForm::Operator { .. })
}
pub const fn is_dense_only(self) -> bool {
matches!(self, DeclaredHessianForm::Dense)
}
}
const SMALL_OUTER_BFGS_MAX_PARAMS: usize = 8;
const SECOND_ORDER_GEOMETRY_PROBE_MAX_PARAMS: usize = 64;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct OuterThetaLayout {
pub n_params: usize,
pub psi_dim: usize,
}
impl OuterThetaLayout {
pub const fn new(n_params: usize, psi_dim: usize) -> Self {
Self { n_params, psi_dim }
}
pub const fn rho_dim(&self) -> usize {
self.n_params.saturating_sub(self.psi_dim)
}
fn validate_capability(&self, context: &str) -> Result<(), EstimationError> {
if self.psi_dim > self.n_params {
return Err(EstimationError::RemlOptimizationFailed(format!(
"{context}: invalid outer theta layout (psi_dim={} exceeds n_params={})",
self.psi_dim, self.n_params
)));
}
Ok::<(), _>(())
}
fn validate_point_len(
&self,
theta: &Array1<f64>,
context: &str,
) -> Result<(), ObjectiveEvalError> {
if theta.len() != self.n_params {
return Err(ObjectiveEvalError::recoverable(format!(
"{context}: outer theta length mismatch: got {}, expected {} (rho_dim={}, psi_dim={})",
theta.len(),
self.n_params,
self.rho_dim(),
self.psi_dim
)));
}
Ok::<(), _>(())
}
fn validate_gradient_len(
&self,
gradient: &Array1<f64>,
context: &str,
) -> Result<(), ObjectiveEvalError> {
if gradient.len() != self.n_params {
return Err(ObjectiveEvalError::recoverable(format!(
"{context}: outer gradient length mismatch: got {}, expected {} (rho_dim={}, psi_dim={})",
gradient.len(),
self.n_params,
self.rho_dim(),
self.psi_dim
)));
}
Ok::<(), _>(())
}
fn validate_hessian_shape(
&self,
hessian: &Array2<f64>,
context: &str,
) -> Result<(), ObjectiveEvalError> {
if hessian.nrows() != self.n_params || hessian.ncols() != self.n_params {
return Err(ObjectiveEvalError::recoverable(format!(
"{context}: outer Hessian shape mismatch: got {}x{}, expected {}x{} (rho_dim={}, psi_dim={})",
hessian.nrows(),
hessian.ncols(),
self.n_params,
self.n_params,
self.rho_dim(),
self.psi_dim
)));
}
Ok::<(), _>(())
}
fn validate_efs_eval(&self, eval: &EfsEval, context: &str) -> Result<(), ObjectiveEvalError> {
if eval.steps.len() != self.n_params {
return Err(ObjectiveEvalError::recoverable(format!(
"{context}: outer EFS step length mismatch: got {}, expected {} (rho_dim={}, psi_dim={})",
eval.steps.len(),
self.n_params,
self.rho_dim(),
self.psi_dim
)));
}
if let Some(ref psi_gradient) = eval.psi_gradient
&& psi_gradient.len() != self.psi_dim
{
return Err(ObjectiveEvalError::recoverable(format!(
"{context}: outer EFS psi-gradient length mismatch: got {}, expected {}",
psi_gradient.len(),
self.psi_dim
)));
}
if let Some(ref psi_indices) = eval.psi_indices {
if psi_indices.len() != self.psi_dim {
return Err(ObjectiveEvalError::recoverable(format!(
"{context}: outer EFS psi-index count mismatch: got {}, expected {}",
psi_indices.len(),
self.psi_dim
)));
}
if psi_indices.iter().any(|&idx| idx >= self.n_params) {
return Err(ObjectiveEvalError::recoverable(format!(
"{context}: outer EFS psi index out of range for n_params={}",
self.n_params
)));
}
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct OuterCapability {
pub gradient: Derivative,
pub hessian: DeclaredHessianForm,
pub n_params: usize,
pub psi_dim: usize,
pub fixed_point_available: bool,
pub barrier_config: Option<BarrierConfig>,
pub prefer_gradient_only: bool,
pub disable_fixed_point: bool,
}
impl OuterCapability {
pub const fn theta_layout(&self) -> OuterThetaLayout {
OuterThetaLayout::new(self.n_params, self.psi_dim)
}
pub fn validate_layout(&self, context: &str) -> Result<(), EstimationError> {
self.theta_layout().validate_capability(context)
}
pub const fn all_penalty_like(&self) -> bool {
self.psi_dim == 0
}
pub const fn has_psi_coords(&self) -> bool {
self.psi_dim > 0
}
fn efs_plan_eligible(&self) -> bool {
self.fixed_point_available
&& !self.disable_fixed_point
&& self.all_penalty_like()
&& self.n_params > SMALL_OUTER_BFGS_MAX_PARAMS
}
fn hybrid_efs_plan_eligible(&self) -> bool {
self.fixed_point_available
&& !self.disable_fixed_point
&& self.has_psi_coords()
&& self.n_params > SMALL_OUTER_BFGS_MAX_PARAMS
}
fn declared_hessian_for_planning(&self) -> Derivative {
if self.hessian.is_analytic() {
Derivative::Analytic
} else {
Derivative::Unavailable
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Solver {
Arc,
Bfgs,
Efs,
HybridEfs,
}
#[inline]
fn effective_seed_budget(
requested_budget: usize,
solver: Solver,
risk_profile: crate::seeding::SeedRiskProfile,
screening_enabled: bool,
) -> usize {
let requested_budget = requested_budget.max(1);
let capped = match (solver, risk_profile) {
(Solver::Efs | Solver::HybridEfs, _) => 1,
(Solver::Arc, crate::seeding::SeedRiskProfile::Survival) => 1,
(Solver::Arc, crate::seeding::SeedRiskProfile::GeneralizedLinear) if screening_enabled => 1,
(Solver::Arc, crate::seeding::SeedRiskProfile::GeneralizedLinear) => 2,
_ => requested_budget,
};
requested_budget.min(capped)
}
#[inline]
fn should_screen_seeds(
config: &OuterConfig,
solver: Solver,
generated_seed_count: usize,
seed_budget: usize,
) -> bool {
config.screening_cap.is_some()
&& generated_seed_count > seed_budget
&& matches!(
solver,
Solver::Arc | Solver::Bfgs | Solver::Efs | Solver::HybridEfs
)
}
#[inline]
fn expensive_unsuccessful_seed_limit(
solver: Solver,
risk_profile: crate::seeding::SeedRiskProfile,
) -> Option<usize> {
match (solver, risk_profile) {
(Solver::Efs | Solver::HybridEfs, _) => Some(1),
(Solver::Arc, crate::seeding::SeedRiskProfile::Survival) => Some(1),
(Solver::Arc, crate::seeding::SeedRiskProfile::GeneralizedLinear) => Some(2),
_ => None,
}
}
const SEED_SCREENING_CASCADE_MULTIPLIERS: [usize; 3] = [1, 4, 16];
const SEED_SCREENING_UNCAPPED: usize = 0;
fn rank_seeds_with_screening(
obj: &mut dyn OuterObjective,
config: &OuterConfig,
context: &str,
seeds: &[Array1<f64>],
) -> Vec<Array1<f64>> {
let Some(screening_cap) = config.screening_cap.as_ref() else {
return seeds.to_vec();
};
let initial_cap = config.seed_config.screen_max_inner_iterations.max(1);
let previous_cap = screening_cap.swap(initial_cap, Ordering::Relaxed);
let cascade_caps = [
PriorityBudgetStage {
cap: initial_cap.saturating_mul(SEED_SCREENING_CASCADE_MULTIPLIERS[0]),
},
PriorityBudgetStage {
cap: initial_cap.saturating_mul(SEED_SCREENING_CASCADE_MULTIPLIERS[1]),
},
PriorityBudgetStage {
cap: initial_cap.saturating_mul(SEED_SCREENING_CASCADE_MULTIPLIERS[2]),
},
PriorityBudgetStage {
cap: SEED_SCREENING_UNCAPPED,
},
];
let cascade_start = std::time::Instant::now();
log::info!(
"[STAGE] {context}: seed screening cascade start seeds={} initial_cap={} stages={}",
seeds.len(),
initial_cap,
cascade_caps.len(),
);
let cascade_result = rank_indices_with_budget_cascade(
seeds.len(),
&cascade_caps,
|stage, cap, idx| {
screening_cap.store(cap, Ordering::Relaxed);
obj.reset();
screening_cap.store(cap, Ordering::Relaxed);
let seed_started = std::time::Instant::now();
let result = obj.eval_screening_proxy(&seeds[idx]);
let seed_elapsed = seed_started.elapsed().as_secs_f64();
match result {
Ok(cost) if cost.is_finite() => {
log::info!(
"[STAGE] {context}: seed-screen stage={} seed={}/{} cap={} elapsed={:.3}s cost={:.6e}",
stage,
idx + 1,
seeds.len(),
if cap == 0 {
"uncapped".to_string()
} else {
cap.to_string()
},
seed_elapsed,
cost,
);
Ok(cost)
}
Ok(cost) => {
log::info!(
"[STAGE] {context}: seed-screen stage={} seed={}/{} cap={} elapsed={:.3}s cost=non-finite ({:.3e})",
stage,
idx + 1,
seeds.len(),
if cap == 0 {
"uncapped".to_string()
} else {
cap.to_string()
},
seed_elapsed,
cost,
);
Ok(cost)
}
Err(_) => {
log::info!(
"[STAGE] {context}: seed-screen stage={} seed={}/{} cap={} elapsed={:.3}s rejected (error)",
stage,
idx + 1,
seeds.len(),
if cap == 0 {
"uncapped".to_string()
} else {
cap.to_string()
},
seed_elapsed,
);
Err(())
}
}
},
|PriorityStageSummary {
stage,
cap,
ranked,
rejected,
}| {
log::info!(
"[STAGE] {context}: seed-screen stage={} cap={} elapsed={:.3}s ranked={} rejected={}",
stage,
if cap == 0 {
"uncapped".to_string()
} else {
cap.to_string()
},
cascade_start.elapsed().as_secs_f64(),
ranked,
rejected,
);
if ranked > 0 && stage > 0 {
let final_cap = if cap == 0 {
"uncapped".to_string()
} else {
cap.to_string()
};
log::info!(
"[OUTER] {context}: seed screening cap escalated from {} to {} \
(initial cap was too shallow for this problem; {}/{} seeds ranked)",
initial_cap,
final_cap,
ranked,
seeds.len(),
);
}
},
);
let rejected = cascade_result.rejected;
let final_cap_used = cascade_result.final_cap;
let stages_consumed = cascade_result.stages_consumed;
let ranked = cascade_result.ranked_indices;
screening_cap.store(previous_cap, Ordering::Relaxed);
obj.reset();
log::info!(
"[OUTER] {context}: seed screening cascade complete elapsed={:.3}s stages_used={} final_cap={} ranked={}/{}",
cascade_start.elapsed().as_secs_f64(),
stages_consumed,
if final_cap_used == 0 {
"uncapped".to_string()
} else {
final_cap_used.to_string()
},
ranked.len(),
seeds.len(),
);
if ranked.is_empty() {
log::info!(
"[OUTER] {context}: no finite seed cost even with full inner budget \
({} seeds, {} rejected, {} cascade stages tried); keeping heuristic order",
seeds.len(),
rejected,
stages_consumed,
);
return seeds.to_vec();
}
let mut ordered = Vec::with_capacity(seeds.len());
let mut seen = vec![false; seeds.len()];
for idx in ranked {
seen[idx] = true;
ordered.push(seeds[idx].clone());
}
for (idx, seed) in seeds.iter().enumerate() {
if !seen[idx] {
ordered.push(seed.clone());
}
}
let rho_dim = obj.capability().theta_layout().rho_dim();
if rho_dim > 0 && ordered.len() > 1 {
let upper: Vec<f64> = match config.bounds.as_ref() {
Some((_, hi)) => hi.to_vec(),
None => vec![config.rho_bound; rho_dim],
};
let (interior, boundary): (Vec<Array1<f64>>, Vec<Array1<f64>>) = ordered
.into_iter()
.partition(|seed| !seed_is_oversmoothing_boundary(seed, rho_dim, &upper));
if !interior.is_empty() && !boundary.is_empty() {
log::info!(
"[OUTER] {context}: demoted {} over-smoothing boundary seed(s) below {} \
interior seed(s) so the outer descent does not originate on the flat \
ρ=bound plateau",
boundary.len(),
interior.len(),
);
}
ordered = interior;
ordered.extend(boundary);
}
log::debug!(
"[OUTER] {context}: seed screening ranked {}/{} candidates at cap={} \
(initial cap={}, stages used={}); rejected={}",
ordered.len() - rejected,
seeds.len(),
if final_cap_used == 0 {
"uncapped".to_string()
} else {
final_cap_used.to_string()
},
initial_cap,
stages_consumed,
rejected,
);
ordered
}
const OVERSMOOTH_BOUNDARY_MARGIN: f64 = 0.5;
fn seed_is_oversmoothing_boundary(seed: &Array1<f64>, rho_dim: usize, upper: &[f64]) -> bool {
if rho_dim == 0 || seed.len() < rho_dim {
return false;
}
(0..rho_dim).all(|i| {
let hi = upper.get(i).copied().unwrap_or(f64::INFINITY);
hi.is_finite() && seed[i] >= hi - OVERSMOOTH_BOUNDARY_MARGIN
})
}
#[inline]
fn candidate_improves_best(candidate: &OuterResult, best: Option<&OuterResult>) -> bool {
match best {
None => true,
Some(best) if candidate.converged != best.converged => candidate.converged,
Some(best) => candidate.final_value < best.final_value,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HessianSource {
Analytic,
BfgsApprox,
EfsFixedPoint,
HybridEfsFixedPoint,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OuterEvalOrder {
Value,
ValueAndGradient,
ValueGradientHessian,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct OuterPlan {
pub solver: Solver,
pub hessian_source: HessianSource,
}
pub(crate) const EFS_FIRST_ORDER_FALLBACK_MARKER: &str = "[outer-efs-first-order-fallback]";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FallbackPolicy {
Automatic,
Disabled,
}
impl std::fmt::Display for OuterPlan {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"solver={:?}, hessian_source={:?}",
self.solver, self.hessian_source
)
}
}
impl OuterPlan {
pub fn routing_log_line(&self) -> String {
let matrix_free = false;
format!(
"solver={:?};hessian={:?};matrix-free={}",
self.solver, self.hessian_source, matrix_free
)
}
}
pub fn plan(cap: &OuterCapability) -> OuterPlan {
use Derivative::*;
use HessianSource as H;
use Solver as S;
match (cap.gradient, cap.declared_hessian_for_planning()) {
(Analytic, Analytic) => OuterPlan {
solver: S::Arc,
hessian_source: H::Analytic,
},
(Analytic, Unavailable) if cap.efs_plan_eligible() => OuterPlan {
solver: S::Efs,
hessian_source: H::EfsFixedPoint,
},
(Unavailable, Unavailable) if cap.efs_plan_eligible() => OuterPlan {
solver: S::Efs,
hessian_source: H::EfsFixedPoint,
},
(Analytic, Unavailable) if cap.hybrid_efs_plan_eligible() => OuterPlan {
solver: S::HybridEfs,
hessian_source: H::HybridEfsFixedPoint,
},
(Unavailable, Unavailable) if cap.hybrid_efs_plan_eligible() => OuterPlan {
solver: S::HybridEfs,
hessian_source: H::HybridEfsFixedPoint,
},
(Analytic, Unavailable) => OuterPlan {
solver: S::Bfgs,
hessian_source: H::BfgsApprox,
},
(Unavailable, _) => OuterPlan {
solver: S::Bfgs,
hessian_source: H::BfgsApprox,
},
}
}
pub fn log_plan(context: &str, cap: &OuterCapability, the_plan: &OuterPlan) {
let hess_warning = match the_plan.hessian_source {
HessianSource::BfgsApprox if cap.n_params > 0 => {
" [no Hessian: BFGS approximation]".to_string()
}
_ => String::new(),
};
let barrier_note = if cap.barrier_config.is_some() && cap.efs_plan_eligible() {
" [EFS with runtime barrier-curvature guard]"
} else {
""
};
let hybrid_note = if the_plan.solver == Solver::HybridEfs {
" [hybrid EFS(ρ) + preconditioned-gradient(ψ)]"
} else {
""
};
log::info!(
"[OUTER] {context}: n_params={}, gradient={:?}, hessian={:?} -> {} [{}]{hess_warning}{barrier_note}{hybrid_note}",
cap.n_params,
cap.gradient,
cap.hessian,
the_plan,
the_plan.routing_log_line(),
);
}
fn requests_immediate_first_order_fallback(message: &str) -> bool {
message.contains(EFS_FIRST_ORDER_FALLBACK_MARKER)
}
fn disable_fixed_point(cap: &OuterCapability) -> Option<OuterCapability> {
(!cap.disable_fixed_point && (cap.efs_plan_eligible() || cap.hybrid_efs_plan_eligible())).then(
|| {
let mut degraded = cap.clone();
degraded.disable_fixed_point = true;
degraded
},
)
}
fn automatic_fallback_attempts(cap: &OuterCapability) -> Vec<OuterCapability> {
let mut attempts = Vec::new();
if cap.gradient == Derivative::Analytic
&& matches!(plan(cap).solver, Solver::Efs | Solver::HybridEfs)
&& let Some(no_fp_cap) = disable_fixed_point(cap)
{
attempts.push(no_fp_cap.clone());
return attempts;
}
if matches!(plan(cap).solver, Solver::Arc) {
return attempts;
}
attempts
}
fn disabled_fallback_hybrid_efs_has_standalone_bfgs_primary(
cap: &OuterCapability,
config: &OuterConfig,
) -> bool {
config.fallback_policy == FallbackPolicy::Disabled
&& cap.gradient == Derivative::Analytic
&& matches!(plan(cap).solver, Solver::HybridEfs)
}
fn primary_capability_for_config(
mut cap: OuterCapability,
config: &OuterConfig,
context: &str,
) -> OuterCapability {
if disabled_fallback_hybrid_efs_has_standalone_bfgs_primary(&cap, config) {
log::info!(
"[OUTER] {context}: HybridEFS requires the automatic first-order \
escape path for ψ coordinates; fallback is disabled, so routing the \
primary attempt to analytic-gradient BFGS"
);
cap.disable_fixed_point = true;
}
cap
}
pub struct OuterEval {
pub cost: f64,
pub gradient: Array1<f64>,
pub hessian: HessianResult,
pub inner_beta_hint: Option<Array1<f64>>,
}
impl OuterEval {
pub fn infeasible(n_params: usize) -> Self {
Self {
cost: f64::INFINITY,
gradient: Array1::zeros(n_params),
hessian: HessianResult::Unavailable,
inner_beta_hint: None,
}
}
}
pub enum HessianResult {
Analytic(Array2<f64>),
Operator(Arc<dyn OuterHessianOperator>),
Unavailable,
}
impl Clone for OuterEval {
fn clone(&self) -> Self {
Self {
cost: self.cost,
gradient: self.gradient.clone(),
hessian: self.hessian.clone(),
inner_beta_hint: self.inner_beta_hint.clone(),
}
}
}
impl std::fmt::Debug for OuterEval {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OuterEval")
.field("cost", &self.cost)
.field("gradient", &self.gradient)
.field("hessian", &self.hessian)
.finish()
}
}
impl Clone for HessianResult {
fn clone(&self) -> Self {
match self {
Self::Analytic(h) => Self::Analytic(h.clone()),
Self::Operator(op) => Self::Operator(Arc::clone(op)),
Self::Unavailable => Self::Unavailable,
}
}
}
impl std::fmt::Debug for HessianResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Analytic(h) => f
.debug_tuple("Analytic")
.field(&format!("{}x{}", h.nrows(), h.ncols()))
.finish(),
Self::Operator(op) => f
.debug_tuple("Operator")
.field(&format!("dim={}", op.dim()))
.finish(),
Self::Unavailable => f.write_str("Unavailable"),
}
}
}
impl HessianResult {
pub fn unwrap_analytic(self) -> Array2<f64> {
match self {
HessianResult::Analytic(h) => h,
HessianResult::Operator(_) => {
outer_strategy_contract_panic(
"expected dense analytic Hessian but got HessianResult::Operator",
)
}
HessianResult::Unavailable => {
outer_strategy_contract_panic(
"expected analytic Hessian but got HessianResult::Unavailable",
)
}
}
}
pub fn is_analytic(&self) -> bool {
matches!(
self,
HessianResult::Analytic(_) | HessianResult::Operator(_)
)
}
pub fn into_option(self) -> Option<Array2<f64>> {
match self {
HessianResult::Analytic(h) => Some(h),
HessianResult::Operator(_) => None,
HessianResult::Unavailable => None,
}
}
pub fn dim(&self) -> Option<usize> {
match self {
HessianResult::Analytic(h) => Some(h.nrows()),
HessianResult::Operator(op) => Some(op.dim()),
HessianResult::Unavailable => None,
}
}
pub fn materialize_dense(&self) -> Result<Option<Array2<f64>>, String> {
match self {
HessianResult::Analytic(h) => Ok(Some(h.clone())),
HessianResult::Operator(op) => op.materialize_dense().map(Some),
HessianResult::Unavailable => Ok(None),
}
}
pub fn add_rho_block_dense(&mut self, rho_block: &Array2<f64>) -> Result<(), String> {
if rho_block.nrows() != rho_block.ncols() {
return Err(OuterStrategyError::RhoBlockShape {
reason: format!(
"rho-block Hessian update must be square, got {}x{}",
rho_block.nrows(),
rho_block.ncols()
),
}
.into());
}
match self {
HessianResult::Analytic(h) => {
if rho_block.nrows() > h.nrows() || rho_block.ncols() > h.ncols() {
return Err(OuterStrategyError::RhoBlockShape {
reason: format!(
"rho-block Hessian update shape mismatch: got {}x{}, outer Hessian is {}x{}",
rho_block.nrows(),
rho_block.ncols(),
h.nrows(),
h.ncols()
),
}
.into());
}
let k = rho_block.nrows();
let mut sl = h.slice_mut(ndarray::s![..k, ..k]);
sl += rho_block;
Ok(())
}
HessianResult::Operator(op) => {
let base = Arc::clone(op);
let dim = base.dim();
if rho_block.nrows() > dim {
return Err(OuterStrategyError::RhoBlockShape {
reason: format!(
"rho-block Hessian update dimension mismatch: got {}x{}, operator dim is {}",
rho_block.nrows(),
rho_block.ncols(),
dim
),
}
.into());
}
*self = HessianResult::Operator(Arc::new(RhoBlockAdditiveOuterHessian {
base,
rho_block: rho_block.clone(),
dim,
}));
Ok(())
}
HessianResult::Unavailable => Ok(()),
}
}
}
#[derive(Clone, Debug)]
pub struct EfsEval {
pub cost: f64,
pub steps: Vec<f64>,
pub beta: Option<Array1<f64>>,
pub psi_gradient: Option<Array1<f64>>,
pub psi_indices: Option<Vec<usize>>,
pub inner_hessian_scale: Option<f64>,
pub logdet_enclosure_gap: Option<f64>,
}
impl EfsEval {
#[must_use]
pub fn with_logdet_enclosure_gap(mut self, gap: Option<f64>) -> Self {
self.logdet_enclosure_gap = gap;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SeedOutcome {
Installed,
NoSlot,
}
pub trait OuterObjective {
fn capability(&self) -> OuterCapability;
fn eval_cost(&mut self, rho: &Array1<f64>) -> Result<f64, EstimationError>;
fn eval_screening_proxy(&mut self, rho: &Array1<f64>) -> Result<f64, EstimationError> {
self.eval_cost(rho)
}
fn eval(&mut self, rho: &Array1<f64>) -> Result<OuterEval, EstimationError>;
fn eval_with_order(
&mut self,
rho: &Array1<f64>,
order: OuterEvalOrder,
) -> Result<OuterEval, EstimationError> {
match order {
OuterEvalOrder::Value => {
let cost = self.eval_cost(rho)?;
Ok(OuterEval {
cost,
gradient: Array1::zeros(rho.len()),
hessian: HessianResult::Unavailable,
inner_beta_hint: None,
})
}
OuterEvalOrder::ValueAndGradient | OuterEvalOrder::ValueGradientHessian => {
self.eval(rho)
}
}
}
fn eval_efs(&mut self, rho: &Array1<f64>) -> Result<EfsEval, EstimationError> {
Err(EstimationError::RemlOptimizationFailed(format!(
"EFS evaluation not implemented for this objective at rho_dim={}",
rho.len()
)))
}
fn reset(&mut self);
fn seed_inner_state(&mut self, beta: &Array1<f64>) -> Result<SeedOutcome, EstimationError>;
fn allow_continuation_prewarm(&self) -> bool {
false
}
fn outer_device_admission(&self) -> Option<crate::gpu::policy::RemlOuterAdmission> {
None
}
fn requires_continuation_path_entry(&self) -> bool {
false
}
fn curvature_homotopy_entry(
&mut self,
rho: &Array1<f64>,
) -> Option<Result<bool, EstimationError>> {
if let Some(idx) = rho.iter().position(|v| !v.is_finite()) {
return Some(Err(EstimationError::RemlOptimizationFailed(format!(
"curvature-homotopy entry received non-finite rho[{idx}]"
))));
}
None
}
fn accept_seed_without_outer_iterations(
&mut self,
rho: &Array1<f64>,
) -> Result<Option<f64>, EstimationError> {
if rho.is_empty() {
return Ok(None);
}
Ok(None)
}
fn finalize_outer_result(
&mut self,
rho: &Array1<f64>,
plan: &OuterPlan,
) -> Result<(), EstimationError> {
log::debug!(
"[OUTER] finalize: re-installing best rho into the objective (solver {:?})",
plan.solver
);
self.eval_cost(rho).map(|_| ())
}
}
#[derive(serde::Serialize, serde::Deserialize)]
struct IteratePayload {
schema: u32,
rho: Vec<f64>,
#[serde(default)]
beta: Vec<f64>,
cost: f64,
eval_id: u64,
}
const ITERATE_PAYLOAD_SCHEMA: u32 = 2;
fn encode_iterate(
rho: &Array1<f64>,
beta: Option<&Array1<f64>>,
cost: f64,
eval_id: u64,
) -> Option<Vec<u8>> {
let p = IteratePayload {
schema: ITERATE_PAYLOAD_SCHEMA,
rho: rho.to_vec(),
beta: beta.map(|b| b.to_vec()).unwrap_or_default(),
cost,
eval_id,
};
serde_json::to_vec(&p).ok()
}
fn decode_iterate(bytes: &[u8], expected_rho_dim: usize) -> Option<IteratePayload> {
let p: IteratePayload = serde_json::from_slice(bytes).ok()?;
if p.schema != ITERATE_PAYLOAD_SCHEMA {
return None;
}
if p.rho.len() != expected_rho_dim {
return None;
}
if !p.rho.iter().all(|x| x.is_finite()) || !p.cost.is_finite() {
return None;
}
if !p.beta.iter().all(|x| x.is_finite()) {
return None;
}
Some(p)
}
#[derive(Debug)]
pub(crate) enum CacheSeedDecision {
ExactFinal {
rho: Array1<f64>,
beta: Vec<f64>,
final_value: f64,
iterations: usize,
prior_obj_display: f64,
},
Seed {
rho: Array1<f64>,
beta: Vec<f64>,
prior_obj_display: f64,
iteration: u64,
},
Discard {
reason: &'static str,
prior_obj_display: f64,
all_rho_finite: Option<bool>,
},
}
pub(crate) fn classify_cache_entry_for_outer(
loaded: &crate::cache::LoadedEntry,
expected_rho_dim: usize,
) -> CacheSeedDecision {
let entry = &loaded.entry;
let Some(payload) = decode_iterate(&entry.payload, expected_rho_dim) else {
return CacheSeedDecision::Discard {
reason: "payload-shape-mismatch",
prior_obj_display: entry.objective.unwrap_or(f64::NAN),
all_rho_finite: None,
};
};
let cached_rho = Array1::from_vec(payload.rho);
let prior_obj_display = entry.objective.unwrap_or(f64::NAN);
if matches!(entry.objective, Some(v) if !v.is_finite()) {
return CacheSeedDecision::Discard {
reason: "non-finite-payload",
prior_obj_display,
all_rho_finite: Some(cached_rho.iter().all(|v| v.is_finite())),
};
}
if !cached_rho.iter().all(|v| v.is_finite()) {
return CacheSeedDecision::Discard {
reason: "non-finite-payload",
prior_obj_display,
all_rho_finite: Some(false),
};
}
if loaded.source == LoadSource::Exact && entry.kind == crate::cache::EntryKind::Final {
return CacheSeedDecision::ExactFinal {
rho: cached_rho,
beta: payload.beta,
final_value: entry.objective.unwrap_or(payload.cost),
iterations: entry
.iteration
.unwrap_or(payload.eval_id)
.min(usize::MAX as u64) as usize,
prior_obj_display,
};
}
CacheSeedDecision::Seed {
rho: cached_rho,
beta: payload.beta,
prior_obj_display,
iteration: entry.iteration.unwrap_or(payload.eval_id),
}
}
pub(crate) fn cache_entry_would_help_outer(
loaded: &crate::cache::LoadedEntry,
expected_rho_dim: usize,
) -> bool {
matches!(
classify_cache_entry_for_outer(loaded, expected_rho_dim),
CacheSeedDecision::ExactFinal { .. } | CacheSeedDecision::Seed { .. }
)
}
struct CheckpointingObjective<'a> {
inner: &'a mut dyn OuterObjective,
session: Arc<CacheSession>,
mirror_sessions: Vec<Arc<CacheSession>>,
eval_counter: AtomicU64,
last_inner_beta: std::sync::Mutex<Option<Array1<f64>>>,
}
impl<'a> CheckpointingObjective<'a> {
fn new(
inner: &'a mut dyn OuterObjective,
session: Arc<CacheSession>,
mirror_sessions: Vec<Arc<CacheSession>>,
) -> Self {
Self {
inner,
session,
mirror_sessions,
eval_counter: AtomicU64::new(0),
last_inner_beta: std::sync::Mutex::new(None),
}
}
fn last_inner_beta(&self) -> Option<Array1<f64>> {
self.last_inner_beta.lock().ok().and_then(|g| g.clone())
}
fn note(&self, rho: &Array1<f64>, beta: Option<&Array1<f64>>, cost: f64) {
if !cost.is_finite() {
return;
}
if let Some(b) = beta {
if !b.iter().all(|v| v.is_finite()) {
return;
}
if let Ok(mut guard) = self.last_inner_beta.lock() {
*guard = Some(b.clone());
}
}
let i = self.eval_counter.fetch_add(1, Ordering::Relaxed);
if let Some(bytes) = encode_iterate(rho, beta, cost, i) {
self.session.checkpoint(&bytes, Some(cost), Some(i));
for mirror in &self.mirror_sessions {
mirror.checkpoint(&bytes, Some(cost), Some(i));
}
}
}
}
impl<'a> OuterObjective for CheckpointingObjective<'a> {
fn capability(&self) -> OuterCapability {
self.inner.capability()
}
fn eval_cost(&mut self, rho: &Array1<f64>) -> Result<f64, EstimationError> {
let v = self.inner.eval_cost(rho)?;
self.note(rho, None, v);
Ok(v)
}
fn eval_screening_proxy(&mut self, rho: &Array1<f64>) -> Result<f64, EstimationError> {
self.inner.eval_screening_proxy(rho)
}
fn eval(&mut self, rho: &Array1<f64>) -> Result<OuterEval, EstimationError> {
let r = self.inner.eval(rho)?;
self.note(rho, r.inner_beta_hint.as_ref(), r.cost);
Ok(r)
}
fn eval_with_order(
&mut self,
rho: &Array1<f64>,
order: OuterEvalOrder,
) -> Result<OuterEval, EstimationError> {
let r = self.inner.eval_with_order(rho, order)?;
self.note(rho, r.inner_beta_hint.as_ref(), r.cost);
Ok(r)
}
fn eval_efs(&mut self, rho: &Array1<f64>) -> Result<EfsEval, EstimationError> {
let r = self.inner.eval_efs(rho)?;
self.note(rho, None, r.cost);
Ok(r)
}
fn seed_inner_state(&mut self, beta: &Array1<f64>) -> Result<SeedOutcome, EstimationError> {
let result = self.inner.seed_inner_state(beta);
if matches!(result, Ok(SeedOutcome::Installed))
&& beta.iter().all(|v| v.is_finite())
&& let Ok(mut guard) = self.last_inner_beta.lock()
{
*guard = Some(beta.clone());
}
result
}
fn allow_continuation_prewarm(&self) -> bool {
self.inner.allow_continuation_prewarm()
}
fn requires_continuation_path_entry(&self) -> bool {
self.inner.requires_continuation_path_entry()
}
fn reset(&mut self) {
self.inner.reset();
}
}
pub struct ClosureObjective<
S,
Fc,
Fe,
Fr = fn(&mut S),
Fefs = fn(&mut S, &Array1<f64>) -> Result<EfsEval, EstimationError>,
Feo = fn(&mut S, &Array1<f64>, OuterEvalOrder) -> Result<OuterEval, EstimationError>,
Fsp = fn(&mut S, &Array1<f64>) -> Result<f64, EstimationError>,
Fseed = fn(&mut S, &Array1<f64>) -> Result<(), EstimationError>,
> {
pub(crate) state: S,
pub(crate) cap: OuterCapability,
pub(crate) cost_fn: Fc,
pub(crate) eval_fn: Fe,
pub(crate) eval_order_fn: Option<Feo>,
pub(crate) reset_fn: Option<Fr>,
pub(crate) efs_fn: Option<Fefs>,
pub(crate) screening_proxy_fn: Option<Fsp>,
pub(crate) seed_fn: Option<Fseed>,
pub(crate) continuation_prewarm: bool,
}
impl<S, Fc, Fe, Fr, Fefs, Feo, Fsp, Fseed> OuterObjective
for ClosureObjective<S, Fc, Fe, Fr, Fefs, Feo, Fsp, Fseed>
where
Fc: FnMut(&mut S, &Array1<f64>) -> Result<f64, EstimationError>,
Fe: FnMut(&mut S, &Array1<f64>) -> Result<OuterEval, EstimationError>,
Fr: FnMut(&mut S),
Fefs: FnMut(&mut S, &Array1<f64>) -> Result<EfsEval, EstimationError>,
Feo: FnMut(&mut S, &Array1<f64>, OuterEvalOrder) -> Result<OuterEval, EstimationError>,
Fsp: FnMut(&mut S, &Array1<f64>) -> Result<f64, EstimationError>,
Fseed: FnMut(&mut S, &Array1<f64>) -> Result<(), EstimationError>,
{
fn capability(&self) -> OuterCapability {
self.cap.clone()
}
fn eval_cost(&mut self, rho: &Array1<f64>) -> Result<f64, EstimationError> {
crate::solver::estimate::reml::runtime::record_current_outer_theta_for_ift(rho);
(self.cost_fn)(&mut self.state, rho)
}
fn eval_screening_proxy(&mut self, rho: &Array1<f64>) -> Result<f64, EstimationError> {
crate::solver::estimate::reml::runtime::record_current_outer_theta_for_ift(rho);
match self.screening_proxy_fn.as_mut() {
Some(f) => f(&mut self.state, rho),
None => (self.cost_fn)(&mut self.state, rho),
}
}
fn eval(&mut self, rho: &Array1<f64>) -> Result<OuterEval, EstimationError> {
crate::solver::estimate::reml::runtime::record_current_outer_theta_for_ift(rho);
(self.eval_fn)(&mut self.state, rho)
}
fn eval_with_order(
&mut self,
rho: &Array1<f64>,
order: OuterEvalOrder,
) -> Result<OuterEval, EstimationError> {
crate::solver::estimate::reml::runtime::record_current_outer_theta_for_ift(rho);
match self.eval_order_fn.as_mut() {
Some(f) => f(&mut self.state, rho, order),
None => (self.eval_fn)(&mut self.state, rho),
}
}
fn eval_efs(&mut self, rho: &Array1<f64>) -> Result<EfsEval, EstimationError> {
crate::solver::estimate::reml::runtime::record_current_outer_theta_for_ift(rho);
match self.efs_fn.as_mut() {
Some(f) => f(&mut self.state, rho),
None => Err(EstimationError::RemlOptimizationFailed(
"EFS evaluation not implemented for this objective".to_string(),
)),
}
}
fn seed_inner_state(&mut self, beta: &Array1<f64>) -> Result<SeedOutcome, EstimationError> {
if beta.is_empty() {
return Ok(SeedOutcome::Installed);
}
match self.seed_fn.as_mut() {
Some(f) => f(&mut self.state, beta).map(|()| SeedOutcome::Installed),
None => Ok(SeedOutcome::NoSlot),
}
}
fn allow_continuation_prewarm(&self) -> bool {
self.continuation_prewarm && self.seed_fn.is_some()
}
fn reset(&mut self) {
if let Some(f) = self.reset_fn.as_mut() {
f(&mut self.state);
}
}
}
impl<S, Fc, Fe, Fr, Fefs, Feo, Fsp> ClosureObjective<S, Fc, Fe, Fr, Fefs, Feo, Fsp>
where
Fc: FnMut(&mut S, &Array1<f64>) -> Result<f64, EstimationError>,
Fe: FnMut(&mut S, &Array1<f64>) -> Result<OuterEval, EstimationError>,
Fr: FnMut(&mut S),
Fefs: FnMut(&mut S, &Array1<f64>) -> Result<EfsEval, EstimationError>,
Feo: FnMut(&mut S, &Array1<f64>, OuterEvalOrder) -> Result<OuterEval, EstimationError>,
Fsp: FnMut(&mut S, &Array1<f64>) -> Result<f64, EstimationError>,
{
pub fn with_seed_inner_state<Fseed>(
self,
seed_fn: Fseed,
) -> ClosureObjective<S, Fc, Fe, Fr, Fefs, Feo, Fsp, Fseed>
where
Fseed: FnMut(&mut S, &Array1<f64>) -> Result<(), EstimationError>,
{
ClosureObjective {
state: self.state,
cap: self.cap,
cost_fn: self.cost_fn,
eval_fn: self.eval_fn,
eval_order_fn: self.eval_order_fn,
reset_fn: self.reset_fn,
efs_fn: self.efs_fn,
screening_proxy_fn: self.screening_proxy_fn,
seed_fn: Some(seed_fn),
continuation_prewarm: self.continuation_prewarm,
}
}
}
fn into_objective_error(context: &str, err: EstimationError) -> ObjectiveEvalError {
ObjectiveEvalError::recoverable(format!("{context}: {err}"))
}
fn finite_cost_or_error(context: &str, cost: f64) -> Result<f64, ObjectiveEvalError> {
if cost.is_finite() {
Ok(cost)
} else {
Err(ObjectiveEvalError::recoverable(format!(
"{context}: objective returned a non-finite cost"
)))
}
}
fn finite_outer_eval_or_error(
context: &str,
layout: OuterThetaLayout,
eval: OuterEval,
) -> Result<OuterEval, ObjectiveEvalError> {
layout.validate_gradient_len(&eval.gradient, context)?;
if !eval.cost.is_finite() {
return Err(ObjectiveEvalError::recoverable(format!(
"{context}: objective returned a non-finite cost"
)));
}
if !eval.gradient.iter().all(|v| v.is_finite()) {
return Err(ObjectiveEvalError::recoverable(format!(
"{context}: objective returned a non-finite gradient"
)));
}
match &eval.hessian {
HessianResult::Analytic(hessian) => {
layout.validate_hessian_shape(hessian, context)?;
if !hessian.iter().all(|v| v.is_finite()) {
return Err(ObjectiveEvalError::recoverable(format!(
"{context}: objective returned a non-finite Hessian"
)));
}
}
HessianResult::Operator(op) => {
if op.dim() != layout.n_params {
return Err(ObjectiveEvalError::recoverable(format!(
"{context}: outer Hessian operator dimension mismatch: got {}, expected {} (rho_dim={}, psi_dim={})",
op.dim(),
layout.n_params,
layout.rho_dim(),
layout.psi_dim
)));
}
}
HessianResult::Unavailable => {}
}
Ok(eval)
}
fn finite_outer_first_order_eval_or_error(
context: &str,
layout: OuterThetaLayout,
eval: OuterEval,
) -> Result<OuterEval, ObjectiveEvalError> {
layout.validate_gradient_len(&eval.gradient, context)?;
if !eval.cost.is_finite() {
return Err(ObjectiveEvalError::recoverable(format!(
"{context}: objective returned a non-finite cost"
)));
}
if !eval.gradient.iter().all(|v| v.is_finite()) {
return Err(ObjectiveEvalError::recoverable(format!(
"{context}: objective returned a non-finite gradient"
)));
}
Ok(eval)
}
fn validate_second_order_seed_hessian(
context: &str,
layout: OuterThetaLayout,
eval: &OuterEval,
) -> Result<(), ObjectiveEvalError> {
if layout.n_params > SECOND_ORDER_GEOMETRY_PROBE_MAX_PARAMS || !eval.hessian.is_analytic() {
return Ok(());
}
if matches!(
&eval.hessian,
HessianResult::Operator(op) if !op.materialization_capability().is_available()
) {
return Ok(());
}
let Some(hessian) = eval.hessian.materialize_dense().map_err(|message| {
ObjectiveEvalError::recoverable(format!(
"{context}: analytic outer Hessian materialization failed during second-order seed validation: {message}"
))
})?
else {
return Ok(());
};
layout.validate_hessian_shape(&hessian, context)?;
if !hessian.iter().all(|value| value.is_finite()) {
return Err(ObjectiveEvalError::recoverable(format!(
"{context}: analytic outer Hessian probe encountered non-finite entries"
)));
}
Ok(())
}
struct OuterFirstOrderBridge<'a> {
obj: &'a mut dyn OuterObjective,
layout: OuterThetaLayout,
outer_inner_cap: Option<InnerProgressFeedback>,
iter_count: usize,
g_norm_initial: Option<f64>,
last_g_norm: Option<f64>,
last_value_grad_rho: Option<Array1<f64>>,
value_probe_cache: Vec<ValueProbeCacheEntry>,
cost_stall: Option<CostStallGuard>,
consecutive_probe_refusals: usize,
}
const VALUE_PROBE_CACHE_CAPACITY: usize = 256;
const VALUE_PROBE_REJECT_COST_FLOOR: f64 = 1.0e11;
const PROBE_REFUSAL_FATAL_THRESHOLD: usize = 150;
const PROBE_REFUSAL_FATAL_THRESHOLD_NAN_SEED: usize = 25;
const PROBE_REFUSAL_FATAL_SENTINEL: &str = "OUTER_PROBE_REFUSAL_FATAL";
const COST_STALL_CONVERGED_SENTINEL: &str = "OUTER_COST_STALL_CONVERGED";
enum CostStallVerdict {
Continue,
Converged,
FlatValleyStall { residual_grad_norm: f64 },
}
const COST_STALL_WINDOW: usize = 6;
#[derive(Clone)]
struct CostStallExit {
rho: Array1<f64>,
value: f64,
grad_norm: f64,
iterations: usize,
converged: bool,
}
struct CostStallGuard {
rel_tol: f64,
window: usize,
grad_threshold: f64,
best_value: f64,
best_rho: Option<Array1<f64>>,
best_grad_norm: f64,
no_improve_streak: usize,
accepted_iters: usize,
exit: Arc<Mutex<Option<CostStallExit>>>,
}
impl CostStallGuard {
fn new(
rel_tol: f64,
window: usize,
grad_threshold: f64,
exit: Arc<Mutex<Option<CostStallExit>>>,
) -> Self {
Self {
rel_tol,
window,
grad_threshold,
best_value: f64::INFINITY,
best_rho: None,
best_grad_norm: f64::INFINITY,
no_improve_streak: 0,
accepted_iters: 0,
exit,
}
}
fn observe(&mut self, rho: &Array1<f64>, value: f64, grad_norm: f64) -> CostStallVerdict {
if !value.is_finite() {
self.no_improve_streak = 0;
return CostStallVerdict::Continue;
}
self.accepted_iters = self.accepted_iters.saturating_add(1);
let improvement = self.best_value - value;
let floor = self.rel_tol * (1.0 + self.best_value.abs());
if value < self.best_value {
self.best_value = value;
self.best_rho = Some(rho.clone());
self.best_grad_norm = grad_norm;
}
if improvement <= floor {
self.no_improve_streak = self.no_improve_streak.saturating_add(1);
} else {
self.no_improve_streak = 0;
}
if self.no_improve_streak < self.window {
return CostStallVerdict::Continue;
}
let best_rho = self.best_rho.clone().unwrap_or_else(|| rho.clone());
let best_value = if self.best_value.is_finite() {
self.best_value
} else {
value
};
let best_grad_norm = if self.best_grad_norm.is_finite() {
self.best_grad_norm
} else {
grad_norm
};
let converged = best_grad_norm.is_finite() && best_grad_norm <= self.grad_threshold;
if let Ok(mut slot) = self.exit.lock() {
*slot = Some(CostStallExit {
rho: best_rho,
value: best_value,
grad_norm: best_grad_norm,
iterations: self.accepted_iters,
converged,
});
}
if converged {
CostStallVerdict::Converged
} else {
CostStallVerdict::FlatValleyStall {
residual_grad_norm: best_grad_norm,
}
}
}
}
#[derive(Clone)]
struct ValueProbeCacheEntry {
rho: Array1<f64>,
outcome: CachedValueProbeOutcome,
}
#[derive(Clone)]
enum CachedValueProbeOutcome {
Cost(f64),
Recoverable(String),
Fatal(String),
}
fn trial_rho_distance(reference: Option<&Array1<f64>>, trial: &Array1<f64>) -> f64 {
let Some(reference) = reference else {
return f64::NAN;
};
if reference.len() != trial.len() {
return f64::NAN;
}
reference
.iter()
.zip(trial.iter())
.map(|(a, b)| {
let d = b - a;
d * d
})
.sum::<f64>()
.sqrt()
}
fn same_outer_point(a: &Array1<f64>, b: &Array1<f64>) -> bool {
a.len() == b.len()
&& a.iter()
.zip(b.iter())
.all(|(left, right)| left.to_bits() == right.to_bits())
}
fn cached_value_probe_result(outcome: &CachedValueProbeOutcome) -> Result<f64, ObjectiveEvalError> {
match outcome {
CachedValueProbeOutcome::Cost(cost) => Ok(*cost),
CachedValueProbeOutcome::Recoverable(message) => {
Err(ObjectiveEvalError::recoverable(message.clone()))
}
CachedValueProbeOutcome::Fatal(message) => Err(ObjectiveEvalError::Fatal {
message: message.clone(),
}),
}
}
fn cache_value_probe_result(result: &Result<f64, ObjectiveEvalError>) -> CachedValueProbeOutcome {
match result {
Ok(cost) => CachedValueProbeOutcome::Cost(*cost),
Err(ObjectiveEvalError::Recoverable { message }) => {
CachedValueProbeOutcome::Recoverable(message.clone())
}
Err(ObjectiveEvalError::Fatal { message }) => {
CachedValueProbeOutcome::Fatal(message.clone())
}
}
}
fn value_probe_outcome_label(outcome: &CachedValueProbeOutcome) -> &'static str {
match outcome {
CachedValueProbeOutcome::Cost(_) => "cost",
CachedValueProbeOutcome::Recoverable(_) => "recoverable",
CachedValueProbeOutcome::Fatal(_) => "fatal",
}
}
fn value_probe_reject_outcome(outcome: &CachedValueProbeOutcome) -> bool {
match outcome {
CachedValueProbeOutcome::Cost(cost) => *cost >= VALUE_PROBE_REJECT_COST_FLOOR,
CachedValueProbeOutcome::Recoverable(_) | CachedValueProbeOutcome::Fatal(_) => true,
}
}
fn remember_value_probe(
cache: &mut Vec<ValueProbeCacheEntry>,
rho: &Array1<f64>,
outcome: CachedValueProbeOutcome,
) {
if let Some(entry) = cache
.iter_mut()
.find(|entry| same_outer_point(&entry.rho, rho))
{
entry.outcome = outcome;
return;
}
if cache.len() == VALUE_PROBE_CACHE_CAPACITY {
cache.remove(0);
}
cache.push(ValueProbeCacheEntry {
rho: rho.clone(),
outcome,
});
}
impl ZerothOrderObjective for OuterFirstOrderBridge<'_> {
fn eval_cost(&mut self, x: &Array1<f64>) -> Result<f64, ObjectiveEvalError> {
if let Some(feedback) = self.outer_inner_cap.as_ref() {
feedback
.cap
.store(SEED_SCREENING_UNCAPPED, Ordering::Relaxed);
}
self.layout
.validate_point_len(x, "outer eval_cost failed")?;
let trial_rho_distance = trial_rho_distance(self.last_value_grad_rho.as_ref(), x);
let stage_start = std::time::Instant::now();
if let Some(entry) = self
.value_probe_cache
.iter()
.find(|entry| same_outer_point(&entry.rho, x))
{
let outcome_label = value_probe_outcome_label(&entry.outcome);
log::info!(
"[STAGE] outer eval start order=Value dim={} trial_rho_distance={:.3e} (first-order bridge, iter={}, cached=true)",
x.len(),
trial_rho_distance,
self.iter_count
);
match &entry.outcome {
CachedValueProbeOutcome::Cost(cost) => log::info!(
"[STAGE] outer eval end order=Value elapsed={:.3}s cost={:.6e} trial_rho_distance={:.3e} (first-order bridge, iter={}, cached=true)",
stage_start.elapsed().as_secs_f64(),
cost,
trial_rho_distance,
self.iter_count
),
CachedValueProbeOutcome::Recoverable(_) | CachedValueProbeOutcome::Fatal(_) => {
log::info!(
"[STAGE] outer eval end order=Value elapsed={:.3}s outcome={} trial_rho_distance={:.3e} (first-order bridge, iter={}, cached=true)",
stage_start.elapsed().as_secs_f64(),
outcome_label,
trial_rho_distance,
self.iter_count
);
}
}
return cached_value_probe_result(&entry.outcome);
}
log::info!(
"[STAGE] outer eval start order=Value dim={} trial_rho_distance={:.3e} (first-order bridge, iter={})",
x.len(),
trial_rho_distance,
self.iter_count
);
let result = self
.obj
.eval_with_order(x, OuterEvalOrder::Value)
.map_err(|err| into_objective_error("outer eval_cost failed", err))
.and_then(|eval| finite_cost_or_error("outer eval_cost failed", eval.cost));
let cached_outcome = cache_value_probe_result(&result);
remember_value_probe(&mut self.value_probe_cache, x, cached_outcome);
match &result {
Ok(cost) => {
self.consecutive_probe_refusals = 0;
log::info!(
"[STAGE] outer eval end order=Value elapsed={:.3}s cost={:.6e} trial_rho_distance={:.3e} (first-order bridge, iter={})",
stage_start.elapsed().as_secs_f64(),
cost,
trial_rho_distance,
self.iter_count
);
}
Err(ObjectiveEvalError::Recoverable { .. }) => {
log::info!(
"[STAGE] outer eval end order=Value elapsed={:.3}s outcome=recoverable trial_rho_distance={:.3e} (first-order bridge, iter={})",
stage_start.elapsed().as_secs_f64(),
trial_rho_distance,
self.iter_count
);
self.consecutive_probe_refusals =
self.consecutive_probe_refusals.saturating_add(1);
let threshold = if self.last_value_grad_rho.is_none() {
PROBE_REFUSAL_FATAL_THRESHOLD_NAN_SEED
} else {
PROBE_REFUSAL_FATAL_THRESHOLD
};
if self.iter_count == 0 && self.consecutive_probe_refusals >= threshold {
log::warn!(
"[OUTER] probe-refusal non-termination guard fired after {} consecutive \
infeasible cost probes with no accepted gradient step \
(nan_seed={}); escalating to Fatal to abort this seed \
(first-order bridge, iter={})",
self.consecutive_probe_refusals,
self.last_value_grad_rho.is_none(),
self.iter_count,
);
return Err(ObjectiveEvalError::Fatal {
message: format!(
"{PROBE_REFUSAL_FATAL_SENTINEL}: {consecutive} consecutive \
infeasible probes with no accepted outer step",
consecutive = self.consecutive_probe_refusals,
),
});
}
}
Err(ObjectiveEvalError::Fatal { .. }) => {
log::info!(
"[STAGE] outer eval end order=Value elapsed={:.3}s outcome=fatal trial_rho_distance={:.3e} (first-order bridge, iter={})",
stage_start.elapsed().as_secs_f64(),
trial_rho_distance,
self.iter_count
);
}
}
result
}
}
impl FirstOrderObjective for OuterFirstOrderBridge<'_> {
fn eval_grad(&mut self, x: &Array1<f64>) -> Result<FirstOrderSample, ObjectiveEvalError> {
self.layout.validate_point_len(x, "outer eval failed")?;
if let Some(feedback) = self.outer_inner_cap.as_ref() {
let g_ratio = match (self.last_g_norm, self.g_norm_initial) {
(Some(g), Some(g0)) if g0 > 0.0 => Some(g / g0),
_ => None,
};
let snapshot = feedback.snapshot();
let accepted_iter = feedback.accepted_iter.load(Ordering::Relaxed);
let cap = first_order_inner_cap_schedule(accepted_iter, g_ratio, snapshot);
let prev = feedback.cap.swap(cap, Ordering::Relaxed);
if prev != cap {
let ratio_str = match g_ratio {
Some(r) => format!("{:.3e}", r),
None => "n/a".to_string(),
};
let snap_str = match snapshot {
Some(s) => format!(
"last_iters={} converged={} ift_residual={} accept_rho={}",
s.last_iters,
s.last_converged,
match s.last_ift_residual {
Some(r) => format!("{:.3e}", r),
None => "n/a".to_string(),
},
match s.last_accept_rho {
Some(r) => format!("{:.3}", r),
None => "n/a".to_string(),
},
),
None => "no-history".to_string(),
};
log::info!(
"[OUTER schedule] inner-PIRLS cap transition accepted_iter={} eval_count={} g_ratio={} {} prev={} new={} ({})",
accepted_iter,
self.iter_count,
ratio_str,
snap_str,
prev,
cap,
if cap == 0 { "uncapped" } else { "capped" }
);
}
}
let stage_start = std::time::Instant::now();
log::info!(
"[STAGE] outer eval start order=ValueAndGradient dim={} (first-order bridge, iter={})",
x.len(),
self.iter_count
);
let eval = self
.obj
.eval_with_order(x, OuterEvalOrder::ValueAndGradient)
.map_err(|err| into_objective_error("outer eval failed", err))?;
let eval = finite_outer_first_order_eval_or_error("outer eval failed", self.layout, eval)?;
let g_norm = eval.gradient.iter().map(|v| v * v).sum::<f64>().sqrt();
let gradient = eval.gradient;
if self.g_norm_initial.is_none() && g_norm.is_finite() && g_norm > 0.0 {
self.g_norm_initial = Some(g_norm);
}
if g_norm.is_finite() {
self.last_g_norm = Some(g_norm);
}
self.last_value_grad_rho = Some(x.clone());
self.consecutive_probe_refusals = 0;
self.value_probe_cache
.retain(|entry| value_probe_reject_outcome(&entry.outcome));
log::info!(
"[STAGE] outer eval end order=ValueAndGradient elapsed={:.3}s cost={:.6e} |g|={:.3e} (first-order bridge, iter={})",
stage_start.elapsed().as_secs_f64(),
eval.cost,
g_norm,
self.iter_count,
);
crate::solver::visualizer::record_outer_eval(eval.cost, g_norm);
self.iter_count = self.iter_count.saturating_add(1);
if let Some(guard) = self.cost_stall.as_mut() {
match guard.observe(x, eval.cost, g_norm) {
CostStallVerdict::Continue => {}
CostStallVerdict::Converged => {
log::info!(
"[OUTER] cost-stall convergence: REML objective improved < {:.3e} \
(relative) over {} consecutive accepted outer steps AND the projected \
gradient cleared the outer tolerance (|g|={:.3e} <= {:.3e}); accepting \
best-so-far as a stationary optimum (value={:.6e}).",
guard.rel_tol,
guard.window,
guard.best_grad_norm,
guard.grad_threshold,
guard.best_value,
);
return Err(ObjectiveEvalError::Fatal {
message: COST_STALL_CONVERGED_SENTINEL.to_string(),
});
}
CostStallVerdict::FlatValleyStall { residual_grad_norm } => {
log::warn!(
"[OUTER] cost-stall FLAT-VALLEY STALL: REML objective improved < {:.3e} \
(relative) over {} consecutive accepted outer steps but the projected \
gradient is still ABOVE the outer tolerance (|g|={:.3e} > {:.3e}); \
halting on a weakly-identified ρ valley floor and reporting NON-CONVERGED \
(residual outer non-stationarity, value={:.6e}).",
guard.rel_tol,
guard.window,
residual_grad_norm,
guard.grad_threshold,
guard.best_value,
);
return Err(ObjectiveEvalError::Fatal {
message: COST_STALL_CONVERGED_SENTINEL.to_string(),
});
}
}
}
Ok(FirstOrderSample {
value: eval.cost,
gradient,
})
}
}
const INNER_CAP_CONVERGENCE_OVERRIDE_RATIO: f64 = 0.01;
const INNER_CAP_FLOOR: usize = 3;
const INNER_CAP_CEILING: usize = 64;
fn first_order_inner_cap_schedule(
iter_count: usize,
g_ratio: Option<f64>,
last: Option<InnerProgressSnapshot>,
) -> usize {
if matches!(g_ratio, Some(r) if r < INNER_CAP_CONVERGENCE_OVERRIDE_RATIO) {
return 0;
}
if let Some(snap) = last {
let next = if snap.last_converged {
let mut margin = match snap.last_ift_residual {
Some(r) if r < 0.01 => 1usize,
Some(r) if r >= 0.10 => 4usize,
_ => 2usize,
};
if matches!(snap.last_accept_rho, Some(r) if r < 0.5) {
margin = margin.saturating_add(2);
}
snap.last_iters.saturating_add(margin)
} else {
let multiplier = if matches!(snap.last_accept_rho, Some(r) if r < 0.3) {
3
} else {
2
};
snap.last_iters
.saturating_mul(multiplier)
.max(snap.last_iters.saturating_add(4))
};
return next.clamp(INNER_CAP_FLOOR, INNER_CAP_CEILING);
}
match iter_count {
0 => 3,
1 => 5,
_ => 10,
}
}
#[cfg(test)]
mod outer_inner_cap_schedule_tests {
use super::{InnerProgressSnapshot, first_order_inner_cap_schedule};
fn snap(last_iters: usize, last_converged: bool) -> Option<InnerProgressSnapshot> {
Some(InnerProgressSnapshot {
last_iters,
last_converged,
last_ift_residual: None,
last_accept_rho: None,
})
}
fn snap_with_accept_rho(
last_iters: usize,
last_converged: bool,
accept_rho: f64,
) -> Option<InnerProgressSnapshot> {
Some(InnerProgressSnapshot {
last_iters,
last_converged,
last_ift_residual: None,
last_accept_rho: Some(accept_rho),
})
}
fn snap_with_residual(
last_iters: usize,
last_converged: bool,
residual: f64,
) -> Option<InnerProgressSnapshot> {
Some(InnerProgressSnapshot {
last_iters,
last_converged,
last_ift_residual: Some(residual),
last_accept_rho: None,
})
}
#[test]
fn snapshot_distinguishes_zero_residual_from_no_signal() {
use super::InnerProgressFeedback;
use crate::solver::estimate::reml::runtime::IFT_RESIDUAL_NO_SIGNAL_BITS;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize};
let make_feedback =
|iters: usize, converged: bool, residual_bits: u64| InnerProgressFeedback {
cap: Arc::new(AtomicUsize::new(0)),
accepted_iter: Arc::new(AtomicUsize::new(0)),
last_iters: Arc::new(AtomicUsize::new(iters)),
last_converged: Arc::new(AtomicBool::new(converged)),
ift_residual: Arc::new(AtomicU64::new(residual_bits)),
accept_rho: Arc::new(AtomicU64::new(
crate::solver::estimate::reml::runtime::IFT_RESIDUAL_NO_SIGNAL_BITS,
)),
};
let fb = make_feedback(5, true, IFT_RESIDUAL_NO_SIGNAL_BITS);
let snap = fb.snapshot().expect("iters > 0, snapshot present");
assert!(
snap.last_ift_residual.is_none(),
"sentinel must decode to None"
);
let fb = make_feedback(5, true, 0.0_f64.to_bits());
let snap = fb.snapshot().expect("iters > 0, snapshot present");
assert_eq!(
snap.last_ift_residual,
Some(0.0),
"residual of exactly 0.0 must round-trip as a real signal, \
not be confused with the no-signal sentinel",
);
let fb = make_feedback(5, true, 0.05_f64.to_bits());
let snap = fb.snapshot().expect("snapshot present");
assert_eq!(snap.last_ift_residual, Some(0.05));
let fb = make_feedback(0, false, IFT_RESIDUAL_NO_SIGNAL_BITS);
assert!(fb.snapshot().is_none());
}
#[test]
fn schedule_falls_back_to_iter_tier_without_feedback() {
assert_eq!(first_order_inner_cap_schedule(0, None, None), 3);
assert_eq!(first_order_inner_cap_schedule(1, None, None), 5);
assert_eq!(first_order_inner_cap_schedule(2, None, None), 10);
assert_eq!(first_order_inner_cap_schedule(20, None, None), 10);
}
#[test]
fn schedule_uses_last_iters_plus_margin_when_converged() {
assert_eq!(first_order_inner_cap_schedule(2, None, snap(4, true)), 6);
assert_eq!(first_order_inner_cap_schedule(5, None, snap(12, true)), 14);
}
#[test]
fn schedule_geometric_backoff_when_last_hit_cap() {
assert_eq!(first_order_inner_cap_schedule(2, None, snap(5, false)), 10);
assert_eq!(first_order_inner_cap_schedule(2, None, snap(1, false)), 5);
assert_eq!(first_order_inner_cap_schedule(2, None, snap(30, false)), 60);
}
#[test]
fn schedule_clamps_floor_and_ceiling() {
assert_eq!(first_order_inner_cap_schedule(2, None, snap(0, true)), 3);
assert_eq!(first_order_inner_cap_schedule(2, None, snap(100, true)), 64);
}
#[test]
fn schedule_uncaps_when_outer_converged() {
assert_eq!(first_order_inner_cap_schedule(0, Some(0.0001), None), 0);
assert_eq!(
first_order_inner_cap_schedule(0, Some(0.005), snap(4, true)),
0
);
assert_eq!(
first_order_inner_cap_schedule(20, Some(0.001), snap(50, false)),
0
);
}
#[test]
fn schedule_ignores_modest_g_ratio_decay() {
assert_eq!(
first_order_inner_cap_schedule(2, Some(0.30), snap(4, true)),
6
);
assert_eq!(
first_order_inner_cap_schedule(2, Some(0.05), snap(4, true)),
6
);
}
#[test]
fn schedule_uses_ift_residual_to_pick_margin() {
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_residual(4, true, 0.005)),
5
);
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_residual(4, true, 0.0001)),
5
);
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_residual(4, true, 0.05)),
6
);
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_residual(4, true, 0.20)),
8
);
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_residual(4, true, 0.80)),
8
);
let residuals = [0.001, 0.05, 0.30];
let caps: Vec<usize> = residuals
.iter()
.map(|&r| first_order_inner_cap_schedule(2, None, snap_with_residual(4, true, r)))
.collect();
for w in caps.windows(2) {
assert!(
w[0] <= w[1],
"ift-residual margin policy regressed monotonicity: {caps:?}"
);
}
}
#[test]
fn schedule_bumps_margin_on_poor_lm_accept_rho() {
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_accept_rho(4, true, 0.95)),
6
);
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_accept_rho(4, true, 0.5)),
6
);
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_accept_rho(4, true, 0.4)),
8
);
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_accept_rho(4, true, 0.1)),
8
);
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_accept_rho(4, true, 0.49)),
8
);
}
#[test]
fn schedule_escalates_geometric_backoff_on_very_poor_accept_rho() {
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: false,
last_ift_residual: None,
last_accept_rho: Some(0.15),
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 12);
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: false,
last_ift_residual: None,
last_accept_rho: Some(0.4),
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 8);
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: false,
last_ift_residual: None,
last_accept_rho: Some(0.9),
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 8);
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: false,
last_ift_residual: None,
last_accept_rho: None,
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 8);
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: false,
last_ift_residual: None,
last_accept_rho: Some(0.3),
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 8);
}
#[test]
fn schedule_skips_lm_accept_rho_bump_when_signal_absent() {
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: true,
last_ift_residual: None,
last_accept_rho: None,
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 6);
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: true,
last_ift_residual: None,
last_accept_rho: Some(0.5),
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 6);
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: true,
last_ift_residual: None,
last_accept_rho: Some(1.0),
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 6);
}
#[test]
fn schedule_combines_ift_residual_and_lm_accept_rho() {
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: true,
last_ift_residual: Some(0.30),
last_accept_rho: Some(0.20),
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 10);
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: true,
last_ift_residual: Some(0.005),
last_accept_rho: Some(0.30),
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 7);
}
}
struct OuterSecondOrderBridge<'a> {
obj: &'a mut dyn OuterObjective,
layout: OuterThetaLayout,
hessian_source: HessianSource,
materialize_operator_max_dim: usize,
eval_count: usize,
outer_inner_cap: Option<InnerProgressFeedback>,
g_norm_initial: Option<f64>,
last_g_norm: Option<f64>,
last_value_grad_rho: Option<Array1<f64>>,
}
impl ZerothOrderObjective for OuterSecondOrderBridge<'_> {
fn eval_cost(&mut self, x: &Array1<f64>) -> Result<f64, ObjectiveEvalError> {
if let Some(feedback) = self.outer_inner_cap.as_ref() {
feedback
.cap
.store(SEED_SCREENING_UNCAPPED, Ordering::Relaxed);
}
self.layout
.validate_point_len(x, "outer eval_cost failed")?;
let trial_rho_distance = trial_rho_distance(self.last_value_grad_rho.as_ref(), x);
let stage_start = std::time::Instant::now();
log::info!(
"[STAGE] outer eval start order=Value dim={} trial_rho_distance={:.3e}",
x.len(),
trial_rho_distance
);
let eval = self
.obj
.eval_with_order(x, OuterEvalOrder::Value)
.map_err(|err| into_objective_error("outer eval_cost failed", err))?;
let cost = finite_cost_or_error("outer eval_cost failed", eval.cost)?;
log::info!(
"[STAGE] outer eval end order=Value elapsed={:.3}s cost={:.6e} trial_rho_distance={:.3e}",
stage_start.elapsed().as_secs_f64(),
cost,
trial_rho_distance
);
Ok(cost)
}
}
impl FirstOrderObjective for OuterSecondOrderBridge<'_> {
fn eval_grad(&mut self, x: &Array1<f64>) -> Result<FirstOrderSample, ObjectiveEvalError> {
self.layout.validate_point_len(x, "outer eval failed")?;
if let Some(feedback) = self.outer_inner_cap.as_ref() {
let arc_iter = feedback.accepted_iter.load(Ordering::Relaxed);
let g_ratio = match (self.last_g_norm, self.g_norm_initial) {
(Some(g), Some(g0)) if g0 > 0.0 => Some(g / g0),
_ => None,
};
let snapshot = feedback.snapshot();
let cap = first_order_inner_cap_schedule(arc_iter, g_ratio, snapshot);
let prev = feedback.cap.swap(cap, Ordering::Relaxed);
if prev != cap {
let ratio_str = match g_ratio {
Some(r) => format!("{:.3e}", r),
None => "n/a".to_string(),
};
let snap_str = match snapshot {
Some(s) => format!(
"last_iters={} converged={} ift_residual={} accept_rho={}",
s.last_iters,
s.last_converged,
match s.last_ift_residual {
Some(r) => format!("{:.3e}", r),
None => "n/a".to_string(),
},
match s.last_accept_rho {
Some(r) => format!("{:.3}", r),
None => "n/a".to_string(),
},
),
None => "no-history".to_string(),
};
log::info!(
"[OUTER schedule] inner-PIRLS cap transition (ARC bridge) arc_iter={} g_ratio={} {} prev={} new={} ({})",
arc_iter,
ratio_str,
snap_str,
prev,
cap,
if cap == 0 { "uncapped" } else { "capped" }
);
}
}
let stage_start = std::time::Instant::now();
log::info!(
"[STAGE] outer eval start order=ValueAndGradient dim={}",
x.len()
);
let eval = self
.obj
.eval_with_order(x, OuterEvalOrder::ValueAndGradient)
.map_err(|err| into_objective_error("outer eval failed", err))?;
let eval = finite_outer_first_order_eval_or_error("outer eval failed", self.layout, eval)?;
self.eval_count += 1;
let g_norm = eval.gradient.iter().map(|v| v * v).sum::<f64>().sqrt();
if self.g_norm_initial.is_none() && g_norm.is_finite() && g_norm > 0.0 {
self.g_norm_initial = Some(g_norm);
}
if g_norm.is_finite() {
self.last_g_norm = Some(g_norm);
}
self.last_value_grad_rho = Some(x.clone());
log::info!(
"[STAGE] outer eval end order=ValueAndGradient elapsed={:.3}s cost={:.6e} |g|={:.3e}",
stage_start.elapsed().as_secs_f64(),
eval.cost,
g_norm,
);
log::info!(
"[OUTER] eval#{n} (grad) cost={cost:.6e} |g|={gnorm:.3e} rho=[{rho}]",
n = self.eval_count,
cost = eval.cost,
gnorm = g_norm,
rho = x
.iter()
.map(|v| format!("{v:.3}"))
.collect::<Vec<_>>()
.join(","),
);
crate::solver::visualizer::record_outer_eval(eval.cost, g_norm);
Ok(FirstOrderSample {
value: eval.cost,
gradient: eval.gradient,
})
}
}
impl SecondOrderObjective for OuterSecondOrderBridge<'_> {
fn eval_hessian(&mut self, x: &Array1<f64>) -> Result<SecondOrderSample, ObjectiveEvalError> {
self.layout.validate_point_len(x, "outer eval failed")?;
if let Some(feedback) = self.outer_inner_cap.as_ref() {
let arc_iter = feedback.accepted_iter.load(Ordering::Relaxed);
let g_ratio = match (self.last_g_norm, self.g_norm_initial) {
(Some(g), Some(g0)) if g0 > 0.0 => Some(g / g0),
_ => None,
};
let snapshot = feedback.snapshot();
let cap = first_order_inner_cap_schedule(arc_iter, g_ratio, snapshot);
let prev = feedback.cap.swap(cap, Ordering::Relaxed);
if prev != cap {
let ratio_str = match g_ratio {
Some(r) => format!("{:.3e}", r),
None => "n/a".to_string(),
};
let snap_str = match snapshot {
Some(s) => format!(
"last_iters={} converged={} ift_residual={} accept_rho={}",
s.last_iters,
s.last_converged,
match s.last_ift_residual {
Some(r) => format!("{:.3e}", r),
None => "n/a".to_string(),
},
match s.last_accept_rho {
Some(r) => format!("{:.3}", r),
None => "n/a".to_string(),
},
),
None => "no-history".to_string(),
};
log::info!(
"[OUTER schedule] inner-PIRLS cap transition (ARC bridge) arc_iter={} g_ratio={} {} prev={} new={} ({})",
arc_iter,
ratio_str,
snap_str,
prev,
cap,
if cap == 0 { "uncapped" } else { "capped" }
);
}
}
let stage_start = std::time::Instant::now();
log::info!(
"[STAGE] outer eval start order=ValueGradientHessian dim={}",
x.len()
);
let eval = self
.obj
.eval_with_order(x, OuterEvalOrder::ValueGradientHessian)
.map_err(|err| into_objective_error("outer eval failed", err))?;
let eval = finite_outer_eval_or_error("outer eval failed", self.layout, eval)?;
self.eval_count += 1;
let g_norm = eval.gradient.iter().map(|v| v * v).sum::<f64>().sqrt();
if self.g_norm_initial.is_none() && g_norm.is_finite() && g_norm > 0.0 {
self.g_norm_initial = Some(g_norm);
}
if g_norm.is_finite() {
self.last_g_norm = Some(g_norm);
}
self.last_value_grad_rho = Some(x.clone());
log::info!(
"[STAGE] outer eval end order=ValueGradientHessian elapsed={:.3}s cost={:.6e} |g|={:.3e}",
stage_start.elapsed().as_secs_f64(),
eval.cost,
g_norm,
);
log::info!(
"[OUTER] eval#{n} (hess) cost={cost:.6e} |g|={gnorm:.3e} rho=[{rho}]",
n = self.eval_count,
cost = eval.cost,
gnorm = g_norm,
rho = x
.iter()
.map(|v| format!("{v:.3}"))
.collect::<Vec<_>>()
.join(","),
);
let hessian = build_bridge_hessian_for_source(
self.hessian_source,
eval.hessian,
self.materialize_operator_max_dim,
)?;
Ok(SecondOrderSample {
value: eval.cost,
gradient: eval.gradient,
hessian,
})
}
}
struct OuterAcceptObserver {
feedback: InnerProgressFeedback,
}
impl OptimizerObserver for OuterAcceptObserver {
fn on_step_accepted(&mut self, info: &StepInfo) {
log::trace!(
"outer step accepted iter={} step_norm={:.3e} predicted_decrease={:.3e} actual_decrease={:.3e}",
info.iter,
info.step_norm,
info.predicted_decrease,
info.actual_decrease,
);
self.feedback.accepted_iter.fetch_add(1, Ordering::Relaxed);
}
}
struct OuterToOptHessianOperator(Arc<dyn OuterHessianOperator>);
impl HessianOperator for OuterToOptHessianOperator {
fn dim(&self) -> usize {
self.0.dim()
}
fn apply_into(&self, v: &Array1<f64>, out: &mut Array1<f64>) -> Result<(), ObjectiveEvalError> {
self.0
.apply_into(v, out)
.map_err(|message| ObjectiveEvalError::Fatal {
message: format!("outer Hessian operator apply_into failed: {message}"),
})
}
fn apply_mat(&self, x: ArrayView2<'_, f64>) -> Result<Array2<f64>, ObjectiveEvalError> {
self.0
.mul_mat(x)
.map_err(|message| ObjectiveEvalError::Fatal {
message: format!("outer Hessian operator mul_mat failed: {message}"),
})
}
fn materialization(&self) -> HessianMaterialization {
match self.0.materialization_capability() {
OuterHessianMaterialization::Unavailable => HessianMaterialization::Unavailable,
OuterHessianMaterialization::RepeatedHvp => HessianMaterialization::RepeatedHvp,
OuterHessianMaterialization::BatchedHvp => HessianMaterialization::BatchedHvp,
OuterHessianMaterialization::Explicit => HessianMaterialization::Explicit,
}
}
fn materialize_dense(&self) -> Result<Array2<f64>, ObjectiveEvalError> {
self.0
.materialize_dense()
.map_err(|message| ObjectiveEvalError::Fatal {
message: format!("outer Hessian operator materialization failed: {message}"),
})
}
}
fn hessian_result_to_value(hessian: HessianResult) -> HessianValue {
match hessian {
HessianResult::Analytic(h) => HessianValue::Dense(h),
HessianResult::Operator(op) => {
HessianValue::Operator(Arc::new(OuterToOptHessianOperator(op)))
}
HessianResult::Unavailable => HessianValue::Unavailable,
}
}
struct OuterOperatorBridge<'a> {
obj: &'a mut dyn OuterObjective,
layout: OuterThetaLayout,
outer_inner_cap: Option<InnerProgressFeedback>,
eval_count: usize,
g_norm_initial: Option<f64>,
last_g_norm: Option<f64>,
last_value_grad_rho: Option<Array1<f64>>,
}
impl ZerothOrderObjective for OuterOperatorBridge<'_> {
fn eval_cost(&mut self, x: &Array1<f64>) -> Result<f64, ObjectiveEvalError> {
if let Some(feedback) = self.outer_inner_cap.as_ref() {
feedback
.cap
.store(SEED_SCREENING_UNCAPPED, Ordering::Relaxed);
}
self.layout
.validate_point_len(x, "outer eval_cost failed")?;
let trial_rho_distance = trial_rho_distance(self.last_value_grad_rho.as_ref(), x);
let stage_start = std::time::Instant::now();
log::info!(
"[STAGE] outer eval start order=Value dim={} trial_rho_distance={:.3e} (operator bridge)",
x.len(),
trial_rho_distance
);
let eval = self
.obj
.eval_with_order(x, OuterEvalOrder::Value)
.map_err(|err| into_objective_error("outer eval_cost failed", err))?;
let cost = finite_cost_or_error("outer eval_cost failed", eval.cost)?;
log::info!(
"[STAGE] outer eval end order=Value elapsed={:.3}s cost={:.6e} trial_rho_distance={:.3e} (operator bridge)",
stage_start.elapsed().as_secs_f64(),
cost,
trial_rho_distance
);
Ok(cost)
}
}
impl FirstOrderObjective for OuterOperatorBridge<'_> {
fn eval_grad(&mut self, x: &Array1<f64>) -> Result<FirstOrderSample, ObjectiveEvalError> {
self.layout.validate_point_len(x, "outer eval failed")?;
let eval = self
.obj
.eval_with_order(x, OuterEvalOrder::ValueAndGradient)
.map_err(|err| into_objective_error("outer eval failed", err))?;
let eval = finite_outer_first_order_eval_or_error("outer eval failed", self.layout, eval)?;
let g_norm = eval.gradient.iter().map(|v| v * v).sum::<f64>().sqrt();
if self.g_norm_initial.is_none() && g_norm.is_finite() && g_norm > 0.0 {
self.g_norm_initial = Some(g_norm);
}
if g_norm.is_finite() {
self.last_g_norm = Some(g_norm);
}
self.last_value_grad_rho = Some(x.clone());
Ok(FirstOrderSample {
value: eval.cost,
gradient: eval.gradient,
})
}
}
impl OperatorObjective for OuterOperatorBridge<'_> {
fn eval_value_grad_op(
&mut self,
x: &Array1<f64>,
) -> Result<OperatorSample, ObjectiveEvalError> {
self.layout.validate_point_len(x, "outer eval failed")?;
if let Some(feedback) = self.outer_inner_cap.as_ref() {
let g_ratio = match (self.last_g_norm, self.g_norm_initial) {
(Some(g), Some(g0)) if g0 > 0.0 => Some(g / g0),
_ => None,
};
let snapshot = feedback.snapshot();
let cap = first_order_inner_cap_schedule(self.eval_count, g_ratio, snapshot);
let previous_cap = feedback.cap.swap(cap, Ordering::Relaxed);
if previous_cap != cap {
log::trace!("outer operator bridge updated inner cap from {previous_cap} to {cap}");
}
}
let stage_start = std::time::Instant::now();
log::info!(
"[STAGE] outer eval start order=ValueGradientHessian dim={} (operator bridge)",
x.len(),
);
let eval = self
.obj
.eval_with_order(x, OuterEvalOrder::ValueGradientHessian)
.map_err(|err| into_objective_error("outer eval failed", err))?;
let eval = finite_outer_eval_or_error("outer eval failed", self.layout, eval)?;
self.eval_count += 1;
let g_norm = eval.gradient.iter().map(|v| v * v).sum::<f64>().sqrt();
if self.g_norm_initial.is_none() && g_norm.is_finite() && g_norm > 0.0 {
self.g_norm_initial = Some(g_norm);
}
if g_norm.is_finite() {
self.last_g_norm = Some(g_norm);
}
self.last_value_grad_rho = Some(x.clone());
log::info!(
"[STAGE] outer eval end elapsed={:.3}s cost={:.6e} |g|={:.3e} (operator bridge)",
stage_start.elapsed().as_secs_f64(),
eval.cost,
g_norm,
);
Ok(OperatorSample {
value: eval.cost,
gradient: eval.gradient,
hessian: hessian_result_to_value(eval.hessian),
})
}
}
#[inline]
fn project_to_bounds(x: &Array1<f64>, bounds: Option<&(Array1<f64>, Array1<f64>)>) -> Array1<f64> {
match bounds {
Some((lower, upper)) => {
let mut out = x.clone();
for idx in 0..out.len() {
out[idx] = out[idx].clamp(lower[idx], upper[idx]);
}
out
}
None => x.clone(),
}
}
fn build_bridge_hessian_for_source(
source: HessianSource,
hessian: HessianResult,
materialize_operator_max_dim: usize,
) -> Result<Option<Array2<f64>>, ObjectiveEvalError> {
match source {
HessianSource::Analytic => match hessian {
HessianResult::Analytic(h) => Ok(Some(h)),
HessianResult::Operator(op)
if op.materialization_capability().is_available()
&& op.dim() <= materialize_operator_max_dim =>
{
op.materialize_dense()
.map(Some)
.map_err(|message| ObjectiveEvalError::Fatal {
message: format!(
"outer Hessian operator materialization failed: {message}"
),
})
}
HessianResult::Operator(op) => Err(ObjectiveEvalError::Fatal {
message: format!(
"outer plan declared HessianSource::Analytic but the runtime returned a \
non-materializable Hessian operator (dim={}, materialization={:?}); \
finite-difference Hessian estimation is not permitted on the analytic route",
op.dim(),
op.materialization_capability(),
),
}),
HessianResult::Unavailable => Err(ObjectiveEvalError::Fatal {
message: "outer plan declared HessianSource::Analytic but the runtime returned \
HessianResult::Unavailable; finite-difference Hessian estimation is \
not permitted on the analytic route"
.to_string(),
}),
},
HessianSource::BfgsApprox
| HessianSource::EfsFixedPoint
| HessianSource::HybridEfsFixedPoint => Ok(None),
}
}
struct OuterFixedPointBridge<'a> {
obj: &'a mut dyn OuterObjective,
layout: OuterThetaLayout,
barrier_config: Option<BarrierConfig>,
fixed_point_tolerance: f64,
consecutive_psi_zero_iters: usize,
}
impl OuterFixedPointBridge<'_> {
fn reject_nonstationary_tiny_psi_step(
&self,
step: &Array1<f64>,
psi_indices: Option<&[usize]>,
psi_gradient: Option<&Array1<f64>>,
cost: f64,
) -> Result<(), ObjectiveEvalError> {
let Some(psi_indices) = psi_indices else {
return Ok(());
};
let Some(psi_gradient) = psi_gradient else {
return Ok(());
};
let psi_step_inf = psi_indices
.iter()
.map(|&idx| step[idx].abs())
.fold(0.0_f64, f64::max);
let psi_grad_inf = psi_gradient.iter().map(|v| v.abs()).fold(0.0_f64, f64::max);
if psi_step_inf <= self.fixed_point_tolerance && psi_grad_inf > self.fixed_point_tolerance {
return Err(ObjectiveEvalError::recoverable(format!(
"{} HybridEFS ψ nonstationary: ||Δψ||∞={:.3e} <= tol={:.3e} \
but raw ||gψ||∞={:.3e} (rho_dim={}, psi_dim={}, n_params={}, cost={:.6e})",
EFS_FIRST_ORDER_FALLBACK_MARKER,
psi_step_inf,
self.fixed_point_tolerance,
psi_grad_inf,
self.layout.rho_dim(),
self.layout.psi_dim,
self.layout.n_params,
cost,
)));
}
Ok(())
}
}
const MAX_EFS_BACKTRACK: usize = 8;
const EFS_NEGLIGIBLE_STEP: f64 = 1e-12;
const EFS_LINESEARCH_THRESHOLD: f64 = 0.5;
const EFS_COST_DESCENT_TOL: f64 = 1e-12;
const MAX_CONSECUTIVE_PSI_STAGNATION: usize = 1;
impl FixedPointObjective for OuterFixedPointBridge<'_> {
fn eval_step(&mut self, x: &Array1<f64>) -> Result<FixedPointSample, ObjectiveEvalError> {
self.layout.validate_point_len(x, "outer EFS eval failed")?;
let eval = match self.obj.eval_efs(x) {
Ok(eval) => eval,
Err(err @ EstimationError::GradientUnavailable { .. })
if requests_immediate_first_order_fallback(&err.to_string()) =>
{
log::warn!(
"[STAGE] EFS -> gradient fallback: gradient unavailable at \
fixed-point dispatch; retrying with fixed-point disabled \
(rho_dim={}, psi_dim={}, n_params={})",
self.layout.rho_dim(),
self.layout.psi_dim,
self.layout.n_params,
);
return Err(ObjectiveEvalError::recoverable(format!(
"outer EFS eval failed: {err}"
)));
}
Err(err) => return Err(into_objective_error("outer EFS eval failed", err)),
};
self.layout
.validate_efs_eval(&eval, "outer EFS eval failed")?;
if !eval.cost.is_finite() {
return Err(ObjectiveEvalError::recoverable(
"outer EFS eval failed: objective returned a non-finite cost".to_string(),
));
}
if let Some((idx, value)) = eval.steps.iter().enumerate().find(|(_, v)| !v.is_finite()) {
let psi_indices = eval.psi_indices.as_deref();
let coord_kind = match psi_indices {
Some(indices) if indices.contains(&idx) => "ψ",
Some(_) => "ρ/τ",
None => "ρ",
};
return Err(ObjectiveEvalError::recoverable(format!(
"outer EFS eval failed: non-finite {coord_kind} step at coord {idx} \
(step[{idx}]={value}, rho_dim={}, psi_dim={}, n_params={}, cost={:.6e})",
self.layout.rho_dim(),
self.layout.psi_dim,
self.layout.n_params,
eval.cost,
)));
}
if let Some(ref barrier_cfg) = self.barrier_config
&& let Some(ref beta) = eval.beta
{
const LOCAL_CONCENTRATION_RATIO: f64 = 0.1;
const BARRIER_CURVATURE_SATURATION: f64 = 1.0;
const BARRIER_CURVATURE_RELATIVE_THRESHOLD: f64 = 0.05;
if let Some(hessian_scale) = eval.inner_hessian_scale
&& hessian_scale.is_finite()
&& hessian_scale > 0.0
&& barrier_cfg.barrier_curvature_is_significant(
beta,
hessian_scale,
BARRIER_CURVATURE_RELATIVE_THRESHOLD,
)
{
return Err(ObjectiveEvalError::recoverable(format!(
"{} EFS barrier curvature significant relative to inner Hessian \
(rho_dim={}, psi_dim={}, n_params={}, cost={:.6e}, ref_diag={:.3e})",
EFS_FIRST_ORDER_FALLBACK_MARKER,
self.layout.rho_dim(),
self.layout.psi_dim,
self.layout.n_params,
eval.cost,
hessian_scale,
)));
}
if barrier_cfg.barrier_curvature_locally_concentrated(
beta,
LOCAL_CONCENTRATION_RATIO,
BARRIER_CURVATURE_SATURATION,
) {
return Err(ObjectiveEvalError::recoverable(format!(
"{} EFS barrier curvature locally concentrated \
(rho_dim={}, psi_dim={}, n_params={}, cost={:.6e})",
EFS_FIRST_ORDER_FALLBACK_MARKER,
self.layout.rho_dim(),
self.layout.psi_dim,
self.layout.n_params,
eval.cost,
)));
}
}
let status = FixedPointStatus::Continue;
let raw_step = Array1::from_vec(eval.steps);
let psi_indices = eval.psi_indices.clone();
self.reject_nonstationary_tiny_psi_step(
&raw_step,
psi_indices.as_deref(),
eval.psi_gradient.as_ref(),
eval.cost,
)?;
let max_step_abs = raw_step.iter().map(|s| s.abs()).fold(0.0_f64, f64::max);
let current_cost = eval.cost;
if self.fixed_point_step_converged(x, &raw_step, psi_indices.as_deref()) {
if psi_indices.is_some() {
self.consecutive_psi_zero_iters = 0;
}
return Ok(FixedPointSample {
value: current_cost,
step: raw_step,
status: FixedPointStatus::Stop,
});
}
if max_step_abs < EFS_NEGLIGIBLE_STEP {
if psi_indices.is_some() {
self.consecutive_psi_zero_iters = 0;
}
return Ok(FixedPointSample {
value: current_cost,
step: raw_step,
status,
});
}
if self.barrier_config.is_none() && max_step_abs < EFS_LINESEARCH_THRESHOLD {
if psi_indices.is_some() {
self.consecutive_psi_zero_iters = 0;
}
return Ok(FixedPointSample {
value: current_cost,
step: raw_step,
status,
});
}
if let Some(scaled) = self.efs_backtrack(x, &raw_step, current_cost, MAX_EFS_BACKTRACK)? {
if psi_indices.is_some() {
self.consecutive_psi_zero_iters = 0;
}
return Ok(FixedPointSample {
value: current_cost,
step: scaled,
status,
});
}
if let Some(psi_idx) = psi_indices.as_ref() {
let mut rho_only = raw_step.clone();
for &i in psi_idx {
rho_only[i] = 0.0;
}
let max_rho_abs = rho_only.iter().map(|s| s.abs()).fold(0.0_f64, f64::max);
if max_rho_abs >= EFS_NEGLIGIBLE_STEP
&& let Some(scaled) =
self.efs_backtrack(x, &rho_only, current_cost, MAX_EFS_BACKTRACK)?
{
self.consecutive_psi_zero_iters = self.consecutive_psi_zero_iters.saturating_add(1);
log::info!(
"[HYBRID-EFS] full-vector backtrack exhausted; ρ/τ-only step \
accepted. Consecutive ψ-zero iters = {}",
self.consecutive_psi_zero_iters,
);
if self.consecutive_psi_zero_iters >= MAX_CONSECUTIVE_PSI_STAGNATION {
log::info!(
"[STAGE] HybridEFS -> joint gradient (BFGS/L-BFGS) fallback: \
{} consecutive ψ-zero iterations after exhausted backtracking \
(rho_dim={}, psi_dim={}, n_params={}, cost={:.6e})",
self.consecutive_psi_zero_iters,
self.layout.rho_dim(),
self.layout.psi_dim,
self.layout.n_params,
current_cost,
);
return Err(ObjectiveEvalError::recoverable(format!(
"{} HybridEFS ψ stagnation: {} consecutive iterations \
exhausted backtracking and zeroed ψ step \
(rho_dim={}, psi_dim={}, n_params={}, cost={:.6e})",
EFS_FIRST_ORDER_FALLBACK_MARKER,
self.consecutive_psi_zero_iters,
self.layout.rho_dim(),
self.layout.psi_dim,
self.layout.n_params,
current_cost,
)));
}
return Ok(FixedPointSample {
value: current_cost,
step: scaled,
status,
});
}
log::info!(
"[STAGE] HybridEFS -> joint gradient fallback: ρ/τ-only step also \
failed all {} halvings (rho_dim={}, psi_dim={}, n_params={}, \
cost={:.6e})",
MAX_EFS_BACKTRACK,
self.layout.rho_dim(),
self.layout.psi_dim,
self.layout.n_params,
current_cost,
);
return Err(ObjectiveEvalError::recoverable(format!(
"{} HybridEFS step rejected after {} halvings on full vector \
and {} halvings on ρ/τ-only fallback \
(rho_dim={}, psi_dim={}, n_params={}, cost={:.6e})",
EFS_FIRST_ORDER_FALLBACK_MARKER,
MAX_EFS_BACKTRACK,
MAX_EFS_BACKTRACK,
self.layout.rho_dim(),
self.layout.psi_dim,
self.layout.n_params,
current_cost,
)));
}
log::info!(
"[STAGE] EFS -> gradient fallback: no α ∈ {{1, …, 2^-{}}} decreased the \
cost (rho_dim={}, n_params={}, cost={:.6e})",
MAX_EFS_BACKTRACK,
self.layout.rho_dim(),
self.layout.n_params,
current_cost,
);
Err(ObjectiveEvalError::recoverable(format!(
"{} EFS step rejected after {} halvings on pure-ρ vector \
(rho_dim={}, n_params={}, cost={:.6e})",
EFS_FIRST_ORDER_FALLBACK_MARKER,
MAX_EFS_BACKTRACK,
self.layout.rho_dim(),
self.layout.n_params,
current_cost,
)))
}
}
impl OuterFixedPointBridge<'_> {
fn efs_backtrack(
&mut self,
x: &Array1<f64>,
raw_step: &Array1<f64>,
current_cost: f64,
max_halvings: usize,
) -> Result<Option<Array1<f64>>, ObjectiveEvalError> {
let cost_floor = current_cost + EFS_COST_DESCENT_TOL * current_cost.abs().max(1.0);
let mut alpha = 1.0_f64;
for bt in 0..=max_halvings {
let trial_step = raw_step * alpha;
let trial = x + &trial_step;
match self.obj.eval_cost(&trial) {
Ok(c) if c.is_finite() && c <= cost_floor => {
if bt > 0 {
log::debug!(
"[EFS] backtrack accepted at α=2^-{bt}={alpha:.4e} \
after {bt} halvings (cost: {current_cost:.6e} → {c:.6e})"
);
}
return Ok(Some(trial_step));
}
Ok(c) => {
log::trace!(
"[EFS] backtrack α=2^-{bt}={alpha:.4e}: trial cost {c:.6e} \
not below current {current_cost:.6e}, halving"
);
}
Err(err) => {
log::trace!(
"[EFS] backtrack α=2^-{bt}={alpha:.4e}: trial eval failed \
({err}), halving"
);
}
}
alpha *= 0.5;
}
Ok(None)
}
fn fixed_point_step_converged(
&self,
x: &Array1<f64>,
step: &Array1<f64>,
psi_indices: Option<&[usize]>,
) -> bool {
if x.len() != step.len() {
return false;
}
for idx in 0..step.len() {
let scale = match psi_indices {
Some(indices) if indices.contains(&idx) => x[idx].abs().max(1.0),
_ => 1.0,
};
let normalized = step[idx].abs() / scale;
if !normalized.is_finite() || normalized > self.fixed_point_tolerance {
return false;
}
}
true
}
}
fn solution_into_outer_result(
solution: Solution,
converged: bool,
plan_used: OuterPlan,
) -> OuterResult {
let mut result = OuterResult::new(
solution.final_point,
solution.final_value,
solution.iterations,
converged,
plan_used,
);
result.final_grad_norm = solution.final_gradient_norm;
result.final_gradient = solution.final_gradient;
result.final_hessian = solution.final_hessian;
result
}
fn outer_result_with_gradient_norm(
rho: Array1<f64>,
final_value: f64,
iterations: usize,
final_grad_norm: Option<f64>,
converged: bool,
plan_used: OuterPlan,
) -> OuterResult {
let mut result = OuterResult::new(rho, final_value, iterations, converged, plan_used);
result.final_grad_norm = final_grad_norm;
result
}
fn outer_result_with_gradient(
rho: Array1<f64>,
final_value: f64,
iterations: usize,
final_grad_norm: Option<f64>,
final_gradient: Option<Array1<f64>>,
converged: bool,
plan_used: OuterPlan,
) -> OuterResult {
let mut result = outer_result_with_gradient_norm(
rho,
final_value,
iterations,
final_grad_norm,
converged,
plan_used,
);
result.final_gradient = final_gradient;
result
}
use crate::inference::diagnostics::format_top_abs as format_top_abs_components;
fn bfgs_line_search_failure_message(
context: &str,
solution: &Solution,
max_attempts: usize,
failure_reason: impl std::fmt::Debug,
) -> String {
let grad_norm = solution
.final_gradient_norm
.or_else(|| {
solution
.final_gradient
.as_ref()
.map(|gradient| gradient.iter().map(|v| v * v).sum::<f64>().sqrt())
})
.unwrap_or(f64::NAN);
let gradient_detail = solution
.final_gradient
.as_ref()
.map(|gradient| format_top_abs_components(gradient, "top_abs_gradient", 6))
.unwrap_or_else(|| "top_abs_gradient=<unavailable>".to_string());
format!(
"{context}: BFGS line search failed; reason={failure_reason:?} \
max_attempts={max_attempts} iterations={} final_value={:.6e} \
|g|={:.3e} func_evals={} grad_evals={} {} {}",
solution.iterations,
solution.final_value,
grad_norm,
solution.func_evals,
solution.grad_evals,
format_top_abs_components(&solution.final_point, "top_abs_rho", 6),
gradient_detail,
)
}
#[derive(Clone, Debug)]
struct OuterConfig {
tolerance: f64,
max_iter: usize,
bounds: Option<(Array1<f64>, Array1<f64>)>,
seed_config: crate::seeding::SeedConfig,
rho_bound: f64,
heuristic_lambdas: Option<Vec<f64>>,
initial_rho: Option<Array1<f64>>,
fallback_policy: FallbackPolicy,
screening_cap: Option<Arc<AtomicUsize>>,
screen_initial_rho: bool,
outer_inner_cap: Option<InnerProgressFeedback>,
operator_initial_trust_radius: Option<f64>,
arc_initial_regularization: Option<f64>,
objective_scale: Option<f64>,
bfgs_step_cap: Option<f64>,
bfgs_step_cap_psi: Option<f64>,
cache_session: Option<Arc<CacheSession>>,
cache_mirror_sessions: Vec<Arc<CacheSession>>,
rho_uncertainty_problem_size: crate::inference::rho_uncertainty::RhoUncertaintyProblemSize,
}
impl Default for OuterConfig {
fn default() -> Self {
Self {
tolerance: 1e-5,
max_iter: 200,
bounds: None,
seed_config: crate::seeding::SeedConfig::default(),
rho_bound: 30.0,
heuristic_lambdas: None,
initial_rho: None,
fallback_policy: FallbackPolicy::Automatic,
screening_cap: None,
screen_initial_rho: false,
outer_inner_cap: None,
operator_initial_trust_radius: None,
arc_initial_regularization: None,
objective_scale: None,
bfgs_step_cap: None,
bfgs_step_cap_psi: None,
cache_session: None,
cache_mirror_sessions: Vec::new(),
rho_uncertainty_problem_size:
crate::inference::rho_uncertainty::RhoUncertaintyProblemSize::default(),
}
}
}
pub struct OuterProblem {
n_params: usize,
gradient: Derivative,
hessian: DeclaredHessianForm,
prefer_gradient_only: bool,
disable_fixed_point: bool,
psi_dim: usize,
barrier_config: Option<BarrierConfig>,
tolerance: f64,
max_iter: usize,
bounds: Option<(Array1<f64>, Array1<f64>)>,
rho_bound: f64,
seed_config: crate::seeding::SeedConfig,
heuristic_lambdas: Option<Vec<f64>>,
initial_rho: Option<Array1<f64>>,
fallback_policy: FallbackPolicy,
screening_cap: Option<Arc<AtomicUsize>>,
screen_initial_rho: bool,
outer_inner_cap: Option<InnerProgressFeedback>,
operator_initial_trust_radius: Option<f64>,
arc_initial_regularization: Option<f64>,
objective_scale: Option<f64>,
bfgs_step_cap: Option<f64>,
bfgs_step_cap_psi: Option<f64>,
cache_session: Option<Arc<CacheSession>>,
cache_mirror_sessions: Vec<Arc<CacheSession>>,
rho_uncertainty_problem_size: crate::inference::rho_uncertainty::RhoUncertaintyProblemSize,
continuation_prewarm: bool,
}
impl OuterProblem {
pub fn new(n_params: usize) -> Self {
Self {
n_params,
gradient: Derivative::Unavailable,
hessian: DeclaredHessianForm::Unavailable,
prefer_gradient_only: false,
disable_fixed_point: false,
psi_dim: 0,
barrier_config: None,
tolerance: 1e-5,
max_iter: 200,
bounds: None,
rho_bound: 30.0,
seed_config: crate::seeding::SeedConfig::default(),
heuristic_lambdas: None,
initial_rho: None,
fallback_policy: FallbackPolicy::Automatic,
screening_cap: None,
screen_initial_rho: false,
outer_inner_cap: None,
operator_initial_trust_radius: None,
arc_initial_regularization: None,
objective_scale: None,
bfgs_step_cap: None,
bfgs_step_cap_psi: None,
cache_session: None,
cache_mirror_sessions: Vec::new(),
rho_uncertainty_problem_size:
crate::inference::rho_uncertainty::RhoUncertaintyProblemSize::default(),
continuation_prewarm: true,
}
}
pub fn with_gradient(mut self, d: Derivative) -> Self {
self.gradient = d;
self
}
pub fn with_hessian(mut self, form: DeclaredHessianForm) -> Self {
self.hessian = form;
self
}
pub fn with_prefer_gradient_only(mut self, prefer_gradient_only: bool) -> Self {
self.prefer_gradient_only = prefer_gradient_only;
self
}
pub fn with_disable_fixed_point(mut self, disable: bool) -> Self {
self.disable_fixed_point = disable;
self
}
pub fn with_psi_dim(mut self, dim: usize) -> Self {
self.psi_dim = dim;
self
}
pub fn with_barrier(mut self, cfg: Option<BarrierConfig>) -> Self {
self.barrier_config = cfg;
self
}
pub fn with_tolerance(mut self, tol: f64) -> Self {
self.tolerance = tol;
self
}
pub fn with_max_iter(mut self, n: usize) -> Self {
self.max_iter = n;
self
}
pub fn with_bounds(mut self, lo: Array1<f64>, hi: Array1<f64>) -> Self {
self.bounds = Some((lo, hi));
self
}
pub fn with_rho_bound(mut self, b: f64) -> Self {
self.rho_bound = b;
self
}
pub fn with_seed_config(mut self, sc: crate::seeding::SeedConfig) -> Self {
self.seed_config = sc;
self
}
pub fn with_heuristic_lambdas(mut self, h: Vec<f64>) -> Self {
self.heuristic_lambdas = Some(h);
self
}
pub fn with_initial_rho(mut self, rho: Array1<f64>) -> Self {
self.initial_rho = Some(rho);
self
}
pub fn with_continuation_prewarm(mut self, enabled: bool) -> Self {
self.continuation_prewarm = enabled;
self
}
pub fn with_screening_cap(mut self, screening_cap: Arc<AtomicUsize>) -> Self {
self.screening_cap = Some(screening_cap);
self
}
pub fn with_screen_initial_rho(mut self, screen_initial_rho: bool) -> Self {
self.screen_initial_rho = screen_initial_rho;
self
}
pub fn with_outer_inner_cap(mut self, feedback: InnerProgressFeedback) -> Self {
self.outer_inner_cap = Some(feedback);
self
}
pub fn with_operator_initial_trust_radius(mut self, radius: Option<f64>) -> Self {
self.operator_initial_trust_radius = sanitized_operator_trust_restart_radius(radius);
self
}
pub fn with_arc_initial_regularization(mut self, sigma: Option<f64>) -> Self {
self.arc_initial_regularization = sigma.filter(|v| v.is_finite() && *v > 0.0);
self
}
pub fn with_objective_scale(mut self, scale: Option<f64>) -> Self {
self.objective_scale = scale.filter(|v| v.is_finite() && *v > 0.0);
self
}
pub fn with_bfgs_step_cap(mut self, cap: Option<f64>) -> Self {
self.bfgs_step_cap = cap.filter(|v| v.is_finite() && *v > 0.0);
self
}
pub fn with_bfgs_step_cap_psi(mut self, cap: Option<f64>) -> Self {
self.bfgs_step_cap_psi = cap.filter(|v| v.is_finite() && *v > 0.0);
self
}
pub fn with_cache_session(mut self, session: Arc<CacheSession>) -> Self {
self.cache_session = Some(session);
self
}
pub fn with_cache_mirror_sessions(mut self, sessions: Vec<Arc<CacheSession>>) -> Self {
self.cache_mirror_sessions = sessions;
self
}
pub fn with_problem_size(mut self, n_obs: usize, p_coefficients: usize) -> Self {
self.rho_uncertainty_problem_size =
crate::inference::rho_uncertainty::RhoUncertaintyProblemSize {
n_obs: Some(n_obs),
p_coefficients: Some(p_coefficients),
};
self
}
pub fn with_fallback_policy(mut self, policy: FallbackPolicy) -> Self {
self.fallback_policy = policy;
self
}
fn capability(&self) -> OuterCapability {
OuterCapability {
gradient: self.gradient,
hessian: self.hessian,
prefer_gradient_only: self.prefer_gradient_only,
disable_fixed_point: self.disable_fixed_point,
n_params: self.n_params,
psi_dim: self.psi_dim,
fixed_point_available: false,
barrier_config: self.barrier_config.clone(),
}
}
fn config(&self) -> OuterConfig {
OuterConfig {
tolerance: self.tolerance,
max_iter: self.max_iter,
bounds: self.bounds.clone(),
seed_config: self.seed_config,
rho_bound: self.rho_bound,
heuristic_lambdas: self.heuristic_lambdas.clone(),
initial_rho: self.initial_rho.clone(),
fallback_policy: self.fallback_policy,
screening_cap: self.screening_cap.clone(),
screen_initial_rho: self.screen_initial_rho,
outer_inner_cap: self.outer_inner_cap.clone(),
operator_initial_trust_radius: self.operator_initial_trust_radius,
arc_initial_regularization: self.arc_initial_regularization,
objective_scale: self.objective_scale,
bfgs_step_cap: self.bfgs_step_cap,
bfgs_step_cap_psi: self.bfgs_step_cap_psi,
cache_session: self.cache_session.clone(),
cache_mirror_sessions: self.cache_mirror_sessions.clone(),
rho_uncertainty_problem_size: self.rho_uncertainty_problem_size,
}
}
pub fn build_objective<S, Fc, Fe, Fr, Fefs>(
&self,
state: S,
cost_fn: Fc,
eval_fn: Fe,
reset_fn: Option<Fr>,
efs_fn: Option<Fefs>,
) -> ClosureObjective<S, Fc, Fe, Fr, Fefs>
where
Fc: FnMut(&mut S, &Array1<f64>) -> Result<f64, EstimationError>,
Fe: FnMut(&mut S, &Array1<f64>) -> Result<OuterEval, EstimationError>,
Fr: FnMut(&mut S),
Fefs: FnMut(&mut S, &Array1<f64>) -> Result<EfsEval, EstimationError>,
{
let mut cap = self.capability();
cap.fixed_point_available = efs_fn.is_some();
ClosureObjective {
state,
cap,
cost_fn,
eval_fn,
eval_order_fn: None,
reset_fn,
efs_fn,
screening_proxy_fn: None::<fn(&mut S, &Array1<f64>) -> Result<f64, EstimationError>>,
seed_fn: None::<fn(&mut S, &Array1<f64>) -> Result<(), EstimationError>>,
continuation_prewarm: self.continuation_prewarm,
}
}
pub fn build_objective_with_eval_order<S, Fc, Fe, Feo, Fr, Fefs>(
&self,
state: S,
cost_fn: Fc,
eval_fn: Fe,
eval_order_fn: Feo,
reset_fn: Option<Fr>,
efs_fn: Option<Fefs>,
) -> ClosureObjective<S, Fc, Fe, Fr, Fefs, Feo>
where
Fc: FnMut(&mut S, &Array1<f64>) -> Result<f64, EstimationError>,
Fe: FnMut(&mut S, &Array1<f64>) -> Result<OuterEval, EstimationError>,
Feo: FnMut(&mut S, &Array1<f64>, OuterEvalOrder) -> Result<OuterEval, EstimationError>,
Fr: FnMut(&mut S),
Fefs: FnMut(&mut S, &Array1<f64>) -> Result<EfsEval, EstimationError>,
{
let mut cap = self.capability();
cap.fixed_point_available = efs_fn.is_some();
ClosureObjective {
state,
cap,
cost_fn,
eval_fn,
eval_order_fn: Some(eval_order_fn),
reset_fn,
efs_fn,
screening_proxy_fn: None::<fn(&mut S, &Array1<f64>) -> Result<f64, EstimationError>>,
seed_fn: None::<fn(&mut S, &Array1<f64>) -> Result<(), EstimationError>>,
continuation_prewarm: self.continuation_prewarm,
}
}
pub fn build_objective_with_screening_proxy<S, Fc, Fe, Feo, Fr, Fefs, Fsp>(
&self,
state: S,
cost_fn: Fc,
eval_fn: Fe,
eval_order_fn: Feo,
reset_fn: Option<Fr>,
efs_fn: Option<Fefs>,
screening_proxy_fn: Fsp,
) -> ClosureObjective<S, Fc, Fe, Fr, Fefs, Feo, Fsp>
where
Fc: FnMut(&mut S, &Array1<f64>) -> Result<f64, EstimationError>,
Fe: FnMut(&mut S, &Array1<f64>) -> Result<OuterEval, EstimationError>,
Feo: FnMut(&mut S, &Array1<f64>, OuterEvalOrder) -> Result<OuterEval, EstimationError>,
Fr: FnMut(&mut S),
Fefs: FnMut(&mut S, &Array1<f64>) -> Result<EfsEval, EstimationError>,
Fsp: FnMut(&mut S, &Array1<f64>) -> Result<f64, EstimationError>,
{
let mut cap = self.capability();
cap.fixed_point_available = efs_fn.is_some();
ClosureObjective {
state,
cap,
cost_fn,
eval_fn,
eval_order_fn: Some(eval_order_fn),
reset_fn,
efs_fn,
screening_proxy_fn: Some(screening_proxy_fn),
seed_fn: None::<fn(&mut S, &Array1<f64>) -> Result<(), EstimationError>>,
continuation_prewarm: self.continuation_prewarm,
}
}
pub fn run(
&self,
obj: &mut dyn OuterObjective,
context: &str,
) -> Result<OuterResult, EstimationError> {
let mut config = self.config();
let Some(session) = config.cache_session.clone() else {
return run_outer(obj, &config, context);
};
let key_hex = session.key().to_hex();
let short_key = &key_hex[..8.min(key_hex.len())];
let mut had_hit = false;
let mut cached_inner_beta: Option<Array1<f64>> = None;
if let Some(loaded) = session.try_load_with_source() {
match classify_cache_entry_for_outer(&loaded, self.n_params) {
CacheSeedDecision::ExactFinal {
rho,
beta: _beta_final,
final_value,
iterations,
prior_obj_display,
} => {
let cap = primary_capability_for_config(obj.capability(), &config, context);
let plan_used = plan(&cap);
log::info!(
"[CACHE] final-hit key={}.. context={} rho_dim={} prior_obj={:.6e} iter={} action=skip-outer-validation",
short_key,
context,
rho.len(),
prior_obj_display,
iterations,
);
let mut result =
OuterResult::new(rho, final_value, iterations, true, plan_used);
result.rho_uncertainty_diagnostic = Some(compute_rho_uncertainty_diagnostic(
obj, &config, context, &result,
));
return Ok(result);
}
CacheSeedDecision::Seed {
rho,
beta,
prior_obj_display,
iteration,
} => {
let beta_len = beta.len();
let beta_arr = if beta.is_empty() {
None
} else {
Some(Array1::from_vec(beta))
};
if config
.initial_rho
.as_ref()
.is_none_or(|initial| initial != rho)
{
log::info!(
"[CACHE] hit key={}.. context={} rho_dim={} beta_dim={} prior_obj={:.6e} iter={}",
short_key,
context,
rho.len(),
beta_len,
prior_obj_display,
iteration,
);
config.initial_rho = Some(rho);
config.screen_initial_rho = false;
had_hit = true;
} else {
log::info!(
"[CACHE] hit key={}.. context={} rho_dim={} beta_dim={} already-aligned prior_obj={:.6e}",
short_key,
context,
rho.len(),
beta_len,
prior_obj_display,
);
had_hit = true;
}
cached_inner_beta = beta_arr;
}
CacheSeedDecision::Discard {
reason: "payload-shape-mismatch",
..
} => {
log::info!(
"[CACHE] skip key={}.. context={} reason=payload-shape-mismatch n_params={}",
short_key,
context,
self.n_params,
);
}
CacheSeedDecision::Discard {
reason,
prior_obj_display,
all_rho_finite,
} => {
log::info!(
"[CACHE] skip key={}.. context={} reason={} prior_obj={:.6e} all_rho_finite={}",
short_key,
context,
reason,
prior_obj_display,
all_rho_finite.unwrap_or(false),
);
}
}
} else {
log::info!(
"[CACHE] miss key={}.. context={} reason=fresh-fingerprint n_params={}",
short_key,
context,
self.n_params,
);
}
let mut checkpointing = CheckpointingObjective::new(
obj,
Arc::clone(&session),
config.cache_mirror_sessions.clone(),
);
if let Some(beta) = cached_inner_beta.as_ref() {
match checkpointing.seed_inner_state(beta) {
Ok(SeedOutcome::Installed) => log::info!(
"[CACHE] beta-warm key={}.. context={} beta_dim={} action=installed",
short_key,
context,
beta.len(),
),
Ok(SeedOutcome::NoSlot) => log::warn!(
"[CACHE] beta-warm key={}.. context={} beta_dim={} action=skip \
reason=objective_has_no_inner_beta_slot",
short_key,
context,
beta.len(),
),
Err(err) => log::warn!(
"[CACHE] beta-warm key={}.. context={} beta_dim={} action=skip err={}",
short_key,
context,
beta.len(),
err,
),
}
}
let result = run_outer(&mut checkpointing, &config, context);
let final_beta = checkpointing.last_inner_beta();
if let Ok(result) = result.as_ref()
&& result.final_value.is_finite()
&& result.converged
&& let Some(bytes) = encode_iterate(
&result.rho,
final_beta.as_ref(),
result.final_value,
result.iterations as u64,
)
{
let saved = session.finalize(
&bytes,
Some(result.final_value),
Some(result.iterations as u64),
);
if saved {
log::info!(
"[CACHE] save key={}.. context={} final_obj={:.6e} iter={} resumed={}",
short_key,
context,
result.final_value,
result.iterations,
had_hit,
);
}
for mirror in &config.cache_mirror_sessions {
let mirror_saved = mirror.finalize(
&bytes,
Some(result.final_value),
Some(result.iterations as u64),
);
if mirror_saved {
let mirror_hex = mirror.key().to_hex();
log::info!(
"[CACHE] save key={}.. context={} mirror final_obj={:.6e} iter={}",
&mirror_hex[..8.min(mirror_hex.len())],
context,
result.final_value,
result.iterations,
);
}
}
}
result
}
}
#[derive(Clone, Debug)]
pub struct OuterResult {
pub rho: Array1<f64>,
pub final_value: f64,
pub iterations: usize,
pub final_grad_norm: Option<f64>,
pub final_gradient: Option<Array1<f64>>,
pub final_hessian: Option<Array2<f64>>,
pub converged: bool,
pub plan_used: OuterPlan,
pub operator_trust_radius: Option<f64>,
pub operator_stop_reason: Option<OperatorTrustRegionStopReason>,
pub criterion_certificate: Option<CriterionCertificate>,
pub rho_uncertainty_diagnostic:
Option<crate::inference::rho_uncertainty::RhoUncertaintyDiagnostic>,
}
impl OuterResult {
pub fn new(
rho: Array1<f64>,
final_value: f64,
iterations: usize,
converged: bool,
plan_used: OuterPlan,
) -> Self {
Self {
rho,
final_value,
iterations,
final_grad_norm: None,
final_gradient: None,
final_hessian: None,
converged,
plan_used,
operator_trust_radius: None,
operator_stop_reason: None,
criterion_certificate: None,
rho_uncertainty_diagnostic: None,
}
}
pub fn final_grad_norm_report(&self) -> String {
match self.final_grad_norm {
Some(g) => format!("{g:.3e}"),
None => "n/a".to_string(),
}
}
}
const CERTIFICATE_Z_GATE: f64 = 4.0;
const CERTIFICATE_RELATIVE_GATE: f64 = 1e-3;
const CERTIFICATE_RAIL_MARGIN: f64 = 0.5;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct CriterionCertificate {
pub grad_norm: f64,
pub analytic_directional: f64,
pub fd_directional: f64,
pub fd_error: f64,
pub agreement_z: f64,
pub fd_step: f64,
pub hessian_pd: Option<bool>,
pub lambdas_railed: Vec<usize>,
}
impl CriterionCertificate {
pub fn first_order_consistent(&self) -> bool {
let diff = (self.analytic_directional - self.fd_directional).abs();
let scale = self
.analytic_directional
.abs()
.max(self.fd_directional.abs());
diff <= (CERTIFICATE_Z_GATE * self.fd_error).max(CERTIFICATE_RELATIVE_GATE * scale)
}
pub fn is_clean(&self) -> bool {
self.first_order_consistent()
&& self.hessian_pd != Some(false)
&& self.lambdas_railed.is_empty()
}
pub fn summary(&self) -> String {
format!(
"grad·v={:.6e} fd·v={:.6e}±{:.1e} z={:.2} |g|={:.3e} hessian_pd={} railed={:?} → {}",
self.analytic_directional,
self.fd_directional,
self.fd_error,
self.agreement_z,
self.grad_norm,
match self.hessian_pd {
Some(true) => "yes",
Some(false) => "NO",
None => "n/a",
},
self.lambdas_railed,
if self.first_order_consistent() {
"consistent"
} else {
"GRADIENT-OBJECTIVE DESYNC"
},
)
}
}
fn certificate_audit_direction(theta: &Array1<f64>, context: &str) -> Array1<f64> {
let mut seed: u64 = 0xcbf2_9ce4_8422_2325;
let mut fnv = |byte: u8| {
seed ^= u64::from(byte);
seed = seed.wrapping_mul(0x0000_0100_0000_01b3);
};
for byte in context.bytes() {
fnv(byte);
}
for &x in theta.iter() {
for byte in x.to_bits().to_le_bytes() {
fnv(byte);
}
}
let mut state = seed;
let mut next_unit = move || {
state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
let mut z = state;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
z ^= z >> 31;
((z >> 11) as f64 + 0.5) / (1u64 << 53) as f64
};
let mut direction = Array1::<f64>::zeros(theta.len());
let mut i = 0;
while i < direction.len() {
let (u1, u2) = (next_unit(), next_unit());
let radius = (-2.0 * u1.ln()).sqrt();
let angle = 2.0 * std::f64::consts::PI * u2;
direction[i] = radius * angle.cos();
if i + 1 < direction.len() {
direction[i + 1] = radius * angle.sin();
}
i += 2;
}
let norm = direction.dot(&direction).sqrt();
if norm.is_finite() && norm > f64::EPSILON {
direction.mapv_inplace(|v| v / norm);
direction
} else {
let mut fallback = Array1::<f64>::zeros(theta.len());
fallback[0] = 1.0;
fallback
}
}
fn certificate_hessian_is_pd(hessian: &Array2<f64>) -> Option<bool> {
let n = hessian.nrows();
if n == 0 || hessian.ncols() != n || hessian.iter().any(|v| !v.is_finite()) {
return None;
}
let mut chol = hessian.clone();
for j in 0..n {
for k in 0..j {
let l_jk = chol[[j, k]];
for i in j..n {
chol[[i, j]] -= chol[[i, k]] * l_jk;
}
}
let pivot = chol[[j, j]];
if !(pivot > 0.0) || !pivot.is_finite() {
return Some(false);
}
let inv_sqrt = 1.0 / pivot.sqrt();
for i in j..n {
chol[[i, j]] *= inv_sqrt;
}
}
Some(true)
}
fn certificate_railed_lambdas(
rho: &Array1<f64>,
rho_dim: usize,
config: &OuterConfig,
) -> Vec<usize> {
(0..rho_dim.min(rho.len()))
.filter(|&k| {
let (lo, hi) = match config.bounds.as_ref() {
Some((lo, hi)) if k < lo.len() && k < hi.len() => (lo[k], hi[k]),
Some(_) => return false,
None => (-config.rho_bound, config.rho_bound),
};
(rho[k] - lo).abs() <= CERTIFICATE_RAIL_MARGIN
|| (hi - rho[k]).abs() <= CERTIFICATE_RAIL_MARGIN
})
.collect()
}
fn audit_first_order_optimality(
obj: &mut dyn OuterObjective,
config: &OuterConfig,
context: &str,
result: &OuterResult,
) -> Option<CriterionCertificate> {
let gradient = result.final_gradient.as_ref()?;
if gradient.is_empty()
|| gradient.len() != result.rho.len()
|| gradient.iter().any(|g| !g.is_finite())
|| result.rho.iter().any(|r| !r.is_finite())
{
return None;
}
let theta = &result.rho;
let direction = certificate_audit_direction(theta, context);
let theta_scale = theta.iter().fold(0.0_f64, |acc, &v| acc.max(v.abs()));
let step = f64::EPSILON.cbrt() * (1.0 + theta_scale);
let mut probe = |scale: f64| -> Option<f64> {
let point = theta + &(scale * &direction);
match obj.eval_cost(&point) {
Ok(value) if value.is_finite() => Some(value),
Ok(value) => {
log::debug!(
"[CERTIFICATE] {context}: audit probe at θ̂{scale:+.3e}·v returned \
non-finite criterion value {value}; certificate skipped"
);
None
}
Err(err) => {
log::debug!(
"[CERTIFICATE] {context}: audit probe at θ̂{scale:+.3e}·v failed ({err}); \
certificate skipped"
);
None
}
}
};
let f_plus_h = probe(step)?;
let f_minus_h = probe(-step)?;
let f_plus_2h = probe(2.0 * step)?;
let f_minus_2h = probe(-2.0 * step)?;
let d_h = (f_plus_h - f_minus_h) / (2.0 * step);
let d_2h = (f_plus_2h - f_minus_2h) / (4.0 * step);
let fd_directional = (4.0 * d_h - d_2h) / 3.0;
let value_scale = f_plus_h
.abs()
.max(f_minus_h.abs())
.max(f_plus_2h.abs())
.max(f_minus_2h.abs());
let roundoff = f64::EPSILON * (1.0 + value_scale) / step;
let fd_error = (d_h - d_2h).abs().max(roundoff);
let analytic_directional = gradient.dot(&direction);
let grad_norm = gradient.dot(gradient).sqrt();
let agreement_z = (analytic_directional - fd_directional).abs() / fd_error;
let rho_dim = obj.capability().theta_layout().rho_dim();
let certificate = CriterionCertificate {
grad_norm,
analytic_directional,
fd_directional,
fd_error,
agreement_z,
fd_step: step,
hessian_pd: result
.final_hessian
.as_ref()
.and_then(certificate_hessian_is_pd),
lambdas_railed: certificate_railed_lambdas(theta, rho_dim, config),
};
if certificate.is_clean() {
log::info!("[CERTIFICATE] {context}: {}", certificate.summary());
} else {
log::warn!(
"[CERTIFICATE warning] {context}: optimality self-audit flagged the returned \
optimum — {}",
certificate.summary(),
);
}
Some(certificate)
}
fn compute_rho_uncertainty_diagnostic(
obj: &mut dyn OuterObjective,
config: &OuterConfig,
context: &str,
result: &OuterResult,
) -> crate::inference::rho_uncertainty::RhoUncertaintyDiagnostic {
let cap = obj.capability();
let layout = cap.theta_layout();
let rho_dim = layout.rho_dim();
let gate = crate::inference::rho_uncertainty::RhoUncertaintyCostGate {
sample_count: 32,
problem_size: config.rho_uncertainty_problem_size,
};
if let Err(reason) = crate::inference::rho_uncertainty::cost_gate_allows(rho_dim, gate) {
return crate::inference::rho_uncertainty::RhoUncertaintyDiagnostic::skipped(reason, 0);
}
if result.rho.len() != layout.n_params {
return crate::inference::rho_uncertainty::RhoUncertaintyDiagnostic::skipped(
format!(
"final outer point length {} does not match objective dimension {}",
result.rho.len(),
layout.n_params
),
0,
);
}
let final_eval = match obj.eval_with_order(&result.rho, OuterEvalOrder::ValueGradientHessian) {
Ok(eval) => eval,
Err(err) => {
return crate::inference::rho_uncertainty::RhoUncertaintyDiagnostic::skipped(
format!("final exact Hessian evaluation failed: {err}"),
1,
);
}
};
let hessian = match final_eval.hessian.materialize_dense() {
Ok(Some(hessian)) => hessian,
Ok(None) => {
return crate::inference::rho_uncertainty::RhoUncertaintyDiagnostic::skipped(
"exact outer Hessian unavailable at fitted rho",
1,
);
}
Err(message) => {
return crate::inference::rho_uncertainty::RhoUncertaintyDiagnostic::skipped(
format!("exact outer Hessian materialization failed: {message}"),
1,
);
}
};
if hessian.nrows() != layout.n_params || hessian.ncols() != layout.n_params {
return crate::inference::rho_uncertainty::RhoUncertaintyDiagnostic::skipped(
format!(
"exact outer Hessian shape {}x{} does not match objective dimension {}",
hessian.nrows(),
hessian.ncols(),
layout.n_params
),
1,
);
}
let mut hessian_rho = Array2::<f64>::zeros((rho_dim, rho_dim));
for row in 0..rho_dim {
for col in 0..rho_dim {
hessian_rho[[row, col]] = hessian[[row, col]];
}
}
let rho_hat = result.rho.slice(ndarray::s![..rho_dim]).to_owned();
let theta_hat = result.rho.clone();
let cost_hat = final_eval.cost;
let final_beta_hint = final_eval.inner_beta_hint.clone();
let diagnostic = {
let mut served_hat_cost = false;
let mut criterion = |rho: &Array1<f64>| -> Option<f64> {
let is_hat = rho.len() == rho_hat.len()
&& rho
.iter()
.zip(rho_hat.iter())
.all(|(&left, &right)| left.to_bits() == right.to_bits());
if is_hat && !served_hat_cost {
served_hat_cost = true;
return Some(cost_hat);
}
let mut theta = theta_hat.clone();
for idx in 0..rho_dim {
theta[idx] = rho[idx];
}
if let Some(beta) = final_beta_hint.as_ref()
&& obj.seed_inner_state(beta).is_err()
{
return None;
}
obj.eval_cost(&theta).ok()
};
crate::inference::rho_uncertainty::rho_uncertainty_diagnostic(
&rho_hat,
&hessian_rho,
gate,
&mut criterion,
)
};
if let Some(beta) = final_beta_hint.as_ref()
&& let Err(err) = obj.seed_inner_state(beta)
{
log::debug!(
"[RHO uncertainty] {context}: final inner-state restore skipped after diagnostic ({err})"
);
}
match &diagnostic.status {
crate::inference::rho_uncertainty::RhoUncertaintyStatus::NoEvidenceOfHeavyTails => {
log::info!(
"[RHO uncertainty] {context}: no heavy-tail evidence at sampled rho proposals k_hat={:.3} evals={}",
diagnostic.k_hat.unwrap_or(f64::NAN),
diagnostic.n_evaluations,
);
}
crate::inference::rho_uncertainty::RhoUncertaintyStatus::HeavyTailsDetected { k_hat } => {
log::warn!(
"[RHO uncertainty] {context}: heavy rho-importance tail detected k_hat={:.3} evals={}",
k_hat,
diagnostic.n_evaluations,
);
}
crate::inference::rho_uncertainty::RhoUncertaintyStatus::Skipped { reason } => {
log::info!("[RHO uncertainty] {context}: skipped ({reason})");
}
}
diagnostic
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OperatorTrustRegionStopReason {
Converged,
RejectFloor,
IterationBudget,
RoutingMismatch,
}
fn run_outer(
obj: &mut dyn OuterObjective,
config: &OuterConfig,
context: &str,
) -> Result<OuterResult, EstimationError> {
let mut result = run_outer_uncertified(obj, config, context)?;
result.criterion_certificate = audit_first_order_optimality(obj, config, context, &result);
result.rho_uncertainty_diagnostic = Some(compute_rho_uncertainty_diagnostic(
obj, config, context, &result,
));
Ok(result)
}
fn run_outer_uncertified(
obj: &mut dyn OuterObjective,
config: &OuterConfig,
context: &str,
) -> Result<OuterResult, EstimationError> {
let cap = primary_capability_for_config(obj.capability(), config, context);
cap.validate_layout(context)?;
if let Some(initial_rho) = config.initial_rho.as_ref() {
cap.theta_layout()
.validate_point_len(initial_rho, "initial outer seed")
.map_err(|err| match err {
ObjectiveEvalError::Recoverable { message }
| ObjectiveEvalError::Fatal { message } => {
EstimationError::RemlOptimizationFailed(format!("{context}: {message}"))
}
})?;
}
crate::solver::estimate::reml::runtime::clear_outer_ift_residual_energy_for_fit();
if let Some(result) = run_per_atom_efs_if_frontier(obj, config, context)? {
return Ok(result);
}
if cap.n_params == 0 {
let cost = obj.eval_cost(&Array1::zeros(0))?;
let the_plan = plan(&cap);
return Ok(outer_result_with_gradient_norm(
Array1::zeros(0),
cost,
0,
Some(0.0),
true,
the_plan,
));
}
let fallback_attempts = match config.fallback_policy {
FallbackPolicy::Automatic => automatic_fallback_attempts(&cap),
FallbackPolicy::Disabled => Vec::new(),
};
let mut attempts: Vec<OuterCapability> = Vec::with_capacity(1 + fallback_attempts.len());
attempts.push(cap.clone());
for degraded in fallback_attempts {
attempts.push(degraded);
}
let mut last_error: Option<EstimationError> = None;
for (attempt_idx, attempt_cap) in attempts.iter().enumerate() {
let the_plan = plan(attempt_cap);
if attempt_idx > 0 {
log::debug!("[OUTER] {context}: primary plan failed; falling back to {the_plan}");
}
log_plan(context, attempt_cap, &the_plan);
obj.reset();
let mut arc_retries_left: u32 = if matches!(the_plan.solver, Solver::Arc) {
2
} else {
0
};
let mut retry_config: Option<OuterConfig> = None;
let mut prev_attempt_grad_norm: Option<f64> = None;
let outcome = loop {
let active_config_owned: OuterConfig =
retry_config.clone().unwrap_or_else(|| config.clone());
let active_config: &OuterConfig = &active_config_owned;
match run_outer_with_plan(obj, active_config, context, attempt_cap, &the_plan) {
Ok(result) => {
if result.converged
|| arc_retries_left == 0
|| matches!(
result.operator_stop_reason,
Some(OperatorTrustRegionStopReason::RejectFloor)
)
{
break Ok(result);
}
let Some(cur_grad_norm) = result.final_grad_norm else {
log::info!(
"[OUTER] {context}: ARC attempt exhausted budget at \
iter={} cost={:.6e} without a final gradient norm; \
falling through to degraded plan",
result.iterations,
result.final_value,
);
break Ok(result);
};
if let Some(prev_g) = prev_attempt_grad_norm {
let progressed = cur_grad_norm.is_finite()
&& prev_g.is_finite()
&& cur_grad_norm < 0.5 * prev_g;
if !progressed {
log::info!(
"[OUTER] {context}: ARC retry stalled at \
iter={} cost={:.6e} |g|={:.6e} (prev |g|={:.6e}); \
deterministic replay suspected, falling through \
to degraded plan",
result.iterations,
result.final_value,
cur_grad_norm,
prev_g,
);
break Ok(result);
}
}
let next_trust_radius =
sanitized_operator_trust_restart_radius(result.operator_trust_radius);
log::info!(
"[OUTER] {context}: ARC attempt exhausted budget at \
iter={} cost={:.6e} |g|={:.6e}; resuming from last \
rho + trust_radius={:?}, inner-PIRLS uncapped \
(objective caches wiped; operator-TR Cauchy/Newton \
state is not resumable)",
result.iterations,
result.final_value,
cur_grad_norm,
next_trust_radius,
);
let cap_feedback = active_config.outer_inner_cap.clone();
let mut next = active_config.clone();
prev_attempt_grad_norm = Some(cur_grad_norm);
next.initial_rho = Some(result.rho.clone());
next.operator_initial_trust_radius = next_trust_radius;
retry_config = Some(next);
arc_retries_left -= 1;
obj.reset();
if let Some(feedback) = cap_feedback.as_ref() {
feedback.cap.store(0, Ordering::Relaxed);
}
}
Err(e) => break Err(e),
}
};
match outcome {
Ok(result) => {
if result.converged || attempt_idx + 1 == attempts.len() {
if !result.converged {
log::warn!(
"[OUTER warning] {context}: final outer attempt returned without convergence \
(plan={the_plan}, iterations={}, final_value={:.6e}, |g|={})",
result.iterations,
result.final_value,
result.final_grad_norm_report(),
);
}
return Ok(result);
}
let message = format!(
"{context}: attempt {} (plan={the_plan}) exhausted without convergence",
attempt_idx + 1
);
log::debug!("[OUTER] {message}; trying degraded fallback plan");
last_error = Some(EstimationError::RemlOptimizationFailed(message));
}
Err(e) => {
log::debug!(
"[OUTER] {context}: attempt {} (plan={the_plan}) failed: {e}",
attempt_idx + 1
);
last_error = Some(e);
}
}
}
Err(last_error.unwrap_or_else(|| {
EstimationError::RemlOptimizationFailed(format!("all plan attempts exhausted ({context})"))
}))
}
pub fn is_per_atom_efs_frontier(cap: &OuterCapability) -> bool {
crate::solver::estimate::reml::per_atom_efs::per_atom_efs_eligible(cap)
}
fn run_per_atom_efs_if_frontier(
obj: &mut dyn OuterObjective,
config: &OuterConfig,
context: &str,
) -> Result<Option<OuterResult>, EstimationError> {
let cap = primary_capability_for_config(obj.capability(), config, context);
cap.validate_layout(context)?;
if !is_per_atom_efs_frontier(&cap) {
return Ok(None);
}
let the_plan = plan(&cap);
let rho_dim = cap.theta_layout().rho_dim();
let (lower, upper) = outer_bounds_template(config, cap.n_params);
let seed = match config.initial_rho.as_ref() {
Some(initial) if initial.len() == cap.n_params => initial.clone(),
_ => {
let generated = crate::seeding::generate_rho_candidates(
cap.n_params,
config.heuristic_lambdas.as_deref(),
&config.seed_config,
);
match generated.into_iter().next() {
Some(first) => first,
None => Array1::<f64>::zeros(cap.n_params),
}
}
};
log::info!(
"[OUTER] {context}: frontier ρ-scaling (rho_dim={rho_dim}) → per-atom decoupled EFS primary"
);
let pa_cfg = crate::solver::estimate::reml::per_atom_efs::PerAtomEfsConfig::new(
config.tolerance,
config.max_iter,
lower,
upper,
);
let topology =
crate::solver::estimate::reml::per_atom_efs::SharedBorderTopology::disjoint(rho_dim);
obj.reset();
let result = crate::solver::estimate::reml::per_atom_efs::run_per_atom_efs(
obj, &seed, &pa_cfg, &topology,
)?;
Ok(Some(result.into_outer_result(the_plan)))
}
fn outer_bounds(lo: &Array1<f64>, hi: &Array1<f64>) -> Result<Bounds, EstimationError> {
Bounds::new(lo.clone(), hi.clone(), 1e-6).map_err(|err| {
EstimationError::InvalidInput(format!("outer rho bounds are invalid: {err}"))
})
}
fn outer_bounds_template(config: &OuterConfig, n: usize) -> (Array1<f64>, Array1<f64>) {
config.bounds.clone().unwrap_or_else(|| {
(
Array1::<f64>::from_elem(n, -config.rho_bound),
Array1::<f64>::from_elem(n, config.rho_bound),
)
})
}
fn outer_tolerance(value: f64) -> Result<Tolerance, EstimationError> {
Tolerance::new(value)
.map_err(|err| EstimationError::InvalidInput(format!("outer tolerance is invalid: {err}")))
}
fn outer_gradient_tolerance(config: &OuterConfig) -> GradientTolerance {
let abs = config
.objective_scale
.map(|scale| config.tolerance.max(scale * 1.0e-9))
.unwrap_or(config.tolerance);
GradientTolerance {
abs,
rel_initial_grad: None,
rel_cost: Some(config.tolerance),
projected: true,
}
}
fn outer_max_iterations(value: usize) -> Result<MaxIterations, EstimationError> {
MaxIterations::new(value)
.map_err(|err| EstimationError::InvalidInput(format!("outer max_iter is invalid: {err}")))
}
fn sanitized_operator_trust_restart_radius(radius: Option<f64>) -> Option<f64> {
radius
.filter(|value| value.is_finite() && *value > 0.0)
.map(|value| value.max(OPERATOR_TRUST_RESTART_RADIUS_FLOOR))
}
fn bfgs_axis_step_caps(config: &OuterConfig, layout: OuterThetaLayout) -> Option<Array1<f64>> {
if config.bfgs_step_cap.is_none() && config.bfgs_step_cap_psi.is_none() {
return None;
}
let mut caps = Array1::from_elem(layout.n_params, f64::INFINITY);
if let Some(cap) = config.bfgs_step_cap {
for i in 0..layout.rho_dim() {
caps[i] = cap;
}
}
if let Some(cap) = config.bfgs_step_cap_psi {
for i in layout.rho_dim()..layout.n_params {
caps[i] = cap;
}
}
Some(caps)
}
enum FixedPointOuterRunError {
SeedRejected(EstimationError),
ImmediateFallback(EstimationError),
Failed(EstimationError),
}
fn run_fixed_point_outer_solver(
obj: &mut dyn OuterObjective,
layout: OuterThetaLayout,
barrier_config: Option<BarrierConfig>,
config: &OuterConfig,
context: &str,
seed: &Array1<f64>,
the_plan: OuterPlan,
label: &str,
failure_prefix: &str,
) -> Result<OuterResult, FixedPointOuterRunError> {
let mut objective = OuterFixedPointBridge {
obj,
layout,
barrier_config,
fixed_point_tolerance: config.tolerance,
consecutive_psi_zero_iters: 0,
};
match objective.eval_step(seed) {
Ok(_) => {}
Err(err) => {
let err = match err {
ObjectiveEvalError::Recoverable { message }
| ObjectiveEvalError::Fatal { message } => {
EstimationError::RemlOptimizationFailed(message)
}
};
if requests_immediate_first_order_fallback(&err.to_string()) {
return Err(FixedPointOuterRunError::ImmediateFallback(err));
}
return Err(FixedPointOuterRunError::SeedRejected(err));
}
};
let (lo, hi) = outer_bounds_template(config, layout.n_params);
let bounds = outer_bounds(&lo, &hi).map_err(FixedPointOuterRunError::Failed)?;
let tol = outer_tolerance(config.tolerance).map_err(FixedPointOuterRunError::Failed)?;
let max_iter =
outer_max_iterations(config.max_iter).map_err(FixedPointOuterRunError::Failed)?;
let mut optimizer = FixedPoint::new(seed.clone(), objective)
.with_bounds(bounds)
.with_tolerance(tol)
.with_max_iterations(max_iter);
match optimizer.run() {
Ok(sol) => Ok(solution_into_outer_result(sol, true, the_plan)),
Err(FixedPointError::MaxIterationsReached { last_solution }) => {
log::warn!(
"[OUTER warning] {context}: {label} hit max_iter={} at final_value={:.6e} step_norm={:.3e}",
config.max_iter,
last_solution.final_value,
last_solution.final_gradient_norm.unwrap_or(f64::NAN),
);
Ok(solution_into_outer_result(*last_solution, false, the_plan))
}
Err(e) => Err(FixedPointOuterRunError::Failed(
EstimationError::RemlOptimizationFailed(format!("{failure_prefix}: {e:?}")),
)),
}
}