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 const FD_JACOBIAN_EPS: f64 = 1e-8;
246 let eps = S::from_f64(FD_JACOBIAN_EPS);
247 let mut x_pert = x.to_vec();
248 let mut f_pert = vec![S::ZERO; n];
249
250 for j in 0..n {
251 let x_j = x[j];
252 let h = eps * (S::ONE + x_j.abs());
253
254 x_pert[j] = x_j + h;
255 system.eval(&x_pert, &mut f_pert);
256 x_pert[j] = x_j;
257
258 for i in 0..n {
260 jac[i * n + j] = (f_pert[i] - f0[i]) / h;
261 }
262 }
263 }
264
265 fn build_matrix(&self, data: &[S], n: usize) -> DenseMatrix<S> {
267 let mut m = DenseMatrix::zeros(n, n);
268 for i in 0..n {
269 for j in 0..n {
270 m.set(i, j, data[i * n + j]);
271 }
272 }
273 m
274 }
275
276 fn line_search_update<Sys: NonlinearSystem<S>>(
282 &self,
283 system: &Sys,
284 x: &mut [S],
285 dx: &[S],
286 f_norm: S,
287 f: &mut [S],
288 n_eval_count: &mut usize,
289 ) {
290 let n = x.len();
291 let mut step = S::ONE;
292 let mut x_trial = vec![S::ZERO; n];
293 let mut f_trial = vec![S::ZERO; n];
294
295 let mut best_step = S::ONE;
296 let mut best_norm = S::INFINITY;
297
298 for _ in 0..self.options.max_backsteps {
299 for i in 0..n {
300 x_trial[i] = x[i] + step * dx[i];
301 }
302
303 system.eval(&x_trial, &mut f_trial);
304 *n_eval_count += 1;
305
306 let f_trial_norm = self.norm(&f_trial);
307
308 if f_trial_norm < best_norm {
309 best_norm = f_trial_norm;
310 best_step = step;
311 }
312
313 if f_trial_norm <= f_norm * (S::ONE - self.options.armijo * step) {
314 x.copy_from_slice(&x_trial);
315 f.copy_from_slice(&f_trial);
316 return;
317 }
318
319 step *= S::from_f64(0.5);
320 if step < self.options.min_step {
321 break;
322 }
323 }
324
325 for i in 0..n {
327 x[i] += best_step * dx[i];
328 }
329 system.eval(x, f);
330 *n_eval_count += 1;
331 }
332}
333
334pub fn newton_solve<S, Sys>(
336 system: &Sys,
337 x0: &[S],
338 options: Option<NewtonOptions<S>>,
339) -> Result<NewtonResult<S>, LinalgError>
340where
341 S: Scalar + SimpleEntity + Conjugate<Canonical = S> + ComplexField,
342 Sys: NonlinearSystem<S>,
343{
344 let opts = options.unwrap_or_default();
345 let solver = Newton::new(opts);
346 solver.solve(system, x0)
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 struct SquareRoot;
355
356 impl NonlinearSystem<f64> for SquareRoot {
357 fn dim(&self) -> usize {
358 1
359 }
360
361 fn eval(&self, x: &[f64], f: &mut [f64]) {
362 f[0] = x[0] * x[0] - 2.0;
363 }
364
365 fn jacobian(&self, x: &[f64], jac: &mut [f64]) {
366 jac[0] = 2.0 * x[0];
367 }
368
369 fn has_jacobian(&self) -> bool {
370 true
371 }
372 }
373
374 struct CircleLine;
376
377 impl NonlinearSystem<f64> for CircleLine {
378 fn dim(&self) -> usize {
379 2
380 }
381
382 fn eval(&self, x: &[f64], f: &mut [f64]) {
383 f[0] = x[0] * x[0] + x[1] * x[1] - 1.0;
384 f[1] = x[0] - x[1];
385 }
386
387 fn jacobian(&self, x: &[f64], jac: &mut [f64]) {
388 jac[0] = 2.0 * x[0];
390 jac[1] = 2.0 * x[1];
391 jac[2] = 1.0;
392 jac[3] = -1.0;
393 }
394
395 fn has_jacobian(&self) -> bool {
396 true
397 }
398 }
399
400 struct Rosenbrock;
402
403 impl NonlinearSystem<f64> for Rosenbrock {
404 fn dim(&self) -> usize {
405 2
406 }
407
408 fn eval(&self, x: &[f64], f: &mut [f64]) {
409 f[0] = 1.0 - x[0];
410 f[1] = 10.0 * (x[1] - x[0] * x[0]);
411 }
412
413 fn jacobian(&self, x: &[f64], jac: &mut [f64]) {
414 jac[0] = -1.0;
415 jac[1] = 0.0;
416 jac[2] = -20.0 * x[0];
417 jac[3] = 10.0;
418 }
419
420 fn has_jacobian(&self) -> bool {
421 true
422 }
423 }
424
425 #[test]
426 fn test_newton_sqrt2() {
427 let system = SquareRoot;
428 let options = NewtonOptions::default();
429 let solver = Newton::new(options);
430
431 let result = solver.solve(&system, &[1.5]).unwrap();
432
433 assert!(result.converged);
434 assert!((result.x[0] - 2.0_f64.sqrt()).abs() < 1e-10);
435 }
436
437 #[test]
438 fn test_newton_sqrt2_negative() {
439 let system = SquareRoot;
440 let solver = Newton::default_solver();
441
442 let result = solver.solve(&system, &[-1.5]).unwrap();
443
444 assert!(result.converged);
445 assert!((result.x[0] + 2.0_f64.sqrt()).abs() < 1e-10);
446 }
447
448 #[test]
449 fn test_newton_circle_line() {
450 let system = CircleLine;
451 let solver = Newton::default_solver();
452
453 let result = solver.solve(&system, &[0.5, 0.5]).unwrap();
454
455 assert!(result.converged);
456 let expected = (0.5_f64).sqrt();
457 assert!((result.x[0] - expected).abs() < 1e-10);
458 assert!((result.x[1] - expected).abs() < 1e-10);
459 }
460
461 #[test]
462 fn test_newton_rosenbrock() {
463 let system = Rosenbrock;
464 let options = NewtonOptions::default().line_search(true);
465 let solver = Newton::new(options);
466
467 let result = solver.solve(&system, &[-0.5, 0.5]).unwrap();
468
469 assert!(result.converged);
470 assert!((result.x[0] - 1.0).abs() < 1e-8);
471 assert!((result.x[1] - 1.0).abs() < 1e-8);
472 }
473
474 #[test]
475 fn test_newton_finite_difference() {
476 struct SquareRootFD;
478
479 impl NonlinearSystem<f64> for SquareRootFD {
480 fn dim(&self) -> usize {
481 1
482 }
483 fn eval(&self, x: &[f64], f: &mut [f64]) {
484 f[0] = x[0] * x[0] - 2.0;
485 }
486 }
487
488 let system = SquareRootFD;
489 let solver = Newton::default_solver();
490
491 let result = solver.solve(&system, &[1.5]).unwrap();
492
493 assert!(result.converged);
494 assert!((result.x[0] - 2.0_f64.sqrt()).abs() < 1e-8);
495 assert!(result.n_eval > result.n_jac); }
497
498 #[test]
499 fn test_newton_convenience_function() {
500 let system = SquareRoot;
501 let result = newton_solve(&system, &[1.5], None).unwrap();
502
503 assert!(result.converged);
504 assert!((result.x[0] - 2.0_f64.sqrt()).abs() < 1e-10);
505 }
506
507 #[test]
508 fn test_newton_no_line_search() {
509 let system = Rosenbrock;
510 let options = NewtonOptions::default().line_search(false);
511 let solver = Newton::new(options);
512
513 let result = solver.solve(&system, &[0.9, 0.9]).unwrap();
515
516 assert!(result.converged);
517 }
518
519 #[test]
524 fn test_newton_sqrt2_f32() {
525 struct SquareRootF32;
526 impl NonlinearSystem<f32> for SquareRootF32 {
527 fn dim(&self) -> usize {
528 1
529 }
530 fn eval(&self, x: &[f32], f: &mut [f32]) {
531 f[0] = x[0] * x[0] - 2.0;
532 }
533 fn jacobian(&self, x: &[f32], jac: &mut [f32]) {
534 jac[0] = 2.0 * x[0];
535 }
536 fn has_jacobian(&self) -> bool {
537 true
538 }
539 }
540
541 let options: NewtonOptions<f32> = NewtonOptions::default().atol(1e-6).rtol(1e-6);
543 let solver = Newton::new(options);
544 let result = solver.solve(&SquareRootF32, &[1.5f32]).unwrap();
545
546 assert!(result.converged);
547 assert!((result.x[0] - 2.0f32.sqrt()).abs() < 1e-5);
548 }
549
550 #[test]
555 fn test_newton_nan_initial_guess() {
556 let result = Newton::default_solver().solve(&SquareRoot, &[f64::NAN]);
557
558 match result {
560 Ok(r) => assert!(!r.converged || r.x[0].is_nan()),
561 Err(_) => {} }
563 }
564
565 #[test]
566 fn test_newton_dimension_mismatch() {
567 let result = Newton::default_solver().solve(&SquareRoot, &[1.0, 2.0]);
568 assert!(result.is_err());
569 }
570}