stats_claw/optimizers/
objectives.rs1use super::Objective;
24
25#[derive(Debug, Clone)]
30pub struct Quadratic {
31 center: Vec<f64>,
33}
34
35impl Quadratic {
36 #[must_use]
46 pub const fn new(center: Vec<f64>) -> Self {
47 Self { center }
48 }
49}
50
51impl Objective for Quadratic {
52 fn value(&self, x: &[f64]) -> f64 {
53 x.iter()
54 .zip(&self.center)
55 .map(|(a, c)| (a - c) * (a - c))
56 .sum()
57 }
58
59 fn grad(&self, x: &[f64]) -> Vec<f64> {
60 x.iter()
61 .zip(&self.center)
62 .map(|(a, c)| 2.0 * (a - c))
63 .collect()
64 }
65
66 fn hessian(&self, x: &[f64]) -> Vec<Vec<f64>> {
67 let n = x.len();
68 (0..n)
69 .map(|i| (0..n).map(|j| if i == j { 2.0 } else { 0.0 }).collect())
70 .collect()
71 }
72}
73
74#[derive(Debug, Clone, Copy)]
80pub struct Rosenbrock;
81
82impl Objective for Rosenbrock {
83 fn value(&self, x: &[f64]) -> f64 {
84 let x0 = *x.first().unwrap_or(&0.0);
85 let x1 = *x.get(1).unwrap_or(&0.0);
86 let a = 1.0 - x0;
87 let b = x1 - x0 * x0;
88 b.mul_add(100.0 * b, a * a)
89 }
90
91 fn grad(&self, x: &[f64]) -> Vec<f64> {
92 let x0 = *x.first().unwrap_or(&0.0);
93 let x1 = *x.get(1).unwrap_or(&0.0);
94 let d0 = (-400.0 * x0).mul_add(x1 - x0 * x0, -2.0 * (1.0 - x0));
95 let d1 = 200.0 * (x1 - x0 * x0);
96 vec![d0, d1]
97 }
98
99 fn hessian(&self, x: &[f64]) -> Vec<Vec<f64>> {
100 let x0 = *x.first().unwrap_or(&0.0);
101 let x1 = *x.get(1).unwrap_or(&0.0);
102 let h00 = (-400.0_f64).mul_add(x1, (1200.0 * x0).mul_add(x0, 2.0));
103 let h01 = -400.0 * x0;
104 vec![vec![h00, h01], vec![h01, 200.0]]
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn quadratic_minimum_is_zero_at_center() {
114 let q = Quadratic::new(vec![3.0, -2.0]);
115 assert!(
116 q.value(&[3.0, -2.0]).abs() < 1e-15,
117 "f(c) = {}",
118 q.value(&[3.0, -2.0])
119 );
120 let g = q.grad(&[3.0, -2.0]);
121 assert!(super::super::norm(&g) < 1e-15, "grad at center nonzero");
122 }
123
124 #[test]
125 fn rosenbrock_minimum_is_zero_at_one_one() {
126 let r = Rosenbrock;
127 assert!(
128 r.value(&[1.0, 1.0]).abs() < 1e-15,
129 "f(1,1) = {}",
130 r.value(&[1.0, 1.0])
131 );
132 let g = r.grad(&[1.0, 1.0]);
133 assert!(super::super::norm(&g) < 1e-12, "grad at (1,1) nonzero");
134 }
135
136 #[test]
137 fn rosenbrock_analytic_hessian_matches_finite_difference() {
138 let r = Rosenbrock;
140 let x = [0.5, 0.3];
141 let analytic = r.hessian(&x);
142 let fd = Objective::hessian(&FdRosen, &x);
143 for (ra, rf) in analytic.iter().zip(&fd) {
144 for (a, f) in ra.iter().zip(rf) {
145 assert!((a - f).abs() < 1e-4, "hessian mismatch: {a} vs {f}");
146 }
147 }
148 }
149
150 struct FdRosen;
152 impl Objective for FdRosen {
153 fn value(&self, x: &[f64]) -> f64 {
154 Rosenbrock.value(x)
155 }
156 fn grad(&self, x: &[f64]) -> Vec<f64> {
157 Rosenbrock.grad(x)
158 }
159 }
160}