use std::cell::RefCell;
use std::rc::Rc;
use pounce_algorithm::ipopt_cq::IpoptCqHandle;
use pounce_algorithm::ipopt_data::IpoptDataHandle;
use pounce_algorithm::iterates_vector::{IteratesVector, IteratesVectorMut};
use pounce_algorithm::kkt::pd_full_space_solver::{PdFullSpaceSolver, SigmaOverride};
use pounce_common::types::{Index, Number};
use pounce_linalg::dense_vector::DenseVector;
use pounce_nlp::ipopt_nlp::IpoptNlp;
use crate::backsolver::SensBacksolver;
#[derive(Clone)]
pub struct PdSensBacksolver {
pd: Rc<RefCell<PdFullSpaceSolver>>,
data: IpoptDataHandle,
cq: IpoptCqHandle,
nlp: Rc<RefCell<dyn IpoptNlp>>,
dims: [usize; 8],
template: IteratesVector,
conj: Option<Rc<ConjPair>>,
d_var: Option<Rc<Vec<Number>>>,
d_full: Option<Rc<Vec<Number>>>,
bound_vars: Option<Rc<Vec<crate::backsolver::BoundRow>>>,
declared: Option<Rc<DeclaredFrameBarrier>>,
sigma: EffectiveSigma,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum BoundBlock {
ZL,
ZU,
VL,
VU,
}
#[derive(Clone)]
struct EffectiveSigma {
x: Option<Rc<dyn pounce_linalg::Vector>>,
s: Option<Rc<dyn pounce_linalg::Vector>>,
cap_x: Rc<Vec<Number>>,
ratio_x: Rc<Vec<Number>>,
ratio_s: Rc<Vec<Number>>,
}
pub(crate) struct CorrectorSigma {
pub(crate) sigma_x: Rc<dyn pounce_linalg::Vector>,
pub(crate) sigma_s: Rc<dyn pounce_linalg::Vector>,
pub(crate) ratio_x: Vec<Number>,
pub(crate) ratio_s: Vec<Number>,
}
struct DeclaredFrameBarrier {
sigma_x: Rc<dyn pounce_linalg::Vector>,
sigma_s: Rc<dyn pounce_linalg::Vector>,
slack_x_l: Vec<Number>,
slack_x_u: Vec<Number>,
slack_s_l: Vec<Number>,
slack_s_u: Vec<Number>,
}
struct ConjPair {
e: Vec<Number>,
f: Vec<Number>,
}
impl PdSensBacksolver {
pub(crate) fn activity_handles(
&self,
) -> (&IpoptDataHandle, &IpoptCqHandle, &Rc<RefCell<dyn IpoptNlp>>) {
(&self.data, &self.cq, &self.nlp)
}
pub(crate) fn barrier_mu(&self) -> Number {
let d = self.data.borrow();
let mu = d.curr_mu;
let Some(curr) = d.curr.as_ref() else {
return mu;
};
let blocks = [&curr.z_l, &curr.z_u, &curr.v_l, &curr.v_u];
let n_bound: Index = blocks.iter().map(|v| v.dim()).sum();
if n_bound == 0 {
return mu;
}
if blocks.iter().all(|v| v.amax() == 0.0) {
return 0.0;
}
mu
}
pub fn new(
data: &IpoptDataHandle,
cq: &IpoptCqHandle,
nlp: &Rc<RefCell<dyn IpoptNlp>>,
pd: Rc<RefCell<PdFullSpaceSolver>>,
) -> Result<Self, String> {
let curr = data
.borrow()
.curr
.clone()
.ok_or_else(|| "no current iterate at convergence".to_string())?;
let dims = [
curr.x.dim() as usize,
curr.s.dim() as usize,
curr.y_c.dim() as usize,
curr.y_d.dim() as usize,
curr.z_l.dim() as usize,
curr.z_u.dim() as usize,
curr.v_l.dim() as usize,
curr.v_u.dim() as usize,
];
let (d_var, d_full) = Self::variable_factors(nlp, &dims)?;
let conj = Self::natural_units_conj(nlp, &dims, d_var.as_ref().map(|v| v.as_slice()))?;
let bound_vars = Self::bound_variable_rows(nlp, &dims);
let declared = Self::declared_frame_barrier(data, nlp, &dims);
let sigma = Self::effective_sigma(cq, declared.as_ref(), &dims);
Ok(Self {
pd,
data: Rc::clone(data),
cq: Rc::clone(cq),
nlp: Rc::clone(nlp),
dims,
template: curr,
conj,
d_var,
d_full,
bound_vars,
declared,
sigma,
})
}
fn effective_sigma(
cq: &IpoptCqHandle,
declared: Option<&Rc<DeclaredFrameBarrier>>,
dims: &[usize; 8],
) -> EffectiveSigma {
let cap_x = Rc::new(sigma_pin_caps(cq, dims[0]));
let base_x = match declared {
Some(d) => Rc::clone(&d.sigma_x),
None => cq.borrow().curr_sigma_x(),
};
let base_s = match declared {
Some(d) => Rc::clone(&d.sigma_s),
None => cq.borrow().curr_sigma_s(),
};
let cap_s = sigma_pin_cap(1.0);
let cap_of_x = |i: usize| cap_x.get(i).copied().unwrap_or(Number::INFINITY);
let ratio_x = Rc::new(cap_ratio(&base_x, &cap_of_x));
let ratio_s = Rc::new(cap_ratio(&base_s, &|_| cap_s));
let x = cap_sigma(&base_x, &cap_of_x).or_else(|| declared.map(|d| Rc::clone(&d.sigma_x)));
let s = cap_sigma(&base_s, &|_| cap_s).or_else(|| declared.map(|d| Rc::clone(&d.sigma_s)));
EffectiveSigma {
x,
s,
cap_x,
ratio_x,
ratio_s,
}
}
fn declared_frame_barrier(
data: &IpoptDataHandle,
nlp: &Rc<RefCell<dyn IpoptNlp>>,
dims: &[usize; 8],
) -> Option<Rc<DeclaredFrameBarrier>> {
let (curr, from_crossover) = {
let d = data.borrow();
(d.curr.clone()?, d.curr_from_crossover)
};
if !from_crossover {
return None;
}
let nlp_ref = nlp.borrow();
let (x_l, x_u) = nlp_ref.declared_x_bounds()?;
let (d_l, d_u) = nlp_ref.declared_d_bounds()?;
if x_l.len() != dims[4]
|| x_u.len() != dims[5]
|| d_l.len() != dims[6]
|| d_u.len() != dims[7]
{
return None;
}
let (sigma_x, slack_x_l, slack_x_u) = declared_frame_sigma(
&*nlp_ref.px_l(),
&*nlp_ref.px_u(),
&*curr.x,
&x_l,
&x_u,
&*curr.z_l,
&*curr.z_u,
dims[0],
);
let (sigma_s, slack_s_l, slack_s_u) = declared_frame_sigma(
&*nlp_ref.pd_l(),
&*nlp_ref.pd_u(),
&*curr.s,
&d_l,
&d_u,
&*curr.v_l,
&*curr.v_u,
dims[1],
);
Some(Rc::new(DeclaredFrameBarrier {
sigma_x,
sigma_s,
slack_x_l,
slack_x_u,
slack_s_l,
slack_s_u,
}))
}
fn sigma_override(&self) -> SigmaOverride {
SigmaOverride {
x: self.sigma.x.clone(),
s: self.sigma.s.clone(),
}
}
#[must_use]
pub(crate) fn rescale_bound_multipliers(
&self,
lhs: &mut [Number],
ratio_x: &[Number],
ratio_s: &[Number],
) -> bool {
let unit = |r: &[Number]| r.iter().all(|&v| v == 1.0);
if unit(ratio_x) && unit(ratio_s) {
return true;
}
let off = self.offsets();
let d = self.data.borrow();
let Some(curr) = d.curr.as_ref() else {
return false;
};
let cq_ref = self.cq.borrow();
let nlp_ref = self.nlp.borrow();
let dense = |v: &dyn pounce_linalg::Vector| -> Option<Vec<Number>> {
v.as_any()
.downcast_ref::<DenseVector>()
.map(|d| d.expanded_values())
};
let rows = |m: &dyn pounce_linalg::Matrix| -> Option<Vec<usize>> {
m.as_any()
.downcast_ref::<pounce_linalg::expansion_matrix::ExpansionMatrix>()
.map(|e| {
e.expanded_pos_indices()
.iter()
.map(|&i| i as usize)
.collect()
})
};
let mut fix = |p: &dyn pounce_linalg::Matrix,
z: &dyn pounce_linalg::Vector,
slack: &dyn pounce_linalg::Vector,
ratio: &[Number],
primal_lo: usize,
block_lo: usize,
block_hi: usize,
sign: Number|
-> bool {
if block_hi == block_lo || ratio.iter().all(|&v| v == 1.0) {
return true;
}
let (Some(pos), Some(zv), Some(sv)) = (rows(p), dense(z), dense(slack)) else {
return false;
};
if pos.len() != block_hi - block_lo {
return false;
}
for (k, &i) in pos.iter().enumerate() {
let (Some(&r), Some(&zk), Some(&sk)) = (ratio.get(i), zv.get(k), sv.get(k)) else {
return false;
};
if r == 1.0 || sk == 0.0 || !sk.is_finite() {
continue;
}
let dx = lhs[primal_lo + i];
lhs[block_lo + k] += sign * (1.0 - r) * (zk / sk) * dx;
}
true
};
fix(
&*nlp_ref.px_l(),
&*curr.z_l,
&*cq_ref.curr_slack_x_l(),
ratio_x,
off[0],
off[4],
off[5],
1.0,
) && fix(
&*nlp_ref.px_u(),
&*curr.z_u,
&*cq_ref.curr_slack_x_u(),
ratio_x,
off[0],
off[5],
off[6],
-1.0,
) && fix(
&*nlp_ref.pd_l(),
&*curr.v_l,
&*cq_ref.curr_slack_s_l(),
ratio_s,
off[1],
off[6],
off[7],
1.0,
) && fix(
&*nlp_ref.pd_u(),
&*curr.v_u,
&*cq_ref.curr_slack_s_u(),
ratio_s,
off[1],
off[7],
off[8],
-1.0,
)
}
fn may_refine(&self) -> bool {
self.sigma.x.is_none() && self.sigma.s.is_none()
}
pub(crate) fn barrier_sigma_x(&self) -> Rc<dyn pounce_linalg::Vector> {
match self.sigma.x.as_ref() {
Some(v) => Rc::clone(v),
None => self.cq.borrow().curr_sigma_x(),
}
}
pub(crate) fn barrier_sigma_s(&self) -> Rc<dyn pounce_linalg::Vector> {
match self.sigma.s.as_ref() {
Some(v) => Rc::clone(v),
None => self.cq.borrow().curr_sigma_s(),
}
}
fn solve_released_inner(
&self,
released: &[usize],
rhs: &[Number],
lhs: &mut [Number],
shift: bool,
) -> bool {
if rhs.len() != self.dim() || lhs.len() != self.dim() {
return false;
}
if released.is_empty() {
return self.solve(rhs, lhs);
}
let Some((sigma, sigma_s)) = self.released_sigmas(released) else {
return false;
};
self.solve_released_prebuilt(released, sigma, sigma_s, None, rhs, lhs, shift)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn solve_released_prebuilt(
&self,
released: &[usize],
sigma: Rc<dyn pounce_linalg::Vector>,
sigma_s: Option<Rc<dyn pounce_linalg::Vector>>,
ratios: Option<(&[Number], &[Number])>,
rhs: &[Number],
lhs: &mut [Number],
shift: bool,
) -> bool {
if rhs.len() != self.dim() || lhs.len() != self.dim() {
return false;
}
let mut scaled: Vec<Number> = match self.conj.as_ref() {
Some(c) => rhs.iter().zip(c.e.iter()).map(|(&r, &e)| r * e).collect(),
None => rhs.to_vec(),
};
for &r in released {
if r < scaled.len() {
scaled[r] = 0.0;
}
}
if shift && !self.shift_released_rhs(released, &mut scaled) {
return false;
}
if !self.solve_released_scaled(sigma, sigma_s, &scaled, lhs) {
return false;
}
if let Some((rx, rs)) = ratios {
if !self.rescale_bound_multipliers(lhs, rx, rs) {
return false;
}
}
if let Some(c) = self.conj.as_ref() {
for (l, &f) in lhs.iter_mut().zip(c.f.iter()) {
*l *= f;
}
}
true
}
fn path_pin_add(&self, row: usize) -> Number {
let cap = if row < self.dims[0] {
self.sigma
.cap_x
.get(row)
.copied()
.unwrap_or(Number::INFINITY)
} else {
sigma_pin_cap(1.0)
};
if cap.is_finite() {
cap
} else {
sigma_pin_cap(1.0)
}
}
fn solve_released_pinned_inner(
&self,
released: &[usize],
pinned: &[usize],
rhs: &[Number],
lhs: &mut [Number],
) -> bool {
if pinned.is_empty() {
return false;
}
let n_p = self.dims[0] + self.dims[1];
if pinned.iter().any(|&r| r >= n_p) {
return false;
}
let pins: Vec<(usize, Number)> =
pinned.iter().map(|&r| (r, self.path_pin_add(r))).collect();
let Some((sigma_x, sigma_s)) = self.active_set_sigmas(released, &pins) else {
return false;
};
self.solve_released_prebuilt(released, sigma_x, sigma_s, None, rhs, lhs, false)
}
pub(crate) fn released_sigmas(
&self,
released: &[usize],
) -> Option<(
Rc<dyn pounce_linalg::Vector>,
Option<Rc<dyn pounce_linalg::Vector>>,
)> {
self.active_set_sigmas(released, &[])
}
fn bound_block_of(&self, row: usize) -> Option<(BoundBlock, usize)> {
let z_l = self.dims[0] + self.dims[1] + self.dims[2] + self.dims[3];
let z_u = z_l + self.dims[4];
let v_l = z_u + self.dims[5];
let v_u = v_l + self.dims[6];
let end = v_u + self.dims[7];
if row >= z_l && row < z_u {
Some((BoundBlock::ZL, row - z_l))
} else if row >= z_u && row < v_l {
Some((BoundBlock::ZU, row - z_u))
} else if row >= v_l && row < v_u {
Some((BoundBlock::VL, row - v_l))
} else if row >= v_u && row < end {
Some((BoundBlock::VU, row - v_u))
} else {
None
}
}
fn active_set_sigmas(
&self,
released: &[usize],
pinned: &[(usize, Number)],
) -> Option<(
Rc<dyn pounce_linalg::Vector>,
Option<Rc<dyn pounce_linalg::Vector>>,
)> {
use pounce_linalg::dense_vector::DenseVectorSpace;
let rows = self.bound_vars.as_deref()?;
let dense = |v: Rc<dyn pounce_linalg::Vector>| -> Option<Vec<Number>> {
v.as_any()
.downcast_ref::<DenseVector>()
.map(|d| d.expanded_values())
};
let n_x = self.dims[0];
let mut sigma_x = dense(self.barrier_sigma_x())?;
let mut sigma_s: Option<Vec<Number>> = None;
let (slack_x_l, slack_x_u, slack_s_l, slack_s_u) = match self.declared.as_ref() {
Some(d) => (
d.slack_x_l.clone(),
d.slack_x_u.clone(),
d.slack_s_l.clone(),
d.slack_s_u.clone(),
),
None => {
let cq_ref = self.cq.borrow();
(
dense(cq_ref.curr_slack_x_l())?,
dense(cq_ref.curr_slack_x_u())?,
dense(cq_ref.curr_slack_s_l())?,
dense(cq_ref.curr_slack_s_u())?,
)
}
};
let (z_l, z_u, v_l, v_u) = {
let d = self.data.borrow();
let curr = d.curr.as_ref()?;
(
dense(Rc::clone(&curr.z_l))?,
dense(Rc::clone(&curr.z_u))?,
dense(Rc::clone(&curr.v_l))?,
dense(Rc::clone(&curr.v_u))?,
)
};
for &r in released {
let br = rows.iter().find(|b| b.row == r)?;
let mut fresh = 0.0;
for other in rows.iter().filter(|b| b.var_row == br.var_row) {
if released.contains(&other.row) {
continue;
}
let (blk, k) = self.bound_block_of(other.row)?;
let (z, s) = match blk {
BoundBlock::ZL => (*z_l.get(k)?, *slack_x_l.get(k)?),
BoundBlock::ZU => (*z_u.get(k)?, *slack_x_u.get(k)?),
BoundBlock::VL => (*v_l.get(k)?, *slack_s_l.get(k)?),
BoundBlock::VU => (*v_u.get(k)?, *slack_s_u.get(k)?),
};
if s == 0.0 || !s.is_finite() {
return None;
}
fresh += z / s;
}
if br.var_row < n_x {
let cap = self
.sigma
.cap_x
.get(br.var_row)
.copied()
.unwrap_or(Number::INFINITY);
*sigma_x.get_mut(br.var_row)? = fresh.min(cap);
} else {
let ss = match sigma_s.as_mut() {
Some(v) => v,
None => sigma_s.insert(dense(self.barrier_sigma_s())?),
};
*ss.get_mut(br.var_row - n_x)? = fresh.min(sigma_pin_cap(1.0));
}
}
for &(var_row, add) in pinned {
if var_row < n_x {
let cap = self
.sigma
.cap_x
.get(var_row)
.copied()
.unwrap_or(Number::INFINITY);
let slot = sigma_x.get_mut(var_row)?;
*slot = pinned_entry(*slot, add, cap);
} else {
let ss = match sigma_s.as_mut() {
Some(v) => v,
None => sigma_s.insert(dense(self.barrier_sigma_s())?),
};
let slot = ss.get_mut(var_row - n_x)?;
*slot = pinned_entry(*slot, add, sigma_pin_cap(1.0));
}
}
let pack = |vals: &[Number]| -> Rc<dyn pounce_linalg::Vector> {
let space = DenseVectorSpace::new(vals.len() as Index);
let mut out = DenseVector::new(space);
out.values_mut().copy_from_slice(vals);
Rc::new(out) as Rc<dyn pounce_linalg::Vector>
};
Some((pack(&sigma_x), sigma_s.as_deref().map(pack)))
}
fn shift_released_rhs(&self, released: &[usize], rhs: &mut [Number]) -> bool {
let Some(rows) = self.bound_vars.as_deref() else {
return false;
};
if rows.is_empty() {
return false;
}
let d = self.data.borrow();
let Some(curr) = d.curr.as_ref() else {
return false;
};
let dense = |v: &Rc<dyn pounce_linalg::Vector>| -> Option<Vec<Number>> {
v.as_any()
.downcast_ref::<DenseVector>()
.map(|x| x.expanded_values())
};
let (Some(z_l), Some(z_u), Some(v_l), Some(v_u)) = (
dense(&curr.z_l),
dense(&curr.z_u),
dense(&curr.v_l),
dense(&curr.v_u),
) else {
return false;
};
for &r in released {
let Some(br) = rows.iter().find(|b| b.row == r) else {
return false;
};
let Some((blk, k)) = self.bound_block_of(r) else {
return false;
};
let Some(&z) = (match blk {
BoundBlock::ZL => z_l.get(k),
BoundBlock::ZU => z_u.get(k),
BoundBlock::VL => v_l.get(k),
BoundBlock::VU => v_u.get(k),
}) else {
return false;
};
if r >= rhs.len() || br.var_row >= rhs.len() {
return false;
}
rhs[r] = 0.0;
rhs[br.var_row] += if br.lower { -z } else { z };
}
true
}
fn solve_released_scaled(
&self,
sigma: Rc<dyn pounce_linalg::Vector>,
sigma_s: Option<Rc<dyn pounce_linalg::Vector>>,
rhs: &[Number],
lhs: &mut [Number],
) -> bool {
let off = self.offsets();
let rhs_mut0 = self.template.make_new_zeroed();
let mut rhs_iv = rhs_mut0.freeze();
let mut res_iv = self.template.make_new_zeroed();
if !(write_rhs_block(&mut rhs_iv.x, &rhs[off[0]..off[1]])
&& write_rhs_block(&mut rhs_iv.s, &rhs[off[1]..off[2]])
&& write_rhs_block(&mut rhs_iv.y_c, &rhs[off[2]..off[3]])
&& write_rhs_block(&mut rhs_iv.y_d, &rhs[off[3]..off[4]])
&& write_rhs_block(&mut rhs_iv.z_l, &rhs[off[4]..off[5]])
&& write_rhs_block(&mut rhs_iv.z_u, &rhs[off[5]..off[6]])
&& write_rhs_block(&mut rhs_iv.v_l, &rhs[off[6]..off[7]])
&& write_rhs_block(&mut rhs_iv.v_u, &rhs[off[7]..off[8]]))
{
return false;
}
if !self.pd.borrow_mut().solve_with_sigma(
&self.data,
&self.cq,
&self.nlp,
1.0,
0.0,
&rhs_iv,
&mut res_iv,
true,
false,
SigmaOverride {
x: Some(sigma),
s: sigma_s.or_else(|| self.sigma_override().s),
},
) {
return false;
}
read_res_block(&*res_iv.x, &mut lhs[off[0]..off[1]])
&& read_res_block(&*res_iv.s, &mut lhs[off[1]..off[2]])
&& read_res_block(&*res_iv.y_c, &mut lhs[off[2]..off[3]])
&& read_res_block(&*res_iv.y_d, &mut lhs[off[3]..off[4]])
&& read_res_block(&*res_iv.z_l, &mut lhs[off[4]..off[5]])
&& read_res_block(&*res_iv.z_u, &mut lhs[off[5]..off[6]])
&& read_res_block(&*res_iv.v_l, &mut lhs[off[6]..off[7]])
&& read_res_block(&*res_iv.v_u, &mut lhs[off[7]..off[8]])
}
pub(crate) fn corrector_sigma(&self, pinned: &[(usize, Number)]) -> Option<CorrectorSigma> {
use pounce_linalg::dense_vector::DenseVectorSpace;
let dense = |v: Rc<dyn pounce_linalg::Vector>| -> Option<Vec<Number>> {
v.as_any()
.downcast_ref::<DenseVector>()
.map(|d| d.expanded_values())
};
let (live_x, live_s) = if self.declared.is_some() {
let nlp_ref = self.nlp.borrow();
let (x_l, x_u) = nlp_ref.declared_x_bounds()?;
let (d_l, d_u) = nlp_ref.declared_d_bounds()?;
let d = self.data.borrow();
let curr = d.curr.as_ref()?;
let (sx, _, _) = declared_frame_sigma(
&*nlp_ref.px_l(),
&*nlp_ref.px_u(),
&*curr.x,
&x_l,
&x_u,
&*curr.z_l,
&*curr.z_u,
self.dims[0],
);
let (ss, _, _) = declared_frame_sigma(
&*nlp_ref.pd_l(),
&*nlp_ref.pd_u(),
&*curr.s,
&d_l,
&d_u,
&*curr.v_l,
&*curr.v_u,
self.dims[1],
);
(sx, ss)
} else {
let c = self.cq.borrow();
(c.curr_sigma_x(), c.curr_sigma_s())
};
let caps = sigma_pin_caps(&self.cq, self.dims[0]);
let cap_s = sigma_pin_cap(1.0);
let mut x = dense(live_x)?;
let mut base_x = x.clone();
for (i, v) in x.iter_mut().enumerate() {
let c = caps.get(i).copied().unwrap_or(Number::INFINITY);
if *v > c {
*v = c;
}
}
let mut s = dense(live_s)?;
let mut base_s = s.clone();
for v in s.iter_mut() {
if *v > cap_s {
*v = cap_s;
}
}
for &(var_row, add) in pinned {
if var_row < self.dims[0] {
let c = caps.get(var_row).copied().unwrap_or(Number::INFINITY);
let slot = x.get_mut(var_row)?;
*slot = pinned_entry(*slot, add, c);
*base_x.get_mut(var_row)? += add;
} else {
let k = var_row - self.dims[0];
let slot = s.get_mut(k)?;
*slot = pinned_entry(*slot, add, cap_s);
*base_s.get_mut(k)? += add;
}
}
let pack = |vals: Vec<Number>| -> Rc<dyn pounce_linalg::Vector> {
let mut out = DenseVector::new(DenseVectorSpace::new(vals.len() as Index));
out.values_mut().copy_from_slice(&vals);
Rc::new(out) as Rc<dyn pounce_linalg::Vector>
};
Some(CorrectorSigma {
ratio_x: surviving_fraction(&base_x, &x),
ratio_s: surviving_fraction(&base_s, &s),
sigma_x: pack(x),
sigma_s: pack(s),
})
}
pub(crate) fn pack_natural(
&self,
flat: &[Number],
) -> Option<pounce_algorithm::iterates_vector::IteratesVector> {
let scaled: Vec<Number> = match self.natural_units_factor() {
None => flat.to_vec(),
Some(f) => flat
.iter()
.zip(f)
.map(|(&v, &s)| if s == 0.0 { v } else { v / s })
.collect(),
};
self.pack_public(&scaled).ok().map(|iv| iv.freeze())
}
fn bound_variable_rows(
nlp: &Rc<RefCell<dyn IpoptNlp>>,
dims: &[usize; 8],
) -> Option<Rc<Vec<crate::backsolver::BoundRow>>> {
let nlp_ref = nlp.borrow();
let z_l_off = dims[0] + dims[1] + dims[2] + dims[3];
let v_l_off = z_l_off + dims[4] + dims[5];
let mut out = Vec::with_capacity(dims[4] + dims[5] + dims[6] + dims[7]);
for (pm, n_v, off, lower, base, width) in [
(nlp_ref.px_l(), dims[4], z_l_off, true, 0, dims[0]),
(
nlp_ref.px_u(),
dims[5],
z_l_off + dims[4],
false,
0,
dims[0],
),
(nlp_ref.pd_l(), dims[6], v_l_off, true, dims[0], dims[1]),
(
nlp_ref.pd_u(),
dims[7],
v_l_off + dims[6],
false,
dims[0],
dims[1],
),
] {
if n_v == 0 {
continue;
}
let em = pm
.as_any()
.downcast_ref::<pounce_linalg::expansion_matrix::ExpansionMatrix>()?;
let pos = em.expanded_pos_indices();
if pos.len() != n_v {
return None;
}
for (k, &p) in pos.iter().enumerate() {
let p = p as usize;
if p >= width {
return None;
}
out.push(crate::backsolver::BoundRow {
row: off + k,
var_row: base + p,
lower,
});
}
}
Some(Rc::new(out))
}
#[allow(clippy::type_complexity)]
fn variable_factors(
nlp: &Rc<RefCell<dyn IpoptNlp>>,
dims: &[usize; 8],
) -> Result<(Option<Rc<Vec<Number>>>, Option<Rc<Vec<Number>>>), String> {
let nlp_ref = nlp.borrow();
let Some(d_full) = nlp_ref.variable_scaling() else {
return Ok((None, None));
};
let n_full = nlp_ref.n_full_x() as usize;
if d_full.len() != n_full {
return Err(format!(
"variable scaling length {} != n_full_x {}",
d_full.len(),
n_full
));
}
if let Some(bad) = d_full.iter().find(|v| !v.is_finite() || **v <= 0.0) {
return Err(format!(
"variable scaling factor {bad} is not finite and positive"
));
}
let mut d_var = vec![Number::NAN; dims[0]];
for (full, &factor) in d_full.iter().enumerate() {
if let Some(var) = nlp_ref.full_x_to_var_x(full as Index) {
let slot = d_var.get_mut(var as usize).ok_or_else(|| {
format!("var-x index {var} outside x block of width {}", dims[0])
})?;
*slot = factor;
}
}
if let Some(pos) = d_var.iter().position(|v| v.is_nan()) {
return Err(format!(
"variable scaling left var-x column {pos} of {} unmapped",
dims[0]
));
}
Ok((Some(Rc::new(d_var)), Some(Rc::new(d_full))))
}
pub fn variable_scaling(&self) -> Option<&[Number]> {
self.d_var.as_deref().map(|v| v.as_slice())
}
pub fn variable_scaling_full(&self) -> Option<&[Number]> {
self.d_full.as_deref().map(|v| v.as_slice())
}
fn natural_units_conj(
nlp: &Rc<RefCell<dyn IpoptNlp>>,
dims: &[usize; 8],
d_var: Option<&[Number]>,
) -> Result<Option<Rc<ConjPair>>, String> {
let nlp_ref = nlp.borrow();
let df = nlp_ref.obj_scaling_factor();
let dc = nlp_ref.c_scale_vec();
let dd = nlp_ref.d_scale_vec();
if df == 1.0 && dc.is_none() && dd.is_none() && d_var.is_none() {
return Ok(None);
}
if !df.is_finite() || df == 0.0 {
return Err(format!("invalid obj_scaling_factor {df}"));
}
if let Some(v) = &dc {
if v.len() != dims[2] {
return Err(format!("c_scale length {} != y_c dim {}", v.len(), dims[2]));
}
}
if let Some(v) = &dd {
if v.len() != dims[3] || dims[1] != dims[3] {
return Err(format!(
"d_scale length {} != y_d dim {} (s dim {})",
v.len(),
dims[3],
dims[1]
));
}
}
if let Some(d) = d_var {
if d.len() != dims[0] {
return Err(format!(
"variable scaling length {} != x dim {}",
d.len(),
dims[0]
));
}
}
let bound_row_scale = |pm: Rc<dyn pounce_linalg::matrix::Matrix>,
src: Option<&[Number]>,
n_v: usize,
which: &str|
-> Result<Vec<Number>, String> {
let Some(vals) = src else {
return Ok(vec![1.0; n_v]);
};
if n_v == 0 {
return Ok(Vec::new());
}
let Some(em) = pm
.as_any()
.downcast_ref::<pounce_linalg::expansion_matrix::ExpansionMatrix>()
else {
return Err(format!("{which} is not an ExpansionMatrix"));
};
let pos = em.expanded_pos_indices();
if pos.len() != n_v {
return Err(format!(
"{which} expansion length {} != {} block dim {}",
pos.len(),
which,
n_v
));
}
pos.iter()
.map(|&r| {
vals.get(r as usize).copied().ok_or_else(|| {
format!(
"{which} expansion row {r} out of scale-vector range {}",
vals.len()
)
})
})
.collect()
};
let vl_dd = bound_row_scale(nlp_ref.pd_l(), dd.as_deref(), dims[6], "pd_l")?;
let vu_dd = bound_row_scale(nlp_ref.pd_u(), dd.as_deref(), dims[7], "pd_u")?;
let zl_dx = bound_row_scale(nlp_ref.px_l(), d_var, dims[4], "px_l")?;
let zu_dx = bound_row_scale(nlp_ref.px_u(), d_var, dims[5], "px_u")?;
drop(nlp_ref);
let total: usize = dims.iter().sum();
let mut e = Vec::with_capacity(total);
let mut f = Vec::with_capacity(total);
match d_var {
Some(d) => {
e.extend(d.iter().map(|&di| df / di));
f.extend(d.iter().map(|&di| 1.0 / di));
}
None => {
e.extend(std::iter::repeat_n(df, dims[0]));
f.extend(std::iter::repeat_n(1.0, dims[0]));
}
}
match &dd {
Some(v) => {
e.extend(v.iter().map(|&ddi| df / ddi));
f.extend(v.iter().map(|&ddi| 1.0 / ddi));
}
None => {
e.extend(std::iter::repeat_n(df, dims[1]));
f.extend(std::iter::repeat_n(1.0, dims[1]));
}
}
match &dc {
Some(v) => {
e.extend(v.iter().copied());
f.extend(v.iter().map(|&dci| dci / df));
}
None => {
e.extend(std::iter::repeat_n(1.0, dims[2]));
f.extend(std::iter::repeat_n(1.0 / df, dims[2]));
}
}
match &dd {
Some(v) => {
e.extend(v.iter().copied());
f.extend(v.iter().map(|&ddi| ddi / df));
}
None => {
e.extend(std::iter::repeat_n(1.0, dims[3]));
f.extend(std::iter::repeat_n(1.0 / df, dims[3]));
}
}
e.extend(std::iter::repeat_n(df, dims[4] + dims[5]));
f.extend(zl_dx.iter().map(|&dx| dx / df));
f.extend(zu_dx.iter().map(|&dx| dx / df));
e.extend(std::iter::repeat_n(df, dims[6] + dims[7]));
f.extend(vl_dd.iter().map(|&ddr| ddr / df));
f.extend(vu_dd.iter().map(|&ddr| ddr / df));
Ok(Some(Rc::new(ConjPair { e, f })))
}
pub fn obj_scaling_factor(&self) -> Number {
self.nlp.borrow().obj_scaling_factor()
}
pub fn nlp_scaling(&self) -> (Number, Option<Vec<Number>>, Option<Vec<Number>>) {
let n = self.nlp.borrow();
(n.obj_scaling_factor(), n.c_scale_vec(), n.d_scale_vec())
}
pub fn kkt_perturbations(&self) -> [Number; 4] {
let p = self.data.borrow().perturbations;
[p.delta_x, p.delta_s, p.delta_c, p.delta_d]
}
pub fn pin_rows_and_c_scales(
&self,
pin_g_indices: &[Index],
) -> Result<(Vec<Index>, Vec<Number>), String> {
let y_c_offset = (self.dims[0] + self.dims[1]) as Index;
let nlp = self.nlp.borrow();
let dc = nlp.c_scale_vec();
let n_full_g = nlp.n_full_g();
let mut rows = Vec::with_capacity(pin_g_indices.len());
let mut scales = Vec::with_capacity(pin_g_indices.len());
for &gi in pin_g_indices {
if gi < 0 || (n_full_g > 0 && gi >= n_full_g) {
return Err(format!(
"pin constraint index {gi} out of range [0, m={n_full_g})"
));
}
let Some(ci) = nlp.full_g_to_c_block(gi) else {
return Err(format!(
"pin constraint index {gi} is an inequality (not an equality row); \
parameter pins must be exact equalities"
));
};
rows.push(y_c_offset + ci);
scales.push(dc.as_ref().map(|v| v[ci as usize]).unwrap_or(1.0));
}
Ok((rows, scales))
}
pub fn map_pin_g_to_kkt_rows(&self, pin_g_indices: &[Index]) -> Result<Vec<Index>, String> {
Ok(self.pin_rows_and_c_scales(pin_g_indices)?.0)
}
pub fn pin_c_scales(&self, pin_g_indices: &[Index]) -> Result<Vec<Number>, String> {
Ok(self.pin_rows_and_c_scales(pin_g_indices)?.1)
}
pub fn block_dims(&self) -> [usize; 8] {
self.dims
}
pub fn full_g_to_c_block(&self, full_idx: Index) -> Option<Index> {
self.nlp.borrow().full_g_to_c_block(full_idx)
}
pub fn full_g_to_d_block(&self, full_idx: Index) -> Option<Index> {
self.nlp.borrow().full_g_to_d_block(full_idx)
}
pub fn full_x_to_var_x(&self, full_idx: Index) -> Option<Index> {
self.nlp.borrow().full_x_to_var_x(full_idx)
}
pub fn n_full_x(&self) -> Index {
self.nlp.borrow().n_full_x()
}
pub fn n_full_g(&self) -> Index {
self.nlp.borrow().n_full_g()
}
pub(crate) fn scaled_rhs_factor(&self) -> Option<&[Number]> {
self.conj.as_ref().map(|c| c.e.as_slice())
}
pub(crate) fn offsets_public(&self) -> [usize; 9] {
self.offsets()
}
pub(crate) fn pack_public(&self, flat: &[Number]) -> Result<IteratesVectorMut, ()> {
self.pack(flat)
}
pub(crate) fn curr_flat(&self, out: &mut [Number]) -> Result<(), ()> {
if out.len() != self.dim() {
return Err(());
}
let curr = {
let d = self.data.borrow();
d.curr.clone().ok_or(())?
};
let off = self.offsets();
let blocks: [&Rc<dyn pounce_linalg::vector::Vector>; 8] = [
&curr.x, &curr.s, &curr.y_c, &curr.y_d, &curr.z_l, &curr.z_u, &curr.v_l, &curr.v_u,
];
for (i, b) in blocks.iter().enumerate() {
let vals = crate::vec_util::dense_to_vec(&***b);
let (a, e) = (off[i], off[i + 1]);
if vals.len() != e - a {
return Err(());
}
out[a..e].copy_from_slice(&vals);
}
Ok(())
}
fn offsets(&self) -> [usize; 9] {
let mut o = [0usize; 9];
for i in 0..8 {
o[i + 1] = o[i] + self.dims[i];
}
o
}
fn pack(&self, flat: &[Number]) -> Result<IteratesVectorMut, ()> {
let mut out = self.template.make_new_zeroed();
let off = self.offsets();
let blocks: [&mut Box<dyn pounce_linalg::vector::Vector>; 8] = [
&mut out.x,
&mut out.s,
&mut out.y_c,
&mut out.y_d,
&mut out.z_l,
&mut out.z_u,
&mut out.v_l,
&mut out.v_u,
];
for (i, blk) in blocks.into_iter().enumerate() {
let slice = &flat[off[i]..off[i + 1]];
let dv = blk.as_any_mut().downcast_mut::<DenseVector>().ok_or(())?;
dv.set_values(slice);
}
Ok(out)
}
fn unpack(&self, iv: &IteratesVectorMut, out: &mut [Number]) -> Result<(), ()> {
let off = self.offsets();
let blocks: [&Box<dyn pounce_linalg::vector::Vector>; 8] = [
&iv.x, &iv.s, &iv.y_c, &iv.y_d, &iv.z_l, &iv.z_u, &iv.v_l, &iv.v_u,
];
for (i, blk) in blocks.into_iter().enumerate() {
let dst = &mut out[off[i]..off[i + 1]];
if dst.is_empty() {
continue;
}
let dv = (**blk).as_any().downcast_ref::<DenseVector>().ok_or(())?;
let ev = dv.expanded_values();
dst.copy_from_slice(&ev);
}
Ok(())
}
}
impl PdSensBacksolver {
pub fn solve_many(&self, rhs_flat: &[Number], lhs_flat: &mut [Number], n_rhs: usize) -> bool {
match &self.conj {
None => self.solve_many_scaled_space(rhs_flat, lhs_flat, n_rhs),
Some(c) => {
let total = self.dim();
if rhs_flat.len() != n_rhs * total || lhs_flat.len() != n_rhs * total {
return false;
}
let mut rhs_scaled = rhs_flat.to_vec();
for row in rhs_scaled.chunks_mut(total) {
for (r, &ei) in row.iter_mut().zip(c.e.iter()) {
*r *= ei;
}
}
if !self.solve_many_scaled_space(&rhs_scaled, lhs_flat, n_rhs) {
return false;
}
for row in lhs_flat.chunks_mut(total) {
for (l, &fi) in row.iter_mut().zip(c.f.iter()) {
*l *= fi;
}
}
true
}
}
}
pub fn solve_many_scaled_space(
&self,
rhs_flat: &[Number],
lhs_flat: &mut [Number],
n_rhs: usize,
) -> bool {
let total = self.dim();
if rhs_flat.len() != n_rhs * total || lhs_flat.len() != n_rhs * total {
return false;
}
if n_rhs == 0 {
return true;
}
let off = self.offsets();
let sigma = self.sigma_override();
let allow_inexact = !self.may_refine();
let corrected = !self.may_refine();
if !corrected {
let mut pd_ref = self.pd.borrow_mut();
let fast_flat = pd_ref.solve_many_cached_flat(
&self.data, &self.cq, &self.nlp, n_rhs, rhs_flat, lhs_flat, self.dims,
);
match fast_flat {
Some(true) => return true,
Some(false) => return false,
None => { }
}
}
if !corrected {
let mut pd_ref = self.pd.borrow_mut();
let fast = pd_ref.solve_many_cached(
&self.data,
&self.cq,
&self.nlp,
n_rhs,
|k, iv| {
let row = &rhs_flat[k * total..(k + 1) * total];
let _ = write_rhs_box(&mut iv.x, &row[off[0]..off[1]])
&& write_rhs_box(&mut iv.s, &row[off[1]..off[2]])
&& write_rhs_box(&mut iv.y_c, &row[off[2]..off[3]])
&& write_rhs_box(&mut iv.y_d, &row[off[3]..off[4]])
&& write_rhs_box(&mut iv.z_l, &row[off[4]..off[5]])
&& write_rhs_box(&mut iv.z_u, &row[off[5]..off[6]])
&& write_rhs_box(&mut iv.v_l, &row[off[6]..off[7]])
&& write_rhs_box(&mut iv.v_u, &row[off[7]..off[8]]);
},
|k, iv| {
let row = &mut lhs_flat[k * total..(k + 1) * total];
let _ = read_res_block(&*iv.x, &mut row[off[0]..off[1]])
&& read_res_block(&*iv.s, &mut row[off[1]..off[2]])
&& read_res_block(&*iv.y_c, &mut row[off[2]..off[3]])
&& read_res_block(&*iv.y_d, &mut row[off[3]..off[4]])
&& read_res_block(&*iv.z_l, &mut row[off[4]..off[5]])
&& read_res_block(&*iv.z_u, &mut row[off[5]..off[6]])
&& read_res_block(&*iv.v_l, &mut row[off[6]..off[7]])
&& read_res_block(&*iv.v_u, &mut row[off[7]..off[8]]);
},
);
match fast {
Some(true) => return true,
Some(false) => return false,
None => { }
}
}
let rhs_mut0 = self.template.make_new_zeroed();
let mut rhs_iv = rhs_mut0.freeze();
let mut res_iv = self.template.make_new_zeroed();
let mut pd_ref = self.pd.borrow_mut();
for k in 0..n_rhs {
let rhs_row = &rhs_flat[k * total..(k + 1) * total];
let lhs_row = &mut lhs_flat[k * total..(k + 1) * total];
if !write_rhs_block(&mut rhs_iv.x, &rhs_row[off[0]..off[1]])
|| !write_rhs_block(&mut rhs_iv.s, &rhs_row[off[1]..off[2]])
|| !write_rhs_block(&mut rhs_iv.y_c, &rhs_row[off[2]..off[3]])
|| !write_rhs_block(&mut rhs_iv.y_d, &rhs_row[off[3]..off[4]])
|| !write_rhs_block(&mut rhs_iv.z_l, &rhs_row[off[4]..off[5]])
|| !write_rhs_block(&mut rhs_iv.z_u, &rhs_row[off[5]..off[6]])
|| !write_rhs_block(&mut rhs_iv.v_l, &rhs_row[off[6]..off[7]])
|| !write_rhs_block(&mut rhs_iv.v_u, &rhs_row[off[7]..off[8]])
{
return false;
}
let ok = pd_ref.solve_with_sigma(
&self.data,
&self.cq,
&self.nlp,
1.0,
0.0,
&rhs_iv,
&mut res_iv,
allow_inexact,
false,
sigma.clone(),
);
if !ok {
return false;
}
if !read_res_block(&*res_iv.x, &mut lhs_row[off[0]..off[1]])
|| !read_res_block(&*res_iv.s, &mut lhs_row[off[1]..off[2]])
|| !read_res_block(&*res_iv.y_c, &mut lhs_row[off[2]..off[3]])
|| !read_res_block(&*res_iv.y_d, &mut lhs_row[off[3]..off[4]])
|| !read_res_block(&*res_iv.z_l, &mut lhs_row[off[4]..off[5]])
|| !read_res_block(&*res_iv.z_u, &mut lhs_row[off[5]..off[6]])
|| !read_res_block(&*res_iv.v_l, &mut lhs_row[off[6]..off[7]])
|| !read_res_block(&*res_iv.v_u, &mut lhs_row[off[7]..off[8]])
{
return false;
}
if !self.rescale_bound_multipliers(lhs_row, &self.sigma.ratio_x, &self.sigma.ratio_s) {
return false;
}
}
true
}
}
const SIGMA_OVERFLOW_HEADROOM: Number = 4.0;
fn declared_slack_floor(bound: Number, z_max: Number) -> Number {
let resolvable = Number::EPSILON * bound.abs().max(1.0);
if z_max.is_finite() && z_max > 0.0 {
resolvable.max(z_max / (Number::MAX / SIGMA_OVERFLOW_HEADROOM))
} else {
resolvable
}
}
const SIGMA_PIN_HEADROOM: Number = 64.0;
fn sigma_pin_caps(cq: &IpoptCqHandle, n_x: usize) -> Vec<Number> {
use pounce_linalg::triplet::GenTMatrix;
let mut a_max: Vec<Number> = vec![0.0; n_x];
let (jac_c, jac_d) = {
let c = cq.borrow();
(c.curr_jac_c(), c.curr_jac_d())
};
for jac in [jac_c, jac_d] {
let Some(t) = jac.as_any().downcast_ref::<GenTMatrix>() else {
return vec![Number::INFINITY; n_x];
};
for (&col, &v) in t.jcols().iter().zip(t.values().iter()) {
let j = (col - 1) as usize;
if let Some(slot) = a_max.get_mut(j) {
*slot = slot.max(v.abs());
}
}
}
a_max
.into_iter()
.map(|a| {
if a > 0.0 && a.is_finite() {
sigma_pin_cap(a)
} else {
Number::INFINITY
}
})
.collect()
}
fn sigma_pin_cap(a: Number) -> Number {
let cap = (a / Number::EPSILON) * (a / SIGMA_PIN_HEADROOM);
if cap > 1.0 { cap } else { Number::INFINITY }
}
fn pinned_entry(had: Number, add: Number, cap: Number) -> Number {
(had + add).min(cap)
}
fn cap_ratio(sigma: &Rc<dyn pounce_linalg::Vector>, cap: &dyn Fn(usize) -> Number) -> Vec<Number> {
let Some(dv) = sigma.as_any().downcast_ref::<DenseVector>() else {
return Vec::new();
};
let base = dv.expanded_values();
let used: Vec<Number> = base
.iter()
.enumerate()
.map(|(i, &v)| {
let c = cap(i);
if v > c { c } else { v }
})
.collect();
surviving_fraction(&base, &used)
}
fn surviving_fraction(base: &[Number], used: &[Number]) -> Vec<Number> {
base.iter()
.zip(used)
.map(|(&b, &u)| {
if u < b && b > 0.0 && b.is_finite() {
u / b
} else {
1.0
}
})
.collect()
}
fn cap_sigma(
sigma: &Rc<dyn pounce_linalg::Vector>,
cap: &dyn Fn(usize) -> Number,
) -> Option<Rc<dyn pounce_linalg::Vector>> {
use pounce_linalg::dense_vector::DenseVectorSpace;
let dv = sigma.as_any().downcast_ref::<DenseVector>()?;
let mut vals = dv.expanded_values();
let mut hit = false;
for (i, v) in vals.iter_mut().enumerate() {
let c = cap(i);
if *v > c {
*v = c;
hit = true;
}
}
if !hit {
return None;
}
let mut out = DenseVector::new(DenseVectorSpace::new(vals.len() as Index));
out.values_mut().copy_from_slice(&vals);
Some(Rc::new(out) as Rc<dyn pounce_linalg::Vector>)
}
#[allow(clippy::too_many_arguments)]
fn declared_frame_sigma(
p_l: &dyn pounce_linalg::Matrix,
p_u: &dyn pounce_linalg::Matrix,
primal: &dyn pounce_linalg::Vector,
b_l: &[Number],
b_u: &[Number],
z_l: &dyn pounce_linalg::Vector,
z_u: &dyn pounce_linalg::Vector,
n: usize,
) -> (Rc<dyn pounce_linalg::Vector>, Vec<Number>, Vec<Number>) {
use pounce_linalg::Vector;
use pounce_linalg::dense_vector::DenseVectorSpace;
let z_max = z_l.amax().max(z_u.amax());
let slack = |p: &dyn pounce_linalg::Matrix, b: &[Number], lower: bool| -> DenseVector {
let mut v = DenseVector::new(DenseVectorSpace::new(b.len() as Index));
if !b.is_empty() {
v.values_mut().copy_from_slice(b);
}
let (alpha, beta) = if lower { (1.0, -1.0) } else { (-1.0, 1.0) };
p.trans_mult_vector(alpha, primal, beta, &mut v);
for (s, &bi) in v.values_mut().iter_mut().zip(b.iter()) {
*s = s.max(declared_slack_floor(bi, z_max));
}
v
};
let s_l = slack(p_l, b_l, true);
let s_u = slack(p_u, b_u, false);
let mut sigma = DenseVector::new(DenseVectorSpace::new(n as Index));
sigma.set(0.0);
p_l.add_m_sinv_z(1.0, &s_l, z_l, &mut sigma);
p_u.add_m_sinv_z(1.0, &s_u, z_u, &mut sigma);
(
Rc::new(sigma) as Rc<dyn pounce_linalg::Vector>,
s_l.expanded_values(),
s_u.expanded_values(),
)
}
fn write_rhs_box(b: &mut Box<dyn pounce_linalg::vector::Vector>, slice: &[Number]) -> bool {
if slice.is_empty() {
return true;
}
let Some(dv) = b.as_any_mut().downcast_mut::<DenseVector>() else {
return false;
};
dv.set_values(slice);
true
}
fn write_rhs_block(rc: &mut Rc<dyn pounce_linalg::vector::Vector>, slice: &[Number]) -> bool {
if slice.is_empty() {
return true;
}
let Some(v) = Rc::get_mut(rc) else {
return false;
};
let Some(dv) = v.as_any_mut().downcast_mut::<DenseVector>() else {
return false;
};
dv.set_values(slice);
true
}
fn read_res_block(blk: &dyn pounce_linalg::vector::Vector, dst: &mut [Number]) -> bool {
if dst.is_empty() {
return true;
}
let Some(dv) = blk.as_any().downcast_ref::<DenseVector>() else {
return false;
};
if dv.is_homogeneous() {
let s = dv.scalar();
for x in dst.iter_mut() {
*x = s;
}
} else {
dst.copy_from_slice(dv.values());
}
true
}
impl PdSensBacksolver {
pub fn solve_scaled_space(&self, rhs: &[Number], lhs: &mut [Number]) -> bool {
let total = self.dim();
if rhs.len() != total || lhs.len() != total {
return false;
}
let rhs_mut = match self.pack(rhs) {
Ok(v) => v,
Err(()) => return false,
};
let rhs_iv = rhs_mut.freeze();
let mut res_iv = self.template.make_new_zeroed();
let allow_inexact = !self.may_refine();
let ok = {
let mut pd_ref = self.pd.borrow_mut();
pd_ref.solve_with_sigma(
&self.data,
&self.cq,
&self.nlp,
1.0,
0.0,
&rhs_iv,
&mut res_iv,
allow_inexact,
false,
self.sigma_override(),
)
};
if !ok {
return false;
}
if self.unpack(&res_iv, lhs).is_err() {
return false;
}
self.rescale_bound_multipliers(lhs, &self.sigma.ratio_x, &self.sigma.ratio_s)
}
}
impl SensBacksolver for PdSensBacksolver {
fn dim(&self) -> usize {
self.dims.iter().sum()
}
fn solve_many(&self, rhs: &[Number], lhs: &mut [Number], n_rhs: usize) -> bool {
PdSensBacksolver::solve_many(self, rhs, lhs, n_rhs)
}
fn natural_units_factor(&self) -> Option<&[Number]> {
self.conj.as_ref().map(|c| c.f.as_slice())
}
fn bound_rows(&self) -> Option<&[crate::backsolver::BoundRow]> {
self.bound_vars.as_deref().map(|v| v.as_slice())
}
fn supports_release(&self) -> bool {
self.bound_vars.is_some()
}
fn solve_released(&self, released: &[usize], rhs: &[Number], lhs: &mut [Number]) -> bool {
self.solve_released_inner(released, rhs, lhs, false)
}
fn solve_released_step(&self, released: &[usize], rhs: &[Number], lhs: &mut [Number]) -> bool {
self.solve_released_inner(released, rhs, lhs, true)
}
fn solve_released_pinned(
&self,
released: &[usize],
pinned: &[usize],
rhs: &[Number],
lhs: &mut [Number],
) -> bool {
self.bound_vars.is_some() && self.solve_released_pinned_inner(released, pinned, rhs, lhs)
}
fn solve(&self, rhs: &[Number], lhs: &mut [Number]) -> bool {
match &self.conj {
None => self.solve_scaled_space(rhs, lhs),
Some(c) => {
let total = self.dim();
if rhs.len() != total || lhs.len() != total {
return false;
}
let rhs_scaled: Vec<Number> =
rhs.iter().zip(c.e.iter()).map(|(&r, &ei)| r * ei).collect();
if !self.solve_scaled_space(&rhs_scaled, lhs) {
return false;
}
for (l, &fi) in lhs.iter_mut().zip(c.f.iter()) {
*l *= fi;
}
true
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use pounce_linalg::dense_vector::DenseVectorSpace;
fn dense(vals: &[Number]) -> Rc<dyn pounce_linalg::Vector> {
let mut v = DenseVector::new(DenseVectorSpace::new(vals.len() as Index));
v.values_mut().copy_from_slice(vals);
Rc::new(v) as Rc<dyn pounce_linalg::Vector>
}
#[test]
fn the_ceiling_leaves_the_schur_contribution_representable() {
for a in [1.0, 3.5, 1e-3, 1e6] {
let ratio = a * a / sigma_pin_cap(a);
assert!(
(ratio / (Number::EPSILON * SIGMA_PIN_HEADROOM) - 1.0).abs() < 1e-12,
"a={a:e}: a²/cap = {ratio:e}, want {:e}",
Number::EPSILON * SIGMA_PIN_HEADROOM,
);
}
}
#[test]
fn the_ceiling_is_invariant_to_rescaling_the_variable() {
for c in [1e-3, 1e-1, 1.0, 1e1, 1e3] {
let over = 1e27 / (c * c) > sigma_pin_cap(1.0 / c);
assert!(over, "c={c:e}: rescaling moved the entry under its ceiling");
let under = 1e6 / (c * c) > sigma_pin_cap(1.0 / c);
assert!(
!under,
"c={c:e}: rescaling moved the entry over its ceiling"
);
}
}
#[test]
fn a_ceiling_that_cannot_help_is_no_ceiling() {
for a in [Number::MAX, 1e200, 1e-9, 1e-200, Number::MIN_POSITIVE] {
let cap = sigma_pin_cap(a);
assert!(
cap.is_infinite() || cap > 1.0,
"a={a:e} produced a ceiling of {cap:e}, which would release the bound",
);
}
}
#[test]
fn a_newly_pinned_bound_lands_under_the_ceiling_too() {
let cap = sigma_pin_cap(1.0);
let add = 1e-9 / (1e-14 * 1e-14);
assert!(add > cap, "the fixture needs an addend over the ceiling");
assert_eq!(pinned_entry(1e6, add, cap), cap);
}
#[test]
fn the_correctors_clamp_is_what_keeps_its_addend_under_a_unit_ceiling() {
let mu = 1e-9;
let margin = 1e-10 * 2.0;
let most = mu / (margin * margin);
let unit = sigma_pin_cap(1.0);
assert!(most < unit, "{most:e} should sit under {unit:e}");
assert_eq!(pinned_entry(0.0, most, unit), most);
assert!(sigma_pin_cap(1e-3) < most);
assert_eq!(
pinned_entry(0.0, most, sigma_pin_cap(1e-3)),
sigma_pin_cap(1e-3)
);
let crossover = (mu * Number::EPSILON * SIGMA_PIN_HEADROOM).sqrt() / margin;
assert!(
(1e-2..1e-1).contains(&crossover),
"the corrector's door is this wide: {crossover:e}",
);
}
#[test]
fn two_representable_contributions_can_swamp_a_row_together() {
let cap = sigma_pin_cap(1.0);
let (had, add) = (0.7 * cap, 0.7 * cap);
assert!(had < cap && add < cap && had + add > cap);
assert_eq!(pinned_entry(had, add, cap), cap);
}
#[test]
fn a_pinned_entry_under_the_ceiling_is_just_the_sum() {
let cap = sigma_pin_cap(1.0);
assert_eq!(pinned_entry(2.0, 3.0, cap), 5.0);
assert_eq!(pinned_entry(2.0, 1e30, Number::INFINITY), 1e30 + 2.0);
}
#[test]
fn a_diagonal_under_its_ceiling_is_left_alone() {
let sigma = dense(&[1.0, 1e6, 0.0, 1e12]);
assert!(cap_sigma(&sigma, &|_| sigma_pin_cap(1.0)).is_none());
}
#[test]
fn only_the_entries_over_their_ceiling_move() {
let cap = sigma_pin_cap(1.0);
let sigma = dense(&[1.0, 1e27, 1e6, Number::INFINITY]);
let capped = cap_sigma(&sigma, &|i| if i == 2 { Number::INFINITY } else { cap })
.expect("two entries are over their ceiling");
let got = capped
.as_any()
.downcast_ref::<DenseVector>()
.expect("dense")
.expanded_values();
assert_eq!(got, vec![1.0, cap, 1e6, cap]);
}
}