use std::cell::RefCell;
use std::rc::Rc;
use pounce_common::types::Number;
use crate::tnlp::{
BoundsInfo, IndexStyle, IpoptCq, IpoptData, MetaData, NlpInfo, ScalingRequest, Solution,
SparsityRequest, StartingPoint, TNLP,
};
const MIN_FACTOR: Number = 1e-12;
pub struct ScalingTnlp {
inner: Rc<RefCell<dyn TNLP>>,
d: Vec<Number>,
obj_scaling: Number,
g_scaling: Option<Vec<Number>>,
jac_cols: Vec<usize>,
hess_rc: Vec<(usize, usize)>,
index_offset: usize,
x_scratch: Vec<Number>,
lo_inf: Number,
up_inf: Number,
}
impl ScalingTnlp {
fn ensure_jac_cols(&mut self) -> bool {
if !self.jac_cols.is_empty() {
return true;
}
let Some(info) = self.inner.borrow_mut().get_nlp_info() else {
return false;
};
let nnz = info.nnz_jac_g as usize;
let (mut irow, mut jcol) = (vec![0; nnz], vec![0; nnz]);
let ok = self.inner.borrow_mut().eval_jac_g(
None,
true,
SparsityRequest::Structure {
irow: &mut irow,
jcol: &mut jcol,
},
);
if ok {
self.jac_cols = jcol
.iter()
.map(|&c| c as usize - self.index_offset)
.collect();
}
ok
}
fn ensure_hess_rc(&mut self) -> bool {
if !self.hess_rc.is_empty() {
return true;
}
let Some(info) = self.inner.borrow_mut().get_nlp_info() else {
return false;
};
let nnz = info.nnz_h_lag as usize;
let (mut irow, mut jcol) = (vec![0; nnz], vec![0; nnz]);
let ok = self.inner.borrow_mut().eval_h(
None,
true,
1.0,
None,
true,
SparsityRequest::Structure {
irow: &mut irow,
jcol: &mut jcol,
},
);
if ok {
self.hess_rc = irow
.iter()
.zip(jcol.iter())
.map(|(&r, &c)| {
(
r as usize - self.index_offset,
c as usize - self.index_offset,
)
})
.collect();
}
ok
}
fn with_unscaled<R>(&mut self, x: &[Number], f: impl FnOnce(&mut Self, &[Number]) -> R) -> R {
assert_eq!(
x.len(),
self.d.len(),
"scaling: got {} variables but {} factors",
x.len(),
self.d.len()
);
let mut buf = std::mem::take(&mut self.x_scratch);
buf.clear();
buf.extend(x.iter().zip(self.d.iter()).map(|(&v, &s)| v / s));
let out = f(self, &buf);
self.x_scratch = buf;
out
}
}
pub fn wrap_with_scaling(
inner: Rc<RefCell<dyn TNLP>>,
lo_inf: Number,
up_inf: Number,
) -> Result<Option<Rc<RefCell<dyn TNLP>>>, String> {
let info = inner
.borrow_mut()
.get_nlp_info()
.ok_or_else(|| "scaling: get_nlp_info failed on the inner problem".to_string())?;
let n = info.n as usize;
let m = info.m as usize;
let mut obj_scaling: Number = 1.0;
let mut use_x = false;
let mut x_scaling = vec![1.0; n];
let mut use_g = false;
let mut g_scaling = vec![1.0; m];
let supplied = inner.borrow_mut().get_scaling_parameters(ScalingRequest {
obj_scaling: &mut obj_scaling,
use_x_scaling: &mut use_x,
x_scaling: &mut x_scaling,
use_g_scaling: &mut use_g,
g_scaling: &mut g_scaling,
});
if !supplied || !use_x {
return Ok(None);
}
if x_scaling.len() != n {
return Err(format!(
"scaling: x_scaling has {} entries for {n} variables",
x_scaling.len()
));
}
if x_scaling.iter().all(|&s| s == 1.0) {
return Ok(None);
}
for (i, &s) in x_scaling.iter().enumerate() {
if !s.is_finite() || s <= 0.0 {
return Err(format!(
"scaling: variable {i} has scaling factor {s}; factors must be \
finite and positive (a negative factor would reverse the \
variable and swap its bounds)"
));
}
if s < MIN_FACTOR {
return Err(format!(
"scaling: variable {i} has scaling factor {s}, below the {MIN_FACTOR:e} \
floor; such a factor makes the substitution numerically meaningless"
));
}
}
{
let mut x_l = vec![0.0; n];
let mut x_u = vec![0.0; n];
let mut g_l = vec![0.0; m];
let mut g_u = vec![0.0; m];
let got = inner.borrow_mut().get_bounds_info(BoundsInfo {
x_l: &mut x_l,
x_u: &mut x_u,
g_l: &mut g_l,
g_u: &mut g_u,
});
if got {
for (i, &s) in x_scaling.iter().enumerate() {
if x_l[i] > lo_inf && x_l[i] * s <= lo_inf {
return Err(format!(
"scaling: variable {i} has lower bound {} and scaling factor \
{s}; the scaled bound {} is at or past nlp_lower_bound_inf \
({lo_inf}), where it would be read as no bound at all",
x_l[i],
x_l[i] * s
));
}
if x_u[i] < up_inf && x_u[i] * s >= up_inf {
return Err(format!(
"scaling: variable {i} has upper bound {} and scaling factor \
{s}; the scaled bound {} is at or past nlp_upper_bound_inf \
({up_inf}), where it would be read as no bound at all",
x_u[i],
x_u[i] * s
));
}
}
}
}
let index_offset = match info.index_style {
IndexStyle::C => 0,
IndexStyle::Fortran => 1,
};
Ok(Some(Rc::new(RefCell::new(ScalingTnlp {
inner,
d: x_scaling,
obj_scaling,
g_scaling: if use_g && g_scaling.len() == m {
Some(g_scaling)
} else {
None
},
jac_cols: Vec::new(),
hess_rc: Vec::new(),
index_offset,
x_scratch: Vec::with_capacity(n),
lo_inf,
up_inf,
}))))
}
pub fn factors_of(tnlp: &Rc<RefCell<dyn TNLP>>) -> Option<Vec<Number>> {
tnlp.borrow().scaling_factors()
}
impl TNLP for ScalingTnlp {
fn get_nlp_info(&mut self) -> Option<NlpInfo> {
self.inner.borrow_mut().get_nlp_info()
}
fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
let BoundsInfo { x_l, x_u, g_l, g_u } = b;
let ok = self
.inner
.borrow_mut()
.get_bounds_info(BoundsInfo { x_l, x_u, g_l, g_u });
if !ok {
return false;
}
for (i, &s) in self.d.iter().enumerate() {
if x_l[i] > self.lo_inf {
x_l[i] *= s;
}
if x_u[i] < self.up_inf {
x_u[i] *= s;
}
}
true
}
fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
let StartingPoint {
init_x,
x,
init_z,
z_l,
z_u,
init_lambda,
lambda,
} = sp;
let ok = self.inner.borrow_mut().get_starting_point(StartingPoint {
init_x,
x,
init_z,
z_l,
z_u,
init_lambda,
lambda,
});
if !ok {
return false;
}
if init_x {
for (i, &s) in self.d.iter().enumerate() {
x[i] *= s;
}
}
if init_z {
for (i, &s) in self.d.iter().enumerate() {
z_l[i] /= s;
z_u[i] /= s;
}
}
let _ = (init_lambda, lambda); true
}
fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
self.with_unscaled(x, |me, xu| me.inner.borrow_mut().eval_f(xu, new_x))
}
fn eval_grad_f(&mut self, x: &[Number], new_x: bool, grad_f: &mut [Number]) -> bool {
if !self.with_unscaled(x, |me, xu| {
me.inner.borrow_mut().eval_grad_f(xu, new_x, grad_f)
}) {
return false;
}
for (i, &s) in self.d.iter().enumerate() {
grad_f[i] /= s;
}
true
}
fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
self.with_unscaled(x, |me, xu| me.inner.borrow_mut().eval_g(xu, new_x, g))
}
fn eval_jac_g(&mut self, x: Option<&[Number]>, new_x: bool, mode: SparsityRequest<'_>) -> bool {
match mode {
SparsityRequest::Structure { irow, jcol } => {
let ok = self.inner.borrow_mut().eval_jac_g(
x,
new_x,
SparsityRequest::Structure { irow, jcol },
);
if ok {
self.jac_cols = jcol
.iter()
.map(|&c| c as usize - self.index_offset)
.collect();
}
ok
}
SparsityRequest::Values { values } => {
let ok = match x {
Some(xs) => self.with_unscaled(xs, |me, xu| {
me.inner.borrow_mut().eval_jac_g(
Some(xu),
new_x,
SparsityRequest::Values {
values: &mut *values,
},
)
}),
None => self.inner.borrow_mut().eval_jac_g(
None,
new_x,
SparsityRequest::Values {
values: &mut *values,
},
),
};
if !ok || !self.ensure_jac_cols() {
return false;
}
for (k, v) in values.iter_mut().enumerate() {
*v /= self.d[self.jac_cols[k]];
}
true
}
}
}
fn eval_h(
&mut self,
x: Option<&[Number]>,
new_x: bool,
obj_factor: Number,
lambda: Option<&[Number]>,
new_lambda: bool,
mode: SparsityRequest<'_>,
) -> bool {
match mode {
SparsityRequest::Structure { irow, jcol } => {
let ok = self.inner.borrow_mut().eval_h(
x,
new_x,
obj_factor,
lambda,
new_lambda,
SparsityRequest::Structure { irow, jcol },
);
if ok {
self.hess_rc = irow
.iter()
.zip(jcol.iter())
.map(|(&r, &c)| {
(
r as usize - self.index_offset,
c as usize - self.index_offset,
)
})
.collect();
}
ok
}
SparsityRequest::Values { values } => {
let ok = match x {
Some(xs) => self.with_unscaled(xs, |me, xu| {
me.inner.borrow_mut().eval_h(
Some(xu),
new_x,
obj_factor,
lambda,
new_lambda,
SparsityRequest::Values {
values: &mut *values,
},
)
}),
None => self.inner.borrow_mut().eval_h(
None,
new_x,
obj_factor,
lambda,
new_lambda,
SparsityRequest::Values {
values: &mut *values,
},
),
};
if !ok || !self.ensure_hess_rc() {
return false;
}
for (k, v) in values.iter_mut().enumerate() {
let (r, c) = self.hess_rc[k];
*v /= self.d[r] * self.d[c];
}
true
}
}
}
fn finalize_solution(&mut self, sol: Solution<'_>, ip_data: &IpoptData, ip_cq: &IpoptCq) {
assert_eq!(
sol.x.len(),
self.d.len(),
"scaling: solution has {} variables but {} factors",
sol.x.len(),
self.d.len()
);
let x: Vec<Number> = sol
.x
.iter()
.zip(self.d.iter())
.map(|(&v, &s)| v / s)
.collect();
let z_l: Vec<Number> = sol
.z_l
.iter()
.zip(self.d.iter())
.map(|(&v, &s)| v * s)
.collect();
let z_u: Vec<Number> = sol
.z_u
.iter()
.zip(self.d.iter())
.map(|(&v, &s)| v * s)
.collect();
self.inner.borrow_mut().finalize_solution(
Solution {
status: sol.status,
x: &x,
z_l: &z_l,
z_u: &z_u,
g: sol.g,
lambda: sol.lambda,
obj_value: sol.obj_value,
},
ip_data,
ip_cq,
);
}
fn get_scaling_parameters(&mut self, req: ScalingRequest<'_>) -> bool {
*req.obj_scaling = self.obj_scaling;
*req.use_x_scaling = false;
match &self.g_scaling {
Some(g) if g.len() == req.g_scaling.len() => {
req.g_scaling.copy_from_slice(g);
*req.use_g_scaling = true;
}
_ => *req.use_g_scaling = false,
}
true
}
fn get_var_con_metadata(&mut self, var: &mut MetaData, con: &mut MetaData) -> bool {
self.inner.borrow_mut().get_var_con_metadata(var, con)
}
fn finalize_metadata(&mut self, var: &MetaData, con: &MetaData) {
self.inner.borrow_mut().finalize_metadata(var, con);
}
fn get_variables_linearity(&mut self, types: &mut [crate::tnlp::Linearity]) -> bool {
self.inner.borrow_mut().get_variables_linearity(types)
}
fn get_constraints_linearity(&mut self, types: &mut [crate::tnlp::Linearity]) -> bool {
self.inner.borrow_mut().get_constraints_linearity(types)
}
fn get_objective_variables_linearity(&mut self, types: &mut [crate::tnlp::Linearity]) -> bool {
self.inner
.borrow_mut()
.get_objective_variables_linearity(types)
}
fn scaling_factors(&self) -> Option<Vec<Number>> {
Some(self.d.clone())
}
fn is_presolve_wrapper(&self) -> bool {
self.inner.borrow().is_presolve_wrapper()
}
}