1mod de;
14mod normal;
15mod operator;
16pub use de::{DifferentialEvolutionOptions, DifferentialEvolutionResult, differential_evolution};
17pub use normal::{
18 NormalEquations, NormalOptions, NormalTrace, NormalTrial, XScale, least_squares_normal,
19 least_squares_normal_with_trace, trf_normal_one_step,
20};
21pub use operator::{JacobianOperator, OperatorOptions, least_squares_operator};
22
23use crate::DspError;
24use faer::Mat;
25
26#[derive(Debug)]
27pub struct LeastSquaresResult {
28 pub x: Vec<f64>,
29 pub cost: f64,
30 pub nfev: usize,
31 pub status: i32,
32 pub njev: usize,
33 pub optimality: f64,
34}
35#[derive(Debug, Default)]
38pub struct LeastSquaresTrace {
39 pub evaluations: Vec<Evaluation>,
41 pub jacobian_points: Vec<Vec<f64>>,
43 pub main_x: Vec<Vec<f64>>,
44 pub main_cost: Vec<f64>,
45 pub jac_x: Vec<Vec<f64>>,
47 pub j0: Vec<f64>,
48 pub j0_shape: [usize; 2],
49 pub initial_cost: f64,
50 pub trials: Vec<TrfTrial>,
51}
52#[derive(Debug)]
53pub struct Evaluation {
54 pub x: Vec<f64>,
55 pub cost: f64,
56 pub column: Option<usize>,
57}
58#[derive(Debug, Clone, Copy)]
60pub struct Options {
61 pub max_nfev: usize,
62 pub ftol: Option<f64>,
63 pub xtol: Option<f64>,
64 pub gtol: Option<f64>,
65}
66impl Default for Options {
67 fn default() -> Self {
68 Self {
69 max_nfev: 100,
70 ftol: Some(1e-8),
71 xtol: Some(1e-8),
72 gtol: Some(1e-8),
73 }
74 }
75}
76#[derive(Debug, Default, serde::Serialize)]
77pub struct TrfTrial {
78 pub iteration: usize,
79 pub x: Vec<f64>,
80 pub f: Vec<f64>,
81 #[serde(rename = "J")]
83 pub j: Vec<f64>,
84 pub g: Vec<f64>,
85 pub d: Vec<f64>,
86 #[serde(rename = "J_h")]
87 pub j_h: Vec<f64>,
88 pub diag_h: Vec<f64>,
89 pub g_h: Vec<f64>,
90 pub theta: f64,
91 #[serde(rename = "V")]
93 pub right_vectors: Vec<f64>,
94 pub g_norm: f64,
95 pub v: Vec<f64>,
96 pub dv: Vec<f64>,
97 #[serde(rename = "Delta")]
98 pub delta: f64,
99 pub s: Vec<f64>,
100 pub uf: Vec<f64>,
101 pub alpha_in: f64,
102 pub alpha_out: f64,
103 pub n_iter: usize,
104 pub full_rank: bool,
105 pub p_h: Vec<f64>,
106 pub select: SelectTrace,
107 pub step: Vec<f64>,
108 pub step_h: Vec<f64>,
109 pub x_new: Vec<f64>,
110 pub actual_reduction: f64,
111 pub predicted_reduction: f64,
112 pub ratio: f64,
113 #[serde(rename = "Delta_new")]
114 pub delta_new: f64,
115 pub accepted: bool,
116 pub jacobian_recomputed: bool,
117}
118#[derive(Debug, Default, serde::Serialize)]
119pub struct SelectTrace {
120 pub branch: &'static str,
121 pub p_value: Option<f64>,
122 pub r_value: Option<f64>,
123 pub ag_value: Option<f64>,
124 pub p_stride: Option<f64>,
125 pub r_stride: Option<f64>,
126 pub r_stride_l: Option<f64>,
127 pub r_stride_u: Option<f64>,
128 pub ag_stride: Option<f64>,
129 pub to_tr: Option<f64>,
130 pub to_bound: Option<f64>,
131}
132fn dot(a: &[f64], b: &[f64]) -> f64 {
136 let mut sums = [0.; 8];
137 let chunks = a.len() / 8 * 8;
138 for i in (0..chunks).step_by(8) {
139 for k in 0..8 {
140 sums[k] += a[i + k] * b[i + k];
141 }
142 }
143 let mut sum =
144 ((sums[0] + sums[1]) + (sums[2] + sums[3])) + ((sums[4] + sums[5]) + (sums[6] + sums[7]));
145 for i in chunks..a.len() {
146 sum += a[i] * b[i];
147 }
148 sum
149}
150fn norm(a: &[f64]) -> f64 {
151 dot(a, a).sqrt()
152}
153fn row_major(a: &Mat<f64>) -> Vec<f64> {
154 (0..a.nrows())
155 .flat_map(|i| (0..a.ncols()).map(move |j| a[(i, j)]))
156 .collect()
157}
158fn mv(a: &Mat<f64>, x: &[f64]) -> Vec<f64> {
159 (0..a.nrows())
160 .map(|i| (0..a.ncols()).map(|j| a[(i, j)] * x[j]).sum())
161 .collect()
162}
163fn grad(a: &Mat<f64>, f: &[f64]) -> Vec<f64> {
164 (0..a.ncols())
165 .map(|j| (0..a.nrows()).map(|i| a[(i, j)] * f[i]).sum())
166 .collect()
167}
168fn feasible(x: &mut [f64], lb: &[f64], ub: &[f64], initial: bool) {
169 for ((x, &l), &u) in x.iter_mut().zip(lb).zip(ub) {
170 if initial {
171 let ld = *x - l;
172 let ud = u - *x;
173 if l.is_finite() && ld <= ud.min(1e-10 * l.abs().max(1.)) {
174 *x = l + 1e-10 * l.abs().max(1.);
175 }
176 if u.is_finite() && ud <= ld.min(1e-10 * u.abs().max(1.)) {
177 *x = u - 1e-10 * u.abs().max(1.);
178 }
179 } else {
180 if *x <= l {
181 *x = l.next_up();
182 } else if *x >= u {
183 *x = u.next_down();
184 }
185 }
186 if *x < l || *x > u {
187 *x = 0.5 * (l + u);
188 }
189 }
190}
191fn jacobian<F: FnMut(&[f64], Option<usize>, &mut Vec<f64>)>(
192 fun: &mut F,
193 x: &[f64],
194 f: &[f64],
195 lb: &[f64],
196 ub: &[f64],
197) -> Result<Mat<f64>, DspError> {
198 let mut j = Mat::zeros(f.len(), x.len());
199 let mut y = x.to_vec();
200 let mut fy = vec![0.; f.len()];
201 for k in 0..x.len() {
202 let mut h = f64::EPSILON.sqrt() * if x[k] >= 0. { 1. } else { -1. } * x[k].abs().max(1.);
203 let lower = x[k] - lb[k];
204 let upper = ub[k] - x[k];
205 let fitting = h.abs() <= lower.max(upper);
206 if (x[k] + h < lb[k] || x[k] + h > ub[k]) && fitting {
207 h = -h;
208 }
209 if !fitting {
210 h = if upper >= lower { upper } else { -lower };
211 }
212 y[k] += h;
213 let dx = y[k] - x[k];
214 fun(&y, Some(k), &mut fy);
215 y[k] = x[k];
216 if fy.len() != f.len() || fy.iter().any(|x| !x.is_finite()) {
217 return Err(DspError::InvalidArgument(
218 "nonfinite or inconsistent residual Jacobian".into(),
219 ));
220 }
221 for i in 0..f.len() {
222 j[(i, k)] = (fy[i] - f[i]) / dx;
223 }
224 }
225 Ok(j)
226}
227fn scaling(x: &[f64], g: &[f64], lb: &[f64], ub: &[f64]) -> (Vec<f64>, Vec<f64>) {
228 (0..x.len())
229 .map(|i| {
230 if g[i] < 0. && ub[i].is_finite() {
231 (ub[i] - x[i], -1.)
232 } else if g[i] > 0. && lb[i].is_finite() {
233 (x[i] - lb[i], 1.)
234 } else {
235 (1., 0.)
236 }
237 })
238 .unzip()
239}
240fn bound_step(x: &[f64], s: &[f64], lb: &[f64], ub: &[f64]) -> (f64, Vec<bool>) {
241 let steps: Vec<_> = (0..x.len())
242 .map(|i| {
243 if s[i] == 0. {
244 f64::INFINITY
245 } else {
246 ((lb[i] - x[i]) / s[i]).max((ub[i] - x[i]) / s[i])
247 }
248 })
249 .collect();
250 let t = steps.iter().copied().fold(f64::INFINITY, f64::min);
251 (t, steps.iter().map(|s| *s == t).collect())
252}
253fn quadratic(j: &Mat<f64>, g: &[f64], diag: &[f64], s: &[f64]) -> f64 {
254 let js = mv(j, s);
255 0.5 * (dot(&js, &js) + (0..s.len()).map(|i| s[i] * diag[i] * s[i]).sum::<f64>()) + dot(s, g)
256}
257fn quadratic_line(
258 j: &Mat<f64>,
259 g: &[f64],
260 diag: &[f64],
261 s: &[f64],
262 s0: Option<&[f64]>,
263) -> (f64, f64, f64) {
264 let v = mv(j, s);
265 let a = 0.5 * (dot(&v, &v) + (0..s.len()).map(|i| s[i] * diag[i] * s[i]).sum::<f64>());
266 let mut b = dot(g, s);
267 let mut c = 0.;
268 if let Some(s0) = s0 {
269 let u = mv(j, s0);
270 b += dot(&u, &v);
271 c = 0.5 * dot(&u, &u) + dot(g, s0);
272 b += (0..s.len()).map(|i| s0[i] * diag[i] * s[i]).sum::<f64>();
273 c += 0.5 * (0..s.len()).map(|i| s0[i] * diag[i] * s0[i]).sum::<f64>();
274 }
275 (a, b, c)
276}
277fn minimize((a, b, c): (f64, f64, f64), lo: f64, hi: f64) -> (f64, f64) {
278 let value = |t: f64| t * (a * t + b) + c;
279 let mut best = (lo, value(lo));
280 if value(hi) < best.1 {
281 best = (hi, value(hi));
282 }
283 let e = -0.5 * b / a;
284 if a != 0. && lo < e && e < hi && value(e) < best.1 {
285 best = (e, value(e));
286 }
287 best
288}
289enum QuadraticModel<'a> {
290 Dense(&'a Mat<f64>),
291 Normal(&'a Mat<f64>),
292}
293impl QuadraticModel<'_> {
294 fn value(&self, g: &[f64], diag: &[f64], s: &[f64]) -> f64 {
295 match self {
296 Self::Dense(j) => quadratic(j, g, diag, s),
297 Self::Normal(h) => {
299 0.5 * (dot(s, &mv(h, s)) + (0..s.len()).map(|i| s[i] * diag[i] * s[i]).sum::<f64>())
300 + dot(s, g)
301 }
302 }
303 }
304 fn line(&self, g: &[f64], diag: &[f64], s: &[f64], s0: Option<&[f64]>) -> (f64, f64, f64) {
305 match self {
306 Self::Dense(j) => quadratic_line(j, g, diag, s, s0),
307 Self::Normal(h) => {
309 let hs = mv(h, s);
310 let a =
311 0.5 * (dot(s, &hs) + (0..s.len()).map(|i| s[i] * diag[i] * s[i]).sum::<f64>());
312 let mut b = dot(g, s);
313 let mut c = 0.;
314 if let Some(s0) = s0 {
315 b += dot(s0, &hs);
316 c = 0.5 * dot(s0, &mv(h, s0)) + dot(g, s0);
317 b += (0..s.len()).map(|i| s0[i] * diag[i] * s[i]).sum::<f64>();
318 c += 0.5 * (0..s.len()).map(|i| s0[i] * diag[i] * s0[i]).sum::<f64>();
319 }
320 (a, b, c)
321 }
322 }
323 }
324}
325#[allow(clippy::too_many_arguments)]
326fn select(
327 x: &[f64],
328 model: QuadraticModel<'_>,
329 diag: &[f64],
330 g: &[f64],
331 mut p: Vec<f64>,
332 mut ph: Vec<f64>,
333 d: &[f64],
334 delta: f64,
335 lb: &[f64],
336 ub: &[f64],
337 theta: f64,
338 mut trace: Option<&mut SelectTrace>,
339) -> (Vec<f64>, Vec<f64>, f64) {
340 if (0..x.len()).all(|i| x[i] + p[i] >= lb[i] && x[i] + p[i] <= ub[i]) {
341 let q = model.value(g, diag, &ph);
342 if let Some(t) = trace {
343 t.branch = "p";
344 t.p_value = Some(q);
345 }
346 return (p, ph, -q);
347 }
348 let (stride, hits) = bound_step(x, &p, lb, ub);
349 let mut rh: Vec<_> = ph
350 .iter()
351 .zip(hits)
352 .map(|(v, h)| if h { -v } else { *v })
353 .collect();
354 let r: Vec<_> = rh.iter().zip(d).map(|(a, b)| a * b).collect();
355 for v in &mut p {
356 *v *= stride;
357 }
358 for v in &mut ph {
359 *v *= stride;
360 }
361 let xb: Vec<_> = x.iter().zip(&p).map(|(a, b)| a + b).collect();
362 let a = dot(&rh, &rh);
363 let b = dot(&ph, &rh);
364 let c = dot(&ph, &ph) - delta * delta;
365 let root = (b * b - a * c).sqrt();
366 let q = -(b + root.copysign(b));
367 let tr = (q / a).max(c / q);
368 let (bound, _) = bound_step(&xb, &r, lb, ub);
369 let rs = bound.min(tr);
370 let (lo, hi) = if rs > 0. {
371 (
372 (1. - theta) * stride / rs,
373 if rs == bound { theta * bound } else { tr },
374 )
375 } else {
376 (0., -1.)
377 };
378 if let Some(t) = trace.as_deref_mut() {
379 t.p_stride = Some(stride);
380 t.r_stride_l = Some(lo);
381 t.r_stride_u = Some(hi);
382 t.r_stride = Some(rs);
383 }
384 let mut rv = f64::INFINITY;
385 if lo <= hi {
386 let (t, v) = minimize(model.line(g, diag, &rh, Some(&ph)), lo, hi);
387 rv = v;
388 if let Some(trace) = trace.as_deref_mut() {
389 trace.r_stride = Some(t);
390 }
391 for i in 0..rh.len() {
392 rh[i] = rh[i] * t + ph[i];
393 }
394 }
395 for v in &mut p {
396 *v *= theta;
397 }
398 for v in &mut ph {
399 *v *= theta;
400 }
401 let pv = model.value(g, diag, &ph);
402 let mut ah: Vec<_> = g.iter().map(|v| -v).collect();
403 let mut ag: Vec<_> = ah.iter().zip(d).map(|(a, b)| a * b).collect();
404 let tr = delta / norm(&ah);
405 let (bound, _) = bound_step(x, &ag, lb, ub);
406 let upper = if bound < tr { theta * bound } else { tr };
407 let (stride, av) = minimize(model.line(g, diag, &ah, None), 0., upper);
408 for v in &mut ah {
409 *v *= stride;
410 }
411 for v in &mut ag {
412 *v *= stride;
413 }
414 if let Some(t) = trace {
415 t.p_value = Some(pv);
416 t.r_value = Some(rv);
417 t.ag_value = Some(av);
418 t.ag_stride = Some(stride);
419 t.to_tr = Some(tr);
420 t.to_bound = Some(bound);
421 t.branch = if pv < rv && pv < av {
422 "p"
423 } else if rv < pv && rv < av {
424 "reflected"
425 } else {
426 "anti-gradient"
427 };
428 }
429 if pv < rv && pv < av {
430 (p, ph, -pv)
431 } else if rv < pv && rv < av {
432 (rh.iter().zip(d).map(|(a, b)| a * b).collect(), rh, -rv)
433 } else {
434 (ag, ah, -av)
435 }
436}
437fn termination(
438 actual: f64,
439 cost: f64,
440 step: &[f64],
441 x: &[f64],
442 ratio: f64,
443 ftol: f64,
444 xtol: f64,
445) -> i32 {
446 let ft = actual < ftol * cost && ratio > 0.25;
447 let xt = norm(step) < xtol * (xtol + norm(x));
448 match (ft, xt) {
449 (true, true) => 4,
450 (true, false) => 2,
451 (false, true) => 3,
452 _ => 0,
453 }
454}
455fn phi_and_derivative(suf: &[f64], s: &[f64], alpha: f64, delta: f64) -> (f64, f64) {
456 let p_norm = norm(
457 &suf.iter()
458 .zip(s)
459 .map(|(u, s)| u / (s * s + alpha))
460 .collect::<Vec<_>>(),
461 );
462 let derivative = -crate::numeric::sum(
465 &suf.iter()
466 .zip(s)
467 .map(|(u, s)| u * u / (s * s + alpha).powf(3.))
468 .collect::<Vec<_>>(),
469 ) / p_norm;
470 (p_norm - delta, derivative)
471}
472fn trust(
473 n: usize,
474 m: usize,
475 uf: &[f64],
476 s: &[f64],
477 v: &Mat<f64>,
478 delta: f64,
479 mut alpha: f64,
480) -> (Vec<f64>, f64, usize, bool) {
481 let suf: Vec<_> = s.iter().zip(uf).map(|(a, b)| a * b).collect();
482 let full = m >= n && s[n - 1] > f64::EPSILON * m as f64 * s[0];
483 if full {
484 let p = mv(
485 v,
486 &uf.iter().zip(s).map(|(a, b)| -a / b).collect::<Vec<_>>(),
487 );
488 if norm(&p) <= delta {
489 return (p, 0., 0, full);
490 }
491 }
492 let phi = |a: f64| phi_and_derivative(&suf, s, a, delta);
493 let mut upper = norm(&suf) / delta;
494 let mut lower = if full {
495 let (p, d) = phi(0.);
496 -p / d
497 } else {
498 0.
499 };
500 if !full && alpha == 0. {
501 alpha = (0.001 * upper).max((lower * upper).sqrt());
502 }
503 let mut n_iter = 0;
504 for _ in 0..10 {
505 n_iter += 1;
506 if alpha < lower || alpha > upper {
507 alpha = (0.001 * upper).max((lower * upper).sqrt());
508 }
509 let (p, dp) = phi(alpha);
510 if p < 0. {
511 upper = alpha;
512 }
513 let ratio = p / dp;
514 lower = lower.max(alpha - ratio);
515 alpha -= (p + delta) * ratio / delta;
516 if p.abs() < 0.01 * delta {
517 break;
518 }
519 }
520 let mut p = mv(
521 v,
522 &suf.iter()
523 .zip(s)
524 .map(|(u, s)| -u / (s * s + alpha))
525 .collect::<Vec<_>>(),
526 );
527 let scale = delta / norm(&p);
528 for v in &mut p {
529 *v *= scale;
530 }
531 (p, alpha, n_iter, full)
532}
533pub fn least_squares<F: FnMut(&[f64]) -> Vec<f64>>(
536 fun: F,
537 x0: &[f64],
538 lb: &[f64],
539 ub: &[f64],
540 max_nfev: usize,
541) -> Result<LeastSquaresResult, DspError> {
542 least_squares_with_trace(fun, x0, lb, ub, max_nfev, None)
543}
544pub fn least_squares_with_trace<F: FnMut(&[f64]) -> Vec<f64>>(
546 mut fun: F,
547 x0: &[f64],
548 lb: &[f64],
549 ub: &[f64],
550 max_nfev: usize,
551 trace: Option<&mut LeastSquaresTrace>,
552) -> Result<LeastSquaresResult, DspError> {
553 least_squares_with_options(
554 |x| fun(x),
555 x0,
556 lb,
557 ub,
558 Options {
559 max_nfev,
560 ..Options::default()
561 },
562 trace,
563 )
564}
565pub fn least_squares_with_options<F: FnMut(&[f64]) -> Vec<f64>>(
570 residual: F,
571 x0: &[f64],
572 lb: &[f64],
573 ub: &[f64],
574 options: Options,
575 trace: Option<&mut LeastSquaresTrace>,
576) -> Result<LeastSquaresResult, DspError> {
577 solve(residual, x0, lb, ub, options, trace, None, None)
578}
579
580#[doc(hidden)]
582pub fn least_squares_analytic<F, J>(
583 residual: F,
584 mut jac: J,
585 x0: &[f64],
586 lb: &[f64],
587 ub: &[f64],
588 options: Options,
589 trace: Option<&mut LeastSquaresTrace>,
590) -> Result<LeastSquaresResult, DspError>
591where
592 F: FnMut(&[f64]) -> Vec<f64>,
593 J: FnMut(&[f64]) -> Mat<f64>,
594{
595 solve(residual, x0, lb, ub, options, trace, None, Some(&mut jac))
596}
597
598#[doc(hidden)]
603pub fn trf_one_step<F: FnMut(&[f64]) -> Vec<f64>>(
604 residual: F,
605 x: &[f64],
606 lb: &[f64],
607 ub: &[f64],
608 delta: f64,
609 alpha: f64,
610) -> Result<TrfTrial, DspError> {
611 if !delta.is_finite() || delta <= 0. || !alpha.is_finite() || alpha < 0. {
612 return Err(DspError::InvalidArgument(
613 "invalid replay radius/alpha".into(),
614 ));
615 }
616 let mut trace = LeastSquaresTrace::default();
617 solve(
618 residual,
619 x,
620 lb,
621 ub,
622 Options {
623 max_nfev: 2,
624 gtol: None,
625 ..Options::default()
626 },
627 Some(&mut trace),
628 Some((delta, alpha)),
629 None,
630 )?;
631 trace
632 .trials
633 .pop()
634 .ok_or_else(|| DspError::InvalidArgument("no replay trial".into()))
635}
636
637type AnalyticJacobian<'a> = dyn FnMut(&[f64]) -> Mat<f64> + 'a;
638
639#[allow(clippy::too_many_arguments)]
640fn solve<F: FnMut(&[f64]) -> Vec<f64>>(
641 mut residual: F,
642 x0: &[f64],
643 lb: &[f64],
644 ub: &[f64],
645 options: Options,
646 mut trace: Option<&mut LeastSquaresTrace>,
647 replay: Option<(f64, f64)>,
648 mut analytic: Option<&mut AnalyticJacobian<'_>>,
649) -> Result<LeastSquaresResult, DspError> {
650 let max_nfev = options.max_nfev;
651 let ftol = options.ftol.unwrap_or(0.);
652 let xtol = options.xtol.unwrap_or(0.);
653 let gtol = options.gtol.unwrap_or(0.);
654 if [ftol, xtol, gtol].iter().any(|v| !v.is_finite() || *v < 0.)
655 || [ftol, xtol, gtol].iter().all(|v| *v <= f64::EPSILON)
656 {
657 return Err(DspError::InvalidArgument(
658 "invalid stopping tolerances".into(),
659 ));
660 }
661 if !lb.iter().chain(ub).any(|v| v.is_finite()) {
662 return Err(DspError::InvalidArgument(
663 "bounded TRF requires at least one finite bound".into(),
664 ));
665 }
666 let evaluations = std::cell::RefCell::new(Vec::new());
667 let capture = trace.is_some();
668 let mut fun = |x: &[f64], column: Option<usize>, out: &mut Vec<f64>| {
669 *out = residual(x);
670 if capture {
671 evaluations.borrow_mut().push(Evaluation {
672 x: x.to_vec(),
673 cost: 0.5 * dot(out, out),
674 column,
675 });
676 }
677 };
678 if let Some(t) = trace.as_deref_mut() {
679 *t = LeastSquaresTrace::default();
680 }
681 let n = x0.len();
682 if n == 0
683 || lb.len() != n
684 || ub.len() != n
685 || max_nfev == 0
686 || (0..n).any(|i| {
687 !x0[i].is_finite()
688 || lb[i].is_nan()
689 || ub[i].is_nan()
690 || lb[i] >= ub[i]
691 || x0[i] < lb[i]
692 || x0[i] > ub[i]
693 })
694 {
695 return Err(DspError::InvalidArgument(
696 "invalid least-squares start/bounds/budget".into(),
697 ));
698 }
699 let mut x = x0.to_vec();
700 if replay.is_none() {
701 feasible(&mut x, lb, ub, true);
702 }
703 let mut f = Vec::new();
704 fun(&x, None, &mut f);
705 let m = f.len();
706 if m == 0 || f.iter().any(|v| !v.is_finite()) {
707 return Err(DspError::InvalidArgument("invalid initial residual".into()));
708 }
709 let mut get_jacobian = |x: &[f64], f: &[f64], fun: &mut _| {
710 if let Some(jac) = analytic.as_mut() {
711 let j = jac(x);
712 if j.nrows() != f.len()
713 || j.ncols() != x.len()
714 || row_major(&j).iter().any(|v| !v.is_finite())
715 {
716 return Err(DspError::InvalidArgument(
717 "invalid analytic Jacobian".into(),
718 ));
719 }
720 Ok(j)
721 } else {
722 jacobian(fun, x, f, lb, ub)
723 }
724 };
725 let mut j = get_jacobian(&x, &f, &mut fun)?;
726 let mut cost = 0.5 * dot(&f, &f);
727 let mut njev = 1;
728 if let Some(t) = trace.as_deref_mut() {
729 t.jacobian_points.push(x.clone());
730 t.initial_cost = cost;
731 t.j0_shape = [m, n];
732 t.j0 = (0..m)
733 .flat_map(|i| (0..n).map(move |k| (i, k)))
734 .map(|(i, k)| j[(i, k)])
735 .collect();
736 }
737 let mut g = grad(&j, &f);
738 let (v, _) = scaling(&x, &g, lb, ub);
739 let mut delta = norm(
740 &x.iter()
741 .zip(v)
742 .map(|(x, v)| x / v.sqrt())
743 .collect::<Vec<_>>(),
744 );
745 if delta == 0. {
746 delta = 1.;
747 }
748 let mut alpha = 0.;
749 if let Some((oracle_delta, oracle_alpha)) = replay {
750 delta = oracle_delta;
751 alpha = oracle_alpha;
752 }
753 let mut nfev = 1;
754 let mut status = 0;
755 let mut iteration = 0;
756 loop {
757 let (v, dv) = scaling(&x, &g, lb, ub);
758 let gn = g
759 .iter()
760 .zip(&v)
761 .map(|(g, v)| (g * v).abs())
762 .fold(0., f64::max);
763 if gn < gtol {
764 status = 1;
765 }
766 if status != 0 || nfev == max_nfev {
767 break;
768 }
769 let d: Vec<_> = v.iter().map(|v| v.sqrt()).collect();
770 let diag: Vec<_> = g.iter().zip(&dv).map(|(g, v)| g * v).collect();
771 let gh: Vec<_> = d.iter().zip(&g).map(|(d, g)| d * g).collect();
772 let jh = Mat::from_fn(m, n, |i, k| j[(i, k)] * d[k]);
773 let aug = Mat::from_fn(m + n, n, |i, k| {
774 if i < m {
775 jh[(i, k)]
776 } else if i - m == k {
777 diag[k].sqrt()
778 } else {
779 0.
780 }
781 });
782 use faer::{
784 Par,
785 diag::Diag,
786 dyn_stack::{MemBuffer, MemStack},
787 linalg::svd::{self, ComputeSvdVectors},
788 };
789 let mut u = Mat::zeros(m + n, n);
790 let mut vv = Mat::zeros(n, n);
791 let mut singular = Diag::zeros(n);
792 let mut scratch = MemBuffer::new(svd::svd_scratch::<f64>(
793 m + n,
794 n,
795 ComputeSvdVectors::Thin,
796 ComputeSvdVectors::Thin,
797 Par::Seq,
798 Default::default(),
799 ));
800 svd::svd(
801 aug.as_ref(),
802 singular.as_mut(),
803 Some(u.as_mut()),
804 Some(vv.as_mut()),
805 Par::Seq,
806 MemStack::new(&mut scratch),
807 Default::default(),
808 )
809 .map_err(|_| DspError::InvalidArgument("TRF SVD did not converge".into()))?;
810 let s: Vec<_> = (0..n).map(|i| singular.column_vector()[i]).collect();
811 let uf: Vec<_> = (0..n)
812 .map(|k| (0..m).map(|i| u[(i, k)] * f[i]).sum())
813 .collect();
814 let theta = 0.995_f64.max(1. - gn);
815 let mut actual = -1.;
816 let mut xn = x.clone();
817 let mut fnn = f.clone();
818 let mut cn = cost;
819 while actual <= 0. && nfev < max_nfev {
820 let (ph, a, n_iter, full_rank) = trust(n, m, &uf, &s, &vv, delta, alpha);
821 let mut trial = trace.as_ref().map(|_| TrfTrial {
822 iteration,
823 x: x.clone(),
824 f: f.clone(),
825 j: row_major(&j),
826 g: g.clone(),
827 d: d.clone(),
828 j_h: row_major(&jh),
829 diag_h: diag.clone(),
830 g_h: gh.clone(),
831 theta,
832 right_vectors: row_major(&vv),
833 g_norm: gn,
834 v: v.clone(),
835 dv: dv.clone(),
836 delta,
837 s: s.clone(),
838 uf: uf.clone(),
839 alpha_in: alpha,
840 alpha_out: a,
841 n_iter,
842 full_rank,
843 p_h: ph.clone(),
844 ..Default::default()
845 });
846 alpha = a;
847 let p = ph.iter().zip(&d).map(|(a, b)| a * b).collect();
848 let (step, sh, pred) = select(
849 &x,
850 QuadraticModel::Dense(&jh),
851 &diag,
852 &gh,
853 p,
854 ph,
855 &d,
856 delta,
857 lb,
858 ub,
859 theta,
860 trial.as_mut().map(|t| &mut t.select),
861 );
862 xn = x.iter().zip(&step).map(|(a, b)| a + b).collect();
863 feasible(&mut xn, lb, ub, false);
864 fun(&xn, None, &mut fnn);
865 nfev += 1;
866 if let Some(t) = trace.as_deref_mut() {
867 t.main_x.push(xn.clone());
868 t.main_cost.push(0.5 * dot(&fnn, &fnn));
869 }
870 let shn = norm(&sh);
871 if fnn.len() != m {
872 return Err(DspError::InvalidArgument("residual length changed".into()));
873 }
874 if fnn.iter().any(|v| !v.is_finite()) {
875 if let (Some(t), Some(mut trial)) = (trace.as_deref_mut(), trial) {
876 trial.step = step;
877 trial.step_h = sh;
878 trial.x_new = xn.clone();
879 trial.actual_reduction = f64::NAN;
880 trial.predicted_reduction = pred;
881 trial.ratio = f64::NAN;
882 trial.delta_new = 0.25 * shn;
883 t.trials.push(trial);
884 }
885 delta = 0.25 * shn;
886 continue;
887 }
888 cn = 0.5 * dot(&fnn, &fnn);
889 actual = cost - cn;
890 let ratio = if pred > 0. {
891 actual / pred
892 } else if pred == 0. && actual == 0. {
893 1.
894 } else {
895 0.
896 };
897 let dn = if ratio < 0.25 {
898 0.25 * shn
899 } else if ratio > 0.75 && shn > 0.95 * delta {
900 2. * delta
901 } else {
902 delta
903 };
904 if let (Some(t), Some(mut trial)) = (trace.as_deref_mut(), trial) {
905 trial.step = step.clone();
906 trial.step_h = sh.clone();
907 trial.x_new = xn.clone();
908 trial.actual_reduction = actual;
909 trial.predicted_reduction = pred;
910 trial.ratio = ratio;
911 trial.delta_new = dn;
912 trial.accepted = actual > 0.;
913 t.trials.push(trial);
914 }
915 status = termination(actual, cost, &step, &x, ratio, ftol, xtol);
916 if status != 0 {
917 break;
918 }
919 alpha *= delta / dn;
920 delta = dn;
921 }
922 if actual > 0. {
923 x = xn;
924 f = fnn;
925 cost = cn;
926 j = get_jacobian(&x, &f, &mut fun)?;
927 njev += 1;
928 if let Some(t) = trace.as_deref_mut() {
929 t.jac_x.push(x.clone());
930 t.jacobian_points.push(x.clone());
931 if let Some(trial) = t.trials.last_mut() {
932 trial.jacobian_recomputed = true;
933 }
934 }
935 g = grad(&j, &f);
936 }
937 iteration += 1;
938 }
939 let (v, _) = scaling(&x, &g, lb, ub);
940 let optimality = g
941 .iter()
942 .zip(v)
943 .map(|(g, v)| (g * v).abs())
944 .fold(0., f64::max);
945 if let Some(t) = trace {
946 t.evaluations = evaluations.into_inner();
947 }
948 Ok(LeastSquaresResult {
949 x,
950 cost,
951 nfev,
952 status,
953 njev,
954 optimality,
955 })
956}
957
958#[cfg(test)]
959mod tests {
960 #[test]
961 fn newton_derivative_uses_numpy_power_and_pairwise_sum() {
962 let suf: Vec<_> = (1..10).map(|i| i as f64 * 0.173).collect();
963 let s: Vec<_> = (1..10).map(|i| i as f64 * 0.117).collect();
964 let (_, derivative) = super::phi_and_derivative(&suf, &s, 0.17, 1.);
965 secs_oracle::assert_same(
967 "TRF Newton derivative",
968 &[derivative],
969 &[-10.858296042275914],
970 );
971 }
972
973 #[test]
974 fn feasible_adjacent_bounds_uses_original_active_mask() {
975 let lower = 1.0_f64;
976 let upper = lower.next_up();
977 let mut x = [lower];
978 super::feasible(&mut x, &[lower], &[upper], false);
979 assert_eq!(x[0], upper);
980 super::feasible(&mut x, &[lower], &[upper], false);
981 assert_eq!(x[0], lower);
982 }
983}