use crate::init::OuterIterateSnapshot;
use crate::min_c_1nrm::RestoSolveResult;
use crate::resto_alg_builder::RestoAlgorithmBuilder;
use crate::resto_nlp::BLOCK_X;
use pounce_algorithm::alg_builder::{AlgorithmBuilder, LinearBackendFactory};
use pounce_algorithm::ipopt_alg::IpoptAlgorithm;
use pounce_algorithm::ipopt_cq::{IpoptCalculatedQuantities, IpoptCqHandle};
use pounce_algorithm::ipopt_data::{IpoptData, IpoptDataHandle};
use pounce_algorithm::ipopt_nlp::IpoptNlp;
use pounce_algorithm::iterates_vector::IteratesVector;
use pounce_algorithm::mu::monotone::MonotoneMuUpdate;
use pounce_common::types::Index;
use pounce_linalg::dense_vector::{DenseVector, DenseVectorSpace};
use pounce_linalg::{CompoundVector, Vector};
use pounce_nlp::alg_types::SolverReturn;
use std::cell::RefCell;
use std::rc::Rc;
pub type InnerBackendFactoryFactory = Box<dyn FnMut() -> LinearBackendFactory>;
pub fn make_resto_inner_solver(
resto_builder: RestoAlgorithmBuilder,
inner_alg_builder: AlgorithmBuilder,
mut backend_factory_factory: InnerBackendFactoryFactory,
) -> crate::min_c_1nrm::RestoInnerSolver {
Box::new(
move |outer_data, outer_cq, outer_nlp, orig_progress_cb, print_iter_output, debug_hook| {
run_inner_resto(
outer_data,
outer_cq,
outer_nlp,
&resto_builder,
&inner_alg_builder,
backend_factory_factory(),
orig_progress_cb,
print_iter_output,
debug_hook,
)
},
)
}
pub fn make_default_restoration_factory(
resto_builder: RestoAlgorithmBuilder,
inner_alg_builder: AlgorithmBuilder,
backend_factory_factory: InnerBackendFactoryFactory,
) -> Box<dyn FnMut() -> Box<dyn pounce_algorithm::restoration::RestorationPhase>> {
let mut state = Some((resto_builder, inner_alg_builder, backend_factory_factory));
Box::new(move || {
let (rb, ab, bff) = state
.take()
.expect("restoration factory invoked more than once");
let inner = make_resto_inner_solver(rb, ab, bff);
let driver = crate::min_c_1nrm::MinC1NormRestoration::new().with_inner_solver(inner);
Box::new(driver) as Box<dyn pounce_algorithm::restoration::RestorationPhase>
})
}
pub fn make_default_restoration_factory_provider<F>(
resto_builder: RestoAlgorithmBuilder,
inner_alg_builder: AlgorithmBuilder,
mut bff_mint: F,
) -> Box<dyn FnMut() -> Box<dyn FnMut() -> Box<dyn pounce_algorithm::restoration::RestorationPhase>>>
where
F: FnMut() -> InnerBackendFactoryFactory + 'static,
{
Box::new(move || {
make_default_restoration_factory(
resto_builder.clone(),
inner_alg_builder.clone(),
bff_mint(),
)
})
}
pub fn run_inner_resto(
outer_data: &IpoptDataHandle,
outer_cq: &IpoptCqHandle,
outer_nlp: &Rc<RefCell<dyn IpoptNlp>>,
resto_builder: &RestoAlgorithmBuilder,
inner_alg_builder: &AlgorithmBuilder,
backend_factory: LinearBackendFactory,
orig_progress_cb: Option<pounce_algorithm::restoration::OrigProgressCallback>,
print_iter_output: bool,
debug_hook: Option<Rc<RefCell<dyn pounce_algorithm::debug::DebugHook>>>,
) -> Option<RestoSolveResult> {
let snap = build_outer_snapshot(outer_data, outer_cq)?;
let (n_orig, m_eq, m_ineq, x_ref_vals) = {
let curr = outer_data.borrow().curr.clone()?;
let n_orig = curr.x.dim();
let m_eq = curr.y_c.dim();
let m_ineq = curr.y_d.dim();
let x_ref_vals = expanded_dense_values(&*curr.x, n_orig);
(n_orig, m_eq, m_ineq, x_ref_vals)
};
let mut resto_bundle = resto_builder.build(n_orig, m_eq, m_ineq, &x_ref_vals);
resto_bundle.nlp.set_orig_nlp(Rc::clone(outer_nlp));
resto_bundle.init.set_outer_snapshot(snap);
let inner_data: IpoptDataHandle = Rc::new(RefCell::new(IpoptData::new()));
resto_bundle.nlp.set_inner_data(Rc::clone(&inner_data));
let resto_nlp_rc: Rc<RefCell<dyn IpoptNlp>> = Rc::new(RefCell::new(resto_bundle.nlp));
let orig_curr_inf_pr = outer_cq.borrow().curr_primal_infeasibility_max();
let is_square_problem = n_orig == m_eq;
let kappa_resto = if is_square_problem { 0.0 } else { 0.9 };
let mut alg_bundle = inner_alg_builder.build_with_backend(backend_factory);
if let Some(search_dir) = alg_bundle.search_dir.as_mut() {
search_dir.pd_solver_mut().wrap_aug_solver(|inner| {
Box::new(crate::aug_resto_system_solver::AugRestoSystemSolver::new(
inner,
))
});
}
alg_bundle.init =
Box::new(resto_bundle.init) as Box<dyn pounce_algorithm::init::r#trait::IterateInitializer>;
let mut adapter = crate::conv_check::RestoConvCheckAdapter::new(1e-8, 1e-6, 15, 3000, 3000)
.with_orig_progress_guard(Rc::clone(outer_nlp), orig_curr_inf_pr, kappa_resto);
if let Some(cb) = orig_progress_cb {
adapter = adapter.with_orig_progress_callback(cb);
}
alg_bundle.conv_check =
Box::new(adapter) as Box<dyn pounce_algorithm::conv_check::r#trait::ConvCheck>;
alg_bundle.iter_output = Box::new(
crate::output::RestoIterationOutputAdapter::new().with_orig_nlp(Rc::clone(outer_nlp)),
) as Box<dyn pounce_algorithm::output::r#trait::IterationOutput>;
alg_bundle
.line_search
.acceptor_mut()
.set_theta_max_fact(1e8);
let outer_mu_min = inner_alg_builder.mu.mu_min;
let resto_mu_min = 100.0 * outer_mu_min;
alg_bundle.mu_update = match inner_alg_builder.mu_strategy {
pounce_algorithm::alg_builder::MuStrategyChoice::Monotone => Box::new(
MonotoneMuUpdate::new()
.with_first_iter_resto(true)
.with_mu_min(resto_mu_min),
)
as Box<dyn pounce_algorithm::mu::r#trait::MuUpdate>,
pounce_algorithm::alg_builder::MuStrategyChoice::Adaptive => {
let mut adaptive = pounce_algorithm::mu::adaptive::AdaptiveMuUpdate::new();
adaptive.mu_oracle = inner_alg_builder.mu_oracle;
adaptive.mu_init = inner_alg_builder.mu.mu_init;
adaptive.mu_max = inner_alg_builder.mu.mu_max;
adaptive.mu_max_fact = inner_alg_builder.mu.mu_max_fact;
adaptive.mu_min = resto_mu_min;
adaptive.mu_linear_decrease_factor = inner_alg_builder.mu.mu_linear_decrease_factor;
adaptive.mu_superlinear_decrease_power =
inner_alg_builder.mu.mu_superlinear_decrease_power;
adaptive.barrier_tol_factor = inner_alg_builder.mu.barrier_tol_factor;
adaptive.sigma_min = inner_alg_builder.mu.sigma_min;
adaptive.sigma_max = inner_alg_builder.mu.sigma_max;
adaptive.adaptive_mu_globalization = inner_alg_builder.mu.adaptive_mu_globalization;
Box::new(adaptive) as Box<dyn pounce_algorithm::mu::r#trait::MuUpdate>
}
};
let inner_cq: IpoptCqHandle = Rc::new(RefCell::new(IpoptCalculatedQuantities::new(
Rc::clone(&inner_data),
Rc::clone(&resto_nlp_rc),
)));
{
let (
outer_iter,
outer_regu_x,
outer_alpha_primal,
outer_alpha_primal_char,
outer_alpha_dual,
outer_ls_count,
outer_iters_since_header,
outer_last_output,
) = {
let d = outer_data.borrow();
(
d.iter_count,
d.info_regu_x,
d.info_alpha_primal,
d.info_alpha_primal_char,
d.info_alpha_dual,
d.info_ls_count,
d.info_iters_since_header,
d.info_last_output,
)
};
let mut inner = inner_data.borrow_mut();
inner.iter_count = outer_iter + 1;
inner.info_regu_x = outer_regu_x;
inner.info_alpha_primal = outer_alpha_primal;
inner.info_alpha_primal_char = outer_alpha_primal_char;
inner.info_alpha_dual = outer_alpha_dual;
inner.info_ls_count = outer_ls_count;
inner.info_iters_since_header = outer_iters_since_header;
inner.info_last_output = outer_last_output;
}
inner_data
.borrow_mut()
.set_curr(make_placeholder_resto_iv(n_orig, m_eq, m_ineq));
let resto_of_resto: Box<dyn pounce_algorithm::restoration::RestorationPhase> = Box::new(
crate::resto_resto::RestoRestorationPhase::new(resto_builder.rho)
.with_orig_nlp(Rc::clone(outer_nlp)),
);
let mut alg = IpoptAlgorithm::new(inner_data, inner_cq, alg_bundle)
.with_nlp(Rc::clone(&resto_nlp_rc))
.with_restoration(resto_of_resto);
if let Some(h) = debug_hook {
alg = alg.with_debug_hook(h);
}
alg.print_iter_output = print_iter_output;
let status = alg.optimize();
let final_iv = alg.data.borrow().curr.clone()?;
let xc = final_iv.x.as_any().downcast_ref::<CompoundVector>()?;
let trial_x = clone_dense_block(xc.comp(BLOCK_X))?;
let trial_s = clone_to_dense(&*final_iv.s);
let (inner_iter_count, iters_since_header, last_output) = {
let d = alg.data.borrow();
(d.iter_count, d.info_iters_since_header, d.info_last_output)
};
let outer_tol = outer_data.borrow().tol;
let orig_inf_pr_at_final =
eval_orig_inf_pr_at_inner_curr(&*final_iv.x, &*final_iv.s, outer_nlp).unwrap_or(0.0);
let inner_kkt_err = alg.cq.borrow().curr_nlp_error();
let inner_stationarity_converged = inner_kkt_err <= 10.0 * outer_tol;
let strict_locally_infeasible = !is_square_problem
&& matches!(
status,
SolverReturn::Success | SolverReturn::StopAtAcceptablePoint
)
&& inner_stationarity_converged
&& orig_inf_pr_at_final > (100.0 * outer_tol).max(1e-4);
let alt_locally_infeasible = matches!(
status,
SolverReturn::RestorationFailure
| SolverReturn::MaxiterExceeded
| SolverReturn::ErrorInStepComputation
) && inner_kkt_err <= 1e-2
&& orig_inf_pr_at_final > (100.0 * outer_tol).max(1e-3)
&& inner_iter_count >= 30;
let cycle_locally_infeasible = matches!(status, SolverReturn::MaxiterExceeded)
&& inner_iter_count >= 1000
&& orig_inf_pr_at_final > (100.0 * outer_tol).max(1e-3)
&& orig_inf_pr_at_final.is_finite();
let step_failure_locally_infeasible = matches!(status, SolverReturn::ErrorInStepComputation)
&& inner_iter_count >= 30
&& orig_inf_pr_at_final > (100.0 * outer_tol).max(1e-3)
&& orig_inf_pr_at_final.is_finite();
let locally_infeasible = strict_locally_infeasible
|| alt_locally_infeasible
|| cycle_locally_infeasible
|| step_failure_locally_infeasible;
if std::env::var_os("POUNCE_DBG_RESTO_LOCINF").is_some() {
tracing::debug!(target: "pounce::restoration",
"[PN_RESTO_LOCINF] status={:?} iter={} inner_kkt_err={:.6e} orig_inf_pr={:.6e} outer_tol={:.6e} strict={} alt={} cycle={} step_fail={} → loc_inf={}",
status,
inner_iter_count,
inner_kkt_err,
orig_inf_pr_at_final,
outer_tol,
strict_locally_infeasible,
alt_locally_infeasible,
cycle_locally_infeasible,
step_failure_locally_infeasible,
locally_infeasible
);
}
if !is_resto_success(status) && !locally_infeasible {
return None;
}
Some(RestoSolveResult {
trial_x,
trial_s,
iter_count: inner_iter_count,
iters_since_header,
last_output,
locally_infeasible,
})
}
fn eval_orig_inf_pr_at_inner_curr(
inner_x: &dyn Vector,
inner_s: &dyn Vector,
orig_rc: &Rc<RefCell<dyn IpoptNlp>>,
) -> Option<f64> {
let xc = inner_x.as_any().downcast_ref::<CompoundVector>()?;
let x_orig = xc.comp(BLOCK_X);
let mut orig = orig_rc.borrow_mut();
let m_eq = orig.m_eq();
let m_ineq = orig.m_ineq();
let c_amax = if m_eq > 0 {
let mut buf = DenseVectorSpace::new(m_eq).make_new_dense();
orig.eval_c(x_orig, &mut buf);
buf.amax()
} else {
0.0
};
let d_minus_s_amax = if m_ineq > 0 {
let mut buf = DenseVectorSpace::new(m_ineq).make_new_dense();
orig.eval_d(x_orig, &mut buf);
buf.axpy(-1.0, inner_s);
buf.amax()
} else {
0.0
};
Some(c_amax.max(d_minus_s_amax))
}
fn build_outer_snapshot(
outer_data: &IpoptDataHandle,
outer_cq: &IpoptCqHandle,
) -> Option<OuterIterateSnapshot> {
let curr = outer_data.borrow().curr.clone()?;
let mu = outer_data.borrow().curr_mu;
let cq_ref = outer_cq.borrow();
let c_vec = cq_ref.curr_c();
let d_minus_s_vec = cq_ref.curr_d_minus_s();
drop(cq_ref);
Some(OuterIterateSnapshot {
mu,
s: curr.s.clone(),
z_l: curr.z_l.clone(),
z_u: curr.z_u.clone(),
v_l: curr.v_l.clone(),
v_u: curr.v_u.clone(),
c_vec,
d_minus_s_vec,
})
}
fn make_placeholder_resto_iv(n_orig: Index, m_eq: Index, m_ineq: Index) -> IteratesVector {
use pounce_linalg::CompoundVectorSpace;
let x_total = n_orig + 2 * m_eq + 2 * m_ineq;
let x_space = CompoundVectorSpace::new(5, x_total);
let s0 = DenseVectorSpace::new(n_orig);
x_space.set_comp(0, n_orig, {
let s = Rc::clone(&s0);
move || Box::new(DenseVector::new(Rc::clone(&s)))
});
let s_eq = DenseVectorSpace::new(m_eq);
for i in [1, 2] {
x_space.set_comp(i, m_eq, {
let s = Rc::clone(&s_eq);
move || Box::new(DenseVector::new(Rc::clone(&s)))
});
}
let s_ineq = DenseVectorSpace::new(m_ineq);
for i in [3, 4] {
x_space.set_comp(i, m_ineq, {
let s = Rc::clone(&s_ineq);
move || Box::new(DenseVector::new(Rc::clone(&s)))
});
}
let mut x_cv = CompoundVector::new(x_space);
let zero_n = vec![0.0; n_orig as usize];
let zero_eq = vec![0.0; m_eq as usize];
let zero_ineq = vec![0.0; m_ineq as usize];
downcast_dense_mut(x_cv.comp_mut(0)).set_values(&zero_n);
downcast_dense_mut(x_cv.comp_mut(1)).set_values(&zero_eq);
downcast_dense_mut(x_cv.comp_mut(2)).set_values(&zero_eq);
downcast_dense_mut(x_cv.comp_mut(3)).set_values(&zero_ineq);
downcast_dense_mut(x_cv.comp_mut(4)).set_values(&zero_ineq);
let z_l_space = CompoundVectorSpace::new(5, x_total);
z_l_space.set_comp(0, n_orig, {
let s = Rc::clone(&s0);
move || Box::new(DenseVector::new(Rc::clone(&s)))
});
for i in [1, 2] {
z_l_space.set_comp(i, m_eq, {
let s = Rc::clone(&s_eq);
move || Box::new(DenseVector::new(Rc::clone(&s)))
});
}
for i in [3, 4] {
z_l_space.set_comp(i, m_ineq, {
let s = Rc::clone(&s_ineq);
move || Box::new(DenseVector::new(Rc::clone(&s)))
});
}
let mut z_l_cv = CompoundVector::new(z_l_space);
downcast_dense_mut(z_l_cv.comp_mut(0)).set_values(&zero_n);
downcast_dense_mut(z_l_cv.comp_mut(1)).set_values(&zero_eq);
downcast_dense_mut(z_l_cv.comp_mut(2)).set_values(&zero_eq);
downcast_dense_mut(z_l_cv.comp_mut(3)).set_values(&zero_ineq);
downcast_dense_mut(z_l_cv.comp_mut(4)).set_values(&zero_ineq);
let mut s = DenseVectorSpace::new(m_ineq).make_new_dense();
s.set_values(&zero_ineq);
let mut y_c = DenseVectorSpace::new(m_eq).make_new_dense();
y_c.set_values(&zero_eq);
let mut y_d = DenseVectorSpace::new(m_ineq).make_new_dense();
y_d.set_values(&zero_ineq);
let mut z_u = DenseVectorSpace::new(n_orig).make_new_dense();
z_u.set_values(&zero_n);
let mut v_l = DenseVectorSpace::new(m_ineq).make_new_dense();
v_l.set_values(&zero_ineq);
let mut v_u = DenseVectorSpace::new(m_ineq).make_new_dense();
v_u.set_values(&zero_ineq);
IteratesVector::new(
Rc::new(x_cv),
Rc::new(s),
Rc::new(y_c),
Rc::new(y_d),
Rc::new(z_l_cv),
Rc::new(z_u),
Rc::new(v_l),
Rc::new(v_u),
)
}
fn is_resto_success(status: SolverReturn) -> bool {
matches!(
status,
SolverReturn::Success
| SolverReturn::StopAtAcceptablePoint
| SolverReturn::FeasiblePointFound
)
}
fn expanded_dense_values(v: &dyn Vector, fallback_dim: Index) -> Vec<f64> {
v.as_any()
.downcast_ref::<DenseVector>()
.map(|d| d.expanded_values())
.unwrap_or_else(|| vec![0.0; fallback_dim as usize])
}
fn clone_to_dense(template: &dyn Vector) -> Rc<dyn Vector> {
let n = template.dim();
let mut v = DenseVectorSpace::new(n).make_new_dense();
let vals = expanded_dense_values(template, n);
if !vals.is_empty() {
v.set_values(&vals);
}
Rc::new(v)
}
fn clone_dense_block(v: &dyn Vector) -> Option<Rc<dyn Vector>> {
let dv = v.as_any().downcast_ref::<DenseVector>()?;
let mut out = DenseVectorSpace::new(dv.dim()).make_new_dense();
let vals = dv.expanded_values();
if !vals.is_empty() {
out.set_values(&vals);
}
Some(Rc::new(out))
}
fn downcast_dense_mut(v: &mut dyn Vector) -> &mut DenseVector {
v.as_any_mut()
.downcast_mut::<DenseVector>()
.expect("expected DenseVector component")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn placeholder_resto_iv_has_correct_shapes() {
let iv = make_placeholder_resto_iv(2, 1, 1);
assert_eq!(iv.x.dim(), 2 + 2 * 1 + 2 * 1);
assert_eq!(iv.s.dim(), 1);
assert_eq!(iv.y_c.dim(), 1);
assert_eq!(iv.y_d.dim(), 1);
let zl = iv
.z_l
.as_any()
.downcast_ref::<CompoundVector>()
.expect("z_l compound");
assert_eq!(zl.n_comps(), 5);
assert_eq!(zl.comp(0).dim(), 2);
assert_eq!(zl.comp(1).dim(), 1);
assert_eq!(iv.z_u.dim(), 2);
assert_eq!(iv.v_l.dim(), 1);
assert_eq!(iv.v_u.dim(), 1);
}
#[test]
fn is_resto_success_only_accepts_successful_terminations() {
assert!(is_resto_success(SolverReturn::Success));
assert!(is_resto_success(SolverReturn::StopAtAcceptablePoint));
assert!(is_resto_success(SolverReturn::FeasiblePointFound));
assert!(!is_resto_success(SolverReturn::MaxiterExceeded));
assert!(!is_resto_success(SolverReturn::RestorationFailure));
assert!(!is_resto_success(SolverReturn::InternalError));
assert!(!is_resto_success(SolverReturn::LocalInfeasibility));
}
}