1use std::cmp::Ordering;
19
20use crate::prelude::*;
21
22pub const OPTIMIZE_TOL: f64 = 1.220_703_125e-4;
29
30const BRENT_SQRT_EPS: f64 = 1.490_116_119_384_765_6e-8;
33
34const BRENT_GOLDEN: f64 = 0.381_966_011_250_105_15;
36
37const NELDER_MEAD_STEP: f64 = 0.05;
41
42const NELDER_MEAD_ZERO_STEP: f64 = 0.000_25;
46
47const NELDER_MEAD_COLLAPSED_ULPS: f64 = 16.0;
57
58pub fn brentq<F>(
85 mut f: F,
86 lower: f64,
87 upper: f64,
88 xtol: f64,
89 rtol: f64,
90 max_iter: usize,
91) -> Result<f64, EdgeErrors>
92where
93 F: FnMut(f64) -> f64,
94{
95 if lower.partial_cmp(&upper) != Some(Ordering::Less) {
96 return Err(EdgeErrors::InvalidArgument(format!(
97 "brentq needs lower < upper, got [{lower}, {upper}]"
98 )));
99 }
100 if xtol <= 0.0 {
101 return Err(EdgeErrors::MustBePositive("xtol".to_string()));
102 }
103
104 let (mut xpre, mut xcur) = (lower, upper);
105 let (mut fpre, mut fcur) = (f(xpre), f(xcur));
106
107 if fpre == 0.0 {
108 return Ok(xpre);
109 }
110 if fcur == 0.0 {
111 return Ok(xcur);
112 }
113 if fpre.signum() == fcur.signum() {
114 return Err(EdgeErrors::RootNotBracketed {
115 lower,
116 upper,
117 f_lower: fpre,
118 f_upper: fcur,
119 });
120 }
121
122 let (mut xblk, mut fblk) = (0.0, 0.0);
123 let (mut spre, mut scur) = (0.0, 0.0);
124
125 for _ in 0..max_iter {
126 if fpre != 0.0 && fcur != 0.0 && fpre.signum() != fcur.signum() {
127 xblk = xpre;
128 fblk = fpre;
129 spre = xcur - xpre;
130 scur = spre;
131 }
132 if fblk.abs() < fcur.abs() {
133 xpre = xcur;
134 xcur = xblk;
135 xblk = xpre;
136 fpre = fcur;
137 fcur = fblk;
138 fblk = fpre;
139 }
140
141 let delta = (xtol + rtol * xcur.abs()) / 2.0;
142 let sbis = (xblk - xcur) / 2.0;
143 if fcur == 0.0 || sbis.abs() < delta {
144 return Ok(xcur);
145 }
146
147 if spre.abs() > delta && fcur.abs() < fpre.abs() {
148 let stry = if xpre == xblk {
149 -fcur * (xcur - xpre) / (fcur - fpre)
151 } else {
152 let dpre = (fpre - fcur) / (xpre - xcur);
154 let dblk = (fblk - fcur) / (xblk - xcur);
155 -fcur * (fblk * dblk - fpre * dpre) / (dblk * dpre * (fblk - fpre))
156 };
157 if 2.0 * stry.abs() < (spre.abs()).min(3.0 * sbis.abs() - delta) {
158 spre = scur;
159 scur = stry;
160 } else {
161 spre = sbis;
162 scur = sbis;
163 }
164 } else {
165 spre = sbis;
166 scur = sbis;
167 }
168
169 xpre = xcur;
170 fpre = fcur;
171 if scur.abs() > delta {
172 xcur += scur;
173 } else {
174 xcur += if sbis > 0.0 { delta } else { -delta };
175 }
176 fcur = f(xcur);
177 }
178
179 Err(EdgeErrors::NoConvergence {
180 routine: "brentq",
181 iterations: max_iter,
182 last_delta: (xblk - xcur).abs(),
183 })
184}
185
186pub fn brent_fmin<F>(ax: f64, bx: f64, mut f: F, tol: f64) -> f64
206where
207 F: FnMut(f64) -> f64,
208{
209 let (mut a, mut b) = (ax, bx);
210 let mut v = a + BRENT_GOLDEN * (b - a);
211 let (mut w, mut x) = (v, v);
212 let mut d = 0.0_f64;
213 let mut e = 0.0_f64;
214 let mut fx = f(x);
215 let (mut fv, mut fw) = (fx, fx);
216 let tol3 = tol / 3.0;
217
218 loop {
219 let xm = 0.5 * (a + b);
220 let tol1 = BRENT_SQRT_EPS * x.abs() + tol3;
221 let t2 = 2.0 * tol1;
222 if (x - xm).abs() <= t2 - 0.5 * (b - a) {
223 return x;
224 }
225
226 let (mut p, mut q, mut r) = (0.0_f64, 0.0_f64, 0.0_f64);
227 if e.abs() > tol1 {
228 r = (x - w) * (fx - fv);
229 q = (x - v) * (fx - fw);
230 p = (x - v) * q - (x - w) * r;
231 q = 2.0 * (q - r);
232 if q > 0.0 {
233 p = -p;
234 } else {
235 q = -q;
236 }
237 r = e;
238 e = d;
239 }
240
241 if p.abs() >= (0.5 * q * r).abs() || p <= q * (a - x) || p >= q * (b - x) {
242 e = if x < xm { b - x } else { a - x };
243 d = BRENT_GOLDEN * e;
244 } else {
245 d = p / q;
246 let u = x + d;
247 if u - a < t2 || b - u < t2 {
249 d = if x >= xm { -tol1 } else { tol1 };
250 }
251 }
252
253 let u = if d.abs() >= tol1 {
255 x + d
256 } else if d > 0.0 {
257 x + tol1
258 } else {
259 x - tol1
260 };
261 let fu = f(u);
262
263 if fu <= fx {
264 if u < x {
265 b = x;
266 } else {
267 a = x;
268 }
269 v = w;
270 fv = fw;
271 w = x;
272 fw = fx;
273 x = u;
274 fx = fu;
275 } else {
276 if u < x {
277 a = u;
278 } else {
279 b = u;
280 }
281 if fu <= fw || w == x {
282 v = w;
283 fv = fw;
284 w = u;
285 fw = fu;
286 } else if fu <= fv || v == x || v == w {
287 v = u;
288 fv = fu;
289 }
290 }
291 }
292}
293
294#[derive(Clone, Copy, Debug)]
300pub struct NelderMeadParams {
301 pub xatol: f64,
303 pub fatol: f64,
305 pub max_iter: usize,
307}
308
309impl Default for NelderMeadParams {
310 fn default() -> Self {
313 Self {
314 xatol: 1e-4,
315 fatol: 1e-4,
316 max_iter: 1000,
317 }
318 }
319}
320
321#[derive(Clone, Debug)]
323pub struct NelderMeadResult {
324 pub x: Vec<f64>,
326 pub f: f64,
328 pub iterations: usize,
330 pub converged: bool,
332}
333
334pub fn nelder_mead<F>(
351 mut f: F,
352 x0: &[f64],
353 params: Option<NelderMeadParams>,
354) -> Result<NelderMeadResult, EdgeErrors>
355where
356 F: FnMut(&[f64]) -> f64,
357{
358 let mut stepper = NelderMeadStepper::new(x0, params)?;
359 while let Some(x) = stepper.ask() {
360 let value = f(x);
361 stepper.tell(value);
362 }
363 Ok(stepper.result())
364}
365
366#[derive(Clone, Copy, Debug, PartialEq, Eq)]
368enum NelderMeadPhase {
369 Init(usize),
371 Reflect,
373 Expand,
375 Contract,
377 Shrink(usize),
379 Done,
381}
382
383#[derive(Clone, Debug)]
390pub struct NelderMeadStepper {
391 params: NelderMeadParams,
393 n: usize,
395 simplex: Vec<Vec<f64>>,
397 values: Vec<f64>,
399 order: Vec<usize>,
402 next_iter: usize,
404 iterations: usize,
406 converged: bool,
408 phase: NelderMeadPhase,
410 pending: Vec<f64>,
412 centroid: Vec<f64>,
414 reflected: Vec<f64>,
416 f_reflected: f64,
418 inside: bool,
420 best: usize,
422 worst: usize,
424 second_worst: usize,
426}
427
428impl NelderMeadStepper {
429 pub fn new(x0: &[f64], params: Option<NelderMeadParams>) -> Result<Self, EdgeErrors> {
440 let params = params.unwrap_or_default();
441 let n = x0.len();
442 if n == 0 {
443 return Err(EdgeErrors::MustBePositive("x0.len()".to_string()));
444 }
445 let mut simplex: Vec<Vec<f64>> = Vec::with_capacity(n + 1);
446 simplex.push(x0.to_vec());
447 for i in 0..n {
448 let mut vertex = x0.to_vec();
449 if vertex[i] != 0.0 {
450 vertex[i] *= 1.0 + NELDER_MEAD_STEP;
451 } else {
452 vertex[i] = NELDER_MEAD_ZERO_STEP;
453 }
454 simplex.push(vertex);
455 }
456 let pending = simplex[0].clone();
457 Ok(Self {
458 params,
459 n,
460 simplex,
461 values: vec![0.0; n + 1],
462 order: (0..=n).collect(),
463 next_iter: 0,
464 iterations: 0,
465 converged: false,
466 phase: NelderMeadPhase::Init(0),
467 pending,
468 centroid: vec![0.0; n],
469 reflected: vec![0.0; n],
470 f_reflected: 0.0,
471 inside: false,
472 best: 0,
473 worst: 0,
474 second_worst: 0,
475 })
476 }
477
478 pub fn ask(&self) -> Option<&[f64]> {
484 if self.phase == NelderMeadPhase::Done {
485 None
486 } else {
487 Some(&self.pending)
488 }
489 }
490
491 pub fn tell(&mut self, value: f64) {
497 let n = self.n;
498 match self.phase {
499 NelderMeadPhase::Init(i) => {
500 self.values[i] = value;
501 if i < n {
502 self.phase = NelderMeadPhase::Init(i + 1);
503 self.pending.clone_from(&self.simplex[i + 1]);
504 } else {
505 self.begin_iteration();
506 }
507 }
508 NelderMeadPhase::Reflect => {
509 self.f_reflected = value;
510 let worst = self.worst;
511 if value < self.values[self.best] {
512 self.pending = (0..n)
513 .map(|j| {
514 self.centroid[j] + 2.0 * (self.centroid[j] - self.simplex[worst][j])
515 })
516 .collect();
517 self.phase = NelderMeadPhase::Expand;
518 } else if value < self.values[self.second_worst] {
519 self.simplex[worst].clone_from(&self.reflected);
520 self.values[worst] = value;
521 self.begin_iteration();
522 } else {
523 self.inside = value >= self.values[worst];
524 self.pending = if self.inside {
525 (0..n)
526 .map(|j| {
527 self.centroid[j] - 0.5 * (self.centroid[j] - self.simplex[worst][j])
528 })
529 .collect()
530 } else {
531 (0..n)
532 .map(|j| {
533 self.centroid[j] + 0.5 * (self.centroid[j] - self.simplex[worst][j])
534 })
535 .collect()
536 };
537 self.phase = NelderMeadPhase::Contract;
538 }
539 }
540 NelderMeadPhase::Expand => {
541 let worst = self.worst;
542 if value < self.f_reflected {
543 self.simplex[worst] = std::mem::take(&mut self.pending);
544 self.values[worst] = value;
545 } else {
546 self.simplex[worst].clone_from(&self.reflected);
547 self.values[worst] = self.f_reflected;
548 }
549 self.begin_iteration();
550 }
551 NelderMeadPhase::Contract => {
552 let worst = self.worst;
553 let accept = if self.inside {
554 value < self.values[worst]
555 } else {
556 value <= self.f_reflected
557 };
558 if accept {
559 self.simplex[worst] = std::mem::take(&mut self.pending);
560 self.values[worst] = value;
561 self.begin_iteration();
562 } else {
563 self.shrink_vertex(1);
566 }
567 }
568 NelderMeadPhase::Shrink(pos) => {
569 let i = self.order[pos];
570 self.values[i] = value;
571 if pos < n {
572 self.shrink_vertex(pos + 1);
573 } else {
574 self.begin_iteration();
575 }
576 }
577 NelderMeadPhase::Done => {}
578 }
579 }
580
581 fn shrink_vertex(&mut self, pos: usize) {
588 let i = self.order[pos];
589 let best = self.best;
590 for j in 0..self.n {
591 let anchor = self.simplex[best][j];
592 self.simplex[i][j] = anchor + 0.5 * (self.simplex[i][j] - anchor);
593 }
594 self.pending.clone_from(&self.simplex[i]);
595 self.phase = NelderMeadPhase::Shrink(pos);
596 }
597
598 fn begin_iteration(&mut self) {
600 let n = self.n;
601 if self.next_iter >= self.params.max_iter {
602 self.finish();
603 return;
604 }
605 self.iterations = self.next_iter + 1;
606 self.next_iter += 1;
607
608 let values = &self.values;
609 self.order.sort_by(|&a, &b| values[a].total_cmp(&values[b]));
610 self.best = self.order[0];
611 self.worst = self.order[n];
612 self.second_worst = self.order[n - 1];
613 let best = self.best;
614
615 let spread_x = self.order[1..]
616 .iter()
617 .map(|&i| {
618 self.simplex[i]
619 .iter()
620 .zip(self.simplex[best].iter())
621 .map(|(a, b)| (a - b).abs())
622 .fold(0.0_f64, f64::max)
623 })
624 .fold(0.0_f64, f64::max);
625 let spread_f = self.order[1..]
626 .iter()
627 .map(|&i| (self.values[i] - self.values[best]).abs())
628 .fold(0.0_f64, f64::max);
629 let scale = self.simplex[best]
630 .iter()
631 .fold(f64::MIN_POSITIVE, |m, v| m.max(v.abs()));
632 let collapsed = spread_x <= NELDER_MEAD_COLLAPSED_ULPS * f64::EPSILON * scale;
633 if collapsed || (spread_x <= self.params.xatol && spread_f <= self.params.fatol) {
634 self.converged = true;
635 self.finish();
636 return;
637 }
638
639 self.centroid.iter_mut().for_each(|c| *c = 0.0);
640 for &i in &self.order[..n] {
641 for (c, v) in self.centroid.iter_mut().zip(self.simplex[i].iter()) {
642 *c += *v;
643 }
644 }
645 for c in self.centroid.iter_mut() {
646 *c /= n as f64;
647 }
648 let worst = self.worst;
649 self.reflected = (0..n)
650 .map(|j| self.centroid[j] + (self.centroid[j] - self.simplex[worst][j]))
651 .collect();
652 self.pending.clone_from(&self.reflected);
653 self.phase = NelderMeadPhase::Reflect;
654 }
655
656 fn finish(&mut self) {
658 let values = &self.values;
659 self.order.sort_by(|&a, &b| values[a].total_cmp(&values[b]));
660 self.phase = NelderMeadPhase::Done;
661 }
662
663 pub fn result(&self) -> NelderMeadResult {
669 let best = self.order[0];
670 NelderMeadResult {
671 x: self.simplex[best].clone(),
672 f: self.values[best],
673 iterations: self.iterations,
674 converged: self.converged,
675 }
676 }
677}
678
679#[cfg(test)]
684mod tests {
685 use super::*;
686 use approx::assert_relative_eq;
687
688 #[test]
689 fn test_brentq_finds_a_polynomial_root() {
690 let root = brentq(
692 |x: f64| x.powi(3) - 2.0 * x - 5.0,
693 1.0,
694 3.0,
695 1e-14,
696 1e-15,
697 200,
698 )
699 .unwrap();
700 assert_relative_eq!(root, 2.094_551_481_542_326_5, max_relative = 1e-12);
701 }
702
703 #[test]
704 fn test_brentq_handles_a_root_at_an_endpoint() {
705 let root = brentq(|x: f64| x - 1.0, 1.0, 3.0, 1e-14, 1e-15, 200).unwrap();
706 assert_relative_eq!(root, 1.0, max_relative = 1e-14);
707 }
708
709 #[test]
710 fn test_brentq_rejects_an_unbracketed_interval() {
711 let err = brentq(|x: f64| x * x + 1.0, -1.0, 1.0, 1e-12, 1e-14, 100).unwrap_err();
712 assert!(matches!(err, EdgeErrors::RootNotBracketed { .. }));
713 }
714
715 #[test]
718 fn test_brentq_converges_on_a_steep_function() {
719 let root = brentq(|x: f64| x.exp() - 1e6, 0.0, 30.0, 1e-12, 1e-14, 200).unwrap();
720 assert_relative_eq!(root, 1e6_f64.ln(), max_relative = 1e-10);
721 }
722
723 #[test]
724 fn test_nelder_mead_minimises_rosenbrock() {
725 let res = nelder_mead(
726 |x: &[f64]| 100.0 * (x[1] - x[0] * x[0]).powi(2) + (1.0 - x[0]).powi(2),
727 &[-1.2, 1.0],
728 Some(NelderMeadParams {
729 xatol: 1e-10,
730 fatol: 1e-12,
731 max_iter: 5000,
732 }),
733 )
734 .unwrap();
735 assert!(res.converged);
736 assert_relative_eq!(res.x[0], 1.0, max_relative = 1e-4);
737 assert_relative_eq!(res.x[1], 1.0, max_relative = 1e-4);
738 }
739
740 #[test]
741 fn test_nelder_mead_handles_a_zero_starting_coordinate() {
742 let res = nelder_mead(
744 |x: &[f64]| (x[0] - 3.0).powi(2) + (x[1] + 2.0).powi(2),
745 &[0.0, 0.0],
746 Some(NelderMeadParams {
747 xatol: 1e-10,
748 fatol: 1e-12,
749 max_iter: 5000,
750 }),
751 )
752 .unwrap();
753 assert_relative_eq!(res.x[0], 3.0, max_relative = 1e-5);
754 assert_relative_eq!(res.x[1], -2.0, max_relative = 1e-5);
755 }
756
757 #[test]
758 fn test_nelder_mead_rejects_empty_start() {
759 let err = nelder_mead(|_: &[f64]| 0.0, &[], None).unwrap_err();
760 assert!(matches!(err, EdgeErrors::MustBePositive(_)));
761 }
762
763 fn nelder_mead_reference<F>(
766 mut f: F,
767 x0: &[f64],
768 params: Option<NelderMeadParams>,
769 ) -> Result<NelderMeadResult, EdgeErrors>
770 where
771 F: FnMut(&[f64]) -> f64,
772 {
773 let params = params.unwrap_or_default();
774 let n = x0.len();
775 if n == 0 {
776 return Err(EdgeErrors::MustBePositive("x0.len()".to_string()));
777 }
778
779 let mut simplex: Vec<Vec<f64>> = Vec::with_capacity(n + 1);
781 simplex.push(x0.to_vec());
782 for i in 0..n {
783 let mut vertex = x0.to_vec();
784 if vertex[i] != 0.0 {
785 vertex[i] *= 1.0 + NELDER_MEAD_STEP;
786 } else {
787 vertex[i] = NELDER_MEAD_ZERO_STEP;
788 }
789 simplex.push(vertex);
790 }
791
792 let mut values: Vec<f64> = simplex.iter().map(|v| f(v)).collect();
793 let mut order: Vec<usize> = (0..=n).collect();
794 let mut iterations = 0;
795 let mut converged = false;
796
797 for iter in 0..params.max_iter {
798 iterations = iter + 1;
799
800 order.sort_by(|&a, &b| values[a].total_cmp(&values[b]));
801 let best = order[0];
802 let worst = order[n];
803 let second_worst = order[n - 1];
804
805 let spread_x = order[1..]
807 .iter()
808 .map(|&i| {
809 simplex[i]
810 .iter()
811 .zip(simplex[best].iter())
812 .map(|(a, b)| (a - b).abs())
813 .fold(0.0_f64, f64::max)
814 })
815 .fold(0.0_f64, f64::max);
816 let spread_f = order[1..]
817 .iter()
818 .map(|&i| (values[i] - values[best]).abs())
819 .fold(0.0_f64, f64::max);
820 if spread_x <= params.xatol && spread_f <= params.fatol {
821 converged = true;
822 break;
823 }
824
825 let mut centroid = vec![0.0; n];
827 for &i in &order[..n] {
828 for (c, v) in centroid.iter_mut().zip(simplex[i].iter()) {
829 *c += *v;
830 }
831 }
832 for c in centroid.iter_mut() {
833 *c /= n as f64;
834 }
835
836 let reflected: Vec<f64> = (0..n)
837 .map(|j| centroid[j] + (centroid[j] - simplex[worst][j]))
838 .collect();
839 let f_reflected = f(&reflected);
840
841 if f_reflected < values[best] {
842 let expanded: Vec<f64> = (0..n)
843 .map(|j| centroid[j] + 2.0 * (centroid[j] - simplex[worst][j]))
844 .collect();
845 let f_expanded = f(&expanded);
846 if f_expanded < f_reflected {
847 simplex[worst] = expanded;
848 values[worst] = f_expanded;
849 } else {
850 simplex[worst] = reflected;
851 values[worst] = f_reflected;
852 }
853 } else if f_reflected < values[second_worst] {
854 simplex[worst] = reflected;
855 values[worst] = f_reflected;
856 } else {
857 let inside = f_reflected >= values[worst];
858 let contracted: Vec<f64> = if inside {
859 (0..n)
860 .map(|j| centroid[j] - 0.5 * (centroid[j] - simplex[worst][j]))
861 .collect()
862 } else {
863 (0..n)
864 .map(|j| centroid[j] + 0.5 * (centroid[j] - simplex[worst][j]))
865 .collect()
866 };
867 let f_contracted = f(&contracted);
868 let accept = if inside {
869 f_contracted < values[worst]
870 } else {
871 f_contracted <= f_reflected
872 };
873
874 if accept {
875 simplex[worst] = contracted;
876 values[worst] = f_contracted;
877 } else {
878 let anchor = simplex[best].clone();
880 for &i in &order[1..] {
881 for j in 0..n {
882 simplex[i][j] = anchor[j] + 0.5 * (simplex[i][j] - anchor[j]);
883 }
884 values[i] = f(&simplex[i]);
885 }
886 }
887 }
888 }
889
890 order.sort_by(|&a, &b| values[a].total_cmp(&values[b]));
891 let best = order[0];
892 Ok(NelderMeadResult {
893 x: simplex[best].clone(),
894 f: values[best],
895 iterations,
896 converged,
897 })
898 }
899
900 fn drive_stepper(
902 f: &dyn Fn(&[f64]) -> f64,
903 x0: &[f64],
904 params: NelderMeadParams,
905 ) -> (NelderMeadResult, Vec<Vec<f64>>) {
906 let mut stepper = NelderMeadStepper::new(x0, Some(params)).unwrap();
907 let mut asked = Vec::new();
908 while let Some(x) = stepper.ask() {
909 asked.push(x.to_vec());
910 let v = f(x);
911 stepper.tell(v);
912 }
913 (stepper.result(), asked)
914 }
915
916 #[test]
917 fn test_nelder_mead_stepper_is_bit_identical_to_the_closure_form() {
918 let rosenbrock = |x: &[f64]| (1.0 - x[0]).powi(2) + 100.0 * (x[1] - x[0] * x[0]).powi(2);
919 let quadratic_1d = |x: &[f64]| (x[0] - 0.3).powi(2) + 1.0;
920 let staircase = |x: &[f64]| (x[0] * 4.0).floor().abs() + (x[1] * 4.0).floor().abs();
922 let bumpy =
924 |x: &[f64]| (x[0] * 3.0).sin() * (x[1] * 5.0).cos() + 0.1 * (x[0] * x[0] + x[1] * x[1]);
925 let three_d = |x: &[f64]| {
926 x.iter()
927 .enumerate()
928 .map(|(i, v)| (i as f64 + 1.0) * v * v)
929 .sum()
930 };
931
932 type Case<'a> = (&'a dyn Fn(&[f64]) -> f64, Vec<f64>);
933 let cases: Vec<Case<'_>> = vec![
934 (&rosenbrock, vec![-1.2, 1.0]),
935 (&quadratic_1d, vec![2.0]),
936 (&quadratic_1d, vec![0.0]),
937 (&staircase, vec![1.3, -0.7]),
938 (&bumpy, vec![0.4, 0.9]),
939 (&three_d, vec![1.0, -2.0, 0.5]),
940 ];
941
942 for max_iter in [3, 17, 1000] {
943 let params = NelderMeadParams {
944 xatol: 1e-9,
945 fatol: 1e-9,
946 max_iter,
947 };
948 for (f, x0) in &cases {
949 let mut expected_points = Vec::new();
950 let expected = nelder_mead_reference(
951 |x: &[f64]| {
952 expected_points.push(x.to_vec());
953 f(x)
954 },
955 x0,
956 Some(params),
957 )
958 .unwrap();
959 let (got, got_points) = drive_stepper(*f, x0, params);
960
961 assert_eq!(got_points, expected_points, "evaluation sequence differs");
962 assert_eq!(got.x, expected.x);
963 assert_eq!(got.f.to_bits(), expected.f.to_bits());
964 assert_eq!(got.iterations, expected.iterations);
965 assert_eq!(got.converged, expected.converged);
966 }
967 }
968 }
969}