1use faer::{ComplexField, Conjugate, SimpleEntity};
13use numra_core::Scalar;
14use numra_linalg::{DenseMatrix, LUFactorization, LinalgError, Matrix};
15
16#[derive(Clone, Debug)]
18pub struct NewtonOptions<S: Scalar> {
19 pub max_iter: usize,
21 pub atol: S,
23 pub rtol: S,
25 pub line_search: bool,
27 pub min_step: S,
29 pub max_backsteps: usize,
31 pub armijo: S,
33}
34
35impl<S: Scalar> Default for NewtonOptions<S> {
36 fn default() -> Self {
37 Self {
38 max_iter: 50,
39 atol: S::from_f64(1e-10),
40 rtol: S::from_f64(1e-10),
41 line_search: true,
42 min_step: S::from_f64(1e-4),
43 max_backsteps: 10,
44 armijo: S::from_f64(1e-4),
45 }
46 }
47}
48
49impl<S: Scalar> NewtonOptions<S> {
50 pub fn new() -> Self {
52 Self::default()
53 }
54
55 pub fn max_iter(mut self, max_iter: usize) -> Self {
57 self.max_iter = max_iter;
58 self
59 }
60
61 pub fn atol(mut self, atol: S) -> Self {
63 self.atol = atol;
64 self
65 }
66
67 pub fn rtol(mut self, rtol: S) -> Self {
69 self.rtol = rtol;
70 self
71 }
72
73 pub fn line_search(mut self, enable: bool) -> Self {
75 self.line_search = enable;
76 self
77 }
78}
79
80#[derive(Clone, Debug)]
82pub struct NewtonResult<S: Scalar> {
83 pub x: Vec<S>,
85 pub residual_norm: S,
87 pub iterations: usize,
89 pub n_eval: usize,
91 pub n_jac: usize,
93 pub converged: bool,
95 pub message: String,
97}
98
99pub trait NonlinearSystem<S: Scalar>: Send + Sync {
101 fn dim(&self) -> usize;
103
104 fn eval(&self, x: &[S], f: &mut [S]);
106
107 fn jacobian(&self, x: &[S], jac: &mut [S]) {
110 let _ = (x, jac);
111 }
113
114 fn has_jacobian(&self) -> bool {
116 false
117 }
118}
119
120pub struct Newton<S: Scalar> {
122 options: NewtonOptions<S>,
123}
124
125impl<S: Scalar + SimpleEntity + Conjugate<Canonical = S> + ComplexField> Newton<S> {
126 pub fn new(options: NewtonOptions<S>) -> Self {
128 Self { options }
129 }
130
131 pub fn default_solver() -> Self {
133 Self::new(NewtonOptions::default())
134 }
135
136 pub fn solve<Sys: NonlinearSystem<S>>(
138 &self,
139 system: &Sys,
140 x0: &[S],
141 ) -> Result<NewtonResult<S>, LinalgError> {
142 let n = system.dim();
143 if x0.len() != n {
144 return Err(LinalgError::DimensionMismatch {
145 expected: (n, 1),
146 actual: (x0.len(), 1),
147 });
148 }
149
150 let mut x = x0.to_vec();
151 let mut f = vec![S::ZERO; n];
152 let mut jac_data = vec![S::ZERO; n * n];
153 let mut dx = vec![S::ZERO; n];
154
155 let mut n_eval = 0;
156 let mut n_jac = 0;
157 let mut converged = false;
158 let mut message = String::new();
159
160 system.eval(&x, &mut f);
162 n_eval += 1;
163 let mut f_norm = self.norm(&f);
164 let f0_norm = f_norm;
165
166 for iter in 0..self.options.max_iter {
167 let tol = self.options.atol + self.options.rtol * f0_norm;
169 if f_norm < tol {
170 converged = true;
171 message = format!("Converged after {} iterations", iter);
172 break;
173 }
174
175 if system.has_jacobian() {
177 system.jacobian(&x, &mut jac_data);
178 } else {
179 self.finite_difference_jacobian(system, &x, &f, &mut jac_data);
180 n_eval += n; }
182 n_jac += 1;
183
184 let jac_matrix = self.build_matrix(&jac_data, n);
186 let lu = LUFactorization::new(&jac_matrix)?;
187
188 let neg_f: Vec<S> = f.iter().map(|&v| -v).collect();
190 dx = lu.solve(&neg_f)?;
191
192 if self.options.line_search {
195 self.line_search_update(system, &mut x, &dx, f_norm, &mut f, &mut n_eval);
196 } else {
197 for i in 0..n {
198 x[i] += dx[i];
199 }
200 system.eval(&x, &mut f);
201 n_eval += 1;
202 }
203 f_norm = self.norm(&f);
204 }
205
206 if !converged {
207 message = format!(
208 "Did not converge after {} iterations, residual = {:.2e}",
209 self.options.max_iter,
210 f_norm.to_f64()
211 );
212 }
213
214 Ok(NewtonResult {
215 x,
216 residual_norm: f_norm,
217 iterations: if converged {
218 self.options.max_iter.min(n_jac)
219 } else {
220 self.options.max_iter
221 },
222 n_eval,
223 n_jac,
224 converged,
225 message,
226 })
227 }
228
229 fn norm(&self, v: &[S]) -> S {
231 let sum: S = v.iter().fold(S::ZERO, |acc, &x| acc + x * x);
232 sum.sqrt()
233 }
234
235 fn finite_difference_jacobian<Sys: NonlinearSystem<S>>(
237 &self,
238 system: &Sys,
239 x: &[S],
240 f0: &[S],
241 jac: &mut [S],
242 ) {
243 let n = system.dim();
244 let h_factor = S::EPSILON.sqrt();
245 let mut x_pert = x.to_vec();
246 let mut f_pert = vec![S::ZERO; n];
247
248 for j in 0..n {
249 let x_j = x[j];
250 let h = h_factor * (S::ONE + x_j.abs());
251
252 x_pert[j] = x_j + h;
253 system.eval(&x_pert, &mut f_pert);
254 x_pert[j] = x_j;
255
256 for i in 0..n {
258 jac[i * n + j] = (f_pert[i] - f0[i]) / h;
259 }
260 }
261 }
262
263 fn build_matrix(&self, data: &[S], n: usize) -> DenseMatrix<S> {
265 let mut m = DenseMatrix::zeros(n, n);
266 for i in 0..n {
267 for j in 0..n {
268 m.set(i, j, data[i * n + j]);
269 }
270 }
271 m
272 }
273
274 fn line_search_update<Sys: NonlinearSystem<S>>(
280 &self,
281 system: &Sys,
282 x: &mut [S],
283 dx: &[S],
284 f_norm: S,
285 f: &mut [S],
286 n_eval_count: &mut usize,
287 ) {
288 let n = x.len();
289 let mut step = S::ONE;
290 let mut x_trial = vec![S::ZERO; n];
291 let mut f_trial = vec![S::ZERO; n];
292
293 let mut best_step = S::ONE;
294 let mut best_norm = S::INFINITY;
295
296 for _ in 0..self.options.max_backsteps {
297 for i in 0..n {
298 x_trial[i] = x[i] + step * dx[i];
299 }
300
301 system.eval(&x_trial, &mut f_trial);
302 *n_eval_count += 1;
303
304 let f_trial_norm = self.norm(&f_trial);
305
306 if f_trial_norm < best_norm {
307 best_norm = f_trial_norm;
308 best_step = step;
309 }
310
311 if f_trial_norm <= f_norm * (S::ONE - self.options.armijo * step) {
312 x.copy_from_slice(&x_trial);
313 f.copy_from_slice(&f_trial);
314 return;
315 }
316
317 step *= S::from_f64(0.5);
318 if step < self.options.min_step {
319 break;
320 }
321 }
322
323 for i in 0..n {
325 x[i] += best_step * dx[i];
326 }
327 system.eval(x, f);
328 *n_eval_count += 1;
329 }
330}
331
332pub fn newton_solve<S, Sys>(
334 system: &Sys,
335 x0: &[S],
336 options: Option<NewtonOptions<S>>,
337) -> Result<NewtonResult<S>, LinalgError>
338where
339 S: Scalar + SimpleEntity + Conjugate<Canonical = S> + ComplexField,
340 Sys: NonlinearSystem<S>,
341{
342 let opts = options.unwrap_or_default();
343 let solver = Newton::new(opts);
344 solver.solve(system, x0)
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350
351 struct SquareRoot;
353
354 impl NonlinearSystem<f64> for SquareRoot {
355 fn dim(&self) -> usize {
356 1
357 }
358
359 fn eval(&self, x: &[f64], f: &mut [f64]) {
360 f[0] = x[0] * x[0] - 2.0;
361 }
362
363 fn jacobian(&self, x: &[f64], jac: &mut [f64]) {
364 jac[0] = 2.0 * x[0];
365 }
366
367 fn has_jacobian(&self) -> bool {
368 true
369 }
370 }
371
372 struct CircleLine;
374
375 impl NonlinearSystem<f64> for CircleLine {
376 fn dim(&self) -> usize {
377 2
378 }
379
380 fn eval(&self, x: &[f64], f: &mut [f64]) {
381 f[0] = x[0] * x[0] + x[1] * x[1] - 1.0;
382 f[1] = x[0] - x[1];
383 }
384
385 fn jacobian(&self, x: &[f64], jac: &mut [f64]) {
386 jac[0] = 2.0 * x[0];
388 jac[1] = 2.0 * x[1];
389 jac[2] = 1.0;
390 jac[3] = -1.0;
391 }
392
393 fn has_jacobian(&self) -> bool {
394 true
395 }
396 }
397
398 struct Rosenbrock;
400
401 impl NonlinearSystem<f64> for Rosenbrock {
402 fn dim(&self) -> usize {
403 2
404 }
405
406 fn eval(&self, x: &[f64], f: &mut [f64]) {
407 f[0] = 1.0 - x[0];
408 f[1] = 10.0 * (x[1] - x[0] * x[0]);
409 }
410
411 fn jacobian(&self, x: &[f64], jac: &mut [f64]) {
412 jac[0] = -1.0;
413 jac[1] = 0.0;
414 jac[2] = -20.0 * x[0];
415 jac[3] = 10.0;
416 }
417
418 fn has_jacobian(&self) -> bool {
419 true
420 }
421 }
422
423 #[test]
424 fn test_newton_sqrt2() {
425 let system = SquareRoot;
426 let options = NewtonOptions::default();
427 let solver = Newton::new(options);
428
429 let result = solver.solve(&system, &[1.5]).unwrap();
430
431 assert!(result.converged);
432 assert!((result.x[0] - 2.0_f64.sqrt()).abs() < 1e-10);
433 }
434
435 #[test]
436 fn test_newton_sqrt2_negative() {
437 let system = SquareRoot;
438 let solver = Newton::default_solver();
439
440 let result = solver.solve(&system, &[-1.5]).unwrap();
441
442 assert!(result.converged);
443 assert!((result.x[0] + 2.0_f64.sqrt()).abs() < 1e-10);
444 }
445
446 #[test]
447 fn test_newton_circle_line() {
448 let system = CircleLine;
449 let solver = Newton::default_solver();
450
451 let result = solver.solve(&system, &[0.5, 0.5]).unwrap();
452
453 assert!(result.converged);
454 let expected = (0.5_f64).sqrt();
455 assert!((result.x[0] - expected).abs() < 1e-10);
456 assert!((result.x[1] - expected).abs() < 1e-10);
457 }
458
459 #[test]
460 fn test_newton_rosenbrock() {
461 let system = Rosenbrock;
462 let options = NewtonOptions::default().line_search(true);
463 let solver = Newton::new(options);
464
465 let result = solver.solve(&system, &[-0.5, 0.5]).unwrap();
466
467 assert!(result.converged);
468 assert!((result.x[0] - 1.0).abs() < 1e-8);
469 assert!((result.x[1] - 1.0).abs() < 1e-8);
470 }
471
472 #[test]
473 fn test_newton_finite_difference() {
474 struct SquareRootFD;
476
477 impl NonlinearSystem<f64> for SquareRootFD {
478 fn dim(&self) -> usize {
479 1
480 }
481 fn eval(&self, x: &[f64], f: &mut [f64]) {
482 f[0] = x[0] * x[0] - 2.0;
483 }
484 }
485
486 let system = SquareRootFD;
487 let solver = Newton::default_solver();
488
489 let result = solver.solve(&system, &[1.5]).unwrap();
490
491 assert!(result.converged);
492 assert!((result.x[0] - 2.0_f64.sqrt()).abs() < 1e-8);
493 assert!(result.n_eval > result.n_jac); }
495
496 #[test]
497 fn test_newton_convenience_function() {
498 let system = SquareRoot;
499 let result = newton_solve(&system, &[1.5], None).unwrap();
500
501 assert!(result.converged);
502 assert!((result.x[0] - 2.0_f64.sqrt()).abs() < 1e-10);
503 }
504
505 #[test]
506 fn test_newton_no_line_search() {
507 let system = Rosenbrock;
508 let options = NewtonOptions::default().line_search(false);
509 let solver = Newton::new(options);
510
511 let result = solver.solve(&system, &[0.9, 0.9]).unwrap();
513
514 assert!(result.converged);
515 }
516
517 #[test]
522 fn test_newton_sqrt2_f32() {
523 struct SquareRootF32;
524 impl NonlinearSystem<f32> for SquareRootF32 {
525 fn dim(&self) -> usize {
526 1
527 }
528 fn eval(&self, x: &[f32], f: &mut [f32]) {
529 f[0] = x[0] * x[0] - 2.0;
530 }
531 fn jacobian(&self, x: &[f32], jac: &mut [f32]) {
532 jac[0] = 2.0 * x[0];
533 }
534 fn has_jacobian(&self) -> bool {
535 true
536 }
537 }
538
539 let options: NewtonOptions<f32> = NewtonOptions::default().atol(1e-6).rtol(1e-6);
541 let solver = Newton::new(options);
542 let result = solver.solve(&SquareRootF32, &[1.5f32]).unwrap();
543
544 assert!(result.converged);
545 assert!((result.x[0] - 2.0f32.sqrt()).abs() < 1e-5);
546 }
547
548 #[test]
553 fn test_newton_nan_initial_guess() {
554 let result = Newton::default_solver().solve(&SquareRoot, &[f64::NAN]);
555
556 match result {
558 Ok(r) => assert!(!r.converged || r.x[0].is_nan()),
559 Err(_) => {} }
561 }
562
563 #[test]
564 fn test_newton_dimension_mismatch() {
565 let result = Newton::default_solver().solve(&SquareRoot, &[1.0, 2.0]);
566 assert!(result.is_err());
567 }
568}