use pounce_common::types::Number;
const ECHO_RTOL: Number = 1e-12;
pub(crate) fn is_seed(z: Number) -> bool {
z.is_finite() && z > 0.0
}
pub(crate) fn as_reported_z(z: Number) -> Number {
if is_seed(z) { z } else { 0.0 }
}
pub(crate) fn as_reported_lambda(l: Number) -> Number {
if l.is_finite() { l } else { 0.0 }
}
pub(crate) fn served_bound_multipliers(
init_z: bool,
z: &[Number],
x_l: &[Number],
x_u: &[Number],
) -> Vec<Number> {
z.iter()
.enumerate()
.map(|(j, &v)| {
let fixed = matches!((x_l.get(j), x_u.get(j)), (Some(l), Some(u)) if l == u);
if init_z && !fixed { v } else { Number::NAN }
})
.collect()
}
pub(crate) fn served_row_multipliers(init_lambda: bool, lambda: &[Number]) -> Vec<Number> {
if init_lambda {
lambda.to_vec()
} else {
vec![Number::NAN; lambda.len()]
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct ServedWarmStart {
pub z_l_in: Vec<Number>,
pub z_u_in: Vec<Number>,
pub lambda_in: Vec<Number>,
pub z_l_out: Vec<Number>,
pub z_u_out: Vec<Number>,
pub lambda_out: Vec<Number>,
}
impl ServedWarmStart {
pub(crate) fn echoed_by(&self, z_l: &[Number], z_u: &[Number], lambda: &[Number]) -> bool {
if z_l.len() != self.z_l_out.len()
|| z_u.len() != self.z_u_out.len()
|| lambda.len() != self.lambda_out.len()
{
return false;
}
let same = |got: Number, served: Number| {
(got - served).abs() <= ECHO_RTOL * served.abs() || got.to_bits() == served.to_bits()
};
let mut informative = 0usize;
for (got, &served) in z_l
.iter()
.chain(z_u)
.zip(self.z_l_out.iter().chain(&self.z_u_out))
{
if is_seed(served) {
if !same(*got, served) {
return false;
}
informative += 1;
}
}
for (&got, &served) in lambda.iter().zip(&self.lambda_out) {
if served.is_finite() {
if !same(got, served) {
return false;
}
if served != 0.0 {
informative += 1;
}
}
}
informative > 0
}
}
#[cfg(test)]
mod tests {
use super::*;
fn served() -> ServedWarmStart {
ServedWarmStart {
z_l_out: vec![20.0, 0.0, Number::NAN],
z_u_out: vec![0.0, 0.0, 0.0],
lambda_out: vec![-2.0, 0.0],
..Default::default()
}
}
#[test]
fn an_unchanged_return_is_an_echo_whatever_the_solver_filled() {
let s = served();
assert!(s.echoed_by(&[20.0, 1e-3, 1.0], &[1e-3; 3], &[-2.0, 0.0]));
assert!(s.echoed_by(&[20.0 * (1.0 + 4e-16), 0.0, 1.0], &[0.0; 3], &[-2.0, 0.0]));
}
#[test]
fn any_moved_seed_or_row_multiplier_is_not_an_echo() {
let s = served();
assert!(!s.echoed_by(&[20.0 + 1e-9, 0.0, 1.0], &[0.0; 3], &[-2.0, 0.0]));
assert!(!s.echoed_by(&[20.0, 0.0, 1.0], &[0.0; 3], &[-2.0, 1e-300]));
assert!(!s.echoed_by(&[20.0, 0.0], &[0.0; 3], &[-2.0, 0.0]));
}
#[test]
fn a_start_with_nothing_seeded_is_never_an_echo() {
let s = ServedWarmStart {
z_l_out: vec![0.0, Number::NAN],
z_u_out: vec![0.0, 0.0],
lambda_out: vec![0.0],
..Default::default()
};
assert!(!s.echoed_by(&[1e-3, 1.0], &[1e-3, 1e-3], &[0.0]));
}
}