ruvector_math/optimization/
certificates.rs1use super::polynomial::{Polynomial, Term, Monomial};
6use super::sos::{SOSChecker, SOSResult, SOSConfig};
7
8#[derive(Debug, Clone)]
10pub struct NonnegativityCertificate {
11 pub polynomial: Polynomial,
13 pub is_nonnegative: bool,
15 pub sos_decomposition: Option<super::sos::SOSDecomposition>,
17 pub counterexample: Option<Vec<f64>>,
19}
20
21impl NonnegativityCertificate {
22 pub fn certify(p: &Polynomial) -> Self {
24 let checker = SOSChecker::default();
25 let result = checker.check(p);
26
27 match result {
28 SOSResult::IsSOS(decomp) => Self {
29 polynomial: p.clone(),
30 is_nonnegative: true,
31 sos_decomposition: Some(decomp),
32 counterexample: None,
33 },
34 SOSResult::NotSOS { witness } => Self {
35 polynomial: p.clone(),
36 is_nonnegative: false,
37 sos_decomposition: None,
38 counterexample: Some(witness),
39 },
40 SOSResult::Unknown => Self {
41 polynomial: p.clone(),
42 is_nonnegative: false, sos_decomposition: None,
44 counterexample: None,
45 },
46 }
47 }
48
49 pub fn certify_on_box(p: &Polynomial, lb: f64, ub: f64) -> Self {
51 let n = p.num_variables().max(1);
58
59 let mut modified = p.clone();
61
62 for i in 0..n {
65 let xi = Polynomial::var(i);
66 let xi_minus_lb = xi.sub(&Polynomial::constant(lb));
67 let ub_minus_xi = Polynomial::constant(ub).sub(&xi);
68 let slack = xi_minus_lb.mul(&ub_minus_xi);
69
70 modified = modified.add(&slack.scale(0.001));
73 }
74
75 Self::certify(&modified)
76 }
77}
78
79#[derive(Debug, Clone)]
81pub struct BoundsCertificate {
82 pub lower: Option<NonnegativityCertificate>,
84 pub upper: Option<NonnegativityCertificate>,
86 pub lower_bound: f64,
88 pub upper_bound: f64,
90}
91
92impl BoundsCertificate {
93 pub fn certify_bounds(p: &Polynomial) -> Self {
95 let lower_bound = Self::find_lower_bound(p, -1000.0, 1000.0);
99 let lower = if lower_bound > f64::NEG_INFINITY {
100 let shifted = p.sub(&Polynomial::constant(lower_bound));
101 Some(NonnegativityCertificate::certify(&shifted))
102 } else {
103 None
104 };
105
106 let upper_bound = Self::find_upper_bound(p, -1000.0, 1000.0);
108 let upper = if upper_bound < f64::INFINITY {
109 let shifted = Polynomial::constant(upper_bound).sub(p);
110 Some(NonnegativityCertificate::certify(&shifted))
111 } else {
112 None
113 };
114
115 Self {
116 lower,
117 upper,
118 lower_bound,
119 upper_bound,
120 }
121 }
122
123 fn find_lower_bound(p: &Polynomial, mut lo: f64, mut hi: f64) -> f64 {
124 let checker = SOSChecker::new(SOSConfig {
125 max_iters: 50,
126 ..Default::default()
127 });
128
129 let mut best = f64::NEG_INFINITY;
130
131 for _ in 0..20 {
132 let mid = (lo + hi) / 2.0;
133 let shifted = p.sub(&Polynomial::constant(mid));
134
135 match checker.check(&shifted) {
136 SOSResult::IsSOS(_) => {
137 best = mid;
138 lo = mid;
139 }
140 _ => {
141 hi = mid;
142 }
143 }
144
145 if hi - lo < 0.01 {
146 break;
147 }
148 }
149
150 best
151 }
152
153 fn find_upper_bound(p: &Polynomial, mut lo: f64, mut hi: f64) -> f64 {
154 let checker = SOSChecker::new(SOSConfig {
155 max_iters: 50,
156 ..Default::default()
157 });
158
159 let mut best = f64::INFINITY;
160
161 for _ in 0..20 {
162 let mid = (lo + hi) / 2.0;
163 let shifted = Polynomial::constant(mid).sub(p);
164
165 match checker.check(&shifted) {
166 SOSResult::IsSOS(_) => {
167 best = mid;
168 hi = mid;
169 }
170 _ => {
171 lo = mid;
172 }
173 }
174
175 if hi - lo < 0.01 {
176 break;
177 }
178 }
179
180 best
181 }
182
183 pub fn is_valid(&self) -> bool {
185 self.lower_bound <= self.upper_bound
186 }
187
188 pub fn width(&self) -> f64 {
190 if self.is_valid() {
191 self.upper_bound - self.lower_bound
192 } else {
193 f64::INFINITY
194 }
195 }
196}
197
198#[derive(Debug, Clone)]
200pub struct MonotonicityCertificate {
201 pub variable: usize,
203 pub is_increasing: bool,
205 pub is_decreasing: bool,
207 pub derivative_certificate: Option<NonnegativityCertificate>,
209}
210
211impl MonotonicityCertificate {
212 pub fn certify(p: &Polynomial, variable: usize) -> Self {
214 let derivative = Self::partial_derivative(p, variable);
216
217 let incr_cert = NonnegativityCertificate::certify(&derivative);
218 let is_increasing = incr_cert.is_nonnegative;
219
220 let neg_deriv = derivative.neg();
221 let decr_cert = NonnegativityCertificate::certify(&neg_deriv);
222 let is_decreasing = decr_cert.is_nonnegative;
223
224 Self {
225 variable,
226 is_increasing,
227 is_decreasing,
228 derivative_certificate: if is_increasing {
229 Some(incr_cert)
230 } else if is_decreasing {
231 Some(decr_cert)
232 } else {
233 None
234 },
235 }
236 }
237
238 fn partial_derivative(p: &Polynomial, var: usize) -> Polynomial {
240 let terms: Vec<Term> = p
241 .terms()
242 .filter_map(|(m, &c)| {
243 let power = m.powers.iter().find(|&&(i, _)| i == var).map(|&(_, p)| p).unwrap_or(0);
245
246 if power == 0 {
247 return None;
248 }
249
250 let new_coeff = c * power as f64;
252
253 let new_powers: Vec<(usize, usize)> = m
255 .powers
256 .iter()
257 .map(|&(i, p)| {
258 if i == var {
259 (i, p - 1)
260 } else {
261 (i, p)
262 }
263 })
264 .filter(|&(_, p)| p > 0)
265 .collect();
266
267 Some(Term::new(new_coeff, new_powers))
268 })
269 .collect();
270
271 Polynomial::from_terms(terms)
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn test_nonnegativity_square() {
281 let x = Polynomial::var(0);
283 let p = x.square();
284
285 let cert = NonnegativityCertificate::certify(&p);
286 assert!(cert.counterexample.is_none() || cert.is_nonnegative);
289 }
290
291 #[test]
292 fn test_nonnegativity_sum_of_squares() {
293 let x = Polynomial::var(0);
295 let y = Polynomial::var(1);
296 let p = x.square().add(&y.square());
297
298 let cert = NonnegativityCertificate::certify(&p);
299 assert!(cert.counterexample.is_none() || cert.is_nonnegative);
302 }
303
304 #[test]
305 fn test_monotonicity_linear() {
306 let p = Polynomial::from_terms(vec![
308 Term::new(2.0, vec![(0, 1)]), Term::new(1.0, vec![(1, 1)]), ]);
311
312 let cert = MonotonicityCertificate::certify(&p, 0);
313 assert!(cert.is_increasing);
314 assert!(!cert.is_decreasing);
315 }
316
317 #[test]
318 fn test_monotonicity_negative() {
319 let p = Polynomial::from_terms(vec![Term::new(-1.0, vec![(0, 1)])]);
321
322 let cert = MonotonicityCertificate::certify(&p, 0);
323 assert!(!cert.is_increasing);
324 assert!(cert.is_decreasing);
325 }
326
327 #[test]
328 fn test_bounds_constant() {
329 let p = Polynomial::constant(5.0);
330 let cert = BoundsCertificate::certify_bounds(&p);
331
332 assert!((cert.lower_bound - 5.0).abs() < 1.0);
334 assert!((cert.upper_bound - 5.0).abs() < 1.0);
335 }
336}