use super::*;
use opt::{BacktrackConfig, backtracking_line_search};
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) const ARC_INFEASIBLE_STALL_SENTINEL: &str = "OUTER_ARC_INFEASIBLE_STALL";
pub(crate) enum CostStallVerdict {
Continue,
Converged,
FlatValleyStall { residual_grad_norm: f64 },
StuckKeepDescending {
residual_grad_norm: f64,
escape_threshold: 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;
pub(crate) const FLAT_VALLEY_STALL_GRAD_CEILING: f64 = 5.0;
pub(crate) const FLAT_VALLEY_CONVERGED_REL_GRAD: f64 = 1.0e-3;
pub(crate) const FLAT_VALLEY_STALL_ESCAPE_MARGIN: f64 = 1.5;
pub(crate) const FLAT_VALLEY_CONVERGED_ABS_GRAD_CAP: f64 = 1.0;
#[inline]
pub(crate) fn flat_valley_converged_grad_bound(score: f64) -> f64 {
(FLAT_VALLEY_CONVERGED_REL_GRAD * (1.0 + score.abs())).min(FLAT_VALLEY_CONVERGED_ABS_GRAD_CAP)
}
pub(crate) const STUCK_STALL_MAX_ESCAPES: usize = 8;
#[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) noise_grad_bound: Option<f64>,
}
pub(crate) struct CostStallGuard {
rel_tol: f64,
window: usize,
grad_threshold: f64,
best_value: f64,
best_rho: Option<Array1<f64>>,
best_grad_norm: f64,
best_hessian_psd: Option<bool>,
no_improve_streak: usize,
infeasible_streak: usize,
accepted_iters: usize,
stuck_escapes: usize,
recent: std::collections::VecDeque<(Array1<f64>, f64)>,
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,
best_hessian_psd: None,
no_improve_streak: 0,
infeasible_streak: 0,
accepted_iters: 0,
stuck_escapes: 0,
recent: std::collections::VecDeque::new(),
exit,
}
}
fn record_recent(&mut self, rho: &Array1<f64>, value: f64) {
self.recent.push_back((rho.clone(), value));
while self.recent.len() > self.window + 1 {
self.recent.pop_front();
}
}
fn probe_noise_grad_bound(&self) -> Option<f64> {
if self.recent.len() < 4 {
return None;
}
let mut value_diffs: Vec<f64> = Vec::with_capacity(self.recent.len() - 1);
let mut probe_radius = 0.0_f64;
for (prev, next) in self.recent.iter().zip(self.recent.iter().skip(1)) {
value_diffs.push((next.1 - prev.1).abs());
let step = prev
.0
.iter()
.zip(next.0.iter())
.map(|(a, b)| (a - b) * (a - b))
.sum::<f64>()
.sqrt();
probe_radius = probe_radius.max(step);
}
if !probe_radius.is_finite() || probe_radius <= 0.0 {
return None;
}
value_diffs.sort_by(|a, b| a.total_cmp(b));
let mid = value_diffs.len() / 2;
let median = if value_diffs.len() % 2 == 1 {
value_diffs[mid]
} else {
0.5 * (value_diffs[mid - 1] + value_diffs[mid])
};
if !median.is_finite() {
return None;
}
let best_scale = if self.best_value.is_finite() {
self.best_value.abs()
} else {
0.0
};
let noise_floor = median.max(f64::EPSILON * (1.0 + best_scale));
Some((noise_floor / probe_radius).min(FLAT_VALLEY_CONVERGED_ABS_GRAD_CAP))
}
fn certified_grad_bound(&self) -> f64 {
let score_bound = flat_valley_converged_grad_bound(self.best_value);
let noise_bound = self.probe_noise_grad_bound().unwrap_or(0.0);
self.grad_threshold.max(score_bound).max(noise_bound)
}
pub(crate) fn observe_seed(&mut self, rho: &Array1<f64>, value: f64, grad_norm: f64) {
self.observe_seed_with_curvature(rho, value, grad_norm, None);
}
pub(crate) fn observe_second_order_seed(
&mut self,
rho: &Array1<f64>,
value: f64,
grad_norm: f64,
hessian_psd: Option<bool>,
) {
self.observe_seed_with_curvature(rho, value, grad_norm, hessian_psd);
}
fn observe_seed_with_curvature(
&mut self,
rho: &Array1<f64>,
value: f64,
grad_norm: f64,
hessian_psd: Option<bool>,
) {
if !value.is_finite() {
return;
}
self.best_value = value;
self.best_rho = Some(rho.clone());
self.best_grad_norm = grad_norm;
self.best_hessian_psd = hessian_psd;
self.no_improve_streak = 0;
self.infeasible_streak = 0;
self.accepted_iters = self.accepted_iters.saturating_add(1);
self.record_recent(rho, value);
self.publish_best_so_far();
}
pub(crate) fn observe(
&mut self,
rho: &Array1<f64>,
value: f64,
grad_norm: f64,
inner_converged: bool,
) -> CostStallVerdict {
self.observe_with_curvature(rho, value, grad_norm, inner_converged, None)
}
pub(crate) fn observe_second_order(
&mut self,
rho: &Array1<f64>,
value: f64,
grad_norm: f64,
inner_converged: bool,
hessian_psd: Option<bool>,
) -> CostStallVerdict {
self.observe_with_curvature(rho, value, grad_norm, inner_converged, hessian_psd)
}
fn observe_with_curvature(
&mut self,
rho: &Array1<f64>,
value: f64,
grad_norm: f64,
inner_converged: bool,
hessian_psd: Option<bool>,
) -> CostStallVerdict {
if !value.is_finite() {
self.no_improve_streak = 0;
return CostStallVerdict::Continue;
}
if !inner_converged {
self.infeasible_streak = 0;
self.no_improve_streak = 0;
return CostStallVerdict::Continue;
}
self.infeasible_streak = 0;
self.accepted_iters = self.accepted_iters.saturating_add(1);
self.record_recent(rho, value);
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;
self.best_hessian_psd = hessian_psd;
self.publish_best_so_far();
}
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;
self.stuck_escapes = 0;
}
if self.no_improve_streak < self.window {
return CostStallVerdict::Continue;
}
if self.best_hessian_psd == Some(false) && self.stuck_escapes < STUCK_STALL_MAX_ESCAPES {
self.stuck_escapes = self.stuck_escapes.saturating_add(1);
self.no_improve_streak = 0;
log::warn!(
"[OUTER] ARC cost-stall window filled at a strict-saddle incumbent \
(hessian_psd=NO at best-so-far, value={:.6e}): refusing to certify the \
saddle and returning control to cubic regularization to exploit the \
negative curvature (escape {}/{}).",
self.best_value,
self.stuck_escapes,
STUCK_STALL_MAX_ESCAPES,
);
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;
}
if self.best_hessian_psd == Some(false) {
self.infeasible_streak = 0;
self.no_improve_streak = 0;
log::warn!(
"[OUTER] ARC infeasible-probe run reached a strict-saddle incumbent; \
refusing the stall and returning control to cubic regularization"
);
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,
inner_converged: bool,
hessian_psd: Option<bool>,
) -> CostStallVerdict {
if !value.is_finite() {
return CostStallVerdict::Continue;
}
if !inner_converged {
return self.observe_with_curvature(
rho,
value,
grad_norm,
inner_converged,
hessian_psd,
);
}
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_with_curvature(
rho,
value,
grad_norm,
inner_converged,
hessian_psd,
);
}
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.best_hessian_psd = hessian_psd;
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 score_relative_grad_bound = flat_valley_converged_grad_bound(best_value);
let noise_grad_bound = self.probe_noise_grad_bound();
let converged = best_grad_norm.is_finite()
&& (best_grad_norm <= self.grad_threshold
|| best_grad_norm <= score_relative_grad_bound
|| noise_grad_bound.is_some_and(|bound| best_grad_norm <= bound));
if converged {
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,
noise_grad_bound,
});
}
return CostStallVerdict::Converged;
}
let keep_descending_threshold = (FLAT_VALLEY_STALL_ESCAPE_MARGIN
* score_relative_grad_bound)
.min(FLAT_VALLEY_STALL_GRAD_CEILING);
let non_stationary_stall =
best_grad_norm.is_finite() && best_grad_norm > keep_descending_threshold;
if non_stationary_stall && self.stuck_escapes < STUCK_STALL_MAX_ESCAPES {
self.stuck_escapes = self.stuck_escapes.saturating_add(1);
self.no_improve_streak = 0;
self.infeasible_streak = 0;
return CostStallVerdict::StuckKeepDescending {
residual_grad_norm: best_grad_norm,
escape_threshold: keep_descending_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,
noise_grad_bound,
});
}
CostStallVerdict::FlatValleyStall {
residual_grad_norm: best_grad_norm,
}
}
fn publish_best_so_far(&mut self) {
let Some(best_rho) = self.best_rho.clone() else {
return;
};
if !self.best_value.is_finite() {
return;
}
if let Ok(mut slot) = self.exit.lock() {
*slot = Some(CostStallExit {
rho: best_rho,
value: self.best_value,
grad_norm: self.best_grad_norm,
iterations: self.accepted_iters,
converged: false,
noise_grad_bound: None,
});
}
}
fn defer_finite_second_order_stall(&mut self) {
self.no_improve_streak = 0;
if let Ok(mut slot) = self.exit.lock()
&& let Some(exit) = slot.as_mut()
{
exit.converged = false;
exit.noise_grad_bound = None;
}
}
fn revoke_published_convergence(&mut self) {
if let Ok(mut slot) = self.exit.lock()
&& let Some(exit) = slot.as_mut()
{
exit.converged = false;
}
}
}
#[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::StuckKeepDescending {
residual_grad_norm,
escape_threshold,
} => {
log::warn!(
"[OUTER] cost-stall STUCK (infeasible BFGS probes, NOT a flat \
valley): residual |g|={:.3e} far above the certified-stationary \
band (escape threshold {:.3e}); refusing \
to halt-and-ship and continuing (escape {}/{}, value={:.6e}).",
residual_grad_norm,
escape_threshold,
guard.stuck_escapes,
STUCK_STALL_MAX_ESCAPES,
guard.best_value,
);
}
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,
);
self.iter_count = self.iter_count.saturating_add(1);
let inner_converged = inner_solve_converged(self.outer_inner_cap.as_ref());
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, inner_converged) {
CostStallVerdict::Continue => {}
CostStallVerdict::StuckKeepDescending {
residual_grad_norm,
escape_threshold,
} => {
if let Some(feedback) = self.outer_inner_cap.as_ref() {
feedback.cap.store(0, Ordering::Relaxed);
feedback.force_cold.store(true, Ordering::Relaxed);
}
log::warn!(
"[OUTER] cost-stall STUCK (NOT a flat valley): REML objective improved \
< {:.3e} (relative) over {} accepted outer steps but the projected \
gradient is FAR above the certified-stationary band \
(|g|={:.3e} > escape threshold {:.3e}); refusing \
to halt-and-ship and continuing the descent (escape {}/{}, value={:.6e}).",
guard.rel_tol,
guard.window,
residual_grad_norm,
escape_threshold,
guard.stuck_escapes,
STUCK_STALL_MAX_ESCAPES,
guard.best_value,
);
}
CostStallVerdict::Converged => {
let flat_band = flat_valley_converged_grad_bound(guard.best_value);
let noise_band = guard.probe_noise_grad_bound().unwrap_or(f64::NAN);
log::info!(
"[OUTER] cost-stall convergence: REML objective improved < {:.3e} \
(relative) over {} consecutive accepted outer steps AND the projected \
gradient cleared a stationarity band (|g|={:.3e}; absolute tol {:.3e}, \
score-relative flat band {:.3e}, probe-noise band {:.3e}); accepting \
best-so-far as a stationary optimum (value={:.6e}).",
guard.rel_tol,
guard.window,
guard.best_grad_norm,
guard.grad_threshold,
flat_band,
noise_band,
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 inner_solve_converged(feedback: Option<&InnerProgressFeedback>) -> bool {
match feedback.and_then(InnerProgressFeedback::snapshot) {
Some(snap) => snap.last_converged,
None => true,
}
}
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(","),
);
Ok(FirstOrderSample {
value: eval.cost,
gradient: eval.gradient,
})
}
}
impl OuterSecondOrderBridge<'_> {
fn observe_cost_stall(
&mut self,
x: &Array1<f64>,
cost: f64,
gradient: &Array1<f64>,
hessian_psd: Option<bool>,
) {
let bounds = self.cost_stall_bounds.clone();
let separation_bound_stationary = {
let Some(guard) = self.cost_stall.as_ref() else {
return;
};
lower_bound_outward_active_count(x, gradient, bounds.as_ref(), guard.grad_threshold)
>= LOWER_BOUND_SEPARATION_ACTIVE_MIN
};
let inner_converged = inner_solve_converged(self.outer_inner_cap.as_ref());
let Some(guard) = self.cost_stall.as_mut() else {
return;
};
let projected_g_norm = projected_gradient_norm(x, gradient, bounds.as_ref());
let verdict = if separation_bound_stationary {
guard.observe_constrained_stationary(
x,
cost,
projected_g_norm,
inner_converged,
hessian_psd,
)
} else {
guard.observe_second_order(x, cost, projected_g_norm, inner_converged, hessian_psd)
};
match verdict {
CostStallVerdict::Continue => {}
CostStallVerdict::StuckKeepDescending {
residual_grad_norm,
escape_threshold,
} => {
if let Some(feedback) = self.outer_inner_cap.as_ref() {
feedback.cap.store(0, Ordering::Relaxed);
feedback.force_cold.store(true, Ordering::Relaxed);
}
log::warn!(
"[OUTER] ARC cost-stall STUCK (NOT a flat valley): REML objective improved \
< {:.3e} (relative) over {} outer steps but the projected gradient is FAR \
above the certified-stationary band (|g|={:.3e} > escape threshold \
{:.3e}); refusing to halt-and-ship \
and continuing the descent (escape {}/{}, value={:.6e}).",
guard.rel_tol,
guard.window,
residual_grad_norm,
escape_threshold,
guard.stuck_escapes,
STUCK_STALL_MAX_ESCAPES,
guard.best_value,
);
}
CostStallVerdict::Converged => {
log::info!(
"[OUTER] ARC finite cost stall deferred to exact second-order convergence: \
REML objective improved < {:.3e} (relative) over {} consecutive outer \
steps and the stored best projected gradient is small (|g|={:.3e} <= \
{:.3e}), but only ARC owns the synchronized reduced-Hessian certificate \
(value={:.6e}).",
guard.rel_tol,
guard.window,
guard.best_grad_norm,
guard.certified_grad_bound(),
guard.best_value,
);
guard.defer_finite_second_order_stall();
}
CostStallVerdict::FlatValleyStall { residual_grad_norm } => {
log::warn!(
"[OUTER] ARC finite cost stall deferred: REML objective improved < {:.3e} \
(relative) over {} consecutive outer steps but the stored best projected \
gradient remains above tolerance (|g|={:.3e} > {:.3e}). Returning the \
current finite gradient + Hessian to ARC instead of halting at a stale \
best point (value={:.6e}).",
guard.rel_tol,
guard.window,
residual_grad_norm,
guard.grad_threshold,
guard.best_value,
);
guard.defer_finite_second_order_stall();
}
}
}
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::StuckKeepDescending {
residual_grad_norm,
escape_threshold,
} => {
log::warn!(
"[OUTER] ARC cost-stall STUCK (infeasible run, NOT a flat valley): best \
feasible residual |g|={:.3e} far above the certified-stationary band \
(escape threshold {:.3e}); refusing to \
halt-and-ship and continuing (escape {}/{}, value={:.6e}).",
residual_grad_norm,
escape_threshold,
guard.stuck_escapes,
STUCK_STALL_MAX_ESCAPES,
guard.best_value,
);
None
}
CostStallVerdict::Converged => {
log::warn!(
"[OUTER] ARC infeasible-probe stall: {} consecutive infeasible λ→0 trials \
after the best feasible iterate. Its stored projected gradient is small \
(|g|={:.3e} <= {:.3e}) and its synchronized reduced Hessian does not \
certify negative curvature; halting only as a NON-CONVERGED checkpoint \
(value={:.6e}).",
guard.window,
guard.best_grad_norm,
guard.certified_grad_bound(),
guard.best_value,
);
guard.revoke_published_convergence();
Some(ObjectiveEvalError::Fatal {
message: ARC_INFEASIBLE_STALL_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,
);
guard.revoke_published_convergence();
Some(ObjectiveEvalError::Fatal {
message: ARC_INFEASIBLE_STALL_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(","),
);
let hessian = build_bridge_hessian_for_source(
self.hessian_source,
eval.hessian,
self.materialize_operator_max_dim,
)?;
let hessian_psd = hessian.as_ref().and_then(|dense| {
reduced_hessian_psd_at_point(
x,
&eval.gradient,
dense,
self.cost_stall_bounds
.as_ref()
.map(|(lower, upper)| (lower, upper)),
)
});
self.observe_cost_stall(x, eval.cost, &eval.gradient, hessian_psd);
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 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: eval.hessian,
})
}
}
#[inline]
pub(crate) fn project_gradient_vector(
x: &Array1<f64>,
gradient: &Array1<f64>,
bounds: Option<&(Array1<f64>, Array1<f64>)>,
) -> Array1<f64> {
match bounds {
Some((lower, upper)) => Array1::from_iter((0..gradient.len()).map(|i| {
let gi = gradient[i];
let gi = if x[i] <= lower[i] { gi.min(0.0) } else { gi };
if x[i] >= upper[i] { gi.max(0.0) } else { gi }
})),
None => gradient.clone(),
}
}
pub(crate) fn projected_gradient_norm(
x: &Array1<f64>,
gradient: &Array1<f64>,
bounds: Option<&(Array1<f64>, Array1<f64>)>,
) -> f64 {
project_gradient_vector(x, gradient, bounds)
.iter()
.map(|v| v * v)
.sum::<f64>()
.sqrt()
}
pub(crate) fn reduced_hessian_psd_at_point(
x: &Array1<f64>,
gradient: &Array1<f64>,
hessian: &Array2<f64>,
bounds: Option<(&Array1<f64>, &Array1<f64>)>,
) -> Option<bool> {
let n = x.len();
if gradient.len() != n || hessian.nrows() != n || hessian.ncols() != n {
return None;
}
if bounds.is_some_and(|(lower, upper)| lower.len() != n || upper.len() != n) {
return None;
}
let free: Vec<usize> = (0..n)
.filter(|&index| {
let Some((lower, upper)) = bounds else {
return true;
};
let fixed = lower[index] == upper[index];
let strict_lower = x[index] <= lower[index] + 1.0e-10 && gradient[index] > 0.0;
let strict_upper = x[index] >= upper[index] - 1.0e-10 && gradient[index] < 0.0;
!(fixed || strict_lower || strict_upper)
})
.collect();
if free.is_empty() {
return Some(true);
}
let reduced = Array2::from_shape_fn((free.len(), free.len()), |(row, column)| {
hessian[[free[row], free[column]]]
});
certificate_hessian_is_psd(&reduced)
}
#[cfg(test)]
#[path = "projected_gradient_tests.rs"]
mod projected_gradient_tests;
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()
}
#[cfg(test)]
#[path = "lower_bound_outward_tests.rs"]
mod lower_bound_outward_tests;
#[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: HessianValue,
materialize_operator_max_dim: usize,
) -> Result<Option<Array2<f64>>, ObjectiveEvalError> {
match source {
HessianSource::Analytic => match hessian {
HessianValue::Dense(h) => Ok(Some(h)),
HessianValue::Operator(op)
if op.materialization().is_available()
&& op.dim() <= materialize_operator_max_dim =>
{
op.materialize_dense()
.map(Some)
.map_err(|error| ObjectiveEvalError::Fatal {
message: format!("outer Hessian operator materialization failed: {error}"),
})
}
HessianValue::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(),
),
}),
HessianValue::Unavailable => Err(ObjectiveEvalError::Fatal {
message: "outer plan declared HessianSource::Analytic but the runtime returned \
HessianValue::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,
pub(crate) last_restored_incumbent_streak: Option<usize>,
pub(crate) recurrent_incumbent_exit: Arc<Mutex<Option<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 @ EstimationError::RemlOptimizationFailed(_)) => {
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;
let restored_incumbent_recurred = match (
self.last_restored_incumbent_streak,
eval.consecutive_restored_incumbents,
) {
(Some(previous), Some(current)) => current > previous && current > 1,
_ => false,
};
self.last_restored_incumbent_streak = eval.consecutive_restored_incumbents;
if restored_incumbent_recurred {
let restores = eval.consecutive_restored_incumbents.unwrap_or_default();
if psi_indices.is_some() {
self.consecutive_psi_zero_iters = 0;
}
if let Ok(mut slot) = self.recurrent_incumbent_exit.lock() {
*slot = Some(restores);
}
log::info!(
"[OUTER] fixed-point convergence by recurrent restored incumbent: \
consecutive_restores={} cost={current_cost:.6e} rho_dim={} psi_dim={}",
restores,
self.layout.rho_dim(),
self.layout.psi_dim,
);
return Ok(FixedPointSample {
value: current_cost,
step: raw_step,
status: FixedPointStatus::Stop,
});
}
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 bt = 0usize;
let accepted = backtracking_line_search::<_, ObjectiveEvalError>(
BacktrackConfig {
max_steps: max_halvings + 1,
..BacktrackConfig::default()
},
|alpha| {
bt += 1;
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) {
log::trace!(
"[EFS] backtrack α=2^-{bt}={alpha:.4e}: trial cost {c:.6e} \
not below current {current_cost:.6e}, halving",
bt = bt - 1,
);
}
Ok(Some((c, trial_step)))
}
Err(err) => {
log::trace!(
"[EFS] backtrack α=2^-{bt}={alpha:.4e}: trial eval failed \
({err}), halving",
bt = bt - 1,
);
Ok(None)
}
}
},
|_alpha, c| c.is_finite() && c <= cost_floor,
)?;
Ok(accepted.map(|step| {
let halvings = bt - 1;
if halvings > 0 {
log::debug!(
"[EFS] backtrack accepted at α=2^-{halvings}={alpha:.4e} \
after {halvings} halvings (cost: {current_cost:.6e} → {c:.6e})",
alpha = step.step,
c = step.value,
);
}
step.payload
}))
}
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 gam_problem::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,
)
}