use pounce_common::types::Number;
use std::cell::RefCell;
#[derive(Clone, Debug, Default)]
pub struct UserIterate {
pub x: Vec<Number>,
pub z_l: Vec<Number>,
pub z_u: Vec<Number>,
pub g: Vec<Number>,
pub lambda: Vec<Number>,
pub scaling: Option<IterateScaling>,
pub x_l_alg: Vec<Number>,
pub x_u_alg: Vec<Number>,
pub grad_lag_x: Vec<Number>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct IterateScaling {
pub obj: Number,
pub x: Vec<Number>,
pub x_in_alg: Vec<bool>,
pub g: Vec<Number>,
}
impl IterateScaling {
pub fn unit(obj: Number, n: usize, m: usize) -> Self {
Self {
obj,
x: vec![1.0; n],
x_in_alg: vec![true; n],
g: vec![1.0; m],
}
}
fn fits(&self, n: usize, m: usize) -> bool {
self.x.len() == n && self.x_in_alg.len() == n && self.g.len() == m
}
}
pub fn scaled_iterate(it: &UserIterate) -> UserIterate {
let (n, m) = (it.x.len(), it.g.len());
let s = match it.scaling.as_ref().filter(|s| s.fits(n, m)) {
Some(s) => s.clone(),
None => IterateScaling::unit(1.0, n, m),
};
let mut out = UserIterate {
x: it.x.iter().zip(&s.x).map(|(v, d)| v * d).collect(),
z_l: vec![0.0; it.z_l.len()],
z_u: vec![0.0; it.z_u.len()],
g: it.g.iter().zip(&s.g).map(|(v, d)| v * d).collect(),
lambda: it
.lambda
.iter()
.zip(&s.g)
.map(|(v, d)| s.obj * v / d)
.collect(),
scaling: Some(s.clone()),
x_l_alg: it.x_l_alg.iter().zip(&s.x).map(|(v, d)| v * d).collect(),
x_u_alg: it.x_u_alg.iter().zip(&s.x).map(|(v, d)| v * d).collect(),
grad_lag_x: Vec::new(),
};
for j in 0..n.min(it.z_l.len()).min(it.z_u.len()) {
let f = s.obj / s.x[j];
if !s.x_in_alg[j] && s.obj < 0.0 {
out.z_l[j] = -it.z_u[j] * s.obj;
out.z_u[j] = -it.z_l[j] * s.obj;
} else {
out.z_l[j] = it.z_l[j] * f;
out.z_u[j] = it.z_u[j] * f;
}
}
out
}
thread_local! {
static CURRENT: RefCell<Option<UserIterate>> = const { RefCell::new(None) };
}
pub struct UserIterateGuard {
prev: Option<UserIterate>,
}
impl UserIterateGuard {
pub fn install(it: UserIterate) -> Self {
let prev = CURRENT.with(|c| c.borrow_mut().replace(it));
Self { prev }
}
}
impl Drop for UserIterateGuard {
fn drop(&mut self) {
let prev = self.prev.take();
CURRENT.with(|c| *c.borrow_mut() = prev);
}
}
pub fn with_current_user_iterate<F, R>(f: F) -> Option<R>
where
F: FnOnce(&UserIterate) -> R,
{
CURRENT.with(|c| c.borrow().as_ref().map(f))
}
pub fn current_user_iterate() -> Option<UserIterate> {
CURRENT.with(|c| c.borrow().clone())
}
pub fn is_installed() -> bool {
CURRENT.with(|c| c.borrow().is_some())
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Violations {
pub x_l_violation: Vec<Number>,
pub x_u_violation: Vec<Number>,
pub compl_x_l: Vec<Number>,
pub compl_x_u: Vec<Number>,
pub grad_lag_x: Vec<Number>,
pub constraint_violation: Vec<Number>,
pub compl_g: Vec<Number>,
}
#[derive(Clone, Copy, Debug)]
pub struct DeclaredBounds<'a> {
pub x_l: &'a [Number],
pub x_u: &'a [Number],
pub g_l: &'a [Number],
pub g_u: &'a [Number],
pub lower_inf: Number,
pub upper_inf: Number,
}
pub fn lagrangian_gradient(
it: &UserIterate,
grad_f: &[Number],
jac_rows: &[usize],
jac_cols: &[usize],
jac_vals: &[Number],
) -> Vec<Number> {
let mut s = grad_f.to_vec();
for ((&r, &c), &v) in jac_rows.iter().zip(jac_cols).zip(jac_vals) {
if let (Some(&l), Some(sc)) = (it.lambda.get(r), s.get_mut(c)) {
*sc += v * l;
}
}
for (j, sj) in s.iter_mut().enumerate() {
*sj += it.z_u.get(j).copied().unwrap_or(0.0) - it.z_l.get(j).copied().unwrap_or(0.0);
}
s
}
pub fn user_violations(
it: &UserIterate,
b: &DeclaredBounds<'_>,
grad_lag: Option<Vec<Number>>,
zero_fixed: bool,
) -> Violations {
let n = it.x.len();
let m = it.g.len();
let mut v = Violations {
x_l_violation: vec![0.0; n],
x_u_violation: vec![0.0; n],
compl_x_l: vec![0.0; n],
compl_x_u: vec![0.0; n],
grad_lag_x: grad_lag.unwrap_or_else(|| vec![0.0; n]),
constraint_violation: vec![0.0; m],
compl_g: vec![0.0; m],
};
let alg = |v: &[Number], j: usize, declared: Number| {
v.get(j)
.copied()
.filter(|b| b.is_finite() && v.len() == n)
.unwrap_or(declared)
};
for j in 0..n {
let x = it.x[j];
let (lo, up) = (b.x_l[j], b.x_u[j]);
if lo > b.lower_inf {
v.x_l_violation[j] = (lo - x).max(0.0);
v.compl_x_l[j] = (x - alg(&it.x_l_alg, j, lo)) * it.z_l[j];
}
if up < b.upper_inf {
v.x_u_violation[j] = (x - up).max(0.0);
v.compl_x_u[j] = (alg(&it.x_u_alg, j, up) - x) * it.z_u[j];
}
if zero_fixed
&& lo == up
&& let Some(gj) = v.grad_lag_x.get_mut(j)
{
*gj = 0.0;
}
}
for i in 0..m {
let (g, lam) = (it.g[i], it.lambda[i]);
let (lo, up) = (b.g_l[i], b.g_u[i]);
if lo == up {
v.constraint_violation[i] = (g - lo).abs();
v.compl_g[i] = -(g - lo) * lam;
continue;
}
let mut viol: Number = 0.0;
let mut compl = 0.0;
if lo > b.lower_inf {
viol = viol.max(lo - g);
compl += lam.min(0.0) * (lo - g);
}
if up < b.upper_inf {
viol = viol.max(g - up);
compl -= lam.max(0.0) * (g - up);
}
v.constraint_violation[i] = viol;
v.compl_g[i] = compl;
}
v
}
pub fn scale_violations(mut v: Violations, it: &UserIterate) -> Violations {
let (n, m) = (v.x_l_violation.len(), v.constraint_violation.len());
let s = match it.scaling.as_ref().filter(|s| s.fits(n, m)) {
Some(s) => s.clone(),
None => IterateScaling::unit(1.0, n, m),
};
for j in 0..n {
v.x_l_violation[j] *= s.x[j];
v.x_u_violation[j] *= s.x[j];
v.compl_x_l[j] *= s.obj;
v.compl_x_u[j] *= s.obj;
if let Some(g) = v.grad_lag_x.get_mut(j) {
*g *= s.obj / s.x[j];
}
}
for i in 0..m {
v.constraint_violation[i] *= s.g[i];
v.compl_g[i] *= s.obj;
}
v
}
pub fn current_violations(
it: &UserIterate,
b: &DeclaredBounds<'_>,
grad_lag: Option<Vec<Number>>,
zero_fixed: bool,
scaled: bool,
) -> Violations {
let v = user_violations(it, b, grad_lag, zero_fixed);
if scaled { scale_violations(v, it) } else { v }
}
#[cfg(test)]
mod tests {
use super::*;
fn it(x: Number) -> UserIterate {
UserIterate {
x: vec![x],
..Default::default()
}
}
#[test]
fn a_negative_objective_factor_swaps_only_a_fixed_variables_multipliers() {
let it = UserIterate {
x: vec![2.0, 1.0],
z_l: vec![0.5, 0.0],
z_u: vec![0.25, 4.0],
g: vec![3.0],
lambda: vec![-1.0],
scaling: Some(IterateScaling {
obj: -2.0,
x: vec![4.0, 1.0],
x_in_alg: vec![true, false],
g: vec![10.0],
}),
..Default::default()
};
let s = scaled_iterate(&it);
assert_eq!(s.x, vec![8.0, 1.0]);
assert_eq!(s.z_l, vec![-0.25, 8.0]);
assert_eq!(s.z_u, vec![-0.125, 0.0]);
assert_eq!(s.g, vec![30.0]);
assert_eq!(s.lambda, vec![0.2]);
}
#[test]
fn a_nested_guard_restores_the_outer_iterate() {
assert!(!is_installed());
{
let _outer = UserIterateGuard::install(it(1.0));
{
let _inner = UserIterateGuard::install(it(2.0));
assert_eq!(with_current_user_iterate(|u| u.x[0]), Some(2.0));
}
assert_eq!(with_current_user_iterate(|u| u.x[0]), Some(1.0));
}
assert!(!is_installed());
}
}