use super::polynomial::{Monomial, Polynomial, Term};
use super::sos::{SOSChecker, SOSConfig, SOSResult};
#[derive(Debug, Clone)]
pub struct NonnegativityCertificate {
pub polynomial: Polynomial,
pub is_nonnegative: bool,
pub sos_decomposition: Option<super::sos::SOSDecomposition>,
pub counterexample: Option<Vec<f64>>,
}
impl NonnegativityCertificate {
pub fn certify(p: &Polynomial) -> Self {
let checker = SOSChecker::default();
let result = checker.check(p);
match result {
SOSResult::IsSOS(decomp) => Self {
polynomial: p.clone(),
is_nonnegative: true,
sos_decomposition: Some(decomp),
counterexample: None,
},
SOSResult::NotSOS { witness } => Self {
polynomial: p.clone(),
is_nonnegative: false,
sos_decomposition: None,
counterexample: Some(witness),
},
SOSResult::Unknown => Self {
polynomial: p.clone(),
is_nonnegative: false, sos_decomposition: None,
counterexample: None,
},
}
}
pub fn certify_on_box(p: &Polynomial, lb: f64, ub: f64) -> Self {
let n = p.num_variables().max(1);
let mut modified = p.clone();
for i in 0..n {
let xi = Polynomial::var(i);
let xi_minus_lb = xi.sub(&Polynomial::constant(lb));
let ub_minus_xi = Polynomial::constant(ub).sub(&xi);
let slack = xi_minus_lb.mul(&ub_minus_xi);
modified = modified.add(&slack.scale(0.001));
}
Self::certify(&modified)
}
}
#[derive(Debug, Clone)]
pub struct BoundsCertificate {
pub lower: Option<NonnegativityCertificate>,
pub upper: Option<NonnegativityCertificate>,
pub lower_bound: f64,
pub upper_bound: f64,
}
impl BoundsCertificate {
pub fn certify_bounds(p: &Polynomial) -> Self {
let lower_bound = Self::find_lower_bound(p, -1000.0, 1000.0);
let lower = if lower_bound > f64::NEG_INFINITY {
let shifted = p.sub(&Polynomial::constant(lower_bound));
Some(NonnegativityCertificate::certify(&shifted))
} else {
None
};
let upper_bound = Self::find_upper_bound(p, -1000.0, 1000.0);
let upper = if upper_bound < f64::INFINITY {
let shifted = Polynomial::constant(upper_bound).sub(p);
Some(NonnegativityCertificate::certify(&shifted))
} else {
None
};
Self {
lower,
upper,
lower_bound,
upper_bound,
}
}
fn find_lower_bound(p: &Polynomial, mut lo: f64, mut hi: f64) -> f64 {
let checker = SOSChecker::new(SOSConfig {
max_iters: 50,
..Default::default()
});
let mut best = f64::NEG_INFINITY;
for _ in 0..20 {
let mid = (lo + hi) / 2.0;
let shifted = p.sub(&Polynomial::constant(mid));
match checker.check(&shifted) {
SOSResult::IsSOS(_) => {
best = mid;
lo = mid;
}
_ => {
hi = mid;
}
}
if hi - lo < 0.01 {
break;
}
}
best
}
fn find_upper_bound(p: &Polynomial, mut lo: f64, mut hi: f64) -> f64 {
let checker = SOSChecker::new(SOSConfig {
max_iters: 50,
..Default::default()
});
let mut best = f64::INFINITY;
for _ in 0..20 {
let mid = (lo + hi) / 2.0;
let shifted = Polynomial::constant(mid).sub(p);
match checker.check(&shifted) {
SOSResult::IsSOS(_) => {
best = mid;
hi = mid;
}
_ => {
lo = mid;
}
}
if hi - lo < 0.01 {
break;
}
}
best
}
pub fn is_valid(&self) -> bool {
self.lower_bound <= self.upper_bound
}
pub fn width(&self) -> f64 {
if self.is_valid() {
self.upper_bound - self.lower_bound
} else {
f64::INFINITY
}
}
}
#[derive(Debug, Clone)]
pub struct MonotonicityCertificate {
pub variable: usize,
pub is_increasing: bool,
pub is_decreasing: bool,
pub derivative_certificate: Option<NonnegativityCertificate>,
}
impl MonotonicityCertificate {
pub fn certify(p: &Polynomial, variable: usize) -> Self {
let derivative = Self::partial_derivative(p, variable);
let incr_cert = NonnegativityCertificate::certify(&derivative);
let is_increasing = incr_cert.is_nonnegative;
let neg_deriv = derivative.neg();
let decr_cert = NonnegativityCertificate::certify(&neg_deriv);
let is_decreasing = decr_cert.is_nonnegative;
Self {
variable,
is_increasing,
is_decreasing,
derivative_certificate: if is_increasing {
Some(incr_cert)
} else if is_decreasing {
Some(decr_cert)
} else {
None
},
}
}
fn partial_derivative(p: &Polynomial, var: usize) -> Polynomial {
let terms: Vec<Term> = p
.terms()
.filter_map(|(m, &c)| {
let power = m
.powers
.iter()
.find(|&&(i, _)| i == var)
.map(|&(_, p)| p)
.unwrap_or(0);
if power == 0 {
return None;
}
let new_coeff = c * power as f64;
let new_powers: Vec<(usize, usize)> = m
.powers
.iter()
.map(|&(i, p)| if i == var { (i, p - 1) } else { (i, p) })
.filter(|&(_, p)| p > 0)
.collect();
Some(Term::new(new_coeff, new_powers))
})
.collect();
Polynomial::from_terms(terms)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nonnegativity_square() {
let x = Polynomial::var(0);
let p = x.square();
let cert = NonnegativityCertificate::certify(&p);
assert!(cert.counterexample.is_none() || cert.is_nonnegative);
}
#[test]
fn test_nonnegativity_sum_of_squares() {
let x = Polynomial::var(0);
let y = Polynomial::var(1);
let p = x.square().add(&y.square());
let cert = NonnegativityCertificate::certify(&p);
assert!(cert.counterexample.is_none() || cert.is_nonnegative);
}
#[test]
fn test_monotonicity_linear() {
let p = Polynomial::from_terms(vec![
Term::new(2.0, vec![(0, 1)]), Term::new(1.0, vec![(1, 1)]), ]);
let cert = MonotonicityCertificate::certify(&p, 0);
assert!(cert.is_increasing);
assert!(!cert.is_decreasing);
}
#[test]
fn test_monotonicity_negative() {
let p = Polynomial::from_terms(vec![Term::new(-1.0, vec![(0, 1)])]);
let cert = MonotonicityCertificate::certify(&p, 0);
assert!(!cert.is_increasing);
assert!(cert.is_decreasing);
}
#[test]
fn test_bounds_constant() {
let p = Polynomial::constant(5.0);
let cert = BoundsCertificate::certify_bounds(&p);
assert!((cert.lower_bound - 5.0).abs() < 1.0);
assert!((cert.upper_bound - 5.0).abs() < 1.0);
}
}