use super::*;
pub(crate) struct OuterFirstOrderBridge<'a> {
pub(crate) obj: &'a mut dyn OuterObjective,
pub(crate) layout: OuterThetaLayout,
pub(crate) outer_inner_cap: Option<InnerProgressFeedback>,
pub(crate) iter_count: usize,
pub(crate) g_norm_initial: Option<f64>,
pub(crate) last_g_norm: Option<f64>,
pub(crate) last_value_grad_rho: Option<Array1<f64>>,
pub(crate) value_probe_cache: Vec<ValueProbeCacheEntry>,
pub(crate) cost_stall: Option<CostStallGuard>,
pub(crate) cost_stall_bounds: Option<(Array1<f64>, Array1<f64>)>,
pub(crate) consecutive_probe_refusals: usize,
}
pub(crate) const VALUE_PROBE_CACHE_CAPACITY: usize = 256;
pub(crate) const VALUE_PROBE_REJECT_COST_FLOOR: f64 = 1.0e11;
pub(crate) const PROBE_REFUSAL_FATAL_THRESHOLD: usize = 150;
pub(crate) const PROBE_REFUSAL_FATAL_THRESHOLD_NAN_SEED: usize = 25;
pub(crate) const PROBE_REFUSAL_FATAL_SENTINEL: &str = "OUTER_PROBE_REFUSAL_FATAL";
pub(crate) const COST_STALL_CONVERGED_SENTINEL: &str = "OUTER_COST_STALL_CONVERGED";
pub(crate) enum CostStallVerdict {
Continue,
Converged,
FlatValleyStall { residual_grad_norm: f64 },
}
pub(crate) const COST_STALL_WINDOW: usize = 6;
pub(crate) const ARC_COST_STALL_WINDOW: usize = 3;
pub(crate) const COST_STALL_REL_TOL_FLOOR: f64 = 1.0e-7;
pub(crate) const COST_STALL_PROJECTED_GRAD_FLOOR: f64 = 1.0e-3;
#[derive(Clone)]
pub(crate) struct CostStallExit {
pub(crate) rho: Array1<f64>,
pub(crate) value: f64,
pub(crate) grad_norm: f64,
pub(crate) iterations: usize,
pub(crate) converged: bool,
}
pub(crate) 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,
infeasible_streak: usize,
accepted_iters: usize,
exit: Arc<Mutex<Option<CostStallExit>>>,
}
impl CostStallGuard {
pub(crate) 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,
infeasible_streak: 0,
accepted_iters: 0,
exit,
}
}
pub(crate) fn observe_seed(&mut self, rho: &Array1<f64>, value: f64, grad_norm: f64) {
if !value.is_finite() {
return;
}
self.best_value = value;
self.best_rho = Some(rho.clone());
self.best_grad_norm = grad_norm;
self.no_improve_streak = 0;
self.infeasible_streak = 0;
self.accepted_iters = self.accepted_iters.saturating_add(1);
}
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.infeasible_streak = 0;
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;
}
let kkt_stationary_at_bound = grad_norm.is_finite() && grad_norm <= self.grad_threshold;
if improvement <= floor || kkt_stationary_at_bound {
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;
}
self.publish_stall(rho, value, grad_norm)
}
pub(crate) fn observe_infeasible(&mut self, rho: &Array1<f64>) -> CostStallVerdict {
if self.best_rho.is_none() || !self.best_value.is_finite() {
return CostStallVerdict::Continue;
}
self.infeasible_streak = self.infeasible_streak.saturating_add(1);
if self.infeasible_streak < self.window {
return CostStallVerdict::Continue;
}
self.publish_stall(rho, self.best_value, self.best_grad_norm)
}
pub(crate) fn observe_constrained_stationary(
&mut self,
rho: &Array1<f64>,
value: f64,
grad_norm: f64,
) -> CostStallVerdict {
if !value.is_finite() {
return CostStallVerdict::Continue;
}
let regresses = self.best_value.is_finite()
&& value > self.best_value + self.rel_tol * (1.0 + self.best_value.abs());
if regresses {
return self.observe(rho, value, grad_norm);
}
self.infeasible_streak = 0;
self.accepted_iters = self.accepted_iters.saturating_add(1);
self.best_value = value;
self.best_rho = Some(rho.clone());
self.best_grad_norm = grad_norm;
self.no_improve_streak = self.window;
self.publish_stall(rho, value, grad_norm)
}
fn publish_stall(&mut self, rho: &Array1<f64>, value: f64, grad_norm: f64) -> CostStallVerdict {
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)]
pub(crate) struct ValueProbeCacheEntry {
rho: Array1<f64>,
outcome: CachedValueProbeOutcome,
}
#[derive(Clone)]
pub(crate) enum CachedValueProbeOutcome {
Cost(f64),
Recoverable(String),
Fatal(String),
}
pub(crate) 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()
}
pub(crate) 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())
}
pub(crate) 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(),
}),
}
}
pub(crate) 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())
}
}
}
pub(crate) fn value_probe_outcome_label(outcome: &CachedValueProbeOutcome) -> &'static str {
match outcome {
CachedValueProbeOutcome::Cost(_) => "cost",
CachedValueProbeOutcome::Recoverable(_) => "recoverable",
CachedValueProbeOutcome::Fatal(_) => "fatal",
}
}
pub(crate) fn value_probe_reject_outcome(outcome: &CachedValueProbeOutcome) -> bool {
match outcome {
CachedValueProbeOutcome::Cost(cost) => *cost >= VALUE_PROBE_REJECT_COST_FLOOR,
CachedValueProbeOutcome::Recoverable(_) | CachedValueProbeOutcome::Fatal(_) => true,
}
}
pub(crate) 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
);
if let Some(guard) = self.cost_stall.as_mut() {
match guard.observe_infeasible(x) {
CostStallVerdict::Continue => {}
CostStallVerdict::Converged => {
log::info!(
"[OUTER] cost-stall convergence (infeasible BFGS probes): {} \
consecutive infeasible probes after a finite seed/iterate; \
accepting best-so-far as a stationary optimum (value={:.6e}).",
guard.infeasible_streak,
guard.best_value,
);
return Err(ObjectiveEvalError::Fatal {
message: COST_STALL_CONVERGED_SENTINEL.to_string(),
});
}
CostStallVerdict::FlatValleyStall { residual_grad_norm } => {
log::warn!(
"[OUTER] cost-stall halt (infeasible BFGS probes): {} \
consecutive infeasible probes after a finite seed/iterate; \
halting at best-so-far with residual |g|={:.3e} \
(value={:.6e}).",
guard.infeasible_streak,
residual_grad_norm,
guard.best_value,
);
return Err(ObjectiveEvalError::Fatal {
message: COST_STALL_CONVERGED_SENTINEL.to_string(),
});
}
}
}
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() {
let projected_g_norm =
projected_gradient_norm(x, &gradient, self.cost_stall_bounds.as_ref());
match guard.observe(x, eval.cost, projected_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,
})
}
}
pub(crate) const INNER_CAP_CONVERGENCE_OVERRIDE_RATIO: f64 = 0.01;
pub(crate) const INNER_CAP_FLOOR: usize = 3;
pub(crate) const INNER_CAP_CEILING: usize = 64;
pub(crate) 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)]
#[path = "inner_cap_schedule_tests.rs"]
mod inner_cap_schedule_tests;
pub(crate) struct OuterSecondOrderBridge<'a> {
pub(crate) obj: &'a mut dyn OuterObjective,
pub(crate) layout: OuterThetaLayout,
pub(crate) hessian_source: HessianSource,
pub(crate) materialize_operator_max_dim: usize,
pub(crate) eval_count: usize,
pub(crate) outer_inner_cap: Option<InnerProgressFeedback>,
pub(crate) g_norm_initial: Option<f64>,
pub(crate) last_g_norm: Option<f64>,
pub(crate) last_value_grad_rho: Option<Array1<f64>>,
pub(crate) cost_stall: Option<CostStallGuard>,
pub(crate) cost_stall_bounds: Option<(Array1<f64>, 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 OuterSecondOrderBridge<'_> {
fn observe_cost_stall(
&mut self,
x: &Array1<f64>,
eval: &OuterEval,
) -> Option<ObjectiveEvalError> {
let bounds = self.cost_stall_bounds.clone();
let separation_bound_stationary = {
let guard = self.cost_stall.as_ref()?;
lower_bound_outward_active_count(
x,
&eval.gradient,
bounds.as_ref(),
guard.grad_threshold,
) >= LOWER_BOUND_SEPARATION_ACTIVE_MIN
};
let guard = self.cost_stall.as_mut()?;
let projected_g_norm = projected_gradient_norm(x, &eval.gradient, bounds.as_ref());
let verdict = if separation_bound_stationary {
guard.observe_constrained_stationary(x, eval.cost, 0.0)
} else {
guard.observe(x, eval.cost, projected_g_norm)
};
match verdict {
CostStallVerdict::Continue => None,
CostStallVerdict::Converged => {
log::info!(
"[OUTER] ARC cost-stall convergence: REML objective improved < {:.3e} \
(relative) over {} consecutive 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,
);
Some(ObjectiveEvalError::Fatal {
message: COST_STALL_CONVERGED_SENTINEL.to_string(),
})
}
CostStallVerdict::FlatValleyStall { residual_grad_norm } => {
log::warn!(
"[OUTER] ARC cost-stall FLAT-VALLEY STALL: REML objective improved < {:.3e} \
(relative) over {} consecutive 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,
);
Some(ObjectiveEvalError::Fatal {
message: COST_STALL_CONVERGED_SENTINEL.to_string(),
})
}
}
}
fn observe_cost_stall_infeasible(&mut self, x: &Array1<f64>) -> Option<ObjectiveEvalError> {
let guard = self.cost_stall.as_mut()?;
match guard.observe_infeasible(x) {
CostStallVerdict::Continue => None,
CostStallVerdict::Converged => {
log::info!(
"[OUTER] ARC cost-stall convergence (infeasible run): {} consecutive \
infeasible λ→0 trials after the best feasible iterate, whose projected \
gradient cleared the outer tolerance (|g|={:.3e} <= {:.3e}); accepting \
best feasible as a stationary optimum (value={:.6e}).",
guard.window,
guard.best_grad_norm,
guard.grad_threshold,
guard.best_value,
);
Some(ObjectiveEvalError::Fatal {
message: COST_STALL_CONVERGED_SENTINEL.to_string(),
})
}
CostStallVerdict::FlatValleyStall { residual_grad_norm } => {
log::warn!(
"[OUTER] ARC cost-stall halt (infeasible run): {} consecutive infeasible \
λ→0 trials after the best feasible iterate, whose projected gradient is \
still ABOVE the outer tolerance (|g|={:.3e} > {:.3e}); halting at the best \
feasible iterate and reporting NON-CONVERGED (value={:.6e}).",
guard.window,
residual_grad_norm,
guard.grad_threshold,
guard.best_value,
);
Some(ObjectiveEvalError::Fatal {
message: COST_STALL_CONVERGED_SENTINEL.to_string(),
})
}
}
}
}
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))?;
if !eval.cost.is_finite() {
if let Some(err) = self.observe_cost_stall_infeasible(x) {
return Err(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(","),
);
crate::solver::visualizer::record_outer_eval(eval.cost, g_norm);
if let Some(err) = self.observe_cost_stall(x, &eval) {
return Err(err);
}
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,
})
}
}
pub(crate) struct OuterAcceptObserver {
pub(crate) 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);
}
}
pub(crate) 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}"),
})
}
}
pub(crate) 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,
}
}
pub(crate) struct OuterOperatorBridge<'a> {
pub(crate) obj: &'a mut dyn OuterObjective,
pub(crate) layout: OuterThetaLayout,
pub(crate) outer_inner_cap: Option<InnerProgressFeedback>,
pub(crate) eval_count: usize,
pub(crate) g_norm_initial: Option<f64>,
pub(crate) last_g_norm: Option<f64>,
pub(crate) 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]
pub(crate) fn projected_gradient_norm(
x: &Array1<f64>,
gradient: &Array1<f64>,
bounds: Option<&(Array1<f64>, Array1<f64>)>,
) -> f64 {
let sumsq = match bounds {
Some((lower, upper)) => (0..gradient.len())
.map(|i| {
let gi = gradient[i];
let gi = if x[i] <= lower[i] { gi.max(0.0) } else { gi };
let gi = if x[i] >= upper[i] { gi.min(0.0) } else { gi };
gi * gi
})
.sum::<f64>(),
None => gradient.iter().map(|v| v * v).sum::<f64>(),
};
sumsq.sqrt()
}
pub(crate) const LOWER_BOUND_SEPARATION_ACTIVE_MIN: usize = 2;
#[inline]
pub(crate) fn lower_bound_outward_active_count(
x: &Array1<f64>,
gradient: &Array1<f64>,
bounds: Option<&(Array1<f64>, Array1<f64>)>,
grad_threshold: f64,
) -> usize {
let Some((lower, _upper)) = bounds else {
return 0;
};
let tol = 1.0e-10;
let outward_floor = grad_threshold.max(COST_STALL_PROJECTED_GRAD_FLOOR);
(0..x.len().min(gradient.len()).min(lower.len()))
.filter(|&i| x[i] <= lower[i] + tol && gradient[i] < -outward_floor)
.count()
}
#[inline]
pub(crate) 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(),
}
}
pub(crate) 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),
}
}
pub(crate) struct OuterFixedPointBridge<'a> {
pub(crate) obj: &'a mut dyn OuterObjective,
pub(crate) layout: OuterThetaLayout,
pub(crate) barrier_config: Option<BarrierConfig>,
pub(crate) fixed_point_tolerance: f64,
pub(crate) 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(())
}
}
pub(crate) const MAX_EFS_BACKTRACK: usize = 8;
pub(crate) const EFS_NEGLIGIBLE_STEP: f64 = 1e-12;
pub(crate) const EFS_LINESEARCH_THRESHOLD: f64 = 0.5;
pub(crate) const EFS_COST_DESCENT_TOL: f64 = 1e-12;
pub(crate) 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
}
}
pub(crate) 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
}
pub(crate) 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
}
pub(crate) 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;
pub(crate) 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,
)
}