conspire/math/optimize/newton_raphson/
mod.rs1#[cfg(test)]
2mod test;
3
4use super::{
5 super::{
6 Banded, Hessian, Jacobian, Matrix, Scalar, Solution, SquareMatrix, Tensor, TensorVec,
7 Vector,
8 },
9 EqualityConstraint, FirstOrderRootFinding, LineSearch, OptimizeError, SecondOrderOptimization,
10};
11use crate::ABS_TOL;
12use std::ops::{Div, Mul};
13
14#[derive(Debug)]
16pub struct NewtonRaphson {
17 pub abs_tol: Scalar,
19 pub line_search: Option<LineSearch>,
21 pub max_steps: usize,
23}
24
25impl Default for NewtonRaphson {
26 fn default() -> Self {
27 Self {
28 abs_tol: ABS_TOL,
29 line_search: None,
30 max_steps: 25,
31 }
32 }
33}
34
35impl<F, J, X> FirstOrderRootFinding<F, J, X> for NewtonRaphson
36where
37 F: Jacobian,
38 for<'a> &'a F: Div<J, Output = X> + From<&'a X>,
39 J: Hessian,
40 X: Solution,
41 for<'a> &'a X: Mul<Scalar, Output = X>,
42 for<'a> &'a Matrix: Mul<&'a X, Output = Vector>,
43{
44 fn root(
45 &self,
46 function: impl Fn(&X) -> Result<F, OptimizeError>,
47 jacobian: impl Fn(&X) -> Result<J, OptimizeError>,
48 initial_guess: X,
49 equality_constraint: EqualityConstraint,
50 ) -> Result<X, OptimizeError> {
51 match equality_constraint {
52 EqualityConstraint::Linear(constraint_matrix, constraint_rhs) => constrained(
53 self,
54 |_: &X| panic!("No line search in root finding"),
55 function,
56 jacobian,
57 initial_guess,
58 None,
59 constraint_matrix,
60 constraint_rhs,
61 ),
62 EqualityConstraint::None => unconstrained(
63 self,
64 |_: &X| panic!("No line search in root finding"),
65 function,
66 jacobian,
67 initial_guess,
68 ),
69 }
70 }
71}
72
73impl<J, H, X> SecondOrderOptimization<Scalar, J, H, X> for NewtonRaphson
74where
75 H: Hessian,
76 J: Jacobian,
77 for<'a> &'a J: Div<H, Output = X> + From<&'a X>,
78 X: Solution,
79 for<'a> &'a X: Mul<Scalar, Output = X>,
80 for<'a> &'a Matrix: Mul<&'a X, Output = Vector>,
81{
82 fn minimize(
83 &self,
84 function: impl Fn(&X) -> Result<Scalar, OptimizeError>,
85 jacobian: impl Fn(&X) -> Result<J, OptimizeError>,
86 hessian: impl Fn(&X) -> Result<H, OptimizeError>,
87 initial_guess: X,
88 equality_constraint: EqualityConstraint,
89 banded: Option<Banded>,
90 ) -> Result<X, OptimizeError> {
91 match equality_constraint {
92 EqualityConstraint::Linear(constraint_matrix, constraint_rhs) => constrained(
93 self,
94 function,
95 jacobian,
96 hessian,
97 initial_guess,
98 banded,
99 constraint_matrix,
100 constraint_rhs,
101 ),
102 EqualityConstraint::None => {
103 unconstrained(self, function, jacobian, hessian, initial_guess)
104 }
105 }
106 }
107}
108
109fn unconstrained<J, H, X>(
110 newton_raphson: &NewtonRaphson,
111 function: impl Fn(&X) -> Result<Scalar, OptimizeError>,
112 jacobian: impl Fn(&X) -> Result<J, OptimizeError>,
113 hessian: impl Fn(&X) -> Result<H, OptimizeError>,
114 initial_guess: X,
115) -> Result<X, OptimizeError>
116where
117 H: Hessian,
118 J: Jacobian,
119 for<'a> &'a J: Div<H, Output = X> + From<&'a X>,
120 X: Solution,
121 for<'a> &'a X: Mul<Scalar, Output = X>,
122 for<'a> &'a Matrix: Mul<&'a X, Output = Vector>,
123{
124 let mut decrement;
125 let mut residual;
126 let mut solution = initial_guess;
127 let mut tangent;
128 for _ in 0..newton_raphson.max_steps {
129 residual = jacobian(&solution)?;
130 tangent = hessian(&solution)?;
131 if residual.norm_inf() < newton_raphson.abs_tol {
132 return Ok(solution);
133 } else {
134 decrement = &residual / tangent;
135 if let Some(line_search) = &newton_raphson.line_search {
136 decrement *=
137 line_search.backtrack(&function, &jacobian, &solution, &decrement, &1.0)?
138 }
139 solution -= decrement
140 }
141 }
142 Err(OptimizeError::MaximumStepsReached(
143 newton_raphson.max_steps,
144 format!("{:?}", &newton_raphson),
145 ))
146}
147
148#[allow(clippy::too_many_arguments)]
149fn constrained<J, H, X>(
150 newton_raphson: &NewtonRaphson,
151 _function: impl Fn(&X) -> Result<Scalar, OptimizeError>,
152 jacobian: impl Fn(&X) -> Result<J, OptimizeError>,
153 hessian: impl Fn(&X) -> Result<H, OptimizeError>,
154 initial_guess: X,
155 banded: Option<Banded>,
156 constraint_matrix: Matrix,
157 constraint_rhs: Vector,
158) -> Result<X, OptimizeError>
159where
160 H: Hessian,
161 J: Jacobian,
162 X: Solution,
163 for<'a> &'a Matrix: Mul<&'a X, Output = Vector>,
164{
165 if newton_raphson.line_search.is_some() {
166 panic!("Line search needs the exact penalty function in constrained optimization.");
167 }
168 let mut decrement;
169 let num_variables = initial_guess.num_entries();
170 let num_constraints = constraint_rhs.len();
171 let num_total = num_variables + num_constraints;
172 let mut multipliers = Vector::zero(num_constraints);
173 let mut residual = Vector::zero(num_total);
174 let mut solution = initial_guess;
175 let mut tangent = SquareMatrix::zero(num_total);
176 constraint_matrix
177 .iter()
178 .enumerate()
179 .for_each(|(i, constraint_matrix_i)| {
180 constraint_matrix_i
181 .iter()
182 .enumerate()
183 .for_each(|(j, constraint_matrix_ij)| {
184 tangent[i + num_variables][j] = -constraint_matrix_ij;
185 tangent[j][i + num_variables] = -constraint_matrix_ij;
186 })
187 });
188 for _ in 0..newton_raphson.max_steps {
189 (jacobian(&solution)? - &multipliers * &constraint_matrix).fill_into_chained(
190 &constraint_rhs - &constraint_matrix * &solution,
191 &mut residual,
192 );
193 hessian(&solution)?.fill_into(&mut tangent);
194 if residual.norm_inf() < newton_raphson.abs_tol {
195 return Ok(solution);
196 } else if let Some(ref band) = banded {
197 decrement = tangent.solve_lu_banded(&residual, band)?
198 } else {
199 decrement = tangent.solve_lu(&residual)?
200 }
201 solution.decrement_from_chained(&mut multipliers, decrement)
202 }
205 Err(OptimizeError::MaximumStepsReached(
206 newton_raphson.max_steps,
207 format!("{:?}", &newton_raphson),
208 ))
209}