use crate::alg_builder::{WarmStartOptions, WarmStartRecentering};
use crate::eq_mult::least_square::LeastSquareMults;
use crate::eq_mult::r#trait::EqMultCalculator;
use crate::init::default::push_x_into_interior;
use crate::init::r#trait::IterateInitializer;
use crate::ipopt_cq::IpoptCqHandle;
use crate::ipopt_data::IpoptDataHandle;
use crate::ipopt_nlp::IpoptNlp;
use crate::iterates_vector::IteratesVector;
use crate::kkt::aug_system_solver::AugSystemSolver;
use pounce_common::types::Number;
use pounce_linalg::Vector;
use pounce_linalg::compound_vector::CompoundVector;
use pounce_linalg::dense_vector::{DenseVector, DenseVectorSpace};
use std::cell::RefCell;
use std::rc::Rc;
const MU_FLOOR: Number = 1e-11;
const MU_CEILING: Number = 0.1;
const MU_ESCALATION_TRIGGER: Number = 10.0;
const SEED_REJECTION_TRIGGER: Number = 10.0;
#[derive(Debug, Clone, Copy)]
struct SeedMeasurement {
mu_hat: Number,
inf_pr: Number,
seeded_z_amax: Number,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BlockVerdict {
#[default]
Absent,
Accepted,
Reconstructed,
Discarded,
Rejected,
Unseeded,
}
#[derive(Debug, Clone)]
pub struct WarmStartDiagnostics {
pub primal_residual: Number,
pub dual_residual: Number,
pub complementarity: Number,
pub mu_in: Number,
pub mu_out: Number,
pub bound_duals: BlockVerdict,
pub eq_duals: BlockVerdict,
pub bound_duals_reconstructed: usize,
pub bound_duals_rejected: usize,
pub eq_duals_rejected: bool,
pub stationarity_split: bool,
pub recentering_disabled: bool,
}
impl Default for WarmStartDiagnostics {
fn default() -> Self {
Self {
primal_residual: Number::NAN,
dual_residual: Number::NAN,
complementarity: Number::NAN,
mu_in: Number::NAN,
mu_out: Number::NAN,
bound_duals: BlockVerdict::Absent,
eq_duals: BlockVerdict::Absent,
bound_duals_reconstructed: 0,
bound_duals_rejected: 0,
eq_duals_rejected: false,
stationarity_split: false,
recentering_disabled: false,
}
}
}
pub struct WarmStartIterateInitializer {
opts: WarmStartOptions,
}
impl WarmStartIterateInitializer {
pub fn new() -> Self {
Self {
opts: WarmStartOptions::default(),
}
}
pub fn with_options(opts: WarmStartOptions) -> Self {
Self { opts }
}
}
impl Default for WarmStartIterateInitializer {
fn default() -> Self {
Self::new()
}
}
impl IterateInitializer for WarmStartIterateInitializer {
fn set_initial_iterates(
&mut self,
data: &IpoptDataHandle,
cq: &IpoptCqHandle,
nlp: &Rc<RefCell<dyn IpoptNlp>>,
aug_solver: &mut dyn AugSystemSolver,
) -> bool {
let needs_seed_from_nlp = {
let borrow = data.borrow();
match borrow.curr.as_ref() {
None => return false,
Some(c) => !is_initialized(&c.x),
}
};
if needs_seed_from_nlp && !seed_from_nlp(data, nlp, &self.opts) {
return false;
}
let mut diag = WarmStartDiagnostics {
mu_in: data.borrow().curr_mu,
recentering_disabled: self.opts.recentering == WarmStartRecentering::None,
..Default::default()
};
let dual_info =
self.opts.recentering == WarmStartRecentering::Residual && any_dual_seeded(data);
let bound_fill_only = !dual_info && self.opts.recentering == WarmStartRecentering::Residual;
let mu_hat = if dual_info {
let (seed, unseeded) = recenter_from_residuals(data, cq, &self.opts, true, &mut diag);
reconstruct_eq_duals(data, cq, nlp, aug_solver, &self.opts, &mut diag);
if diag.eq_duals != BlockVerdict::Reconstructed {
let split = refine_bound_duals_from_stationarity(
data, cq, nlp, &self.opts, seed, &unseeded, &mut diag,
);
diag.stationarity_split = split;
}
Some(seed.mu_hat)
} else if bound_fill_only {
let (seed, _unseeded) = recenter_from_residuals(data, cq, &self.opts, false, &mut diag);
diag.eq_duals = BlockVerdict::Unseeded;
Some(seed.mu_hat)
} else {
None
};
let mu_may_move = dual_info;
{
let mut borrow = data.borrow_mut();
let curr = borrow.curr.as_ref().unwrap();
let cap = if self.opts.mult_init_max > 0.0 {
self.opts.mult_init_max
} else {
f64::INFINITY
};
let z_floor = self.opts.mult_bound_push.max(0.0);
let z_nan = self.opts.bound_mult_init_val;
let new_curr = IteratesVector::new(
Rc::clone(&curr.x),
Rc::clone(&curr.s),
clone_clamped(&curr.y_c, -cap, cap, 0.0),
clone_clamped(&curr.y_d, -cap, cap, 0.0),
clone_clamped(&curr.z_l, z_floor, cap, z_nan),
clone_clamped(&curr.z_u, z_floor, cap, z_nan),
clone_clamped(&curr.v_l, z_floor, cap, z_nan),
clone_clamped(&curr.v_u, z_floor, cap, z_nan),
);
borrow.set_curr(new_curr);
}
if self.opts.target_mu > 0.0 {
data.borrow_mut().curr_mu = self.opts.target_mu;
} else if mu_hat.is_some() {
let mu = final_mu(data, cq, &mut diag, mu_may_move);
data.borrow_mut().curr_mu = mu;
}
{
let mut borrow = data.borrow_mut();
diag.mu_out = borrow.curr_mu;
for token in diag.info_string_tokens() {
borrow.append_info_string(token);
}
borrow.warm_start_diagnostics = Some(diag);
}
true
}
}
impl WarmStartDiagnostics {
fn info_string_tokens(&self) -> Vec<&'static str> {
let mut out = Vec::new();
match self.bound_duals {
BlockVerdict::Reconstructed => out.push("wz"),
BlockVerdict::Rejected => out.push("wz!"),
_ => {}
}
match self.eq_duals {
BlockVerdict::Reconstructed => out.push("wy"),
BlockVerdict::Discarded => out.push("wy0"),
_ => {}
}
if self.eq_duals_rejected {
out.push("wy!");
}
if self.mu_out > self.mu_in * 10.0 {
out.push("wmu");
}
out
}
}
fn recenter_from_residuals(
data: &IpoptDataHandle,
cq: &IpoptCqHandle,
opts: &WarmStartOptions,
seed_implies_barrier: bool,
diag: &mut WarmStartDiagnostics,
) -> (SeedMeasurement, [Vec<bool>; 4]) {
let inf_pr = cq.borrow().curr_primal_infeasibility_max();
diag.primal_residual = inf_pr;
let mu_hat = safe_mu(data.borrow().curr_mu);
let mut unseeded: [Vec<bool>; 4] = [vec![], vec![], vec![], vec![]];
let mut seeded_z_amax = 0.0;
let curr = match data.borrow().curr.clone() {
Some(c) => c,
None => {
return (
SeedMeasurement {
mu_hat,
inf_pr,
seeded_z_amax,
},
unseeded,
);
}
};
let cq_ref = cq.borrow();
let slacks = [
cq_ref.curr_slack_x_l(),
cq_ref.curr_slack_x_u(),
cq_ref.curr_slack_s_l(),
cq_ref.curr_slack_s_u(),
];
drop(cq_ref);
let blocks = [&curr.z_l, &curr.z_u, &curr.v_l, &curr.v_u];
let swamping = if seed_implies_barrier {
swamping_residual(inf_pr, mu_hat)
} else {
0.0
};
let mut rebuilt: [Option<Rc<dyn Vector>>; 4] = [None, None, None, None];
let mut n_reconstructed = 0usize;
let mut n_rejected = 0usize;
let mut n_total = 0usize;
for (i, (z, slack)) in blocks.iter().zip(slacks.iter()).enumerate() {
if z.dim() == 0 {
continue;
}
n_total += z.dim() as usize;
let Some(mut vals) = flatten(&***z) else {
continue;
};
let Some(sl) = flatten(&**slack) else {
continue;
};
if sl.len() != vals.len() {
continue;
}
if seed_is_incoherent(&vals, &sl, mu_hat, inf_pr) {
let fill = opts.mult_bound_push.max(0.0);
for v in vals.iter_mut() {
*v = fill;
}
n_rejected += vals.len();
let mut out = z.make_new();
if scatter(&mut *out, &vals) {
rebuilt[i] = Some(Rc::from(out));
}
continue;
}
let mut touched = 0usize;
let mut mask = vec![false; vals.len()];
for (k, (v, s)) in vals.iter_mut().zip(sl.iter()).enumerate() {
if !(v.is_nan() || *v == 0.0) {
if v.is_finite() {
seeded_z_amax = seeded_z_amax.max(v.abs());
}
continue;
}
touched += 1;
mask[k] = true;
let filled = if slack_is_swamped(*s, swamping) {
opts.mult_bound_push.max(0.0)
} else if s.is_finite() && *s > 0.0 {
mu_hat / *s
} else {
mu_hat
};
*v = filled.clamp(
opts.mult_bound_push.max(MU_FLOOR),
opts.mult_init_max_or_inf(),
);
}
if touched == 0 {
continue;
}
unseeded[i] = mask;
n_reconstructed += touched;
let mut out = z.make_new();
if scatter(&mut *out, &vals) {
rebuilt[i] = Some(Rc::from(out));
}
}
if n_total > 0 {
diag.bound_duals = if n_rejected > 0 {
BlockVerdict::Rejected
} else if n_reconstructed == 0 {
BlockVerdict::Accepted
} else {
BlockVerdict::Reconstructed
};
}
diag.bound_duals_reconstructed = n_reconstructed;
diag.bound_duals_rejected = n_rejected;
if rebuilt.iter().any(|r| r.is_some()) {
let pick = |i: usize, orig: &Rc<dyn Vector>| -> Rc<dyn Vector> {
rebuilt[i].clone().unwrap_or_else(|| Rc::clone(orig))
};
let new_curr = IteratesVector::new(
Rc::clone(&curr.x),
Rc::clone(&curr.s),
Rc::clone(&curr.y_c),
Rc::clone(&curr.y_d),
pick(0, &curr.z_l),
pick(1, &curr.z_u),
pick(2, &curr.v_l),
pick(3, &curr.v_u),
);
data.borrow_mut().set_curr(new_curr);
}
(
SeedMeasurement {
mu_hat,
inf_pr,
seeded_z_amax,
},
unseeded,
)
}
fn swamping_residual(inf_pr: Number, mu_hat: Number) -> Number {
if inf_pr.is_finite() && inf_pr > SEED_REJECTION_TRIGGER * mu_hat {
inf_pr
} else {
0.0
}
}
fn slack_is_swamped(slack: Number, swamping: Number) -> bool {
swamping > slack
}
fn seed_is_incoherent(vals: &[Number], sl: &[Number], mu_hat: Number, inf_pr: Number) -> bool {
let mut acc = 0.0;
let mut n = 0usize;
for (v, s) in vals.iter().zip(sl.iter()) {
if v.is_nan() || *v == 0.0 {
continue;
}
if !v.is_finite() {
return true;
}
if !s.is_finite() || *s <= 0.0 {
continue;
}
acc += v.abs() * *s;
n += 1;
}
if n == 0 {
return false;
}
let supported = if inf_pr.is_finite() && inf_pr > mu_hat {
inf_pr
} else {
mu_hat
};
acc / (n as Number) > SEED_REJECTION_TRIGGER * supported
}
fn eq_seed_is_incoherent(
r_x: &dyn Vector,
grad_f: &dyn Vector,
seeded_z_amax: Number,
mu_hat: Number,
) -> bool {
let resid = r_x.amax();
if !resid.is_finite() {
return true;
}
let scale = grad_f.amax().max(seeded_z_amax).max(mu_hat);
if !scale.is_finite() || scale <= 0.0 {
return false;
}
resid > SEED_REJECTION_TRIGGER * scale
}
fn refine_bound_duals_from_stationarity(
data: &IpoptDataHandle,
cq: &IpoptCqHandle,
nlp: &Rc<RefCell<dyn IpoptNlp>>,
opts: &WarmStartOptions,
seed: SeedMeasurement,
unseeded: &[Vec<bool>; 4],
diag: &mut WarmStartDiagnostics,
) -> bool {
let SeedMeasurement {
mu_hat,
inf_pr,
seeded_z_amax,
} = seed;
if unseeded.iter().all(|m| m.is_empty()) {
return false;
}
let curr = match data.borrow().curr.clone() {
Some(c) => c,
None => return false,
};
let (r_x, r_s, slacks, grad_f) = {
let cq_ref = cq.borrow();
let grad_f = cq_ref.curr_grad_f();
let jc_t = cq_ref.curr_jac_c_t_times_curr_y_c();
let jd_t = cq_ref.curr_jac_d_t_times_curr_y_d();
let mut r_x = grad_f.make_new();
r_x.copy(&*grad_f);
r_x.add_two_vectors(1.0, &*jc_t, 1.0, &*jd_t, 1.0);
let mut r_s = curr.y_d.make_new();
r_s.copy(&*curr.y_d);
r_s.scal(-1.0);
let slacks = [
cq_ref.curr_slack_x_l(),
cq_ref.curr_slack_x_u(),
cq_ref.curr_slack_s_l(),
cq_ref.curr_slack_s_u(),
];
(r_x, r_s, slacks, grad_f)
};
if eq_seed_is_incoherent(&*r_x, &*grad_f, seeded_z_amax, mu_hat) {
diag.eq_duals_rejected = true;
return false;
}
let nlp_ref = nlp.borrow();
let targets: [Option<Vec<Number>>; 4] = [
project(&*r_x, &*nlp_ref.px_l(), 1.0, &curr.z_l),
project(&*r_x, &*nlp_ref.px_u(), -1.0, &curr.z_u),
project(&*r_s, &*nlp_ref.pd_l(), 1.0, &curr.v_l),
project(&*r_s, &*nlp_ref.pd_u(), -1.0, &curr.v_u),
];
drop(nlp_ref);
let blocks = [&curr.z_l, &curr.z_u, &curr.v_l, &curr.v_u];
let mut rebuilt: [Option<Rc<dyn Vector>>; 4] = [None, None, None, None];
for (i, block) in blocks.iter().enumerate() {
let mask = &unseeded[i];
if mask.is_empty() {
continue;
}
let (Some(target), Some(mut vals), Some(sl)) =
(targets[i].clone(), flatten(&***block), flatten(&*slacks[i]))
else {
continue;
};
if target.len() != vals.len() || sl.len() != vals.len() || mask.len() != vals.len() {
continue;
}
let cap = opts.mult_init_max_or_inf();
let hard_floor = opts.mult_bound_push.max(MU_FLOOR);
let swamping = swamping_residual(inf_pr, mu_hat);
for k in 0..vals.len() {
if !mask[k] {
continue;
}
if slack_is_swamped(sl[k], swamping) {
vals[k] = hard_floor.min(cap);
continue;
}
let compl_floor = if sl[k].is_finite() && sl[k] > 0.0 {
mu_hat / sl[k]
} else {
mu_hat
};
let split = if target[k].is_finite() {
target[k].min(SEED_REJECTION_TRIGGER * compl_floor)
} else {
0.0
};
vals[k] = split.max(compl_floor).max(hard_floor).min(cap);
}
let mut out = block.make_new();
if scatter(&mut *out, &vals) {
rebuilt[i] = Some(Rc::from(out));
}
}
if rebuilt.iter().all(|r| r.is_none()) {
return false;
}
let pick = |i: usize, orig: &Rc<dyn Vector>| -> Rc<dyn Vector> {
rebuilt[i].clone().unwrap_or_else(|| Rc::clone(orig))
};
let new_curr = IteratesVector::new(
Rc::clone(&curr.x),
Rc::clone(&curr.s),
Rc::clone(&curr.y_c),
Rc::clone(&curr.y_d),
pick(0, &curr.z_l),
pick(1, &curr.z_u),
pick(2, &curr.v_l),
pick(3, &curr.v_u),
);
data.borrow_mut().set_curr(new_curr);
true
}
fn project(
r: &dyn Vector,
p: &dyn pounce_linalg::Matrix,
sign: Number,
template: &Rc<dyn Vector>,
) -> Option<Vec<Number>> {
if template.dim() == 0 {
return None;
}
let mut out = template.make_new();
out.set(0.0);
p.trans_mult_vector(sign, r, 0.0, &mut *out);
flatten(&*out)
}
fn reconstruct_eq_duals(
data: &IpoptDataHandle,
cq: &IpoptCqHandle,
nlp: &Rc<RefCell<dyn IpoptNlp>>,
aug_solver: &mut dyn AugSystemSolver,
opts: &WarmStartOptions,
diag: &mut WarmStartDiagnostics,
) {
let curr = match data.borrow().curr.clone() {
Some(c) => c,
None => return,
};
let (n_yc, n_yd) = (curr.y_c.dim(), curr.y_d.dim());
if n_yc + n_yd == 0 {
return;
}
if n_yc == curr.x.dim() {
diag.eq_duals = BlockVerdict::Accepted;
return;
}
let seeded = !is_identically_zero(&curr.y_c) || !is_identically_zero(&curr.y_d);
if seeded {
diag.eq_duals = BlockVerdict::Accepted;
return;
}
let mut new_y_c = DenseVectorSpace::new(n_yc).make_new_dense();
let mut new_y_d = DenseVectorSpace::new(n_yd).make_new_dense();
new_y_c.set(0.0);
new_y_d.set(0.0);
let ok = LeastSquareMults::new().calculate_y_eq(
data,
cq,
nlp,
aug_solver,
&mut new_y_c,
&mut new_y_d,
);
if !ok {
diag.eq_duals = BlockVerdict::Discarded;
return;
}
let norm = new_y_c.amax().max(new_y_d.amax());
if !norm.is_finite() || (opts.constr_mult_init_max > 0.0 && norm > opts.constr_mult_init_max) {
diag.eq_duals = BlockVerdict::Discarded;
return;
}
diag.eq_duals = BlockVerdict::Reconstructed;
let new_curr = IteratesVector::new(
Rc::clone(&curr.x),
Rc::clone(&curr.s),
Rc::new(new_y_c),
Rc::new(new_y_d),
Rc::clone(&curr.z_l),
Rc::clone(&curr.z_u),
Rc::clone(&curr.v_l),
Rc::clone(&curr.v_u),
);
data.borrow_mut().set_curr(new_curr);
}
fn final_mu(
data: &IpoptDataHandle,
cq: &IpoptCqHandle,
diag: &mut WarmStartDiagnostics,
may_move: bool,
) -> Number {
let (compl, inf_du) = {
let cq_ref = cq.borrow();
(
cq_ref.curr_avrg_compl(),
cq_ref.curr_dual_infeasibility_max(),
)
};
diag.complementarity = compl;
diag.dual_residual = inf_du;
let mu_in = data.borrow().curr_mu;
if !mu_in.is_finite() || mu_in <= 0.0 {
return MU_CEILING;
}
if may_move && compl.is_finite() && compl > MU_ESCALATION_TRIGGER * mu_in {
safe_mu(compl).max(mu_in)
} else {
mu_in
}
}
fn safe_mu(mu: Number) -> Number {
if !mu.is_finite() || mu <= 0.0 {
return MU_CEILING;
}
mu.clamp(MU_FLOOR, MU_CEILING)
}
fn flatten(v: &dyn Vector) -> Option<Vec<Number>> {
if let Some(d) = v.as_any().downcast_ref::<DenseVector>() {
return d.is_initialized().then(|| d.expanded_values());
}
if let Some(c) = v.as_any().downcast_ref::<CompoundVector>() {
let mut out = Vec::with_capacity(v.dim() as usize);
for i in 0..c.n_comps() {
out.extend(flatten(c.comp(i))?);
}
return Some(out);
}
None
}
fn scatter(v: &mut dyn Vector, src: &[Number]) -> bool {
if v.dim() as usize != src.len() {
return false;
}
if v.as_any().is::<DenseVector>() {
let d = v.as_any_mut().downcast_mut::<DenseVector>().unwrap();
d.values_mut().copy_from_slice(src);
return true;
}
if v.as_any().is::<CompoundVector>() {
let c = v.as_any_mut().downcast_mut::<CompoundVector>().unwrap();
let mut off = 0usize;
for i in 0..c.n_comps() {
let comp = c.comp_mut(i);
let n = comp.dim() as usize;
if !scatter(comp, &src[off..off + n]) {
return false;
}
off += n;
}
return true;
}
false
}
fn is_identically_zero(v: &Rc<dyn Vector>) -> bool {
if v.dim() == 0 {
return true;
}
match flatten(&**v) {
Some(vals) => vals.iter().all(|e| *e == 0.0),
None => true,
}
}
fn seed_from_nlp(
data: &IpoptDataHandle,
nlp: &Rc<RefCell<dyn IpoptNlp>>,
opts: &WarmStartOptions,
) -> bool {
if !nlp.borrow_mut().prepare_warm_start() {
return false;
}
let (n_x, n_s, n_yc, n_yd, n_zl, n_zu, n_vl, n_vu) = {
let borrow = data.borrow();
let c = borrow.curr.as_ref().unwrap();
(
c.x.dim(),
c.s.dim(),
c.y_c.dim(),
c.y_d.dim(),
c.z_l.dim(),
c.z_u.dim(),
c.v_l.dim(),
c.v_u.dim(),
)
};
let mut x = DenseVectorSpace::new(n_x).make_new_dense();
nlp.borrow_mut().get_starting_x(&mut x);
{
let nlp_ref = nlp.borrow();
push_x_into_interior(
&mut x,
&*nlp_ref.px_l(),
nlp_ref.x_l(),
&*nlp_ref.px_u(),
nlp_ref.x_u(),
opts.bound_push,
opts.bound_frac,
);
}
let mut s = DenseVectorSpace::new(n_s).make_new_dense();
nlp.borrow_mut().eval_d(&x, &mut s);
{
let nlp_ref = nlp.borrow();
push_x_into_interior(
&mut s,
&*nlp_ref.pd_l(),
nlp_ref.d_l(),
&*nlp_ref.pd_u(),
nlp_ref.d_u(),
opts.slack_bound_push,
opts.slack_bound_frac,
);
}
let mut y_c = DenseVectorSpace::new(n_yc).make_new_dense();
let mut y_d = DenseVectorSpace::new(n_yd).make_new_dense();
y_c.set(0.0);
y_d.set(0.0);
nlp.borrow_mut().get_starting_y(&mut y_c, &mut y_d);
let mut z_l = DenseVectorSpace::new(n_zl).make_new_dense();
let mut z_u = DenseVectorSpace::new(n_zu).make_new_dense();
let mut v_l = DenseVectorSpace::new(n_vl).make_new_dense();
let mut v_u = DenseVectorSpace::new(n_vu).make_new_dense();
z_l.set(Number::NAN);
z_u.set(Number::NAN);
v_l.set(0.0);
v_u.set(0.0);
nlp.borrow_mut()
.get_starting_z(&mut z_l, &mut z_u, &mut v_l, &mut v_u);
nlp.borrow_mut().finish_warm_start();
let iv = IteratesVector::new(
Rc::new(x),
Rc::new(s),
Rc::new(y_c),
Rc::new(y_d),
Rc::new(z_l),
Rc::new(z_u),
Rc::new(v_l),
Rc::new(v_u),
);
data.borrow_mut().set_curr(iv);
true
}
fn is_initialized(v: &Rc<dyn Vector>) -> bool {
if v.dim() == 0 {
return true;
}
v.as_any()
.downcast_ref::<DenseVector>()
.map(|d| d.is_initialized())
.unwrap_or(true)
}
fn resolve_nan_seeds(v: &mut dyn Vector, fill: f64) {
if v.as_any().is::<DenseVector>() {
let d = v.as_any_mut().downcast_mut::<DenseVector>().unwrap();
for e in d.values_mut() {
if e.is_nan() {
*e = fill;
}
}
} else if v.as_any().is::<CompoundVector>() {
let c = v.as_any_mut().downcast_mut::<CompoundVector>().unwrap();
for i in 0..c.n_comps() {
resolve_nan_seeds(c.comp_mut(i), fill);
}
} else {
debug_assert!(false, "resolve_nan_seeds: unhandled Vector implementation");
}
}
fn clone_clamped(v: &Rc<dyn Vector>, lo: f64, hi: f64, nan_fill: f64) -> Rc<dyn Vector> {
let n = v.dim();
if n == 0 {
return Rc::clone(v);
}
let mut out = v.make_new();
let initialized = v
.as_any()
.downcast_ref::<DenseVector>()
.map(|d| d.is_initialized())
.unwrap_or(true);
if initialized {
out.copy(&**v);
resolve_nan_seeds(&mut *out, nan_fill);
} else {
out.set(0.0);
}
let mut cap_hi = v.make_new();
cap_hi.set(hi);
out.element_wise_min(&*cap_hi);
let mut cap_lo = v.make_new();
cap_lo.set(lo);
out.element_wise_max(&*cap_lo);
Rc::from(out)
}
#[cfg(test)]
mod tests_nan_seed {
use super::*;
use pounce_linalg::compound_vector::CompoundVectorSpace;
use pounce_linalg::dense_vector::DenseVectorSpace;
#[test]
fn nan_entries_take_the_fill_before_clamping() {
let space = DenseVectorSpace::new(3);
let mut d = space.make_new_dense();
d.values_mut().copy_from_slice(&[0.5, f64::NAN, 2e7]);
let v: Rc<dyn Vector> = Rc::from(d);
let out = clone_clamped(&v, 1e-3, 1e6, 7.0);
let out = out.as_any().downcast_ref::<DenseVector>().unwrap();
assert_eq!(out.values()[0], 0.5);
assert_eq!(out.values()[1], 7.0); assert_eq!(out.values()[2], 1e6); }
#[test]
fn nan_resolves_inside_a_compound_vector() {
let inner = DenseVectorSpace::new(2);
let space = CompoundVectorSpace::new(2, 4);
for icomp in 0..2 {
let inner = Rc::clone(&inner);
space.set_comp(icomp, 2, move || {
let mut d = inner.make_new_dense();
d.set(0.0);
Box::new(d)
});
}
let mut cv = CompoundVector::new(Rc::clone(&space));
for (icomp, vals) in [[0.5, f64::NAN], [f64::NAN, 2e7]].into_iter().enumerate() {
let c = cv.comp_mut(icomp as pounce_common::types::Index);
let d = c.as_any_mut().downcast_mut::<DenseVector>().unwrap();
d.values_mut().copy_from_slice(&vals);
}
let v: Rc<dyn Vector> = Rc::from(cv);
let out = clone_clamped(&v, 1e-3, 1e6, 7.0);
let out = out.as_any().downcast_ref::<CompoundVector>().unwrap();
let flat: Vec<f64> = (0..out.n_comps())
.flat_map(|i| {
out.comp(i)
.as_any()
.downcast_ref::<DenseVector>()
.unwrap()
.values()
.to_vec()
})
.collect();
assert_eq!(flat[0], 0.5);
assert_eq!(flat[1], 7.0); assert_eq!(flat[2], 7.0);
assert_eq!(flat[3], 1e6); }
}
#[cfg(test)]
mod tests {
use super::*;
use pounce_linalg::dense_vector::DenseVectorSpace;
fn dense(n: i32, fill: f64) -> Rc<dyn Vector> {
let space = DenseVectorSpace::new(n);
let mut v = space.make_new_dense();
v.set(fill);
Rc::new(v)
}
#[test]
fn clamps_multipliers_to_cap() {
let v = dense(3, 1e10);
let out = clone_clamped(&v, 0.0, 1e6, 0.0);
assert_eq!(out.amax(), 1e6);
let v2 = dense(3, -1e10);
let out2 = clone_clamped(&v2, -1e6, 1e6, 0.0);
assert_eq!(out2.amax(), 1e6);
}
#[test]
fn clamps_bound_mults_nonneg() {
let v = dense(3, -5.0);
let out = clone_clamped(&v, 0.0, 1e6, 0.0);
assert_eq!(out.amax(), 0.0);
}
#[test]
fn empty_vector_short_circuits() {
let v = dense(0, 0.0);
let out = clone_clamped(&v, 0.0, 1.0, 0.0);
assert_eq!(out.dim(), 0);
}
#[test]
fn in_range_values_pass_through_untouched() {
let v = dense(3, 0.5);
let out = clone_clamped(&v, 0.0, 1.0, 0.0);
assert!((out.max() - 0.5).abs() < 1e-15);
assert!((out.min() - 0.5).abs() < 1e-15);
}
#[test]
fn mult_bound_push_floors_zero_bound_multipliers() {
let v = dense(3, 0.0);
let out = clone_clamped(&v, 1e-3, 1e6, 0.0);
assert!((out.min() - 1e-3).abs() < 1e-18);
let v2 = dense(3, 0.7);
let out2 = clone_clamped(&v2, 1e-3, 1e6, 0.0);
assert!((out2.max() - 0.7).abs() < 1e-15);
}
#[test]
fn uninitialized_source_collapses_to_zero() {
let space = DenseVectorSpace::new(4);
let v: Rc<dyn Vector> = Rc::new(space.make_new_dense());
let out = clone_clamped(&v, 0.0, 1e6, 0.0);
assert_eq!(out.amax(), 0.0);
}
}
fn any_dual_seeded(data: &IpoptDataHandle) -> bool {
let Some(curr) = data.borrow().curr.clone() else {
return false;
};
for y in [&curr.y_c, &curr.y_d] {
if y.dim() > 0 && !is_identically_zero(y) {
return true;
}
}
for z in [&curr.z_l, &curr.z_u, &curr.v_l, &curr.v_u] {
if z.dim() == 0 {
continue;
}
if let Some(vals) = flatten(&**z) {
if vals.iter().any(|v| v.is_finite() && *v > 0.0) {
return true;
}
}
}
false
}