1use std::fmt;
62use std::sync::OnceLock;
63
64#[derive(Clone, Copy, Debug, PartialEq)]
70pub struct ClosedInterval {
71 pub lo: f64,
72 pub hi: f64,
73}
74
75impl ClosedInterval {
76 #[inline]
77 pub const fn new(lo: f64, hi: f64) -> Self {
78 Self { lo, hi }
79 }
80
81 #[inline]
82 pub const fn point(value: f64) -> Self {
83 Self {
84 lo: value,
85 hi: value,
86 }
87 }
88
89 #[inline]
90 pub const fn entire() -> Self {
91 Self {
92 lo: f64::NEG_INFINITY,
93 hi: f64::INFINITY,
94 }
95 }
96
97 #[inline]
98 pub fn contains(self, value: f64) -> bool {
99 self.lo <= value && value <= self.hi
100 }
101
102 #[inline]
103 pub fn contains_zero(self) -> bool {
104 self.contains(0.0)
105 }
106
107 #[inline]
108 fn is_valid(self) -> bool {
109 !self.lo.is_nan() && !self.hi.is_nan() && self.lo <= self.hi
110 }
111
112 #[inline]
113 fn hull(self, other: Self) -> Self {
114 Self {
115 lo: self.lo.min(other.lo),
116 hi: self.hi.max(other.hi),
117 }
118 }
119
120 #[inline]
121 fn intersection(self, other: Self) -> Option<Self> {
122 let intersection = Self {
123 lo: self.lo.max(other.lo),
124 hi: self.hi.min(other.hi),
125 };
126 (intersection.lo <= intersection.hi).then_some(intersection)
127 }
128
129 #[inline]
130 fn max_abs(self) -> f64 {
131 self.lo.abs().max(self.hi.abs())
132 }
133
134 #[inline]
135 fn widen(self, radius: f64) -> Self {
136 if radius == 0.0 {
137 return self;
138 }
139 if radius == f64::INFINITY {
140 return Self::entire();
141 }
142 Self {
143 lo: next_down(self.lo - radius),
144 hi: next_up(self.hi + radius),
145 }
146 }
147
148 #[inline]
149 pub fn add(self, other: Self) -> Self {
151 Self {
152 lo: sum_down(self.lo, other.lo),
153 hi: sum_up(self.hi, other.hi),
154 }
155 }
156
157 #[inline]
158 pub fn sub(self, other: Self) -> Self {
160 Self {
161 lo: sum_down(self.lo, -other.hi),
162 hi: sum_up(self.hi, -other.lo),
163 }
164 }
165
166 #[inline]
167 pub fn neg(self) -> Self {
169 Self {
170 lo: -self.hi,
171 hi: -self.lo,
172 }
173 }
174
175 pub fn mul(self, other: Self) -> Self {
177 let pairs = [
178 (self.lo, other.lo),
179 (self.lo, other.hi),
180 (self.hi, other.lo),
181 (self.hi, other.hi),
182 ];
183 let mut lo = f64::INFINITY;
184 let mut hi = f64::NEG_INFINITY;
185 for (left, right) in pairs {
186 lo = lo.min(product_down(left, right));
187 hi = hi.max(product_up(left, right));
188 }
189 Self { lo, hi }
190 }
191
192 #[inline]
193 pub fn scale(self, value: f64) -> Self {
196 self.mul(Self::point(value))
197 }
198
199 fn square(self) -> Self {
200 if self.lo >= 0.0 {
201 Self {
202 lo: product_down(self.lo, self.lo).max(0.0),
203 hi: product_up(self.hi, self.hi),
204 }
205 } else if self.hi <= 0.0 {
206 Self {
207 lo: product_down(self.hi, self.hi).max(0.0),
208 hi: product_up(self.lo, self.lo),
209 }
210 } else {
211 Self {
212 lo: 0.0,
213 hi: product_up(self.lo, self.lo).max(product_up(self.hi, self.hi)),
214 }
215 }
216 }
217
218 fn ln_positive(self) -> Self {
220 assert!(
221 self.lo > 0.0,
222 "ln_positive requires a strictly positive interval, got lo={}",
223 self.lo
224 );
225 let lo = certified_ln_positive(self.lo)
226 .expect("ln_positive lower endpoint is finite and positive");
227 let hi = certified_ln_positive(self.hi)
228 .expect("ln_positive upper endpoint is finite and positive");
229 Self::new(lo.lo, hi.hi)
230 }
231
232 fn div_positive(self, denominator: Self) -> Self {
234 assert!(
235 denominator.lo > 0.0,
236 "div_positive requires a strictly positive denominator interval, got lo={}",
237 denominator.lo
238 );
239 let reciprocal = Self {
240 lo: quotient_down(1.0, denominator.hi).max(0.0),
241 hi: quotient_up(1.0, denominator.lo),
242 };
243 self.mul(reciprocal)
244 }
245
246 fn div_nonzero(self, denominator: Self) -> Self {
248 if denominator.lo > 0.0 {
249 self.div_positive(denominator)
250 } else {
251 assert!(
252 denominator.hi < 0.0,
253 "div_nonzero requires a denominator interval excluding zero, got {denominator:?}"
254 );
255 self.div_positive(denominator.neg()).neg()
256 }
257 }
258
259 #[inline]
260 fn nonnegative(self) -> Self {
261 Self {
262 lo: self.lo.max(0.0),
263 hi: self.hi.max(0.0),
264 }
265 }
266}
267
268#[derive(Clone, Copy, Debug, PartialEq)]
279pub struct ScoreJet {
280 pub value: f64,
281 pub derivative: f64,
282 pub curvature: f64,
283 pub third: f64,
284}
285
286#[derive(Clone, Copy, Debug, PartialEq)]
288pub struct ScoreSample {
289 pub x: f64,
290 pub value: f64,
291 pub derivative: f64,
292 pub curvature: f64,
293 pub third: f64,
294}
295
296#[derive(Clone, Copy, Debug, PartialEq)]
309pub struct ScoreValueEnclosure {
310 pub value: ClosedInterval,
311 pub evaluation_error: f64,
312}
313
314#[derive(Clone, Copy, Debug, PartialEq)]
320pub struct DerivativeEnclosure {
321 pub score: ScoreValueEnclosure,
322 pub derivative: ClosedInterval,
323 pub curvature: ClosedInterval,
324}
325
326#[derive(Clone, Copy, Debug, PartialEq)]
334pub struct ResolutionFlatRegion {
335 pub sample: ScoreSample,
336 pub bracket: ClosedInterval,
337 pub score: ClosedInterval,
339 pub max_score_gap: f64,
340 pub score_resolution: f64,
341}
342
343#[derive(Clone, Copy, Debug, PartialEq)]
347pub struct StationaryPoint {
348 pub sample: ScoreSample,
349 pub bracket: ClosedInterval,
350 pub score: ScoreValueEnclosure,
352 pub curvature: ClosedInterval,
358}
359
360#[derive(Clone, Copy, Debug, PartialEq)]
363pub struct GlobalScoreCertificate {
364 pub selected: ClosedInterval,
366 pub maximum: ClosedInterval,
368 pub maximum_excess: f64,
373 pub comparison_resolution: f64,
377}
378
379#[derive(Clone, Copy, Debug, PartialEq, Eq)]
380pub enum ScoreOptimumLocation {
381 LowerBoundary,
382 UpperBoundary,
383 Stationary(usize),
384 ResolutionFlat(usize),
385}
386
387#[derive(Clone, Copy, Debug, PartialEq)]
394pub struct DominatedRegion {
395 pub bracket: ClosedInterval,
396 pub score: ScoreValueEnclosure,
397 pub incumbent_lower: f64,
398}
399
400#[derive(Clone, Debug, PartialEq)]
405pub struct ScoreSearchResult {
406 pub optimum: ScoreSample,
407 pub location: ScoreOptimumLocation,
408 pub lower_boundary: ScoreSample,
409 pub upper_boundary: ScoreSample,
410 pub stationary_points: Vec<StationaryPoint>,
411 pub resolution_flat_regions: Vec<ResolutionFlatRegion>,
412 pub dominated_regions: Vec<DominatedRegion>,
416 pub value_certificate: GlobalScoreCertificate,
417}
418
419#[derive(Debug)]
421pub enum ScoreSearchError<E> {
422 InvalidDomain {
423 lo: f64,
424 hi: f64,
425 },
426 InvalidResolution {
427 resolution: f64,
428 },
429 PointEvaluation {
430 x: f64,
431 source: E,
432 },
433 EnclosureEvaluation {
434 lo: f64,
435 hi: f64,
436 source: E,
437 },
438 NonFiniteSample {
439 sample: ScoreSample,
440 },
441 InvalidEnclosure {
442 lo: f64,
443 hi: f64,
444 enclosure: DerivativeEnclosure,
445 },
446 ScoreValueEnclosureMissesEndpoint {
447 lo: f64,
448 hi: f64,
449 endpoint: ScoreSample,
450 score: ScoreValueEnclosure,
451 },
452 DisjointEndpointEnclosure {
453 lo: f64,
454 hi: f64,
455 endpoint: ScoreSample,
456 endpoint_derivative: ClosedInterval,
457 enclosure: DerivativeEnclosure,
458 },
459 InconsistentRootEnclosure {
463 lo: f64,
464 hi: f64,
465 left_derivative: ClosedInterval,
466 right_derivative: ClosedInterval,
467 curvature: ClosedInterval,
468 left_newton: ClosedInterval,
469 right_newton: ClosedInterval,
470 point_newton: ClosedInterval,
471 },
472 Unresolved {
476 lo: f64,
477 hi: f64,
478 requested_resolution: f64,
479 enclosure: DerivativeEnclosure,
480 },
481 SubdivisionBudget {
490 lo: f64,
491 hi: f64,
492 cell_lo: f64,
493 cell_hi: f64,
494 requested_resolution: f64,
495 subdivisions: usize,
496 budget: usize,
497 depth_bound: u32,
498 enclosure: DerivativeEnclosure,
499 },
500}
501
502pub fn subdivision_budget(lo: f64, hi: f64, resolution: f64) -> (usize, u32) {
617 let width = hi - lo;
618 if !(width.is_finite() && width > 0.0 && resolution.is_finite() && resolution > 0.0) {
619 return (1, 0);
620 }
621 let levels = (width / resolution).log2().ceil();
622 let depth_bound = if levels.is_finite() && levels >= 1.0 {
623 levels.min(u32::MAX as f64) as u32
626 } else {
627 1
628 };
629 let depth = depth_bound as usize;
630 (8 * depth * depth, depth_bound)
631}
632
633impl<E: fmt::Display> fmt::Display for ScoreSearchError<E> {
634 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
635 match self {
636 Self::InvalidDomain { lo, hi } => {
637 write!(f, "score search: invalid domain [{lo}, {hi}]")
638 }
639 Self::InvalidResolution { resolution } => {
640 write!(f, "score search: invalid resolution {resolution}")
641 }
642 Self::PointEvaluation { x, source } => {
643 write!(f, "score search: evaluation failed at {x}: {source}")
644 }
645 Self::EnclosureEvaluation { lo, hi, source } => write!(
646 f,
647 "score search: score/derivative enclosure failed on [{lo}, {hi}]: {source}"
648 ),
649 Self::NonFiniteSample { sample } => write!(
650 f,
651 "score search: non-finite jet at {} (value {}, derivative {}, curvature {}, third {})",
652 sample.x, sample.value, sample.derivative, sample.curvature, sample.third
653 ),
654 Self::InvalidEnclosure { lo, hi, enclosure } => write!(
655 f,
656 "score search: invalid score/derivative enclosure on [{lo}, {hi}]: {enclosure:?}"
657 ),
658 Self::ScoreValueEnclosureMissesEndpoint {
659 lo,
660 hi,
661 endpoint,
662 score,
663 } => write!(
664 f,
665 "score search: exact score range {:?} plus evaluator error {} on [{lo}, {hi}] misses the rounded endpoint value {} at {}",
666 score.value, score.evaluation_error, endpoint.value, endpoint.x
667 ),
668 Self::DisjointEndpointEnclosure {
669 lo,
670 hi,
671 endpoint,
672 endpoint_derivative,
673 enclosure,
674 } => write!(
675 f,
676 "score search: derivative enclosures on [{lo}, {hi}] and its endpoint {} are disjoint: endpoint range {endpoint_derivative:?}, cell {enclosure:?}; point estimate {endpoint:?}",
677 endpoint.x
678 ),
679 Self::InconsistentRootEnclosure {
680 lo,
681 hi,
682 left_derivative,
683 right_derivative,
684 curvature,
685 left_newton,
686 right_newton,
687 point_newton,
688 } => write!(
689 f,
690 "score search: interval-Newton certificates for the unique root on [{lo}, {hi}] \
691 are inconsistent: left derivative {left_derivative:?}, right derivative \
692 {right_derivative:?}, curvature {curvature:?}, left image {left_newton:?}, \
693 right image {right_newton:?}, point image {point_newton:?}"
694 ),
695 Self::Unresolved {
696 lo,
697 hi,
698 requested_resolution,
699 enclosure,
700 } => {
701 let evaluation_error = enclosure.score.evaluation_error;
706 let verdict = if evaluation_error >= *requested_resolution {
707 " -- the REQUEST is unsatisfiable: the certified evaluation error at this cell \
708 already reaches the requested resolution, so no bracket narrower than about \
709 twice that error is decidable and no additional subdivision can close it"
710 } else {
711 ""
712 };
713 write!(
714 f,
715 "score search: stationary structure unresolved on [{lo}, {hi}] at requested \
716 resolution {requested_resolution} (certified evaluation error \
717 {evaluation_error:e}){verdict}: {enclosure:?}"
718 )
719 }
720 Self::SubdivisionBudget {
721 lo,
722 hi,
723 cell_lo,
724 cell_hi,
725 requested_resolution,
726 subdivisions,
727 budget,
728 depth_bound,
729 enclosure,
730 } => {
731 let evaluation_error = enclosure.score.evaluation_error;
735 let verdict = if evaluation_error >= *requested_resolution {
736 "a LARGER BUDGET CANNOT HELP -- the certified evaluation error already reaches \
737 the requested resolution, so no subdivision separates stationary structure at \
738 this tolerance; the resolution asked for is finer than the evaluator delivers"
739 } else {
740 "the evaluation error is below the requested resolution, so this cell was still \
741 separable and a larger budget may resolve it"
742 };
743 write!(
744 f,
745 "score search: {subdivisions} cell subdivisions on [{lo}, {hi}] at requested \
746 resolution {requested_resolution} exceed the budget {budget} derived from this \
747 domain's subdivision depth bound {depth_bound}; the criterion is still \
748 undecomposable at [{cell_lo}, {cell_hi}], so it neither excludes nor isolates \
749 stationary structure over a region the search can only enumerate. Certified \
750 evaluation error at this cell is {evaluation_error:e} against requested \
751 resolution {requested_resolution:e}: {verdict}"
752 )
753 }
754 }
755 }
756}
757
758impl<E: std::error::Error + 'static> std::error::Error for ScoreSearchError<E> {}
759
760#[derive(Clone, Copy)]
761struct SearchSample {
762 sample: ScoreSample,
763 point_enclosure: Option<DerivativeEnclosure>,
764}
765
766#[derive(Clone, Copy)]
767struct SearchNode {
768 left: SearchSample,
769 right: SearchSample,
770}
771
772#[derive(Clone, Copy)]
773struct TerminalScoreCandidate {
774 score: ScoreValueEnclosure,
775 comparison_error: f64,
781 point_x: Option<f64>,
785}
786
787impl TerminalScoreCandidate {
788 #[inline]
789 fn point(x: f64, score: ScoreValueEnclosure) -> Self {
790 Self {
791 score,
792 comparison_error: score.evaluation_error,
793 point_x: Some(x),
794 }
795 }
796
797 #[inline]
798 fn region(score: ScoreValueEnclosure, comparison_error: f64) -> Self {
799 Self {
800 score,
801 comparison_error,
802 point_x: None,
803 }
804 }
805}
806
807fn evaluate_sample<E, F>(x: f64, evaluate: &mut F) -> Result<SearchSample, ScoreSearchError<E>>
808where
809 F: FnMut(f64) -> Result<ScoreJet, E>,
810{
811 let jet = evaluate(x).map_err(|source| ScoreSearchError::PointEvaluation { x, source })?;
812 let sample = ScoreSample {
813 x,
814 value: jet.value,
815 derivative: jet.derivative,
816 curvature: jet.curvature,
817 third: jet.third,
818 };
819 if sample.value.is_finite()
820 && sample.derivative.is_finite()
821 && sample.curvature.is_finite()
822 && sample.third.is_finite()
823 {
824 Ok(SearchSample {
825 sample,
826 point_enclosure: None,
827 })
828 } else {
829 Err(ScoreSearchError::NonFiniteSample { sample })
830 }
831}
832
833fn checked_enclosure<E, F>(
834 left: ScoreSample,
835 right: ScoreSample,
836 enclose: &mut F,
837) -> Result<DerivativeEnclosure, ScoreSearchError<E>>
838where
839 F: FnMut(ScoreSample, ScoreSample) -> Result<DerivativeEnclosure, E>,
840{
841 let lo = left.x;
842 let hi = right.x;
843 let enclosure = enclose(left, right)
849 .map_err(|source| ScoreSearchError::EnclosureEvaluation { lo, hi, source })?;
850 if !(enclosure.derivative.is_valid()
851 && enclosure.curvature.is_valid()
852 && enclosure.score.value.is_valid()
853 && enclosure.score.evaluation_error.is_finite()
854 && enclosure.score.evaluation_error >= 0.0)
855 {
856 return Err(ScoreSearchError::InvalidEnclosure { lo, hi, enclosure });
857 }
858 let score = enclosure.score;
859 let resolved_score = score.value.widen(score.evaluation_error);
860 for endpoint in [left, right] {
861 if !resolved_score.contains(endpoint.value) {
862 return Err(ScoreSearchError::ScoreValueEnclosureMissesEndpoint {
863 lo,
864 hi,
865 endpoint,
866 score,
867 });
868 }
869 }
870 Ok(enclosure)
871}
872
873fn certify_point<E, F>(
880 point: &mut SearchSample,
881 enclose: &mut F,
882) -> Result<DerivativeEnclosure, ScoreSearchError<E>>
883where
884 F: FnMut(ScoreSample, ScoreSample) -> Result<DerivativeEnclosure, E>,
885{
886 let enclosure = match point.point_enclosure {
887 Some(enclosure) => enclosure,
888 None => {
889 let enclosure = checked_enclosure(point.sample, point.sample, enclose)?;
890 point.point_enclosure = Some(enclosure);
891 enclosure
892 }
893 };
894 Ok(enclosure)
895}
896
897fn certify_endpoint_derivative<E, F>(
898 point: &mut SearchSample,
899 cell_lo: f64,
900 cell_hi: f64,
901 cell: DerivativeEnclosure,
902 enclose: &mut F,
903) -> Result<ClosedInterval, ScoreSearchError<E>>
904where
905 F: FnMut(ScoreSample, ScoreSample) -> Result<DerivativeEnclosure, E>,
906{
907 let endpoint_derivative = certify_point(point, enclose)?.derivative;
908 endpoint_derivative.intersection(cell.derivative).ok_or(
909 ScoreSearchError::DisjointEndpointEnclosure {
910 lo: cell_lo,
911 hi: cell_hi,
912 endpoint: point.sample,
913 endpoint_derivative,
914 enclosure: cell,
915 },
916 )
917}
918
919#[derive(Clone, Copy, PartialEq, Eq)]
920enum StrictSign {
921 Negative,
922 Positive,
923}
924
925#[inline]
926fn strict_sign(interval: ClosedInterval) -> Option<StrictSign> {
927 if interval.hi < 0.0 {
928 Some(StrictSign::Negative)
929 } else if interval.lo > 0.0 {
930 Some(StrictSign::Positive)
931 } else {
932 None
933 }
934}
935
936#[inline]
937fn is_exact_zero(interval: ClosedInterval) -> bool {
938 interval.lo == 0.0 && interval.hi == 0.0
939}
940
941fn certify_bracket_score<E, Eval, Enclose>(
942 bracket: ClosedInterval,
943 representative: SearchSample,
944 evaluate: &mut Eval,
945 enclose: &mut Enclose,
946) -> Result<ScoreValueEnclosure, ScoreSearchError<E>>
947where
948 Eval: FnMut(f64) -> Result<ScoreJet, E>,
949 Enclose: FnMut(ScoreSample, ScoreSample) -> Result<DerivativeEnclosure, E>,
950{
951 if bracket.lo == bracket.hi {
952 let mut representative = representative;
953 return Ok(certify_point(&mut representative, enclose)?.score);
954 }
955 let left = if representative.sample.x == bracket.lo {
956 representative
957 } else {
958 evaluate_sample(bracket.lo, evaluate)?
959 };
960 let right = if representative.sample.x == bracket.hi {
961 representative
962 } else {
963 evaluate_sample(bracket.hi, evaluate)?
964 };
965 Ok(checked_enclosure(left.sample, right.sample, enclose)?.score)
966}
967
968enum UniqueRootRefinement {
969 Stationary(StationaryPoint),
970 ResolutionFlat {
971 region: ResolutionFlatRegion,
972 maximum: ScoreValueEnclosure,
973 },
974}
975
976fn refine_unique_root<E, Eval, Enclose>(
980 mut left: SearchSample,
981 mut right: SearchSample,
982 resolution: f64,
983 enclosure: DerivativeEnclosure,
984 evaluate: &mut Eval,
985 enclose: &mut Enclose,
986) -> Result<UniqueRootRefinement, ScoreSearchError<E>>
987where
988 Eval: FnMut(f64) -> Result<ScoreJet, E>,
989 Enclose: FnMut(ScoreSample, ScoreSample) -> Result<DerivativeEnclosure, E>,
990{
991 let bracket_lo = left.sample.x;
992 let bracket_hi = right.sample.x;
993 let mut left_derivative =
994 certify_endpoint_derivative(&mut left, bracket_lo, bracket_hi, enclosure, enclose)?;
995 let mut right_derivative =
996 certify_endpoint_derivative(&mut right, bracket_lo, bracket_hi, enclosure, enclose)?;
997 let curvature_sign =
998 strict_sign(enclosure.curvature).ok_or(ScoreSearchError::InvalidEnclosure {
999 lo: left.sample.x,
1000 hi: right.sample.x,
1001 enclosure,
1002 })?;
1003 let increasing = curvature_sign == StrictSign::Positive;
1004 let expected_left_sign = if increasing {
1005 StrictSign::Negative
1006 } else {
1007 StrictSign::Positive
1008 };
1009 let expected_right_sign = if increasing {
1010 StrictSign::Positive
1011 } else {
1012 StrictSign::Negative
1013 };
1014 if strict_sign(left_derivative) != Some(expected_left_sign)
1015 || strict_sign(right_derivative) != Some(expected_right_sign)
1016 {
1017 return Err(ScoreSearchError::InvalidEnclosure {
1018 lo: left.sample.x,
1019 hi: right.sample.x,
1020 enclosure,
1021 });
1022 }
1023
1024 let mut force_midpoint = false;
1025 while right.sample.x - left.sample.x > resolution {
1026 let width = right.sample.x - left.sample.x;
1027 let midpoint = left.sample.x + 0.5 * width;
1028 if !(midpoint > left.sample.x && midpoint < right.sample.x) {
1029 return Err(ScoreSearchError::Unresolved {
1030 lo: left.sample.x,
1031 hi: right.sample.x,
1032 requested_resolution: resolution,
1033 enclosure,
1034 });
1035 }
1036
1037 let base = if left_derivative.max_abs() <= right_derivative.max_abs() {
1047 left.sample
1048 } else {
1049 right.sample
1050 };
1051 let newton = if base.curvature != 0.0 {
1052 base.x - base.derivative / base.curvature
1053 } else {
1054 f64::NAN
1055 };
1056 let guard = 0.25 * width;
1057 let x = if !force_midpoint
1058 && newton.is_finite()
1059 && newton >= left.sample.x + guard
1060 && newton <= right.sample.x - guard
1061 {
1062 newton
1063 } else {
1064 midpoint
1065 };
1066 force_midpoint = false;
1067 if !(x > left.sample.x && x < right.sample.x) {
1068 return Err(ScoreSearchError::Unresolved {
1069 lo: left.sample.x,
1070 hi: right.sample.x,
1071 requested_resolution: resolution,
1072 enclosure,
1073 });
1074 }
1075 let mut sample = evaluate_sample(x, evaluate)?;
1076 let probe_x = sample.sample.x;
1077 let mut point_derivative = certify_endpoint_derivative(
1078 &mut sample,
1079 left.sample.x,
1080 right.sample.x,
1081 enclosure,
1082 enclose,
1083 )?;
1084 let mut root_curvature = enclosure.curvature;
1085 if !is_exact_zero(point_derivative) && strict_sign(point_derivative).is_none() {
1086 let left_cell = checked_enclosure(left.sample, sample.sample, enclose)?;
1092 let right_cell = checked_enclosure(sample.sample, right.sample, enclose)?;
1093 let left_probe_derivative = certify_endpoint_derivative(
1094 &mut sample,
1095 left.sample.x,
1096 probe_x,
1097 left_cell,
1098 enclose,
1099 )?;
1100 let right_probe_derivative = certify_endpoint_derivative(
1101 &mut sample,
1102 probe_x,
1103 right.sample.x,
1104 right_cell,
1105 enclose,
1106 )?;
1107 point_derivative = left_probe_derivative
1108 .intersection(right_probe_derivative)
1109 .ok_or(ScoreSearchError::DisjointEndpointEnclosure {
1110 lo: left.sample.x,
1111 hi: right.sample.x,
1112 endpoint: sample.sample,
1113 endpoint_derivative: left_probe_derivative,
1114 enclosure: right_cell,
1115 })?;
1116 let child_curvature = left_cell.curvature.hull(right_cell.curvature);
1117 root_curvature = enclosure.curvature.intersection(child_curvature).ok_or(
1118 ScoreSearchError::InvalidEnclosure {
1119 lo: left.sample.x,
1120 hi: right.sample.x,
1121 enclosure: right_cell,
1122 },
1123 )?;
1124 }
1125 if is_exact_zero(point_derivative) {
1126 let bracket = ClosedInterval::point(x);
1127 let score = certify_bracket_score(bracket, sample, evaluate, enclose)?;
1128 return Ok(UniqueRootRefinement::Stationary(StationaryPoint {
1129 sample: sample.sample,
1130 bracket,
1131 score,
1132 curvature: root_curvature,
1133 }));
1134 }
1135 if let Some(sign) = strict_sign(point_derivative) {
1136 match (increasing, sign) {
1137 (true, StrictSign::Negative) | (false, StrictSign::Positive) => {
1138 left = sample;
1139 left_derivative = point_derivative;
1140 }
1141 (true, StrictSign::Positive) | (false, StrictSign::Negative) => {
1142 right = sample;
1143 right_derivative = point_derivative;
1144 }
1145 }
1146 continue;
1147 }
1148
1149 let bracket = ClosedInterval::new(left.sample.x, right.sample.x);
1158 let point_newton =
1159 ClosedInterval::point(x).sub(point_derivative.div_nonzero(root_curvature));
1160 let left_newton = ClosedInterval::point(left.sample.x)
1161 .sub(left_derivative.div_nonzero(enclosure.curvature));
1162 let right_newton = ClosedInterval::point(right.sample.x)
1163 .sub(right_derivative.div_nonzero(enclosure.curvature));
1164 let root = bracket
1165 .intersection(point_newton)
1166 .and_then(|root| root.intersection(left_newton))
1167 .and_then(|root| root.intersection(right_newton))
1168 .ok_or(ScoreSearchError::InconsistentRootEnclosure {
1169 lo: left.sample.x,
1170 hi: right.sample.x,
1171 left_derivative,
1172 right_derivative,
1173 curvature: enclosure.curvature,
1174 left_newton,
1175 right_newton,
1176 point_newton,
1177 })?;
1178 if root.hi - root.lo <= resolution {
1179 let score = certify_bracket_score(root, sample, evaluate, enclose)?;
1180 return Ok(UniqueRootRefinement::Stationary(StationaryPoint {
1181 sample: sample.sample,
1182 bracket: root,
1183 score,
1184 curvature: root_curvature,
1185 }));
1186 }
1187 let point_score = certify_point(&mut sample, enclose)?.score;
1196 if let Some((region, maximum)) = score_resolved_concave_maximum(
1197 SearchNode { left, right },
1198 enclosure,
1199 sample.sample,
1200 point_derivative,
1201 root_curvature,
1202 point_score,
1203 ) {
1204 return Ok(UniqueRootRefinement::ResolutionFlat { region, maximum });
1205 }
1206 if root.lo > left.sample.x || root.hi < right.sample.x {
1207 let mut new_left = if root.lo == sample.sample.x {
1208 sample
1209 } else {
1210 evaluate_sample(root.lo, evaluate)?
1211 };
1212 let mut new_right = if root.hi == sample.sample.x {
1213 sample
1214 } else {
1215 evaluate_sample(root.hi, evaluate)?
1216 };
1217 let contracted_enclosure =
1218 checked_enclosure(new_left.sample, new_right.sample, enclose)?;
1219 let point_score = certify_point(&mut sample, enclose)?.score;
1230 let displacement = ClosedInterval::new(root.lo - x, root.hi - x);
1231 let taylor_score = point_score
1232 .value
1233 .add(point_derivative.mul(displacement))
1234 .add(root_curvature.mul(displacement.square()).scale(0.5));
1235 let tightened_score = contracted_enclosure
1236 .score
1237 .value
1238 .intersection(taylor_score)
1239 .ok_or(ScoreSearchError::InvalidEnclosure {
1240 lo: root.lo,
1241 hi: root.hi,
1242 enclosure: contracted_enclosure,
1243 })?;
1244 let contracted_enclosure = DerivativeEnclosure {
1245 score: ScoreValueEnclosure {
1246 value: tightened_score,
1247 evaluation_error: contracted_enclosure.score.evaluation_error,
1248 },
1249 ..contracted_enclosure
1250 };
1251 if let Some(region) = resolution_flat_region(
1252 SearchNode {
1253 left: new_left,
1254 right: new_right,
1255 },
1256 contracted_enclosure,
1257 ) {
1258 return Ok(UniqueRootRefinement::ResolutionFlat {
1259 region,
1260 maximum: contracted_enclosure.score,
1261 });
1262 }
1263 let new_left_derivative = if new_left.sample.x == sample.sample.x {
1264 point_derivative
1265 } else {
1266 certify_endpoint_derivative(
1267 &mut new_left,
1268 root.lo,
1269 root.hi,
1270 contracted_enclosure,
1271 enclose,
1272 )?
1273 };
1274 let new_right_derivative = if new_right.sample.x == sample.sample.x {
1275 point_derivative
1276 } else {
1277 certify_endpoint_derivative(
1278 &mut new_right,
1279 root.lo,
1280 root.hi,
1281 contracted_enclosure,
1282 enclose,
1283 )?
1284 };
1285
1286 let mut preserved_sign_contraction = false;
1292 if root.lo > left.sample.x {
1293 match strict_sign(new_left_derivative) {
1294 Some(sign) if sign == expected_left_sign => {
1295 left = new_left;
1296 left_derivative = new_left_derivative;
1297 preserved_sign_contraction = true;
1298 }
1299 Some(_) => {
1300 return Err(ScoreSearchError::InvalidEnclosure {
1301 lo: root.lo,
1302 hi: root.hi,
1303 enclosure: contracted_enclosure,
1304 });
1305 }
1306 None => {}
1307 }
1308 }
1309 if root.hi < right.sample.x {
1310 match strict_sign(new_right_derivative) {
1311 Some(sign) if sign == expected_right_sign => {
1312 right = new_right;
1313 right_derivative = new_right_derivative;
1314 preserved_sign_contraction = true;
1315 }
1316 Some(_) => {
1317 return Err(ScoreSearchError::InvalidEnclosure {
1318 lo: root.lo,
1319 hi: root.hi,
1320 enclosure: contracted_enclosure,
1321 });
1322 }
1323 None => {}
1324 }
1325 }
1326 if preserved_sign_contraction {
1327 continue;
1328 }
1329 }
1330 if x != midpoint {
1331 force_midpoint = true;
1332 continue;
1333 }
1334 return Err(ScoreSearchError::Unresolved {
1335 lo: left.sample.x,
1336 hi: right.sample.x,
1337 requested_resolution: resolution,
1338 enclosure,
1339 });
1340 }
1341
1342 let midpoint = left.sample.x + 0.5 * (right.sample.x - left.sample.x);
1343 let sample = if midpoint > left.sample.x && midpoint < right.sample.x {
1344 evaluate_sample(midpoint, evaluate)?.sample
1345 } else if left_derivative.max_abs() <= right_derivative.max_abs() {
1346 left.sample
1347 } else {
1348 right.sample
1349 };
1350 let bracket = ClosedInterval::new(left.sample.x, right.sample.x);
1351 let representative = SearchSample {
1352 sample,
1353 point_enclosure: None,
1354 };
1355 let score = certify_bracket_score(bracket, representative, evaluate, enclose)?;
1356 Ok(UniqueRootRefinement::Stationary(StationaryPoint {
1357 sample,
1358 bracket,
1359 score,
1360 curvature: enclosure.curvature,
1361 }))
1362}
1363
1364fn isolate_shared_endpoint_root<E, Eval, Enclose>(
1370 endpoint: SearchSample,
1371 domain_lo: f64,
1372 domain_hi: f64,
1373 resolution: f64,
1374 evaluate: &mut Eval,
1375 enclose: &mut Enclose,
1376) -> Result<Option<StationaryPoint>, ScoreSearchError<E>>
1377where
1378 Eval: FnMut(f64) -> Result<ScoreJet, E>,
1379 Enclose: FnMut(ScoreSample, ScoreSample) -> Result<DerivativeEnclosure, E>,
1380{
1381 let radius = 0.5 * resolution;
1382 let left_x = endpoint.sample.x - radius;
1383 let mut right_x = endpoint.sample.x + radius;
1384 if !(left_x >= domain_lo
1385 && right_x <= domain_hi
1386 && left_x < endpoint.sample.x
1387 && right_x > endpoint.sample.x)
1388 {
1389 return Ok(None);
1390 }
1391 while right_x - left_x > resolution {
1392 right_x = next_down(right_x);
1393 }
1394 if !(right_x > endpoint.sample.x && right_x - left_x <= resolution) {
1395 return Ok(None);
1396 }
1397
1398 let mut left = evaluate_sample(left_x, evaluate)?;
1399 let mut right = evaluate_sample(right_x, evaluate)?;
1400 let probe_enclosure = checked_enclosure(left.sample, right.sample, enclose)?;
1401 if probe_enclosure.curvature.contains_zero() {
1402 return Ok(None);
1403 }
1404 let left_derivative =
1405 certify_endpoint_derivative(&mut left, left_x, right_x, probe_enclosure, enclose)?;
1406 let right_derivative =
1407 certify_endpoint_derivative(&mut right, left_x, right_x, probe_enclosure, enclose)?;
1408 if strict_sign(left_derivative)
1409 .zip(strict_sign(right_derivative))
1410 .is_some_and(|(left_sign, right_sign)| left_sign != right_sign)
1411 {
1412 Ok(Some(StationaryPoint {
1413 sample: endpoint.sample,
1414 bracket: ClosedInterval::new(left_x, right_x),
1415 score: probe_enclosure.score,
1416 curvature: probe_enclosure.curvature,
1417 }))
1418 } else {
1419 Ok(None)
1420 }
1421}
1422
1423fn resolution_flat_region(
1436 node: SearchNode,
1437 enclosure: DerivativeEnclosure,
1438) -> Option<ResolutionFlatRegion> {
1439 let score = enclosure.score;
1440 let max_score_gap = if score.value.lo == score.value.hi {
1441 0.0
1442 } else {
1443 next_up(score.value.hi - score.value.lo)
1444 };
1445 let score_resolution = if score.evaluation_error == 0.0 {
1446 0.0
1447 } else {
1448 next_up(2.0 * score.evaluation_error)
1449 };
1450 if !(max_score_gap.is_finite() && score_resolution.is_finite()) {
1451 return None;
1452 }
1453 let sample = if node.right.sample.value > node.left.sample.value {
1454 node.right.sample
1455 } else {
1456 node.left.sample
1457 };
1458 (max_score_gap <= score_resolution).then_some(ResolutionFlatRegion {
1459 sample,
1460 bracket: ClosedInterval::new(node.left.sample.x, node.right.sample.x),
1461 score: score.value,
1462 max_score_gap,
1463 score_resolution,
1464 })
1465}
1466
1467fn score_resolved_concave_maximum(
1477 node: SearchNode,
1478 enclosure: DerivativeEnclosure,
1479 sample: ScoreSample,
1480 point_derivative: ClosedInterval,
1481 curvature: ClosedInterval,
1482 point_score: ScoreValueEnclosure,
1483) -> Option<(ResolutionFlatRegion, ScoreValueEnclosure)> {
1484 if !(curvature.hi < 0.0 && sample.x >= node.left.sample.x && sample.x <= node.right.sample.x) {
1485 return None;
1486 }
1487
1488 let maximum_excess = point_derivative
1491 .square()
1492 .scale(0.5)
1493 .div_positive(curvature.neg())
1494 .hi;
1495 let comparison_resolution = point_score.evaluation_error;
1496 if !(maximum_excess.is_finite()
1497 && maximum_excess >= 0.0
1498 && comparison_resolution.is_finite()
1499 && maximum_excess <= comparison_resolution)
1500 {
1501 return None;
1502 }
1503
1504 let maximum = ClosedInterval::new(
1508 point_score.value.lo,
1509 enclosure
1510 .score
1511 .value
1512 .hi
1513 .min(sum_up(point_score.value.hi, maximum_excess)),
1514 );
1515 if !maximum.is_valid() {
1516 return None;
1517 }
1518 let region = ResolutionFlatRegion {
1519 sample,
1520 bracket: ClosedInterval::new(node.left.sample.x, node.right.sample.x),
1521 score: enclosure.score.value,
1522 max_score_gap: maximum_excess,
1523 score_resolution: comparison_resolution,
1524 };
1525 Some((
1526 region,
1527 ScoreValueEnclosure {
1528 value: maximum,
1529 evaluation_error: point_score
1530 .evaluation_error
1531 .max(enclosure.score.evaluation_error),
1532 },
1533 ))
1534}
1535
1536fn certified_domain_boundary(
1544 node: &SearchNode,
1545 derivative_sign: StrictSign,
1546 domain_lo: f64,
1547 domain_hi: f64,
1548) -> Option<(ScoreSample, ScoreOptimumLocation)> {
1549 if node.left.sample.x != domain_lo || node.right.sample.x != domain_hi {
1550 return None;
1551 }
1552 Some(match derivative_sign {
1553 StrictSign::Positive => (node.right.sample, ScoreOptimumLocation::UpperBoundary),
1554 StrictSign::Negative => (node.left.sample, ScoreOptimumLocation::LowerBoundary),
1555 })
1556}
1557
1558pub fn maximize_score_1d<E, Eval, Enclose>(
1600 lo: f64,
1601 hi: f64,
1602 resolution: f64,
1603 mut evaluate: Eval,
1604 mut enclose: Enclose,
1605) -> Result<ScoreSearchResult, ScoreSearchError<E>>
1606where
1607 Eval: FnMut(f64) -> Result<ScoreJet, E>,
1608 Enclose: FnMut(ScoreSample, ScoreSample) -> Result<DerivativeEnclosure, E>,
1609{
1610 if !(lo.is_finite() && hi.is_finite() && lo <= hi && (hi - lo).is_finite()) {
1611 return Err(ScoreSearchError::InvalidDomain { lo, hi });
1612 }
1613 if !(resolution.is_finite() && resolution > 0.0) {
1614 return Err(ScoreSearchError::InvalidResolution { resolution });
1615 }
1616
1617 let mut lower_boundary = evaluate_sample(lo, &mut evaluate)?;
1618 if lo == hi {
1619 let score =
1620 checked_enclosure(lower_boundary.sample, lower_boundary.sample, &mut enclose)?.score;
1621 return Ok(ScoreSearchResult {
1622 optimum: lower_boundary.sample,
1623 location: ScoreOptimumLocation::LowerBoundary,
1624 lower_boundary: lower_boundary.sample,
1625 upper_boundary: lower_boundary.sample,
1626 stationary_points: Vec::new(),
1627 resolution_flat_regions: Vec::new(),
1628 dominated_regions: Vec::new(),
1629 value_certificate: GlobalScoreCertificate {
1630 selected: score.value,
1631 maximum: score.value,
1632 maximum_excess: 0.0,
1633 comparison_resolution: 0.0,
1634 },
1635 });
1636 }
1637 let mut upper_boundary = evaluate_sample(hi, &mut evaluate)?;
1638 let lower_boundary_score = certify_point(&mut lower_boundary, &mut enclose)?.score;
1639 let upper_boundary_score = certify_point(&mut upper_boundary, &mut enclose)?.score;
1640 let mut incumbent_lower = lower_boundary_score
1641 .value
1642 .lo
1643 .max(upper_boundary_score.value.lo);
1644 let (mut optimum, mut location) = if upper_boundary.sample.value > lower_boundary.sample.value {
1645 (upper_boundary.sample, ScoreOptimumLocation::UpperBoundary)
1646 } else {
1647 (lower_boundary.sample, ScoreOptimumLocation::LowerBoundary)
1648 };
1649
1650 let (budget, depth_bound) = subdivision_budget(lo, hi, resolution);
1651 let mut subdivisions = 0usize;
1652 let mut stationary_points = Vec::<StationaryPoint>::new();
1653 let mut resolution_flat_regions = Vec::<ResolutionFlatRegion>::new();
1654 let mut dominated_regions = Vec::<DominatedRegion>::new();
1655 let mut terminal_maxima = vec![
1659 TerminalScoreCandidate::point(lower_boundary.sample.x, lower_boundary_score),
1660 TerminalScoreCandidate::point(upper_boundary.sample.x, upper_boundary_score),
1661 ];
1662 let mut stack = vec![SearchNode {
1663 left: lower_boundary,
1664 right: upper_boundary,
1665 }];
1666 while let Some(mut node) = stack.pop() {
1667 let mathematical_enclosure =
1668 checked_enclosure(node.left.sample, node.right.sample, &mut enclose)?;
1669 let enclosure = mathematical_enclosure;
1670 if enclosure.score.value.hi < incumbent_lower {
1671 dominated_regions.push(DominatedRegion {
1672 bracket: ClosedInterval::new(node.left.sample.x, node.right.sample.x),
1673 score: enclosure.score,
1674 incumbent_lower,
1675 });
1676 continue;
1677 }
1678 if !enclosure.derivative.contains_zero() {
1679 let derivative_sign = if enclosure.derivative.lo > 0.0 {
1680 StrictSign::Positive
1681 } else {
1682 StrictSign::Negative
1683 };
1684 if let Some((proven_optimum, proven_location)) =
1685 certified_domain_boundary(&node, derivative_sign, lo, hi)
1686 {
1687 optimum = proven_optimum;
1688 location = proven_location;
1689 }
1690 let endpoint = match derivative_sign {
1691 StrictSign::Positive => &mut node.right,
1692 StrictSign::Negative => &mut node.left,
1693 };
1694 let endpoint_score = certify_point(endpoint, &mut enclose)?.score;
1695 incumbent_lower = incumbent_lower.max(endpoint_score.value.lo);
1696 terminal_maxima.push(TerminalScoreCandidate::point(
1697 endpoint.sample.x,
1698 endpoint_score,
1699 ));
1700 continue;
1701 }
1702
1703 let monotone = !enclosure.curvature.contains_zero();
1704 if monotone {
1705 let node_lo = node.left.sample.x;
1706 let node_hi = node.right.sample.x;
1707 let left_derivative = certify_endpoint_derivative(
1708 &mut node.left,
1709 node_lo,
1710 node_hi,
1711 enclosure,
1712 &mut enclose,
1713 )?;
1714 let right_derivative = certify_endpoint_derivative(
1715 &mut node.right,
1716 node_lo,
1717 node_hi,
1718 enclosure,
1719 &mut enclose,
1720 )?;
1721 let left_sign = strict_sign(left_derivative);
1722 let right_sign = strict_sign(right_derivative);
1723 let mut root_flat = None;
1724 let stationary = if is_exact_zero(left_derivative) {
1725 let score = certify_point(&mut node.left, &mut enclose)?.score;
1726 Some(StationaryPoint {
1727 sample: node.left.sample,
1728 bracket: ClosedInterval::point(node.left.sample.x),
1729 score,
1730 curvature: enclosure.curvature,
1731 })
1732 } else if is_exact_zero(right_derivative) {
1733 let score = certify_point(&mut node.right, &mut enclose)?.score;
1734 Some(StationaryPoint {
1735 sample: node.right.sample,
1736 bracket: ClosedInterval::point(node.right.sample.x),
1737 score,
1738 curvature: enclosure.curvature,
1739 })
1740 } else if left_sign
1741 .zip(right_sign)
1742 .is_some_and(|(left_sign, right_sign)| left_sign != right_sign)
1743 {
1744 match refine_unique_root(
1745 node.left,
1746 node.right,
1747 resolution,
1748 enclosure,
1749 &mut evaluate,
1750 &mut enclose,
1751 )? {
1752 UniqueRootRefinement::Stationary(stationary) => Some(stationary),
1753 UniqueRootRefinement::ResolutionFlat { region, maximum } => {
1754 root_flat = Some((region, maximum));
1755 None
1756 }
1757 }
1758 } else if left_sign.is_none() {
1759 isolate_shared_endpoint_root(
1760 node.left,
1761 lo,
1762 hi,
1763 resolution,
1764 &mut evaluate,
1765 &mut enclose,
1766 )?
1767 } else if right_sign.is_none() {
1768 isolate_shared_endpoint_root(
1769 node.right,
1770 lo,
1771 hi,
1772 resolution,
1773 &mut evaluate,
1774 &mut enclose,
1775 )?
1776 } else {
1777 None
1778 };
1779
1780 if let Some((flat, maximum)) = root_flat {
1781 let index = resolution_flat_regions.len();
1782 if flat.sample.value > optimum.value {
1783 optimum = flat.sample;
1784 location = ScoreOptimumLocation::ResolutionFlat(index);
1785 }
1786 let mut representative = SearchSample {
1787 sample: flat.sample,
1788 point_enclosure: None,
1789 };
1790 let representative_score = certify_point(&mut representative, &mut enclose)?.score;
1791 incumbent_lower = incumbent_lower.max(representative_score.value.lo);
1792 terminal_maxima.push(TerminalScoreCandidate::region(
1793 maximum,
1794 representative_score
1795 .evaluation_error
1796 .max(maximum.evaluation_error),
1797 ));
1798 resolution_flat_regions.push(flat);
1799 continue;
1800 }
1801
1802 if let Some(stationary) = stationary {
1803 let mut representative = SearchSample {
1804 sample: stationary.sample,
1805 point_enclosure: None,
1806 };
1807 let representative_score = certify_point(&mut representative, &mut enclose)?.score;
1808 incumbent_lower = incumbent_lower.max(representative_score.value.lo);
1809 let duplicate = stationary_points
1812 .last()
1813 .is_some_and(|previous| previous.sample.x == stationary.sample.x);
1814 if !duplicate {
1815 let index = stationary_points.len();
1816 if stationary.sample.value > optimum.value {
1817 optimum = stationary.sample;
1818 location = ScoreOptimumLocation::Stationary(index);
1819 }
1820 stationary_points.push(stationary);
1821 }
1822 if enclosure.curvature.hi < 0.0 {
1823 let score = stationary.score;
1824 terminal_maxima.push(if stationary.bracket.lo == stationary.bracket.hi {
1825 TerminalScoreCandidate::point(stationary.sample.x, score)
1826 } else {
1827 TerminalScoreCandidate::region(
1828 score,
1829 representative_score
1830 .evaluation_error
1831 .max(score.evaluation_error),
1832 )
1833 });
1834 } else {
1835 let left_score = certify_point(&mut node.left, &mut enclose)?.score;
1836 let right_score = certify_point(&mut node.right, &mut enclose)?.score;
1837 incumbent_lower = incumbent_lower
1838 .max(left_score.value.lo)
1839 .max(right_score.value.lo);
1840 terminal_maxima.push(TerminalScoreCandidate::point(
1841 node.left.sample.x,
1842 left_score,
1843 ));
1844 terminal_maxima.push(TerminalScoreCandidate::point(
1845 node.right.sample.x,
1846 right_score,
1847 ));
1848 }
1849 continue;
1850 }
1851
1852 if let Some((left_sign, right_sign)) = left_sign.zip(right_sign)
1857 && left_sign == right_sign
1858 {
1859 if let Some((proven_optimum, proven_location)) =
1860 certified_domain_boundary(&node, left_sign, lo, hi)
1861 {
1862 optimum = proven_optimum;
1863 location = proven_location;
1864 }
1865 let endpoint = match left_sign {
1866 StrictSign::Positive => &mut node.right,
1867 StrictSign::Negative => &mut node.left,
1868 };
1869 let endpoint_score = certify_point(endpoint, &mut enclose)?.score;
1870 incumbent_lower = incumbent_lower.max(endpoint_score.value.lo);
1871 terminal_maxima.push(TerminalScoreCandidate::point(
1872 endpoint.sample.x,
1873 endpoint_score,
1874 ));
1875 continue;
1876 }
1877 }
1878
1879 if let Some(flat) = resolution_flat_region(node, mathematical_enclosure) {
1880 let index = resolution_flat_regions.len();
1881 if flat.sample.value > optimum.value {
1882 optimum = flat.sample;
1883 location = ScoreOptimumLocation::ResolutionFlat(index);
1884 }
1885 let mut representative = SearchSample {
1886 sample: flat.sample,
1887 point_enclosure: None,
1888 };
1889 let representative_score = certify_point(&mut representative, &mut enclose)?.score;
1890 incumbent_lower = incumbent_lower.max(representative_score.value.lo);
1891 terminal_maxima.push(TerminalScoreCandidate::region(
1892 enclosure.score,
1893 representative_score
1894 .evaluation_error
1895 .max(enclosure.score.evaluation_error),
1896 ));
1897 resolution_flat_regions.push(flat);
1898 continue;
1899 }
1900
1901 let width = node.right.sample.x - node.left.sample.x;
1902 let midpoint = node.left.sample.x + 0.5 * width;
1903 if width <= resolution || !(midpoint > node.left.sample.x && midpoint < node.right.sample.x)
1904 {
1905 return Err(ScoreSearchError::Unresolved {
1906 lo: node.left.sample.x,
1907 hi: node.right.sample.x,
1908 requested_resolution: resolution,
1909 enclosure,
1910 });
1911 }
1912 subdivisions += 1;
1913 if subdivisions > budget {
1914 return Err(ScoreSearchError::SubdivisionBudget {
1915 lo,
1916 hi,
1917 cell_lo: node.left.sample.x,
1918 cell_hi: node.right.sample.x,
1919 requested_resolution: resolution,
1920 subdivisions,
1921 budget,
1922 depth_bound,
1923 enclosure,
1924 });
1925 }
1926 let middle = evaluate_sample(midpoint, &mut evaluate)?;
1927 stack.push(SearchNode {
1930 left: middle,
1931 right: node.right,
1932 });
1933 stack.push(SearchNode {
1934 left: node.left,
1935 right: middle,
1936 });
1937 }
1938
1939 let mut selected_sample = SearchSample {
1940 sample: optimum,
1941 point_enclosure: None,
1942 };
1943 let selected_score = certify_point(&mut selected_sample, &mut enclose)?.score;
1944 let global_lower = terminal_maxima
1945 .iter()
1946 .map(|candidate| candidate.score.value.lo)
1947 .fold(selected_score.value.lo, f64::max);
1948 let global_upper = terminal_maxima
1949 .iter()
1950 .map(|candidate| candidate.score.value.hi)
1951 .fold(selected_score.value.hi, f64::max);
1952 let candidate_evaluation_error = terminal_maxima
1953 .iter()
1954 .filter(|candidate| candidate.point_x != Some(optimum.x))
1955 .map(|candidate| candidate.comparison_error)
1956 .fold(0.0_f64, f64::max);
1957 let maximum_excess = terminal_maxima
1958 .iter()
1959 .filter(|candidate| candidate.point_x != Some(optimum.x))
1960 .map(|candidate| {
1961 if candidate.score.value.hi <= selected_score.value.lo {
1962 0.0
1963 } else {
1964 next_up(candidate.score.value.hi - selected_score.value.lo)
1965 }
1966 })
1967 .fold(0.0_f64, f64::max);
1968 let comparison_resolution =
1969 add_nonnegative_upward(selected_score.evaluation_error, candidate_evaluation_error);
1970
1971 Ok(ScoreSearchResult {
1972 optimum,
1973 location,
1974 lower_boundary: lower_boundary.sample,
1975 upper_boundary: upper_boundary.sample,
1976 stationary_points,
1977 resolution_flat_regions,
1978 dominated_regions,
1979 value_certificate: GlobalScoreCertificate {
1980 selected: selected_score.value,
1981 maximum: ClosedInterval::new(global_lower, global_upper),
1982 maximum_excess,
1983 comparison_resolution,
1984 },
1985 })
1986}
1987
1988pub fn maximize_score_1d_value_ordered<E, Eval, Enclose>(
2006 lo: f64,
2007 hi: f64,
2008 initial_resolution: f64,
2009 mut evaluate: Eval,
2010 mut enclose: Enclose,
2011) -> Result<ScoreSearchResult, ScoreSearchError<E>>
2012where
2013 Eval: FnMut(f64) -> Result<ScoreJet, E>,
2014 Enclose: FnMut(ScoreSample, ScoreSample) -> Result<DerivativeEnclosure, E>,
2015{
2016 let mut resolution = initial_resolution;
2017 let mut search = maximize_score_1d(lo, hi, resolution, &mut evaluate, &mut enclose)?;
2018 loop {
2019 let certificate = search.value_certificate;
2020 if certificate.maximum_excess <= certificate.comparison_resolution {
2021 return Ok(search);
2022 }
2023 let binary_refinement = 0.5 * resolution;
2024 let value_directed_refinement = if certificate.comparison_resolution > 0.0 {
2025 resolution * (certificate.comparison_resolution / certificate.maximum_excess)
2026 } else {
2027 binary_refinement
2028 };
2029 let next_resolution = binary_refinement.min(value_directed_refinement);
2030 if !(next_resolution.is_finite() && next_resolution > 0.0 && next_resolution < resolution) {
2031 return Ok(search);
2032 }
2033 match maximize_score_1d(lo, hi, next_resolution, &mut evaluate, &mut enclose) {
2034 Ok(refined) => {
2035 search = refined;
2036 resolution = next_resolution;
2037 }
2038 Err(
2049 ScoreSearchError::Unresolved { .. } | ScoreSearchError::SubdivisionBudget { .. },
2050 ) => return Ok(search),
2051 Err(error) => return Err(error),
2052 }
2053 }
2054}
2055
2056#[derive(Clone, Copy, Debug, PartialEq)]
2058pub enum AffineRemlError {
2059 EmptyModes,
2060 EmptyResponses,
2061 ShapeMismatch {
2062 gram_modes: usize,
2063 penalty_modes: usize,
2064 projected_rhs_squared: usize,
2065 responses: usize,
2066 },
2067 InvalidMode {
2068 index: usize,
2069 gram: f64,
2070 penalty: f64,
2071 },
2072 InvalidProjectedSquare {
2073 index: usize,
2074 value: f64,
2075 },
2076 InvalidResponseEnergy {
2077 output: usize,
2078 value: f64,
2079 },
2080 ZeroLambdaResidualUnavailable {
2081 output: usize,
2082 },
2083 InvalidResidualDof {
2084 value: f64,
2085 },
2086 InvalidLogdetConstant {
2087 value: f64,
2088 },
2089 RankMismatch {
2090 supplied: usize,
2091 inferred: usize,
2092 },
2093 InvalidLogLambda {
2094 value: f64,
2095 },
2096 InvalidLogLambdaInterval {
2097 lo: f64,
2098 hi: f64,
2099 },
2100 ElementaryEnclosureUnavailable {
2101 function: &'static str,
2102 lo: f64,
2103 hi: f64,
2104 },
2105 NonPositiveMode {
2106 index: usize,
2107 log_lambda: f64,
2108 value: f64,
2109 },
2110 NonPositiveResidual {
2111 output: usize,
2112 log_lambda: f64,
2113 value: f64,
2114 },
2115 NonPositiveResidualInterval {
2116 output: usize,
2117 lo: f64,
2118 hi: f64,
2119 lower_bound: f64,
2120 },
2121 InconsistentResidualEnclosures {
2122 output: usize,
2123 lo: f64,
2124 hi: f64,
2125 direct: ClosedInterval,
2126 complement: ClosedInterval,
2127 },
2128 UnboundedScoreEvaluationError {
2129 lo: f64,
2130 hi: f64,
2131 error: f64,
2132 },
2133}
2134
2135impl fmt::Display for AffineRemlError {
2136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2137 match self {
2138 Self::EmptyModes => write!(f, "affine REML profile has no modes"),
2139 Self::EmptyResponses => write!(f, "affine REML profile has no responses"),
2140 Self::ShapeMismatch {
2141 gram_modes,
2142 penalty_modes,
2143 projected_rhs_squared,
2144 responses,
2145 } => write!(
2146 f,
2147 "affine REML profile shape mismatch: gram {gram_modes}, penalty {penalty_modes}, projected squares {projected_rhs_squared}, responses {responses}"
2148 ),
2149 Self::InvalidMode {
2150 index,
2151 gram,
2152 penalty,
2153 } => write!(
2154 f,
2155 "affine REML mode {index} must have finite nonnegative (g,s), not both zero; got ({gram}, {penalty})"
2156 ),
2157 Self::InvalidProjectedSquare { index, value } => write!(
2158 f,
2159 "affine REML projected square {index} must be finite and nonnegative, got {value}"
2160 ),
2161 Self::InvalidResponseEnergy { output, value } => write!(
2162 f,
2163 "affine REML response energy {output} must be finite and nonnegative, got {value}"
2164 ),
2165 Self::ZeroLambdaResidualUnavailable { output } => write!(
2166 f,
2167 "affine REML could not certify the zero-smoothing residual for response {output}"
2168 ),
2169 Self::InvalidResidualDof { value } => {
2170 write!(
2171 f,
2172 "affine REML residual dof must be finite and positive, got {value}"
2173 )
2174 }
2175 Self::InvalidLogdetConstant { value } => write!(
2176 f,
2177 "affine REML log-determinant constant must be finite, got {value}"
2178 ),
2179 Self::RankMismatch { supplied, inferred } => write!(
2180 f,
2181 "affine REML determinant rank {supplied} disagrees with {inferred} positive penalty modes"
2182 ),
2183 Self::InvalidLogLambda { value } => {
2184 write!(f, "affine REML invalid log lambda {value}")
2185 }
2186 Self::InvalidLogLambdaInterval { lo, hi } => {
2187 write!(f, "affine REML invalid log-lambda interval [{lo}, {hi}]")
2188 }
2189 Self::ElementaryEnclosureUnavailable { function, lo, hi } => write!(
2190 f,
2191 "affine REML has no finite source-derived {function} enclosure on [{lo}, {hi}]"
2192 ),
2193 Self::NonPositiveMode {
2194 index,
2195 log_lambda,
2196 value,
2197 } => write!(
2198 f,
2199 "affine REML mode {index} is nonpositive at log lambda {log_lambda}: {value}"
2200 ),
2201 Self::NonPositiveResidual {
2202 output,
2203 log_lambda,
2204 value,
2205 } => write!(
2206 f,
2207 "affine REML residual {output} is nonpositive at log lambda {log_lambda}: {value}"
2208 ),
2209 Self::NonPositiveResidualInterval {
2210 output,
2211 lo,
2212 hi,
2213 lower_bound,
2214 } => write!(
2215 f,
2216 "affine REML residual {output} is not certified positive on [{lo}, {hi}] (lower bound {lower_bound})"
2217 ),
2218 Self::InconsistentResidualEnclosures {
2219 output,
2220 lo,
2221 hi,
2222 direct,
2223 complement,
2224 } => write!(
2225 f,
2226 "affine REML residual {output} has disjoint direct {direct:?} and zero-smoothing-complement {complement:?} enclosures on [{lo}, {hi}]"
2227 ),
2228 Self::UnboundedScoreEvaluationError { lo, hi, error } => write!(
2229 f,
2230 "affine REML score evaluator has no finite forward-error bound on [{lo}, {hi}] (bound {error})"
2231 ),
2232 }
2233 }
2234}
2235
2236impl std::error::Error for AffineRemlError {}
2237
2238#[derive(Clone, Debug)]
2249pub struct AffineRemlProfile<'a> {
2250 gram_modes: &'a [f64],
2251 penalty_modes: &'a [f64],
2252 projected_rhs_squared: &'a [f64],
2253 response_energy: &'a [f64],
2254 zero_lambda_residual: Vec<ClosedInterval>,
2263 residual_dof: f64,
2264 logdet_constant: f64,
2265}
2266
2267struct CertifiedCompensatedSum {
2277 leading: f64,
2278 correction: ClosedInterval,
2279}
2280
2281impl CertifiedCompensatedSum {
2282 fn new(value: f64) -> Self {
2283 Self {
2284 leading: value,
2285 correction: ClosedInterval::point(0.0),
2286 }
2287 }
2288
2289 fn add_exact(&mut self, value: f64) -> bool {
2291 let sum = self.leading + value;
2292 if !sum.is_finite() {
2293 return false;
2294 }
2295 let virtual_value = sum - self.leading;
2296 let virtual_leading = sum - virtual_value;
2297 let value_residual = value - virtual_value;
2298 let leading_residual = self.leading - virtual_leading;
2299 let error = leading_residual + value_residual;
2302 self.leading = sum;
2303 self.correction = self.correction.add(ClosedInterval::point(error));
2304 self.correction.is_valid()
2305 }
2306
2307 fn subtract_interval(&mut self, value: ClosedInterval) -> bool {
2308 self.correction = self.correction.sub(value);
2309 self.correction.is_valid()
2310 }
2311
2312 fn enclosure(self) -> Option<ClosedInterval> {
2313 let enclosure = ClosedInterval::point(self.leading).add(self.correction);
2314 (enclosure.is_valid() && enclosure.lo.is_finite() && enclosure.hi.is_finite())
2315 .then_some(enclosure)
2316 }
2317}
2318
2319fn quotient_leading_and_correction(
2330 numerator: f64,
2331 denominator: f64,
2332) -> Option<(f64, ClosedInterval)> {
2333 if numerator == 0.0 {
2334 return Some((0.0, ClosedInterval::point(0.0)));
2335 }
2336 if !(numerator.is_finite() && numerator > 0.0 && denominator.is_finite() && denominator > 0.0) {
2337 return None;
2338 }
2339 let leading = numerator / denominator;
2340 if !(leading.is_finite() && leading >= 0.0) {
2341 return None;
2342 }
2343 if denominator == 1.0 {
2344 return Some((leading, ClosedInterval::point(0.0)));
2345 }
2346 let fused_residual = (-leading).mul_add(denominator, numerator);
2347 if !fused_residual.is_finite() {
2348 return None;
2349 }
2350 let exact_residual = ClosedInterval::new(next_down(fused_residual), next_up(fused_residual));
2351 let correction = exact_residual.div_positive(ClosedInterval::point(denominator));
2352 (correction.is_valid() && correction.lo.is_finite() && correction.hi.is_finite())
2353 .then_some((leading, correction))
2354}
2355
2356fn certified_zero_lambda_residual(
2357 energy: f64,
2358 gram_modes: &[f64],
2359 projected_squares: &[f64],
2360) -> Option<ClosedInterval> {
2361 let mut residual = CertifiedCompensatedSum::new(energy);
2362 for (&gram, &projected_square) in gram_modes.iter().zip(projected_squares) {
2363 if gram == 0.0 || projected_square == 0.0 {
2364 continue;
2365 }
2366 let (leading, correction) = quotient_leading_and_correction(projected_square, gram)?;
2367 if !(residual.add_exact(-leading) && residual.subtract_interval(correction)) {
2368 return None;
2369 }
2370 }
2371 residual.enclosure()
2372}
2373
2374const DETERMINANT_VALUE_OPS_PER_MODE: usize = 8;
2384const RESIDUAL_VALUE_OPS_PER_MODE: usize = 4;
2385const RESIDUAL_LOG_OPS_PER_RESPONSE: usize = 3;
2386const SCORE_COMBINE_OPS: usize = 4;
2387
2388impl<'a> AffineRemlProfile<'a> {
2389 pub fn new(
2390 gram_modes: &'a [f64],
2391 penalty_modes: &'a [f64],
2392 projected_rhs_squared: &'a [f64],
2393 response_energy: &'a [f64],
2394 residual_dof: f64,
2395 determinant_rank: usize,
2396 logdet_constant: f64,
2397 ) -> Result<Self, AffineRemlError> {
2398 let modes = gram_modes.len();
2399 let responses = response_energy.len();
2400 if modes == 0 {
2401 return Err(AffineRemlError::EmptyModes);
2402 }
2403 if responses == 0 {
2404 return Err(AffineRemlError::EmptyResponses);
2405 }
2406 if penalty_modes.len() != modes
2407 || projected_rhs_squared.len() != modes.saturating_mul(responses)
2408 {
2409 return Err(AffineRemlError::ShapeMismatch {
2410 gram_modes: modes,
2411 penalty_modes: penalty_modes.len(),
2412 projected_rhs_squared: projected_rhs_squared.len(),
2413 responses,
2414 });
2415 }
2416 for (index, (&gram, &penalty)) in gram_modes.iter().zip(penalty_modes).enumerate() {
2417 if !(gram.is_finite()
2418 && penalty.is_finite()
2419 && gram >= 0.0
2420 && penalty >= 0.0
2421 && (gram > 0.0 || penalty > 0.0))
2422 {
2423 return Err(AffineRemlError::InvalidMode {
2424 index,
2425 gram,
2426 penalty,
2427 });
2428 }
2429 }
2430 for (index, &value) in projected_rhs_squared.iter().enumerate() {
2431 if !(value.is_finite() && value >= 0.0) {
2432 return Err(AffineRemlError::InvalidProjectedSquare { index, value });
2433 }
2434 }
2435 for (output, &value) in response_energy.iter().enumerate() {
2436 if !(value.is_finite() && value >= 0.0) {
2437 return Err(AffineRemlError::InvalidResponseEnergy { output, value });
2438 }
2439 }
2440 if !(residual_dof.is_finite() && residual_dof > 0.0) {
2441 return Err(AffineRemlError::InvalidResidualDof {
2442 value: residual_dof,
2443 });
2444 }
2445 if !logdet_constant.is_finite() {
2446 return Err(AffineRemlError::InvalidLogdetConstant {
2447 value: logdet_constant,
2448 });
2449 }
2450 let inferred_rank = penalty_modes.iter().filter(|&&value| value > 0.0).count();
2451 if determinant_rank != inferred_rank {
2452 return Err(AffineRemlError::RankMismatch {
2453 supplied: determinant_rank,
2454 inferred: inferred_rank,
2455 });
2456 }
2457 let mut zero_lambda_residual = Vec::with_capacity(responses);
2458 for (output, &energy) in response_energy.iter().enumerate() {
2459 let start = output * modes;
2460 let end = start + modes;
2461 zero_lambda_residual.push(
2462 certified_zero_lambda_residual(
2463 energy,
2464 gram_modes,
2465 &projected_rhs_squared[start..end],
2466 )
2467 .ok_or(AffineRemlError::ZeroLambdaResidualUnavailable { output })?,
2468 );
2469 }
2470 Ok(Self {
2471 gram_modes,
2472 penalty_modes,
2473 projected_rhs_squared,
2474 response_energy,
2475 zero_lambda_residual,
2476 residual_dof,
2477 logdet_constant,
2478 })
2479 }
2480
2481 #[inline]
2482 pub fn num_modes(&self) -> usize {
2483 self.gram_modes.len()
2484 }
2485
2486 #[inline]
2487 pub fn num_responses(&self) -> usize {
2488 self.response_energy.len()
2489 }
2490
2491 pub fn evaluate(&self, log_lambda: f64) -> Result<ScoreJet, AffineRemlError> {
2494 if !log_lambda.is_finite() {
2495 return Err(AffineRemlError::InvalidLogLambda { value: log_lambda });
2496 }
2497 let lambda = certified_exp_representative(log_lambda)
2498 .ok_or(AffineRemlError::InvalidLogLambda { value: log_lambda })?;
2499 if !(lambda.is_finite() && lambda > 0.0) {
2500 return Err(AffineRemlError::InvalidLogLambda { value: log_lambda });
2501 }
2502
2503 let mut normalized_logdet = self.logdet_constant;
2504 let mut determinant_derivative = 0.0;
2505 let mut determinant_curvature = 0.0;
2506 let exp_neg_log_lambda = if log_lambda >= 0.0 {
2507 certified_exp_representative(-log_lambda)
2508 } else {
2509 None
2510 };
2511 for (index, (&gram, &penalty)) in self.gram_modes.iter().zip(self.penalty_modes).enumerate()
2512 {
2513 if gram == 0.0 {
2521 normalized_logdet +=
2522 certified_ln_value(penalty).ok_or(AffineRemlError::NonPositiveMode {
2523 index,
2524 log_lambda,
2525 value: penalty,
2526 })?;
2527 continue;
2528 }
2529 let h = lambda.mul_add(penalty, gram);
2530 if !(h.is_finite() && h > 0.0) {
2531 return Err(AffineRemlError::NonPositiveMode {
2532 index,
2533 log_lambda,
2534 value: h,
2535 });
2536 }
2537 let u = lambda * penalty / h;
2538 let determinant_complement = if penalty == 0.0 { 0.0 } else { gram / h };
2549 let normalized_mode = if penalty == 0.0 {
2561 certified_ln_value(gram)
2562 } else if log_lambda >= 0.0 {
2563 exp_neg_log_lambda
2564 .and_then(|exp_neg_rho| certified_ln_value(penalty + gram * exp_neg_rho))
2565 } else if gram >= penalty * lambda {
2566 certified_ln_value(gram)
2567 .zip(certified_ln_1p_value(penalty * lambda / gram))
2568 .map(|(log_gram, correction)| log_gram - log_lambda + correction)
2569 } else {
2570 certified_ln_value(penalty)
2571 .zip(certified_ln_1p_value(gram / (penalty * lambda)))
2572 .map(|(log_penalty, correction)| log_penalty + correction)
2573 }
2574 .ok_or(AffineRemlError::NonPositiveMode {
2575 index,
2576 log_lambda,
2577 value: h,
2578 })?;
2579 normalized_logdet += normalized_mode;
2580 determinant_derivative -= determinant_complement;
2581 determinant_curvature += u * determinant_complement;
2582 }
2583
2584 let modes = self.num_modes();
2585 let mut residual_log_sum = 0.0;
2586 let mut residual_derivative_sum = 0.0;
2587 let mut residual_curvature_sum = 0.0;
2588 for (output, &energy) in self.response_energy.iter().enumerate() {
2589 let mut residual = energy;
2590 let mut first = 0.0;
2591 let mut second = 0.0;
2592 for i in 0..modes {
2593 let projected_square = self.projected_rhs_squared[output * modes + i];
2594 if projected_square == 0.0 {
2595 continue;
2596 }
2597 if self.gram_modes[i] == 0.0 {
2598 let fitted = positive_ratio_over_product(
2599 projected_square,
2600 self.penalty_modes[i],
2601 lambda,
2602 )
2603 .ok_or(
2604 AffineRemlError::ElementaryEnclosureUnavailable {
2605 function: "gram-zero residual quotient",
2606 lo: log_lambda,
2607 hi: log_lambda,
2608 },
2609 )?;
2610 residual -= fitted;
2611 first += fitted;
2612 second -= fitted;
2613 continue;
2614 }
2615 let h = lambda.mul_add(self.penalty_modes[i], self.gram_modes[i]);
2616 let u = lambda * self.penalty_modes[i] / h;
2617 residual -= projected_square / h;
2618 first += projected_square * u / h;
2619 second += projected_square * u * (1.0 - 2.0 * u) / h;
2620 }
2621 if !(residual.is_finite() && residual > 0.0) {
2622 return Err(AffineRemlError::NonPositiveResidual {
2623 output,
2624 log_lambda,
2625 value: residual,
2626 });
2627 }
2628 let log_derivative = first / residual;
2629 residual_log_sum += certified_ln_value(residual / self.residual_dof).ok_or(
2630 AffineRemlError::NonPositiveResidual {
2631 output,
2632 log_lambda,
2633 value: residual,
2634 },
2635 )?;
2636 residual_derivative_sum += log_derivative;
2637 residual_curvature_sum += second / residual - log_derivative * log_derivative;
2638 }
2639
2640 let outputs = self.num_responses() as f64;
2641 Ok(ScoreJet {
2642 value: -0.5 * (outputs * normalized_logdet + self.residual_dof * residual_log_sum),
2643 derivative: -0.5
2644 * (outputs * determinant_derivative + self.residual_dof * residual_derivative_sum),
2645 curvature: -0.5
2646 * (outputs * determinant_curvature + self.residual_dof * residual_curvature_sum),
2647 third: 0.0,
2656 })
2657 }
2658
2659 pub fn enclose(&self, lo: f64, hi: f64) -> Result<DerivativeEnclosure, AffineRemlError> {
2741 let (direct, direct_third) = self.enclose_direct(lo, hi)?;
2742 if lo == hi {
2743 return Ok(direct);
2744 }
2745 let centre_point = (0.5 * (lo + hi)).clamp(lo, hi);
2751 let (centre, _) = self.enclose_direct(centre_point, centre_point)?;
2752 let offset = ClosedInterval::new(
2757 next_down(lo - centre_point).min(0.0),
2758 next_up(hi - centre_point).max(0.0),
2759 );
2760 let curvature = centred_or(direct.curvature, centre.curvature, direct_third, offset);
2770 let derivative = centred_or(direct.derivative, centre.derivative, curvature, offset);
2771 let value = centred_or(direct.score.value, centre.score.value, derivative, offset);
2772 Ok(DerivativeEnclosure {
2773 score: ScoreValueEnclosure {
2774 value,
2775 evaluation_error: direct.score.evaluation_error,
2776 },
2777 derivative,
2778 curvature,
2779 })
2780 }
2781
2782 fn enclose_direct(
2795 &self,
2796 lo: f64,
2797 hi: f64,
2798 ) -> Result<(DerivativeEnclosure, ClosedInterval), AffineRemlError> {
2799 if !(lo.is_finite() && hi.is_finite() && lo <= hi) {
2800 return Err(AffineRemlError::InvalidLogLambdaInterval { lo, hi });
2801 }
2802 let lambda = exp_interval(lo, hi)?;
2803 if !(lambda.lo.is_finite() && lambda.lo > 0.0 && lambda.hi.is_finite()) {
2804 return Err(AffineRemlError::InvalidLogLambdaInterval { lo, hi });
2805 }
2806 let lambda_relative_error =
2812 certified_exp_relative_forward_error(ClosedInterval::new(lo, hi), lambda);
2813 if !lambda_relative_error.is_finite() {
2814 return Err(AffineRemlError::UnboundedScoreEvaluationError {
2815 lo,
2816 hi,
2817 error: lambda_relative_error,
2818 });
2819 }
2820
2821 let mut normalized_logdet = ClosedInterval::point(self.logdet_constant);
2822 let mut normalized_logdet_magnitude = self.logdet_constant.abs();
2823 let mut normalized_logdet_error = 0.0;
2824 let mut determinant_first = ClosedInterval::point(0.0);
2825 let mut determinant_second = ClosedInterval::point(0.0);
2826 let mut determinant_third = ClosedInterval::point(0.0);
2827 for i in 0..self.num_modes() {
2828 let (normalized_mode, normalized_mode_error) =
2829 normalized_log_mode_enclosure(self.gram_modes[i], self.penalty_modes[i], lo, hi)?;
2830 normalized_logdet = normalized_logdet.add(normalized_mode);
2831 normalized_logdet_magnitude = add_nonnegative_upward(
2832 normalized_logdet_magnitude,
2833 add_nonnegative_upward(normalized_mode.max_abs(), normalized_mode_error),
2834 );
2835 normalized_logdet_error =
2836 add_nonnegative_upward(normalized_logdet_error, normalized_mode_error);
2837
2838 let ranges = mode_ranges(self.gram_modes[i], self.penalty_modes[i], 0.0, lambda)?;
2839 determinant_first = determinant_first.sub(ranges.c);
2840 determinant_second = determinant_second.add(ranges.w);
2841 determinant_third = determinant_third.add(ranges.determinant_third);
2842 }
2843
2844 let mut residual_first_sum = ClosedInterval::point(0.0);
2845 let mut residual_second_sum = ClosedInterval::point(0.0);
2846 let mut residual_third_sum = ClosedInterval::point(0.0);
2847 let mut residual_log_sum = ClosedInterval::point(0.0);
2848 let mut residual_log_magnitude = 0.0;
2849 let mut residual_log_error = 0.0;
2850 let modes = self.num_modes();
2851 for (output, &energy) in self.response_energy.iter().enumerate() {
2852 let mut fitted_quadratic = ClosedInterval::point(0.0);
2853 let mut smoothing_increment = ClosedInterval::point(0.0);
2854 let mut singular_fitted = ClosedInterval::point(0.0);
2855 let mut first = ClosedInterval::point(0.0);
2856 let mut second = ClosedInterval::point(0.0);
2857 let mut third = ClosedInterval::point(0.0);
2858 let mut fitted_magnitude = energy;
2859 for i in 0..modes {
2860 let ranges = mode_ranges(
2861 self.gram_modes[i],
2862 self.penalty_modes[i],
2863 self.projected_rhs_squared[output * modes + i],
2864 lambda,
2865 )?;
2866 fitted_quadratic = fitted_quadratic.add(ranges.v);
2867 smoothing_increment = smoothing_increment.add(ranges.smoothing_increment);
2868 singular_fitted = singular_fitted.add(ranges.singular_fitted);
2869 first = first.add(ranges.p);
2870 second = second.add(ranges.q);
2871 third = third.add(ranges.residual_third);
2872 fitted_magnitude = add_nonnegative_upward(fitted_magnitude, ranges.v.max_abs());
2873 }
2874 let direct_residual = ClosedInterval::point(energy).sub(fitted_quadratic);
2892 let complement_residual = self.zero_lambda_residual[output]
2893 .add(smoothing_increment)
2894 .sub(singular_fitted);
2895 let residual = direct_residual.intersection(complement_residual).ok_or(
2896 AffineRemlError::InconsistentResidualEnclosures {
2897 output,
2898 lo,
2899 hi,
2900 direct: direct_residual,
2901 complement: complement_residual,
2902 },
2903 )?;
2904 if !(residual.lo > 0.0 && residual.is_valid()) {
2905 return Err(AffineRemlError::NonPositiveResidualInterval {
2906 output,
2907 lo,
2908 hi,
2909 lower_bound: residual.lo,
2910 });
2911 }
2912 let first_ratio = first.div_positive(residual).nonnegative();
2913 let second_ratio = second.div_positive(residual);
2914 let third_ratio = third.div_positive(residual);
2915 residual_first_sum = residual_first_sum.add(first_ratio);
2916 residual_second_sum = residual_second_sum.add(second_ratio.sub(first_ratio.square()));
2917 residual_third_sum = residual_third_sum.add(
2920 third_ratio
2921 .sub(second_ratio.mul(first_ratio).scale(3.0))
2922 .add(first_ratio.square().mul(first_ratio).scale(2.0)),
2923 );
2924
2925 let fitted_arithmetic_error = wilkinson_roundoff(
2926 fitted_magnitude,
2927 modes.saturating_mul(RESIDUAL_VALUE_OPS_PER_MODE),
2928 );
2929 let fitted_exp_error = next_up(first.max_abs() * lambda_relative_error);
2932 let resolved_fitted_quadratic = fitted_quadratic.widen(add_nonnegative_upward(
2933 fitted_arithmetic_error,
2934 fitted_exp_error,
2935 ));
2936 let resolved_residual = ClosedInterval::point(energy).sub(resolved_fitted_quadratic);
2937 if !(resolved_residual.lo > 0.0 && resolved_residual.is_valid()) {
2938 return Err(AffineRemlError::NonPositiveResidualInterval {
2939 output,
2940 lo,
2941 hi,
2942 lower_bound: resolved_residual.lo,
2943 });
2944 }
2945 let residual_over_dof = residual.div_positive(ClosedInterval::point(self.residual_dof));
2946 if !(residual_over_dof.lo > 0.0 && residual_over_dof.hi.is_finite()) {
2947 return Err(AffineRemlError::ElementaryEnclosureUnavailable {
2948 function: "ln",
2949 lo: residual_over_dof.lo,
2950 hi: residual_over_dof.hi,
2951 });
2952 }
2953 let residual_log = residual_over_dof.ln_positive();
2954 residual_log_sum = residual_log_sum.add(residual_log);
2955
2956 let residual_error = enclosure_excess(residual, resolved_residual);
2965 let propagated_residual_error = next_up(residual_error / resolved_residual.lo);
2966 let elementary_error = certified_log_forward_error(
2967 residual.div_positive(ClosedInterval::point(self.residual_dof)),
2968 );
2969 let local_log_error = add_nonnegative_upward(
2970 propagated_residual_error,
2971 add_nonnegative_upward(
2972 elementary_error,
2973 wilkinson_roundoff(
2974 add_nonnegative_upward(1.0, residual_log.max_abs()),
2975 RESIDUAL_LOG_OPS_PER_RESPONSE,
2976 ),
2977 ),
2978 );
2979 residual_log_error = add_nonnegative_upward(residual_log_error, local_log_error);
2980 residual_log_magnitude = add_nonnegative_upward(
2981 residual_log_magnitude,
2982 add_nonnegative_upward(residual_log.max_abs(), local_log_error),
2983 );
2984 }
2985
2986 let outputs = self.num_responses() as f64;
2987 let first_bracket = determinant_first
2988 .scale(outputs)
2989 .add(residual_first_sum.scale(self.residual_dof));
2990 let second_bracket = determinant_second
2991 .scale(outputs)
2992 .add(residual_second_sum.scale(self.residual_dof));
2993 let third_bracket = determinant_third
2994 .scale(outputs)
2995 .add(residual_third_sum.scale(self.residual_dof));
2996 let derivative = first_bracket.scale(-0.5);
2997 let curvature = second_bracket.scale(-0.5);
2998 let third = third_bracket.scale(-0.5);
2999 let score_value = normalized_logdet
3000 .scale(outputs)
3001 .add(residual_log_sum.scale(self.residual_dof))
3002 .scale(-0.5);
3003 let score_magnitude = add_nonnegative_upward(
3004 next_up(outputs * normalized_logdet_magnitude),
3005 next_up(self.residual_dof * residual_log_magnitude),
3006 );
3007 normalized_logdet_error = add_nonnegative_upward(
3008 normalized_logdet_error,
3009 wilkinson_roundoff(normalized_logdet_magnitude, self.num_modes()),
3010 );
3011 residual_log_error = add_nonnegative_upward(
3012 residual_log_error,
3013 wilkinson_roundoff(residual_log_magnitude, self.num_responses()),
3014 );
3015 let final_arithmetic_error = wilkinson_roundoff(score_magnitude, SCORE_COMBINE_OPS);
3016 let weighted_component_error = add_nonnegative_upward(
3017 next_up(outputs * normalized_logdet_error),
3018 next_up(self.residual_dof * residual_log_error),
3019 );
3020 let value_evaluation_error =
3021 next_up(0.5 * add_nonnegative_upward(weighted_component_error, final_arithmetic_error));
3022 if !(score_value.is_valid() && value_evaluation_error.is_finite()) {
3023 return Err(AffineRemlError::UnboundedScoreEvaluationError {
3024 lo,
3025 hi,
3026 error: value_evaluation_error,
3027 });
3028 }
3029 let score = ScoreValueEnclosure {
3030 value: score_value,
3031 evaluation_error: value_evaluation_error,
3032 };
3033 Ok((
3034 DerivativeEnclosure {
3035 score,
3036 derivative,
3037 curvature,
3038 },
3039 third,
3040 ))
3041 }
3042
3043 pub fn maximize_value_ordered(
3062 &self,
3063 lo: f64,
3064 hi: f64,
3065 initial_resolution: f64,
3066 ) -> Result<ScoreSearchResult, ScoreSearchError<AffineRemlError>> {
3067 maximize_score_1d_value_ordered(
3068 lo,
3069 hi,
3070 initial_resolution,
3071 |x| self.evaluate(x),
3072 |a, b| self.enclose(a.x, b.x),
3073 )
3074 }
3075}
3076
3077#[derive(Clone, Copy)]
3078struct ModeRanges {
3079 c: ClosedInterval,
3083 w: ClosedInterval,
3085 v: ClosedInterval,
3087 smoothing_increment: ClosedInterval,
3091 singular_fitted: ClosedInterval,
3094 p: ClosedInterval,
3097 q: ClosedInterval,
3100 determinant_third: ClosedInterval,
3107 residual_third: ClosedInterval,
3109}
3110
3111fn normalized_log_mode_enclosure(
3123 gram: f64,
3124 penalty: f64,
3125 lo: f64,
3126 hi: f64,
3127) -> Result<(ClosedInterval, f64), AffineRemlError> {
3128 if penalty == 0.0 {
3129 let range = ClosedInterval::point(gram).ln_positive();
3130 return Ok((
3131 range,
3132 certified_log_forward_error(ClosedInterval::point(gram)),
3133 ));
3134 }
3135 if gram == 0.0 {
3136 let range = ClosedInterval::point(penalty).ln_positive();
3137 return Ok((
3138 range,
3139 certified_log_forward_error(ClosedInterval::point(penalty)),
3140 ));
3141 }
3142
3143 let at_lo = normalized_log_mode_at(gram, penalty, lo)?;
3144 let at_hi = normalized_log_mode_at(gram, penalty, hi)?;
3145 let range = ClosedInterval::new(at_hi.lo, at_lo.hi);
3147 let negative_rho_abs = if lo < 0.0 { -lo } else { 0.0 };
3152 let arithmetic_scale = add_nonnegative_upward(
3153 add_nonnegative_upward(1.0, range.max_abs()),
3154 next_up(2.0 * negative_rho_abs),
3155 );
3156 let arithmetic_error = wilkinson_roundoff(arithmetic_scale, DETERMINANT_VALUE_OPS_PER_MODE);
3157 let mut exp_input_error = 0.0_f64;
3158 if hi >= 0.0 {
3159 let positive_lo = lo.max(0.0);
3160 let exp_neg_rho = exp_interval(-hi, -positive_lo)?;
3161 let argument_lo = ClosedInterval::point(penalty)
3162 .add(ClosedInterval::point(gram).mul(exp_neg_rho))
3163 .lo;
3164 if argument_lo > 0.0 {
3165 exp_input_error = exp_input_error.max(next_up(
3166 gram * certified_exp_forward_error(
3167 ClosedInterval::new(-hi, -positive_lo),
3168 exp_neg_rho,
3169 ) / argument_lo,
3170 ));
3171 } else {
3172 exp_input_error = f64::INFINITY;
3173 }
3174 }
3175 if lo < 0.0 {
3176 let negative_hi = hi.min(0.0);
3177 let exp_rho = exp_interval(lo, negative_hi)?;
3178 if exp_rho.lo > 0.0 {
3179 exp_input_error = exp_input_error.max(certified_exp_relative_forward_error(
3184 ClosedInterval::new(lo, negative_hi),
3185 exp_rho,
3186 ));
3187 } else {
3188 exp_input_error = f64::INFINITY;
3189 }
3190 }
3191 let log_output_error =
3192 certified_log_error_from_output(at_lo).max(certified_log_error_from_output(at_hi));
3193 let log_gram_error = certified_log_forward_error(ClosedInterval::point(gram));
3194 let log_penalty_error = certified_log_forward_error(ClosedInterval::point(penalty));
3195 let log1p_error = certified_ln1p_forward_error();
3196 let elementary_error = add_nonnegative_upward(
3197 exp_input_error,
3198 add_nonnegative_upward(
3199 log_output_error,
3200 add_nonnegative_upward(
3201 log_gram_error,
3202 add_nonnegative_upward(log_penalty_error, log1p_error),
3203 ),
3204 ),
3205 );
3206 Ok((
3207 range,
3208 add_nonnegative_upward(arithmetic_error, elementary_error),
3209 ))
3210}
3211
3212fn normalized_log_mode_at(
3213 gram: f64,
3214 penalty: f64,
3215 rho: f64,
3216) -> Result<ClosedInterval, AffineRemlError> {
3217 if rho >= 0.0 {
3218 let exp_neg_rho = exp_interval(-rho, -rho)?;
3219 let argument =
3220 ClosedInterval::point(penalty).add(ClosedInterval::point(gram).mul(exp_neg_rho));
3221 if !(argument.lo > 0.0 && argument.hi.is_finite()) {
3222 return Err(AffineRemlError::ElementaryEnclosureUnavailable {
3223 function: "ln",
3224 lo: argument.lo,
3225 hi: argument.hi,
3226 });
3227 }
3228 Ok(argument.ln_positive())
3229 } else {
3230 let exp_rho = exp_interval(rho, rho)?;
3231 let argument = ClosedInterval::point(gram).add(ClosedInterval::point(penalty).mul(exp_rho));
3232 if !(argument.lo > 0.0 && argument.hi.is_finite()) {
3233 return Err(AffineRemlError::ElementaryEnclosureUnavailable {
3234 function: "ln",
3235 lo: argument.lo,
3236 hi: argument.hi,
3237 });
3238 }
3239 Ok(argument.ln_positive().sub(ClosedInterval::point(rho)))
3240 }
3241}
3242
3243fn exp_interval(lo: f64, hi: f64) -> Result<ClosedInterval, AffineRemlError> {
3244 let unavailable = || AffineRemlError::ElementaryEnclosureUnavailable {
3245 function: "exp",
3246 lo,
3247 hi,
3248 };
3249 if !(lo.is_finite() && hi.is_finite() && lo <= hi) {
3250 return Err(unavailable());
3251 }
3252 let lower = certified_exp(lo).ok_or_else(unavailable)?;
3253 let upper = certified_exp(hi).ok_or_else(unavailable)?;
3254 let enclosure = ClosedInterval::new(lower.lo.max(0.0), upper.hi).nonnegative();
3255 if !enclosure.is_valid() {
3256 return Err(unavailable());
3257 }
3258 Ok(enclosure)
3259}
3260
3261fn finite_nonnegative_quotient(
3269 numerator: ClosedInterval,
3270 denominator: ClosedInterval,
3271 function: &'static str,
3272) -> Result<ClosedInterval, AffineRemlError> {
3273 if !(numerator.is_valid()
3274 && numerator.lo >= 0.0
3275 && denominator.is_valid()
3276 && denominator.lo > 0.0)
3277 {
3278 return Err(AffineRemlError::ElementaryEnclosureUnavailable {
3279 function,
3280 lo: denominator.lo,
3281 hi: denominator.hi,
3282 });
3283 }
3284 let quotient = ClosedInterval::new(
3285 quotient_down(numerator.lo, denominator.hi).max(0.0),
3286 quotient_up(numerator.hi, denominator.lo),
3287 );
3288 if !(quotient.is_valid() && quotient.hi.is_finite()) {
3289 return Err(AffineRemlError::ElementaryEnclosureUnavailable {
3290 function,
3291 lo: quotient.lo,
3292 hi: quotient.hi,
3293 });
3294 }
3295 Ok(quotient.nonnegative())
3296}
3297
3298fn centred_or(
3330 direct: ClosedInterval,
3331 point: ClosedInterval,
3332 slope: ClosedInterval,
3333 offset: ClosedInterval,
3334) -> ClosedInterval {
3335 if !(slope.is_valid() && slope.lo.is_finite() && slope.hi.is_finite()) {
3336 return direct;
3337 }
3338 let remainder = slope.mul(offset);
3339 if !(remainder.is_valid() && remainder.lo.is_finite() && remainder.hi.is_finite()) {
3340 return direct;
3341 }
3342 let centred = point.add(remainder);
3343 if !centred.is_valid() {
3344 return direct;
3345 }
3346 direct.intersection(centred).unwrap_or(direct)
3351}
3352
3353fn mode_ranges(
3354 gram: f64,
3355 penalty: f64,
3356 projected_square: f64,
3357 lambda: ClosedInterval,
3358) -> Result<ModeRanges, AffineRemlError> {
3359 if penalty == 0.0 {
3360 let v = ClosedInterval::point(projected_square)
3361 .div_positive(ClosedInterval::point(gram))
3362 .nonnegative();
3363 return Ok(ModeRanges {
3364 c: ClosedInterval::point(0.0),
3365 w: ClosedInterval::point(0.0),
3366 v,
3367 smoothing_increment: ClosedInterval::point(0.0),
3368 singular_fitted: ClosedInterval::point(0.0),
3369 p: ClosedInterval::point(0.0),
3370 q: ClosedInterval::point(0.0),
3371 determinant_third: ClosedInterval::point(0.0),
3372 residual_third: ClosedInterval::point(0.0),
3373 });
3374 }
3375 if gram == 0.0 {
3376 let zero = ClosedInterval::point(0.0);
3377 if projected_square == 0.0 {
3378 return Ok(ModeRanges {
3379 c: zero,
3380 w: zero,
3381 v: zero,
3382 smoothing_increment: zero,
3383 singular_fitted: zero,
3384 p: zero,
3385 q: zero,
3386 determinant_third: zero,
3387 residual_third: zero,
3388 });
3389 }
3390
3391 let h = lambda.mul(ClosedInterval::point(penalty)).nonnegative();
3399 let projected = ClosedInterval::point(projected_square);
3400 let v = if h.lo > 0.0 {
3401 finite_nonnegative_quotient(projected, h, "gram-zero residual quotient")?
3402 } else {
3403 let scaled = finite_nonnegative_quotient(
3404 projected,
3405 ClosedInterval::point(penalty),
3406 "gram-zero residual quotient",
3407 )?;
3408 finite_nonnegative_quotient(scaled, lambda, "gram-zero residual quotient")?
3409 };
3410 return Ok(ModeRanges {
3411 c: ClosedInterval::point(0.0),
3412 w: ClosedInterval::point(0.0),
3413 v,
3414 smoothing_increment: zero,
3415 singular_fitted: v,
3416 p: v,
3417 q: v.neg(),
3418 determinant_third: zero,
3422 residual_third: v,
3423 });
3424 }
3425
3426 let t = lambda
3431 .mul(ClosedInterval::point(penalty))
3432 .div_positive(ClosedInterval::point(gram))
3433 .nonnegative();
3434 let scale = ClosedInterval::point(projected_square)
3435 .div_positive(ClosedInterval::point(gram))
3436 .nonnegative();
3437 let kernels = kernel_ranges(t);
3438 Ok(ModeRanges {
3439 c: kernels.v,
3440 w: kernels.w,
3441 v: scale.mul(kernels.v).nonnegative(),
3442 smoothing_increment: scale.mul(kernels.u).nonnegative(),
3443 singular_fitted: ClosedInterval::point(0.0),
3444 p: scale.mul(kernels.w).nonnegative(),
3445 q: scale.mul(kernels.k),
3446 determinant_third: kernels.k,
3447 residual_third: scale.mul(kernels.third),
3448 })
3449}
3450
3451#[derive(Clone, Copy)]
3452struct KernelRanges {
3453 v: ClosedInterval,
3455 u: ClosedInterval,
3457 w: ClosedInterval,
3459 k: ClosedInterval,
3461 third: ClosedInterval,
3475}
3476
3477fn kernel_at(t: ClosedInterval) -> KernelRanges {
3478 let one = ClosedInterval::point(1.0);
3479 let denom = one.add(t);
3480 let v = one.div_positive(denom).nonnegative();
3481 let u = t.mul(v).nonnegative();
3482 let w = u.mul(v).nonnegative();
3483 let k = w.mul(one.sub(t)).div_positive(denom);
3484 let third = w
3488 .mul(one.sub(t.scale(4.0)).add(t.square()))
3489 .div_positive(denom.square());
3490 KernelRanges { v, u, w, k, third }
3491}
3492
3493fn kernel_ranges(t: ClosedInterval) -> KernelRanges {
3494 let left = kernel_at(ClosedInterval::point(t.lo));
3495 let right = kernel_at(ClosedInterval::point(t.hi));
3496 let mut v = ClosedInterval::new(right.v.lo, left.v.hi).nonnegative();
3497 let u = ClosedInterval::new(left.u.lo, right.u.hi).nonnegative();
3498 let mut w = left.w.hull(right.w).nonnegative();
3499 let mut k = left.k.hull(right.k);
3500 let mut third = left.third.hull(right.third);
3501
3502 if t.contains(1.0) {
3503 let critical = kernel_at(ClosedInterval::point(1.0));
3504 w = w.hull(critical.w).nonnegative();
3505 third = third.hull(critical.third);
3509 }
3510
3511 let sqrt_three =
3515 certified_sqrt_positive(3.0).expect("three is a finite positive square-root argument");
3516 let critical_points = [
3517 ClosedInterval::point(2.0).sub(sqrt_three),
3518 ClosedInterval::point(2.0).add(sqrt_three),
3519 ];
3520 for critical in critical_points {
3521 if critical.hi >= t.lo && critical.lo <= t.hi {
3522 k = k.hull(kernel_at(critical).k);
3523 }
3524 }
3525
3526 let sqrt_six =
3529 certified_sqrt_positive(6.0).expect("six is a finite positive square-root argument");
3530 let two_sqrt_six = sqrt_six.scale(2.0);
3531 for critical in [
3532 ClosedInterval::point(5.0).sub(two_sqrt_six),
3533 ClosedInterval::point(5.0).add(two_sqrt_six),
3534 ] {
3535 if critical.hi >= t.lo && critical.lo <= t.hi {
3536 third = third.hull(kernel_at(critical).third);
3537 }
3538 }
3539
3540 v.lo = v.lo.max(0.0);
3543 v.hi = v.hi.min(next_up(1.0));
3544 KernelRanges {
3545 v,
3546 u,
3547 w,
3548 k,
3549 third,
3550 }
3551}
3552
3553const LOG_SERIES_TERMS: usize = 18;
3554const EXP_SERIES_TERMS: usize = 18;
3555const EXP_RANGE_SQUARINGS: usize = 6;
3556
3557fn certified_sqrt_positive(value: f64) -> Option<ClosedInterval> {
3558 if !(value.is_finite() && value > 0.0) {
3559 return None;
3560 }
3561 let guess = value.sqrt();
3565 if !(guess.is_finite() && guess > 0.0) {
3566 return None;
3567 }
3568 let mut lo = next_down(guess);
3569 for _ in 0..8 {
3570 if ClosedInterval::point(lo).square().hi <= value {
3571 break;
3572 }
3573 lo = next_down(lo);
3574 }
3575 let mut hi = next_up(guess);
3576 for _ in 0..8 {
3577 if ClosedInterval::point(hi).square().lo >= value {
3578 break;
3579 }
3580 hi = next_up(hi);
3581 }
3582 (ClosedInterval::point(lo).square().hi <= value
3583 && ClosedInterval::point(hi).square().lo >= value)
3584 .then(|| ClosedInterval::new(lo, hi))
3585}
3586
3587fn certified_log_from_atanh(z: ClosedInterval) -> ClosedInterval {
3590 let z_abs = z.max_abs();
3591 assert!(z_abs <= 1.0 / 3.0 + f64::EPSILON);
3592 let z2 = z.square();
3593 let mut power = z;
3594 let mut sum = z;
3595 for term in 1..LOG_SERIES_TERMS {
3596 power = power.mul(z2);
3597 sum = sum.add(power.div_positive(ClosedInterval::point((2 * term + 1) as f64)));
3598 }
3599 let next_power = power.mul(z2).max_abs();
3600 let first_denominator = (2 * LOG_SERIES_TERMS + 1) as f64;
3601 let geometric_denominator = next_down(1.0 - next_up(z_abs * z_abs));
3602 let tail = if geometric_denominator > 0.0 {
3603 next_up(next_up(2.0 * next_power) / next_down(first_denominator * geometric_denominator))
3604 } else {
3605 f64::INFINITY
3606 };
3607 sum.scale(2.0).widen(tail)
3608}
3609
3610fn certified_ln_two() -> ClosedInterval {
3611 static LN_TWO: OnceLock<ClosedInterval> = OnceLock::new();
3612 *LN_TWO.get_or_init(|| {
3613 let third = ClosedInterval::point(1.0).div_positive(ClosedInterval::point(3.0));
3617 certified_log_from_atanh(third)
3618 })
3619}
3620
3621fn positive_binary64_parts(value: f64) -> Option<(f64, i32)> {
3624 if !(value.is_finite() && value > 0.0) {
3625 return None;
3626 }
3627 let bits = value.to_bits();
3628 let exponent_bits = ((bits >> 52) & 0x7ff) as i32;
3629 let fraction = bits & ((1_u64 << 52) - 1);
3630 if exponent_bits == 0 {
3631 let highest = 63_i32 - fraction.leading_zeros() as i32;
3634 let normalized = fraction << (52 - highest);
3635 let mantissa_bits = (1023_u64 << 52) | (normalized - (1_u64 << 52));
3636 Some((f64::from_bits(mantissa_bits), highest - 1074))
3637 } else {
3638 let mantissa_bits = (1023_u64 << 52) | fraction;
3639 Some((f64::from_bits(mantissa_bits), exponent_bits - 1023))
3640 }
3641}
3642
3643pub fn certified_ln_positive(value: f64) -> Option<ClosedInterval> {
3651 if !(value.is_finite() && value > 0.0) {
3652 return None;
3653 }
3654 if value == 1.0 {
3655 return Some(ClosedInterval::point(0.0));
3656 }
3657 let (mantissa, exponent) = positive_binary64_parts(value)?;
3658 let m = ClosedInterval::point(mantissa);
3659 let z = m
3660 .sub(ClosedInterval::point(1.0))
3661 .div_positive(m.add(ClosedInterval::point(1.0)));
3662 Some(certified_log_from_atanh(z).add(certified_ln_two().scale(exponent as f64)))
3663}
3664
3665pub fn certified_ln_1p(value: f64) -> Option<ClosedInterval> {
3674 if !(value.is_finite() && value > -1.0) {
3675 return None;
3676 }
3677 if value == 0.0 {
3678 return Some(ClosedInterval::point(0.0));
3679 }
3680 if (0.0..=1.0).contains(&value) {
3681 let x = ClosedInterval::point(value);
3682 let z = x.div_positive(ClosedInterval::point(2.0).add(x));
3683 return Some(certified_log_from_atanh(z));
3684 }
3685 if value > 1.0 {
3686 let reciprocal = ClosedInterval::point(1.0)
3687 .div_positive(ClosedInterval::point(value))
3688 .nonnegative();
3689 let z = reciprocal
3690 .div_positive(ClosedInterval::point(2.0).add(reciprocal))
3691 .nonnegative();
3692 return Some(certified_ln_positive(value)?.add(certified_log_from_atanh(z)));
3693 }
3694 let argument = ClosedInterval::point(1.0).add(ClosedInterval::point(value));
3695 if !(argument.lo > 0.0) {
3696 return None;
3697 }
3698 let lo = certified_ln_positive(argument.lo)?;
3699 let hi = certified_ln_positive(argument.hi)?;
3700 Some(ClosedInterval::new(lo.lo, hi.hi))
3701}
3702
3703fn exact_power_of_two(exponent: i32) -> Option<f64> {
3704 match exponent {
3705 -1074..=-1023 => {
3706 let bit = (exponent + 1074) as u32;
3707 Some(f64::from_bits(1_u64 << bit))
3708 }
3709 -1022..=1023 => Some(f64::from_bits(((exponent + 1023) as u64) << 52)),
3710 _ => None,
3711 }
3712}
3713
3714fn positive_ratio_over_product(
3720 numerator: f64,
3721 first_denominator: f64,
3722 second_denominator: f64,
3723) -> Option<f64> {
3724 if numerator == 0.0 {
3725 return Some(0.0);
3726 }
3727 let (numerator_mantissa, numerator_exponent) = positive_binary64_parts(numerator)?;
3728 let (first_mantissa, first_exponent) = positive_binary64_parts(first_denominator)?;
3729 let (second_mantissa, second_exponent) = positive_binary64_parts(second_denominator)?;
3730 let mut mantissa = numerator_mantissa / first_mantissa / second_mantissa;
3731 let mut exponent = numerator_exponent - first_exponent - second_exponent;
3732 if !(mantissa.is_finite() && mantissa > 0.0) {
3733 return None;
3734 }
3735 while mantissa < 1.0 {
3736 mantissa *= 2.0;
3737 exponent -= 1;
3738 }
3739 while mantissa >= 2.0 {
3740 mantissa *= 0.5;
3741 exponent += 1;
3742 }
3743 if exponent < -1075 {
3744 return Some(0.0);
3745 }
3746 if exponent > 1023 {
3747 return None;
3748 }
3749 let value = if exponent == -1075 {
3750 (0.5 * mantissa) * exact_power_of_two(-1074)?
3751 } else {
3752 mantissa * exact_power_of_two(exponent)?
3753 };
3754 (value.is_finite() && value >= 0.0).then_some(value)
3755}
3756
3757pub fn certified_exp(value: f64) -> Option<ClosedInterval> {
3767 if !value.is_finite() {
3768 return None;
3769 }
3770 if value == 0.0 {
3771 return Some(ClosedInterval::point(1.0));
3772 }
3773 let mut exponent = (value / std::f64::consts::LN_2).round() as i32;
3777 exponent = exponent.clamp(-1074, 1023);
3778 let remainder = ClosedInterval::point(value).sub(certified_ln_two().scale(exponent as f64));
3779 if !(remainder.is_valid() && remainder.max_abs() < 4.0) {
3780 return None;
3781 }
3782 let reduction = (1_u64 << EXP_RANGE_SQUARINGS) as f64;
3783 let reduced = remainder.scale(1.0 / reduction);
3784 if !(reduced.max_abs() < 1.0 / 16.0) {
3785 return None;
3786 }
3787 let mut term = ClosedInterval::point(1.0);
3788 let mut sum = term;
3789 for degree in 1..=EXP_SERIES_TERMS {
3790 term = term
3791 .mul(reduced)
3792 .div_positive(ClosedInterval::point(degree as f64));
3793 sum = sum.add(term);
3794 }
3795 let z = reduced.max_abs();
3796 let first_omitted = next_up(term.max_abs() * z / (EXP_SERIES_TERMS + 1) as f64);
3797 let tail = next_up(first_omitted / next_down(1.0 - z));
3799 let mut result = sum.widen(tail);
3800 for _ in 0..EXP_RANGE_SQUARINGS {
3801 result = result.square();
3802 }
3803 result = result.mul(ClosedInterval::point(exact_power_of_two(exponent)?));
3804 Some(result.nonnegative())
3805}
3806
3807#[inline]
3808fn certified_midpoint(interval: ClosedInterval) -> f64 {
3809 let midpoint = interval.lo + 0.5 * (interval.hi - interval.lo);
3810 midpoint.max(interval.lo).min(interval.hi)
3811}
3812
3813#[inline]
3819pub fn certified_exp_representative(value: f64) -> Option<f64> {
3820 certified_exp(value).map(certified_midpoint)
3821}
3822
3823#[inline]
3824fn certified_ln_value(value: f64) -> Option<f64> {
3825 certified_ln_positive(value).map(certified_midpoint)
3826}
3827
3828#[inline]
3829fn certified_ln_1p_value(value: f64) -> Option<f64> {
3830 certified_ln_1p(value).map(certified_midpoint)
3831}
3832
3833fn interval_diameter(interval: ClosedInterval) -> f64 {
3834 if interval.lo == interval.hi {
3835 0.0
3836 } else {
3837 next_up(interval.hi - interval.lo)
3838 }
3839}
3840
3841fn log_series_tail_max() -> f64 {
3842 let z = next_up(1.0 / 3.0);
3843 let z2 = next_up(z * z);
3844 let mut power = z;
3845 for _ in 1..LOG_SERIES_TERMS {
3846 power = next_up(power * z2);
3847 }
3848 power = next_up(power * z2);
3849 let denominator = next_down((2 * LOG_SERIES_TERMS + 1) as f64 * next_down(1.0 - z2));
3850 next_up(next_up(2.0 * power) / denominator)
3851}
3852
3853fn exp_series_relative_tail_max() -> f64 {
3857 let z = next_up(1.0 / 16.0);
3858 let mut term = 1.0;
3859 for degree in 1..=EXP_SERIES_TERMS {
3860 term = next_up(next_up(term * z) / degree as f64);
3861 }
3862 let first_omitted = next_up(next_up(term * z) / (EXP_SERIES_TERMS + 1) as f64);
3863 let absolute_tail = next_up(first_omitted / next_down(1.0 - z));
3864 let mut factor =
3868 ClosedInterval::point(1.0).add(ClosedInterval::point(next_up(2.0 * absolute_tail)));
3869 for _ in 0..EXP_RANGE_SQUARINGS {
3870 factor = factor.square();
3871 }
3872 next_up(factor.hi - 1.0).max(0.0)
3873}
3874
3875fn certified_log_forward_error(input: ClosedInterval) -> f64 {
3878 if !(input.lo > 0.0 && input.hi.is_finite()) {
3879 return f64::INFINITY;
3880 }
3881 let exponent_abs = [input.lo, input.hi]
3882 .into_iter()
3883 .map(|value| {
3884 let bits = value.to_bits();
3885 let exponent_bits = ((bits >> 52) & 0x7ff) as i32;
3886 if exponent_bits == 0 {
3887 let fraction = bits & ((1_u64 << 52) - 1);
3888 let highest = 63_i32 - fraction.leading_zeros() as i32;
3889 (highest - 1074).unsigned_abs() as f64
3890 } else {
3891 (exponent_bits - 1023).unsigned_abs() as f64
3892 }
3893 })
3894 .fold(0.0_f64, f64::max);
3895 let ln_two_uncertainty = next_up(exponent_abs * interval_diameter(certified_ln_two()));
3896 let mantissa_ops = 6 * LOG_SERIES_TERMS + 32;
3899 let mantissa_error =
3900 add_nonnegative_upward(wilkinson_roundoff(1.0, mantissa_ops), log_series_tail_max());
3901 add_nonnegative_upward(ln_two_uncertainty, mantissa_error)
3902}
3903
3904fn certified_log_error_from_output(output: ClosedInterval) -> f64 {
3905 if !output.is_valid() {
3906 return f64::INFINITY;
3907 }
3908 let exponent_abs = next_up(output.max_abs() / certified_ln_two().lo.abs()).ceil() + 1.0;
3910 let ln_two_uncertainty = next_up(exponent_abs * interval_diameter(certified_ln_two()));
3911 let mantissa_ops = 6 * LOG_SERIES_TERMS + 32;
3912 add_nonnegative_upward(
3913 ln_two_uncertainty,
3914 add_nonnegative_upward(wilkinson_roundoff(1.0, mantissa_ops), log_series_tail_max()),
3915 )
3916}
3917
3918fn certified_ln1p_forward_error() -> f64 {
3919 let operations = 6 * LOG_SERIES_TERMS + 36;
3920 add_nonnegative_upward(wilkinson_roundoff(1.0, operations), log_series_tail_max())
3921}
3922
3923fn certified_exp_forward_error(input: ClosedInterval, output: ClosedInterval) -> f64 {
3927 if !(input.is_valid() && output.is_valid() && output.lo >= 0.0) {
3928 return f64::INFINITY;
3929 }
3930 let exponent_abs = next_up(input.max_abs() / certified_ln_two().lo).ceil() + 1.0;
3931 let reduction_error = next_up(exponent_abs * interval_diameter(certified_ln_two()));
3932 if !(reduction_error < 1.0) {
3933 return f64::INFINITY;
3934 }
3935 let propagated_reduction =
3937 next_up(output.max_abs() * reduction_error / next_down(1.0 - reduction_error));
3938 let operations = 6 * EXP_SERIES_TERMS + 4 * EXP_RANGE_SQUARINGS + 40;
3941 let arithmetic = wilkinson_roundoff(output.max_abs(), operations);
3942 let truncation = next_up(output.max_abs() * exp_series_relative_tail_max());
3943 add_nonnegative_upward(
3944 propagated_reduction,
3945 add_nonnegative_upward(arithmetic, truncation),
3946 )
3947}
3948
3949fn certified_exp_relative_forward_error(input: ClosedInterval, output: ClosedInterval) -> f64 {
3961 if !(input.is_valid() && output.is_valid() && output.lo > 0.0 && output.hi.is_finite()) {
3962 return f64::INFINITY;
3963 }
3964 let exponent_abs = next_up(input.max_abs() / certified_ln_two().lo).ceil() + 1.0;
3965 let reduction_error = next_up(exponent_abs * interval_diameter(certified_ln_two()));
3966 if !(reduction_error < 1.0) {
3967 return f64::INFINITY;
3968 }
3969 let relative_reduction = next_up(reduction_error / next_down(1.0 - reduction_error));
3970 let operations = 6 * EXP_SERIES_TERMS + 4 * EXP_RANGE_SQUARINGS + 40;
3971 let relative_arithmetic = wilkinson_roundoff(1.0, operations);
3972 let relative_underflow = next_up(wilkinson_roundoff(0.0, operations) / output.lo);
3973 add_nonnegative_upward(
3974 relative_reduction,
3975 add_nonnegative_upward(
3976 relative_arithmetic,
3977 add_nonnegative_upward(exp_series_relative_tail_max(), relative_underflow),
3978 ),
3979 )
3980}
3981
3982fn add_nonnegative_upward(accumulator: f64, term: f64) -> f64 {
3984 if accumulator == f64::INFINITY || term == f64::INFINITY {
3985 f64::INFINITY
3986 } else if term == 0.0 {
3987 accumulator
3988 } else {
3989 next_up(accumulator + term)
3990 }
3991}
3992
3993fn enclosure_excess(mathematical: ClosedInterval, resolved: ClosedInterval) -> f64 {
3996 let lower = if mathematical.lo == resolved.lo {
3997 0.0
3998 } else {
3999 next_up(mathematical.lo - resolved.lo)
4000 };
4001 let upper = if mathematical.hi == resolved.hi {
4002 0.0
4003 } else {
4004 next_up(resolved.hi - mathematical.hi)
4005 };
4006 lower.max(upper).max(0.0)
4007}
4008
4009fn wilkinson_roundoff(magnitude: f64, operations: usize) -> f64 {
4014 if operations == 0 {
4015 return 0.0;
4016 }
4017 if !(magnitude.is_finite() && magnitude >= 0.0) {
4018 return f64::INFINITY;
4019 }
4020 let operation_count = next_up(operations as f64);
4025 let underflow = next_up(operation_count * f64::from_bits(1));
4026 if magnitude == 0.0 {
4027 return underflow;
4028 }
4029 let unit_roundoff = 0.5 * f64::EPSILON;
4031 let ku = next_up(operation_count * unit_roundoff);
4032 if !(ku < 1.0) {
4033 return f64::INFINITY;
4034 }
4035 let denominator = next_down(1.0 - ku);
4036 if !(denominator > 0.0) {
4037 return f64::INFINITY;
4038 }
4039 let gamma = next_up(ku / denominator);
4040 add_nonnegative_upward(next_up(gamma * magnitude), underflow)
4041}
4042
4043#[inline]
4044fn sum_down(left: f64, right: f64) -> f64 {
4045 let value = left + right;
4046 if sum_is_exact(left, right, value) {
4047 value
4048 } else {
4049 next_down(value)
4050 }
4051}
4052
4053#[inline]
4054fn sum_up(left: f64, right: f64) -> f64 {
4055 let value = left + right;
4056 if sum_is_exact(left, right, value) {
4057 value
4058 } else {
4059 next_up(value)
4060 }
4061}
4062
4063#[inline]
4071fn sum_is_exact(left: f64, right: f64, value: f64) -> bool {
4072 if left == 0.0 || right == 0.0 {
4073 return true;
4074 }
4075 if !(left.is_finite() && right.is_finite() && value.is_finite()) {
4076 return value == left || value == right;
4077 }
4078 let virtual_right = value - left;
4079 let virtual_left = value - virtual_right;
4080 let right_residual = right - virtual_right;
4081 let left_residual = left - virtual_left;
4082 left_residual + right_residual == 0.0
4083}
4084
4085#[inline]
4086fn product_is_exact(left: f64, right: f64) -> bool {
4087 left == 0.0 || right == 0.0 || left.abs() == 1.0 || right.abs() == 1.0
4088}
4089
4090#[inline]
4091fn product_down(left: f64, right: f64) -> f64 {
4092 let value = left * right;
4093 if product_is_exact(left, right) {
4094 if value.is_nan() { 0.0 } else { value }
4095 } else {
4096 next_down(value)
4097 }
4098}
4099
4100#[inline]
4101fn product_up(left: f64, right: f64) -> f64 {
4102 let value = left * right;
4103 if product_is_exact(left, right) {
4104 if value.is_nan() { 0.0 } else { value }
4105 } else {
4106 next_up(value)
4107 }
4108}
4109
4110#[inline]
4111fn quotient_down(numerator: f64, denominator: f64) -> f64 {
4112 let value = numerator / denominator;
4113 if numerator == 0.0 || denominator.abs() == 1.0 {
4114 value
4115 } else {
4116 next_down(value)
4117 }
4118}
4119
4120#[inline]
4121fn quotient_up(numerator: f64, denominator: f64) -> f64 {
4122 let value = numerator / denominator;
4123 if numerator == 0.0 || denominator.abs() == 1.0 {
4124 value
4125 } else {
4126 next_up(value)
4127 }
4128}
4129
4130fn next_down(value: f64) -> f64 {
4133 if value.is_nan() || value == f64::NEG_INFINITY {
4134 return value;
4135 }
4136 if value == 0.0 {
4137 return -f64::from_bits(1);
4138 }
4139 let bits = value.to_bits();
4140 f64::from_bits(if value > 0.0 { bits - 1 } else { bits + 1 })
4141}
4142
4143fn next_up(value: f64) -> f64 {
4146 if value.is_nan() || value == f64::INFINITY {
4147 return value;
4148 }
4149 if value == 0.0 {
4150 return f64::from_bits(1);
4151 }
4152 let bits = value.to_bits();
4153 f64::from_bits(if value > 0.0 { bits + 1 } else { bits - 1 })
4154}
4155
4156#[cfg(test)]
4157mod tests {
4158 use super::*;
4159
4160 fn polynomial_hidden_bump_jet(x: f64) -> ScoreJet {
4161 let p = x * (x - 0.5) * (x - 1.0);
4162 let dp = 3.0 * x * x - 3.0 * x + 0.5;
4163 let ddp = 6.0 * x - 3.0;
4164 ScoreJet {
4165 value: x + 1000.0 * p * p,
4166 derivative: 1.0 + 2000.0 * p * dp,
4167 curvature: 2000.0 * (dp * dp + p * ddp),
4168 third: 2000.0 * (3.0 * dp * ddp + p * 6.0),
4169 }
4170 }
4171
4172 fn polynomial_hidden_bump_enclosure(lo: f64, hi: f64) -> DerivativeEnclosure {
4173 let x = ClosedInterval::new(lo, hi);
4174 let p = x
4175 .mul(x.sub(ClosedInterval::point(0.5)))
4176 .mul(x.sub(ClosedInterval::point(1.0)));
4177 let dp = x
4178 .square()
4179 .scale(3.0)
4180 .sub(x.scale(3.0))
4181 .add(ClosedInterval::point(0.5));
4182 let ddp = x.scale(6.0).sub(ClosedInterval::point(3.0));
4183 let value = x.add(p.square().scale(1000.0));
4184 DerivativeEnclosure {
4185 score: ScoreValueEnclosure {
4186 value,
4187 evaluation_error: wilkinson_roundoff(value.max_abs(), 7),
4188 },
4189 derivative: ClosedInterval::point(1.0).add(p.mul(dp).scale(2000.0)),
4190 curvature: dp.square().add(p.mul(ddp)).scale(2000.0),
4191 }
4192 }
4193
4194 #[test]
4195 fn hidden_between_endpoint_and_midpoint_samples_is_found() {
4196 let result = maximize_score_1d(
4197 0.0,
4198 1.0,
4199 1.0e-9,
4200 |x| -> Result<_, String> { Ok(polynomial_hidden_bump_jet(x)) },
4201 |lo, hi| -> Result<_, String> { Ok(polynomial_hidden_bump_enclosure(lo.x, hi.x)) },
4202 )
4203 .expect("certified search");
4204
4205 assert_eq!(polynomial_hidden_bump_jet(0.0).derivative, 1.0);
4208 assert_eq!(polynomial_hidden_bump_jet(0.5).derivative, 1.0);
4209 assert_eq!(polynomial_hidden_bump_jet(1.0).derivative, 1.0);
4210 assert!(result.optimum.x > 0.5 && result.optimum.x < 1.0);
4211 assert!(result.optimum.value > 2.9);
4212 assert!(
4213 result
4214 .stationary_points
4215 .iter()
4216 .any(|point| point.bracket.contains(result.optimum.x)),
4217 "the hidden global maximizer must have a retained root certificate"
4218 );
4219 assert!(
4220 result
4221 .dominated_regions
4222 .iter()
4223 .all(|region| region.score.value.hi < region.incumbent_lower),
4224 "every skipped stationary branch must carry a strict exact dominance proof"
4225 );
4226 }
4227
4228 fn quartic_jet(x: f64) -> ScoreJet {
4229 ScoreJet {
4230 value: -(x * x - 1.0).powi(2),
4231 derivative: 4.0 * x - 4.0 * x * x * x,
4232 curvature: 4.0 - 12.0 * x * x,
4233 third: -24.0 * x,
4234 }
4235 }
4236
4237 fn quartic_enclosure(lo: f64, hi: f64) -> DerivativeEnclosure {
4238 let x = ClosedInterval::new(lo, hi);
4239 let shifted_square = x.square().sub(ClosedInterval::point(1.0));
4240 let value = shifted_square.square().neg();
4241 if lo == hi && (lo == -1.0 || lo == 0.0 || lo == 1.0) {
4242 return DerivativeEnclosure {
4243 score: ScoreValueEnclosure {
4244 value,
4245 evaluation_error: wilkinson_roundoff(value.max_abs(), 4),
4246 },
4247 derivative: ClosedInterval::point(0.0),
4248 curvature: ClosedInterval::point(quartic_jet(lo).curvature),
4249 };
4250 }
4251 DerivativeEnclosure {
4252 score: ScoreValueEnclosure {
4253 value,
4254 evaluation_error: wilkinson_roundoff(value.max_abs(), 4),
4255 },
4256 derivative: x.scale(4.0).sub(x.mul(x).mul(x).scale(4.0)),
4257 curvature: ClosedInterval::point(4.0).sub(x.square().scale(12.0)),
4258 }
4259 }
4260
4261 #[test]
4262 fn globally_relevant_roots_are_isolated_and_dominated_structure_is_audited() {
4263 let result = maximize_score_1d(
4264 -2.0,
4265 2.0,
4266 1.0e-10,
4267 |x| -> Result<_, String> { Ok(quartic_jet(x)) },
4268 |lo, hi| -> Result<_, String> { Ok(quartic_enclosure(lo.x, hi.x)) },
4269 )
4270 .expect("certified search");
4271 assert_eq!(
4272 result.stationary_points.len(),
4273 2,
4274 "both equal global maxima must survive strict dominance"
4275 );
4276 for expected in [-1.0_f64, 1.0] {
4277 let point = result
4278 .stationary_points
4279 .iter()
4280 .find(|point| (point.sample.x - expected).abs() <= 1.0e-9)
4281 .unwrap_or_else(|| panic!("missing global maximum at {expected}"));
4282 assert!(point.bracket.hi - point.bracket.lo <= 1.0e-10);
4283 }
4284 assert!(
4285 result
4286 .dominated_regions
4287 .iter()
4288 .any(|region| region.bracket.contains(0.0)),
4289 "the strictly inferior stationary minimum must remain auditable as dominated"
4290 );
4291 assert!((result.optimum.x.abs() - 1.0).abs() <= 1.0e-9);
4292 }
4293
4294 #[test]
4295 fn exact_dominance_prunes_an_uninformative_saturated_tail() {
4296 let mut evaluations = 0_usize;
4297 let result = maximize_score_1d(
4298 -1.0,
4299 10.0,
4300 1.0e-9,
4301 |x| -> Result<_, String> {
4302 evaluations += 1;
4303 Ok(ScoreJet {
4304 value: 1.0 - x * x,
4305 derivative: -2.0 * x,
4306 curvature: -2.0,
4307 third: 0.0,
4308 })
4309 },
4310 |left, right| -> Result<_, String> {
4311 let x = ClosedInterval::new(left.x, right.x);
4312 let value = ClosedInterval::point(1.0).sub(x.square());
4313 let root_side_cell = right.x <= 1.0;
4314 Ok(DerivativeEnclosure {
4315 score: ScoreValueEnclosure {
4316 value,
4317 evaluation_error: 1.0e-12,
4318 },
4319 derivative: if root_side_cell || left.x == right.x {
4320 x.scale(-2.0)
4321 } else {
4322 ClosedInterval::new(-100.0, 100.0)
4325 },
4326 curvature: if root_side_cell || left.x == right.x {
4327 ClosedInterval::point(-2.0)
4328 } else {
4329 ClosedInterval::new(-100.0, 100.0)
4330 },
4331 })
4332 },
4333 )
4334 .expect("the exact score incumbent must dominate the uninformative tail");
4335
4336 assert_eq!(result.optimum.x, 0.0);
4337 assert!(result.value_certificate.maximum.contains(1.0));
4338 assert!(
4339 !result.dominated_regions.is_empty(),
4340 "the fixture's saturated tail must be terminated by exact dominance"
4341 );
4342 assert!(
4343 result
4344 .dominated_regions
4345 .iter()
4346 .all(|region| region.score.value.hi < region.incumbent_lower),
4347 "every retained dominance decision must expose its strict exact ordering"
4348 );
4349 assert!(
4350 evaluations < 16,
4351 "the low-score tail was enumerated instead of pruned ({evaluations} evaluations)"
4352 );
4353 }
4354
4355 const ROUNDED_ZERO_ABSCISSA: f64 = 1.5;
4358
4359 #[test]
4375 fn a_rounded_zero_at_a_cell_endpoint_does_not_close_the_cell() {
4376 let mut rounded_zeros = 0_usize;
4377 let result = maximize_score_1d(
4378 0.0,
4379 3.0,
4380 1.0e-9,
4381 |x| -> Result<_, String> {
4382 let shifted = x - 2.5;
4383 let derivative = if x == ROUNDED_ZERO_ABSCISSA {
4384 rounded_zeros += 1;
4385 0.0
4386 } else {
4387 -2.0 * shifted
4388 };
4389 Ok(ScoreJet {
4390 value: 1.0 - shifted * shifted,
4391 derivative,
4392 curvature: -2.0,
4393 third: 0.0,
4394 })
4395 },
4396 |left, right| -> Result<_, String> {
4397 let x = ClosedInterval::new(left.x, right.x);
4398 let shifted = x.sub(ClosedInterval::point(2.5));
4399 let value = ClosedInterval::point(1.0).sub(shifted.square());
4400 Ok(DerivativeEnclosure {
4401 score: ScoreValueEnclosure {
4402 value,
4403 evaluation_error: wilkinson_roundoff(value.max_abs(), 3),
4404 },
4405 derivative: shifted.scale(-2.0),
4408 curvature: ClosedInterval::point(-2.0),
4409 })
4410 },
4411 )
4412 .expect("certified search");
4413
4414 assert!(
4415 rounded_zeros > 0,
4416 "fixture premise unmet: the search never evaluated x = {ROUNDED_ZERO_ABSCISSA}"
4417 );
4418 assert!(
4419 (result.optimum.x - 2.5).abs() <= 1.0e-9,
4420 "reported the maximum at x={} (value {}) instead of x=2.5",
4421 result.optimum.x,
4422 result.optimum.value,
4423 );
4424 assert!(
4425 result.value_certificate.maximum.contains(1.0),
4426 "the exact maximum escaped the global score certificate: {:?}",
4427 result.value_certificate,
4428 );
4429 assert!(
4430 result
4431 .stationary_points
4432 .iter()
4433 .all(|point| point.sample.x != ROUNDED_ZERO_ABSCISSA),
4434 "a derivative that rounded to zero was reported as a stationary point",
4435 );
4436 let root = result
4437 .stationary_points
4438 .iter()
4439 .find(|point| point.bracket.contains(2.5))
4440 .expect("the exact quadratic root must be isolated");
4441 assert_eq!(
4442 root.bracket,
4443 ClosedInterval::point(2.5),
4444 "the cancellation-free point enclosure must preserve the exact dyadic root"
4445 );
4446 }
4447
4448 #[test]
4449 fn adjacent_cell_evidence_is_retained_when_point_derivative_is_uninformative() {
4450 let planted = 0.7_f64;
4451 let result = maximize_score_1d(
4452 0.0,
4453 1.0,
4454 1.0e-9,
4455 |x| -> Result<_, String> {
4456 let shifted = x - planted;
4457 Ok(ScoreJet {
4458 value: 1.0 - shifted * shifted,
4459 derivative: -2.0 * shifted,
4460 curvature: -2.0,
4461 third: 0.0,
4462 })
4463 },
4464 |left, right| -> Result<_, String> {
4465 let x = ClosedInterval::new(left.x, right.x);
4466 let shifted = x.sub(ClosedInterval::point(planted));
4467 let value = ClosedInterval::point(1.0).sub(shifted.square());
4468 let interior_point = left.x == right.x && left.x > 0.0 && left.x < 1.0;
4469 Ok(DerivativeEnclosure {
4470 score: ScoreValueEnclosure {
4471 value,
4472 evaluation_error: wilkinson_roundoff(value.max_abs(), 3),
4473 },
4474 derivative: if interior_point {
4479 ClosedInterval::new(-2.0, 2.0)
4480 } else {
4481 shifted.scale(-2.0)
4482 },
4483 curvature: ClosedInterval::point(-2.0),
4484 })
4485 },
4486 )
4487 .expect("adjacent exact cell evidence must isolate the unique root");
4488
4489 assert!(
4490 (result.optimum.x - planted).abs() <= 1.0e-9,
4491 "selected {}, expected {planted}",
4492 result.optimum.x
4493 );
4494 let stationary = result
4495 .stationary_points
4496 .iter()
4497 .find(|point| point.bracket.contains(planted))
4498 .expect("the planted stationary point must be certified");
4499 assert!(stationary.bracket.hi - stationary.bracket.lo <= 1.0e-9);
4500 }
4501
4502 #[test]
4503 fn interval_newton_stationarity_is_not_preempted_by_a_resolved_score_gap() {
4504 let planted = 0.25;
4505 let resolution = 1.0e-9;
4506 let mut unresolved_root_probes = 0;
4507 let result = maximize_score_1d(
4508 -1.0,
4509 1.0,
4510 resolution,
4511 |x| -> Result<_, String> {
4512 let shifted = x - planted;
4513 Ok(ScoreJet {
4514 value: 1.0 - shifted * shifted,
4515 derivative: -2.0 * shifted,
4516 curvature: -2.0,
4517 third: 0.0,
4518 })
4519 },
4520 |left, right| -> Result<_, String> {
4521 let shifted = ClosedInterval::new(left.x, right.x)
4522 .sub(ClosedInterval::point(planted));
4523 let value = ClosedInterval::point(1.0).sub(shifted.square());
4524 let derivative = shifted
4528 .scale(-2.0)
4529 .add(ClosedInterval::new(-1.0e-12, 1.0e-12));
4530 if left.x == planted && right.x == planted {
4531 unresolved_root_probes += 1;
4532 assert!(derivative.contains_zero());
4533 assert!(derivative.lo < derivative.hi);
4534 }
4535 Ok(DerivativeEnclosure {
4536 score: ScoreValueEnclosure {
4537 value,
4538 evaluation_error: 1.0e-12,
4539 },
4540 derivative,
4541 curvature: ClosedInterval::point(-2.0),
4542 })
4543 },
4544 )
4545 .expect("the certified Newton image must retain the stationarity proof");
4546 assert!(unresolved_root_probes > 0, "the ambiguous root must be probed");
4547 let ScoreOptimumLocation::Stationary(index) = result.location else {
4548 panic!(
4549 "a resolved Newton root lost its stationarity proof: {:?}",
4550 result.location
4551 );
4552 };
4553 let point = result.stationary_points[index];
4554 assert!(point.bracket.contains(planted));
4555 assert!(point.bracket.hi - point.bracket.lo <= resolution);
4556 assert!(point.curvature.hi < 0.0);
4557 assert!(result.resolution_flat_regions.is_empty());
4558 }
4559
4560 #[test]
4561 fn signed_endpoint_newton_reaches_the_existing_score_resolution_floor() {
4562 let planted = 0.8_f64;
4563 let ambiguous_probe = 0.5_f64;
4564 let mut ambiguous_probe_calls = 0_usize;
4565 let result = maximize_score_1d(
4566 0.0,
4567 1.0,
4568 1.0e-9,
4569 |x| -> Result<_, String> {
4570 let shifted = x - planted;
4571 Ok(ScoreJet {
4572 value: 1.0 - shifted * shifted,
4573 derivative: -2.0 * shifted,
4574 curvature: -2.0,
4575 third: 0.0,
4576 })
4577 },
4578 |left, right| -> Result<_, String> {
4579 let x = ClosedInterval::new(left.x, right.x);
4580 let shifted = x.sub(ClosedInterval::point(planted));
4581 let value = ClosedInterval::point(1.0).sub(shifted.square());
4582 let derivative = if left.x == right.x {
4583 if left.x == ambiguous_probe {
4584 ambiguous_probe_calls += 1;
4585 ClosedInterval::new(-2.0, 2.0)
4586 } else {
4587 ClosedInterval::point(-2.0 * (left.x - planted))
4588 }
4589 } else {
4590 ClosedInterval::new(-2.0, 2.0)
4594 };
4595 Ok(DerivativeEnclosure {
4596 score: ScoreValueEnclosure {
4597 value,
4598 evaluation_error: 0.021,
4603 },
4604 derivative,
4605 curvature: ClosedInterval::new(-4.0, -1.0),
4608 })
4609 },
4610 )
4611 .expect("signed endpoint Newton images must reach a typed score-resolution proof");
4612
4613 assert!(
4614 ambiguous_probe_calls > 0,
4615 "fixture premise unmet: the cancellation-heavy midpoint was never certified"
4616 );
4617 let ScoreOptimumLocation::ResolutionFlat(index) = result.location else {
4618 panic!(
4619 "the unique root's location is below the declared information floor: {:?}",
4620 result.location
4621 );
4622 };
4623 let flat = result.resolution_flat_regions[index];
4624 assert!(
4625 flat.bracket.contains(planted),
4626 "contracted flat bracket {:?} lost the unique root",
4627 flat.bracket
4628 );
4629 assert!(
4630 flat.max_score_gap <= flat.score_resolution,
4631 "typed flat proof exceeded its existing evaluator floor: {flat:?}"
4632 );
4633 assert!(result.stationary_points.is_empty());
4634 }
4635
4636 #[test]
4637 fn strict_concavity_certifies_the_quintic_scan_optimum_at_score_resolution() {
4638 let left = SearchSample {
4644 sample: ScoreSample {
4645 x: -12.105_374_438_144_967,
4646 value: 134.053_351_995_058_96,
4647 derivative: 1.0e-3,
4648 curvature: -1.0,
4649 third: 0.0,
4650 },
4651 point_enclosure: None,
4652 };
4653 let right = SearchSample {
4654 sample: ScoreSample {
4655 x: -12.104_760_848_454_575,
4656 value: 134.054_279_259_553_65,
4657 derivative: -1.0e-3,
4658 curvature: -1.0,
4659 third: 0.0,
4660 },
4661 point_enclosure: None,
4662 };
4663 let sample = ScoreSample {
4664 x: left.sample.x + 0.5 * (right.sample.x - left.sample.x),
4665 value: 134.053_9,
4666 derivative: 0.0,
4667 curvature: -1.0,
4668 third: 0.0,
4669 };
4670 let evaluation_error = 3.966_754_013_333_685e-4;
4671 let point_score = ScoreValueEnclosure {
4672 value: ClosedInterval::point(sample.value),
4673 evaluation_error,
4674 };
4675 let enclosure = DerivativeEnclosure {
4676 score: ScoreValueEnclosure {
4677 value: ClosedInterval::new(134.053_351_995_058_96, 134.054_279_259_553_65),
4678 evaluation_error,
4679 },
4680 derivative: ClosedInterval::new(-1.8562e-3, 1.6607e-3),
4681 curvature: ClosedInterval::new(-2.2666, -0.2358),
4682 };
4683
4684 assert!(
4685 resolution_flat_region(SearchNode { left, right }, enclosure).is_none(),
4686 "fixture premise: the full score diameter exceeds pairwise evaluation error"
4687 );
4688 let (flat, maximum) = score_resolved_concave_maximum(
4689 SearchNode { left, right },
4690 enclosure,
4691 sample,
4692 enclosure.derivative,
4693 enclosure.curvature,
4694 point_score,
4695 )
4696 .expect("strict concavity must close the already score-resolved optimum");
4697 assert!(flat.bracket.contains(sample.x));
4698 assert!(flat.max_score_gap < 7.4e-6);
4699 assert!(flat.max_score_gap <= flat.score_resolution);
4700 assert!(
4701 maximum.value.hi < enclosure.score.value.hi,
4702 "the strong-concavity maximum bound must remove the loose cell-wide score tail"
4703 );
4704 }
4705
4706 #[test]
4707 fn monotone_score_selects_exact_boundary() {
4708 let result = maximize_score_1d(
4709 -4.0,
4710 9.0,
4711 1.0e-9,
4712 |x| -> Result<_, String> {
4713 Ok(ScoreJet {
4714 value: 0.3 * x,
4715 derivative: 0.3,
4716 curvature: 0.0,
4717 third: 0.0,
4718 })
4719 },
4720 |left, right| -> Result<_, String> {
4721 let value = ClosedInterval::new(left.x, right.x).scale(0.3);
4722 Ok(DerivativeEnclosure {
4723 score: ScoreValueEnclosure {
4724 value,
4725 evaluation_error: wilkinson_roundoff(value.max_abs(), 1),
4726 },
4727 derivative: ClosedInterval::point(0.3),
4728 curvature: ClosedInterval::point(0.0),
4729 })
4730 },
4731 )
4732 .expect("certified search");
4733 assert_eq!(result.location, ScoreOptimumLocation::UpperBoundary);
4734 assert_eq!(result.optimum.x, 9.0);
4735 assert!(result.stationary_points.is_empty());
4736 assert_eq!(
4737 result.value_certificate.maximum_excess, 0.0,
4738 "the exact same terminal point is not a competing uncertain value"
4739 );
4740 }
4741
4742 #[test]
4743 fn certified_increase_selects_upper_boundary_when_rounded_values_tie() {
4744 let result = maximize_score_1d(
4745 -1.0,
4746 1.0,
4747 1.0e-9,
4748 |_| -> Result<_, String> {
4749 Ok(ScoreJet {
4750 value: 0.0,
4751 derivative: 1.0,
4752 curvature: 0.0,
4753 third: 0.0,
4754 })
4755 },
4756 |left, right| -> Result<_, String> {
4757 Ok(DerivativeEnclosure {
4758 score: ScoreValueEnclosure {
4759 value: ClosedInterval::new(left.x, right.x),
4760 evaluation_error: 1.0,
4761 },
4762 derivative: ClosedInterval::point(1.0),
4763 curvature: ClosedInterval::point(0.0),
4764 })
4765 },
4766 )
4767 .expect("a whole-domain positive derivative orders tied rounded endpoints");
4768 assert_eq!(result.lower_boundary.value, result.upper_boundary.value);
4769 assert_eq!(result.location, ScoreOptimumLocation::UpperBoundary);
4770 assert_eq!(result.optimum.x, 1.0);
4771 assert_eq!(result.value_certificate.maximum_excess, 0.0);
4772 }
4773
4774 #[test]
4775 fn certified_decrease_selects_lower_boundary_when_rounded_values_tie() {
4776 let result = maximize_score_1d(
4777 -1.0,
4778 1.0,
4779 1.0e-9,
4780 |_| -> Result<_, String> {
4781 Ok(ScoreJet {
4782 value: 0.0,
4783 derivative: -1.0,
4784 curvature: 0.0,
4785 third: 0.0,
4786 })
4787 },
4788 |left, right| -> Result<_, String> {
4789 Ok(DerivativeEnclosure {
4790 score: ScoreValueEnclosure {
4791 value: ClosedInterval::new(-right.x, -left.x),
4792 evaluation_error: 1.0,
4793 },
4794 derivative: ClosedInterval::point(-1.0),
4795 curvature: ClosedInterval::point(0.0),
4796 })
4797 },
4798 )
4799 .expect("a whole-domain negative derivative orders tied rounded endpoints");
4800 assert_eq!(result.lower_boundary.value, result.upper_boundary.value);
4801 assert_eq!(result.location, ScoreOptimumLocation::LowerBoundary);
4802 assert_eq!(result.optimum.x, -1.0);
4803 assert_eq!(result.value_certificate.maximum_excess, 0.0);
4804 }
4805
4806 #[test]
4807 fn tangential_nonmaximum_structure_is_closed_by_exact_dominance() {
4808 let result = maximize_score_1d(
4809 -1.0,
4810 1.0,
4811 1.0e-8,
4812 |x| -> Result<_, String> {
4813 Ok(ScoreJet {
4814 value: x * x * x,
4815 derivative: 3.0 * x * x,
4816 curvature: 6.0 * x,
4817 third: 6.0,
4818 })
4819 },
4820 |lo, hi| -> Result<_, String> {
4821 let x = ClosedInterval::new(lo.x, hi.x);
4822 Ok(DerivativeEnclosure {
4823 score: ScoreValueEnclosure {
4824 value: x.mul(x).mul(x),
4825 evaluation_error: f64::EPSILON,
4826 },
4827 derivative: x.square().scale(3.0),
4828 curvature: x.scale(6.0),
4829 })
4830 },
4831 )
4832 .expect("the inferior inflection is immaterial by exact score ordering");
4833 assert_eq!(result.location, ScoreOptimumLocation::UpperBoundary);
4834 assert!(
4835 !result.dominated_regions.is_empty(),
4836 "the search must record the exact dominance proof instead of silently dropping the cell"
4837 );
4838 for region in result.dominated_regions {
4839 assert!(region.score.value.hi < region.incumbent_lower);
4840 }
4841 }
4842
4843 #[test]
4844 fn unresolved_nonflat_cell_remains_typed() {
4845 let error = maximize_score_1d(
4846 0.0,
4847 1.0e-8,
4848 1.0e-8,
4849 |x| -> Result<_, String> {
4850 Ok(ScoreJet {
4851 value: x,
4852 derivative: 0.0,
4853 curvature: 0.0,
4854 third: 0.0,
4855 })
4856 },
4857 |lo, hi| -> Result<_, String> {
4858 Ok(DerivativeEnclosure {
4859 score: ScoreValueEnclosure {
4860 value: ClosedInterval::new(lo.x, hi.x),
4861 evaluation_error: 0.0,
4862 },
4863 derivative: ClosedInterval::new(-1.0, 1.0),
4864 curvature: ClosedInterval::new(-1.0, 1.0),
4865 })
4866 },
4867 )
4868 .expect_err("a derivative enclosure admitting visible score motion is not flat");
4869 assert!(matches!(error, ScoreSearchError::Unresolved { .. }));
4870 }
4871
4872 #[test]
4890 fn undecomposable_criterion_exhausts_the_budget_instead_of_enumerating_the_domain() {
4891 let lo = 0.0;
4892 let hi = 32.0;
4893 let resolution = f64::EPSILON.sqrt();
4894 let flat_error = 5.0e-4;
4895 let (budget, depth_bound) = subdivision_budget(lo, hi, resolution);
4896 assert_eq!(depth_bound, 31, "log2(32 / sqrt(eps)) rounds up to 31");
4897 assert_eq!(
4903 budget,
4904 8 * 31 * 31,
4905 "budget must track the 8 D^2 coefficient in subdivision_budget"
4906 );
4907 let error = maximize_score_1d(
4908 lo,
4909 hi,
4910 resolution,
4911 |_| -> Result<_, String> {
4912 Ok(ScoreJet {
4913 value: 0.0,
4914 derivative: 0.0,
4915 curvature: 0.0,
4916 third: 0.0,
4917 })
4918 },
4919 |left, right| -> Result<_, String> {
4920 let half_width = 0.5 * (right.x - left.x);
4921 Ok(DerivativeEnclosure {
4922 score: ScoreValueEnclosure {
4923 value: ClosedInterval::new(-half_width, half_width),
4924 evaluation_error: flat_error,
4925 },
4926 derivative: ClosedInterval::new(-1.0, 1.0),
4927 curvature: ClosedInterval::new(-1.0, 1.0),
4928 })
4929 },
4930 )
4931 .expect_err("a decomposition this large must refuse, not enumerate");
4932 let ScoreSearchError::SubdivisionBudget {
4933 subdivisions,
4934 budget: reported_budget,
4935 depth_bound: reported_depth,
4936 cell_lo,
4937 cell_hi,
4938 ..
4939 } = error
4940 else {
4941 panic!("expected a subdivision-budget refusal, got {error}");
4942 };
4943 assert_eq!(
4944 subdivisions,
4945 budget + 1,
4946 "the budget stops the split that exceeds it"
4947 );
4948 assert_eq!(reported_budget, budget);
4949 assert_eq!(reported_depth, depth_bound);
4950 assert!(
4951 cell_hi - cell_lo > 2.0 * flat_error,
4952 "the reported cell must be one the search could still have split and \
4953 had not yet certified ({cell_lo}, {cell_hi}); a narrower cell would \
4954 mean the depth floor, not the breadth budget, was binding"
4955 );
4956 }
4957
4958 #[test]
4963 fn a_converging_search_stays_far_under_the_subdivision_budget() {
4964 let lo = 0.0;
4965 let hi = 32.0;
4966 let resolution = f64::EPSILON.sqrt();
4967 let (budget, depth_bound) = subdivision_budget(lo, hi, resolution);
4968 let evaluations = std::cell::Cell::new(0usize);
4969 let result = maximize_score_1d(
4970 lo,
4971 hi,
4972 resolution,
4973 |x| -> Result<_, String> {
4974 evaluations.set(evaluations.get() + 1);
4975 let shifted = x - 7.0;
4976 Ok(ScoreJet {
4977 value: -shifted * shifted,
4978 derivative: -2.0 * shifted,
4979 curvature: -2.0,
4980 third: 0.0,
4981 })
4982 },
4983 |left, right| -> Result<_, String> {
4984 let x = ClosedInterval::new(left.x, right.x);
4985 let shifted = x.sub(ClosedInterval::point(7.0));
4986 Ok(DerivativeEnclosure {
4987 score: ScoreValueEnclosure {
4988 value: shifted.square().scale(-1.0),
4989 evaluation_error: f64::EPSILON * 1024.0,
4990 },
4991 derivative: shifted.scale(-2.0),
4992 curvature: ClosedInterval::point(-2.0),
4993 })
4994 },
4995 )
4996 .expect("a strictly concave criterion is decomposable");
4997 let ScoreOptimumLocation::Stationary(index) = result.location else {
4998 panic!("expected the interior maximum, got {:?}", result.location);
4999 };
5000 let bracket = result.stationary_points[index].bracket;
5001 assert!(
5002 bracket.lo <= 7.0 && bracket.hi >= 7.0,
5003 "certified bracket {bracket:?} must contain the planted maximum"
5004 );
5005 assert!(
5008 evaluations.get() < budget / 8,
5009 "a converging search used {} evaluations against budget {budget} at depth \
5010 bound {depth_bound}; a budget within 8x of a converging search is a \
5011 tuning parameter, not a backstop",
5012 evaluations.get()
5013 );
5014 }
5015
5016 #[test]
5017 fn resolution_flatness_is_exactly_value_diameter_vs_pairwise_error() {
5018 let sample = SearchSample {
5019 sample: ScoreSample {
5020 x: 0.0,
5021 value: 7.0,
5022 derivative: 0.0,
5023 curvature: 0.0,
5024 third: 0.0,
5025 },
5026 point_enclosure: None,
5027 };
5028 let node = SearchNode {
5029 left: sample,
5030 right: SearchSample {
5031 sample: ScoreSample {
5032 x: 1.0,
5033 ..sample.sample
5034 },
5035 point_enclosure: None,
5036 },
5037 };
5038 let error = 0.125;
5039 for (upper, expected) in [(1024.25, true), (next_up(1024.25), false)] {
5040 let enclosure = DerivativeEnclosure {
5041 score: ScoreValueEnclosure {
5042 value: ClosedInterval::new(1024.0, upper),
5045 evaluation_error: error,
5046 },
5047 derivative: ClosedInterval::new(-1.0, 1.0),
5048 curvature: ClosedInterval::new(-1.0, 1.0),
5049 };
5050 assert_eq!(
5051 resolution_flat_region(node, enclosure).is_some(),
5052 expected,
5053 "flatness must be equivalent to outward diameter <= outward 2*value error"
5054 );
5055 }
5056 }
5057
5058 #[test]
5059 fn resolution_flat_cells_remain_regions_instead_of_fake_points() {
5060 let resolution = 0.25;
5061 let result = maximize_score_1d(
5062 0.0,
5063 1.0,
5064 resolution,
5065 |_| -> Result<_, String> {
5066 Ok(ScoreJet {
5067 value: 3.0,
5068 derivative: 0.0,
5069 curvature: 0.0,
5070 third: 0.0,
5071 })
5072 },
5073 |_, _| -> Result<_, String> {
5074 Ok(DerivativeEnclosure {
5075 score: ScoreValueEnclosure {
5076 value: ClosedInterval::point(3.0),
5077 evaluation_error: 0.0,
5078 },
5079 derivative: ClosedInterval::new(-1.0, 1.0),
5080 curvature: ClosedInterval::new(-1.0, 1.0),
5081 })
5082 },
5083 )
5084 .expect("an exactly constant score is resolution-flat");
5085 assert_eq!(result.resolution_flat_regions.len(), 1);
5086 assert!(
5087 result.resolution_flat_regions[0].bracket.hi
5088 - result.resolution_flat_regions[0].bracket.lo
5089 > resolution,
5090 "value resolution may close a wide cell, so callers must not reinterpret it \
5091 as an abscissa-resolved stationary point"
5092 );
5093 }
5094
5095 #[test]
5096 fn directed_arithmetic_preserves_cancellation_and_subnormal_error() {
5097 assert_eq!(
5098 ClosedInterval::point(1.0).sub(ClosedInterval::point(1.0)),
5099 ClosedInterval::point(0.0),
5100 "an exact structural zero must not acquire artificial uncertainty"
5101 );
5102 let minimum_subnormal = f64::from_bits(1);
5103 let underflowing_product =
5104 ClosedInterval::point(minimum_subnormal).mul(ClosedInterval::point(0.5));
5105 assert!(
5106 underflowing_product.lo <= 0.5 * minimum_subnormal
5107 && underflowing_product.hi >= 0.5 * minimum_subnormal
5108 && underflowing_product.lo < 0.0
5109 && underflowing_product.hi > 0.0,
5110 "a nonzero exact product that rounds to zero needs additive subnormal width"
5111 );
5112 assert!(
5113 wilkinson_roundoff(0.0, 1) >= minimum_subnormal,
5114 "a zero-magnitude relative model must still charge additive underflow"
5115 );
5116 }
5117
5118 #[test]
5119 fn certified_elementary_intervals_cover_normal_and_subnormal_lanes() {
5120 for value in [
5121 f64::from_bits(1),
5122 f64::MIN_POSITIVE,
5123 0.5,
5124 1.0,
5125 2.0,
5126 f64::MAX,
5127 ] {
5128 let enclosure = certified_ln_positive(value).expect("certified positive log");
5129 assert!(enclosure.is_valid() && enclosure.lo.is_finite() && enclosure.hi.is_finite());
5130 assert!(
5131 enclosure.contains(value.ln()),
5132 "independent platform log sanity value {} escaped {:?}",
5133 value.ln(),
5134 enclosure
5135 );
5136 }
5137 for value in [-744.0_f64, -708.0, -1.0, 0.0, 1.0, 709.0] {
5138 let enclosure = certified_exp(value).expect("certified exponential");
5139 assert!(enclosure.is_valid() && enclosure.lo >= 0.0);
5140 assert!(
5141 enclosure.contains(value.exp()),
5142 "independent platform exp sanity value {} escaped {:?}",
5143 value.exp(),
5144 enclosure
5145 );
5146 }
5147 for value in [f64::from_bits(1), 1.0e-12, 0.25, 1.0] {
5148 let enclosure = certified_ln_1p(value).expect("certified log1p");
5149 assert!(
5150 enclosure.contains(value.ln_1p()),
5151 "independent platform log1p sanity value {} escaped {:?}",
5152 value.ln_1p(),
5153 enclosure
5154 );
5155 }
5156 }
5157
5158 #[test]
5159 fn exact_range_is_not_compared_to_a_separately_rounded_curvature() {
5160 let denormal = f64::from_bits(1);
5161 let result = maximize_score_1d(
5162 0.0,
5163 1.0,
5164 1.0e-8,
5165 |x| -> Result<_, String> {
5166 Ok(ScoreJet {
5167 value: x,
5168 derivative: 1.0,
5169 curvature: -0.0,
5172 third: 0.0,
5173 })
5174 },
5175 |left, right| -> Result<_, String> {
5176 Ok(DerivativeEnclosure {
5177 score: ScoreValueEnclosure {
5178 value: ClosedInterval::new(left.x, right.x),
5179 evaluation_error: 0.0,
5180 },
5181 derivative: ClosedInterval::point(1.0),
5182 curvature: ClosedInterval::point(-denormal),
5183 })
5184 },
5185 )
5186 .expect("an exact-real enclosure need not contain a separately rounded scalar jet");
5187 assert_eq!(result.location, ScoreOptimumLocation::UpperBoundary);
5188 }
5189
5190 fn affine_fixture() -> AffineRemlProfile<'static> {
5191 const G: &[f64] = &[2.0, 0.5, 0.0, 3.0];
5192 const S: &[f64] = &[1.0, 0.0, 2.0, 0.25];
5193 const Q: &[f64] = &[
5194 0.6, 0.1, 0.02, 0.3, 0.2, 0.4, 0.01, 0.5, ];
5197 const Y2: &[f64] = &[8.0, 10.0];
5198 AffineRemlProfile::new(G, S, Q, Y2, 12.0, 3, 0.7).expect("valid fixture")
5199 }
5200
5201 #[test]
5202 fn affine_reml_jet_matches_test_only_differences() {
5203 let profile = affine_fixture();
5204 for x in [-2.0_f64, -0.4, 0.7, 2.0] {
5205 let h = 1.0e-5;
5206 let center = profile.evaluate(x).unwrap();
5207 let left = profile.evaluate(x - h).unwrap();
5208 let right = profile.evaluate(x + h).unwrap();
5209 let derivative = (right.value - left.value) / (2.0 * h);
5210 let curvature = (right.derivative - left.derivative) / (2.0 * h);
5211 assert!(
5212 (center.derivative - derivative).abs() <= 2.0e-8 * (1.0 + derivative.abs()),
5213 "first derivative mismatch at {x}: analytic {}, difference {derivative}",
5214 center.derivative
5215 );
5216 assert!(
5217 (center.curvature - curvature).abs() <= 2.0e-8 * (1.0 + curvature.abs()),
5218 "curvature mismatch at {x}: analytic {}, difference {curvature}",
5219 center.curvature
5220 );
5221 }
5222 }
5223
5224 #[test]
5225 fn affine_reml_enclosure_contains_value_jets() {
5226 let profile = affine_fixture();
5227 let enclosure = profile.enclose(-2.5, 1.75).expect("enclosure");
5228 let score = enclosure.score;
5229 let resolved_score = score.value.widen(score.evaluation_error);
5230 for x in [-2.5_f64, -1.7, -0.3, 0.0, 0.9, 1.75] {
5231 let jet = profile.evaluate(x).unwrap();
5232 let point = profile.enclose(x, x).expect("point enclosure");
5233 assert!(
5234 resolved_score.contains(jet.value),
5235 "score {} at {x} outside {:?} ± {}",
5236 jet.value,
5237 score.value,
5238 score.evaluation_error
5239 );
5240 assert!(
5241 enclosure
5242 .derivative
5243 .intersection(point.derivative)
5244 .is_some(),
5245 "exact point gradient {:?} at {x} is disjoint from {:?}",
5246 point.derivative,
5247 enclosure.derivative
5248 );
5249 assert!(
5250 enclosure.curvature.intersection(point.curvature).is_some(),
5251 "exact point curvature {:?} at {x} is disjoint from {:?}",
5252 point.curvature,
5253 enclosure.curvature
5254 );
5255 }
5256 }
5257
5258 #[test]
5259 fn affine_reml_zero_smoothing_complement_retains_residual_correlation() {
5260 const MODES: usize = 64;
5273 let grams = [1.0; MODES];
5274 let penalties = [1.0; MODES];
5275 let projected = [1.0; MODES];
5276 let energies = [MODES as f64];
5277 let profile = AffineRemlProfile::new(
5278 &grams,
5279 &penalties,
5280 &projected,
5281 &energies,
5282 MODES as f64,
5283 MODES,
5284 0.0,
5285 )
5286 .expect("valid cancellation fixture");
5287 let rho = -23.025850929940457_f64; let enclosure = profile
5289 .enclose(rho, rho)
5290 .expect("equivalent residual forms must retain their intersection");
5291
5292 assert!(
5293 enclosure.derivative.contains_zero(),
5294 "the analytically constant profile must contain zero derivative: {:?}",
5295 enclosure.derivative
5296 );
5297 assert!(
5298 enclosure.derivative.hi - enclosure.derivative.lo < 1.0e-6,
5299 "the residual complement must remove the independent near-one dependency: {:?}",
5300 enclosure.derivative
5301 );
5302 }
5303
5304 #[test]
5337 fn the_value_enclosure_never_exceeds_the_bound_its_own_derivative_certifies() {
5338 const MODES: usize = 33;
5339 let grams = [1.0; MODES];
5340 let penalties = [1.0; MODES];
5341 let projected = [1.0; MODES];
5342 let energies = [MODES as f64];
5343 let profile = AffineRemlProfile::new(
5344 &grams,
5345 &penalties,
5346 &projected,
5347 &energies,
5348 MODES as f64,
5349 MODES,
5350 0.0,
5351 )
5352 .expect("valid cancellation fixture");
5353
5354 let centre = -12.0_f64;
5355 let mut previous_width = f64::INFINITY;
5356 for exponent in [-1_i32, -2, -3, -4, -5, -6] {
5357 let half = 10.0_f64.powi(exponent);
5358 let (a, b) = (centre - half, centre + half);
5359 let width = b - a;
5360 let cell = profile.enclose(a, b).expect("cell enclosure");
5361 let point = profile.enclose(centre, centre).expect("point enclosure");
5362
5363 assert!(
5367 cell.score.value.lo <= point.score.value.lo
5368 && point.score.value.hi <= cell.score.value.hi,
5369 "w={width:e}: the midpoint value range {:?} escaped the cell range {:?}",
5370 point.score.value,
5371 cell.score.value
5372 );
5373 assert!(
5374 cell.derivative.lo <= point.derivative.lo
5375 && point.derivative.hi <= cell.derivative.hi,
5376 "w={width:e}: the midpoint derivative range {:?} escaped the cell range {:?}",
5377 point.derivative,
5378 cell.derivative
5379 );
5380
5381 let value_width = cell.score.value.hi - cell.score.value.lo;
5382 let point_width = point.score.value.hi - point.score.value.lo;
5383 let derivative_bound = cell.derivative.hi.abs().max(cell.derivative.lo.abs());
5384 let mean_value_bound = point_width + derivative_bound * width;
5385 assert!(
5386 value_width <= mean_value_bound * (1.0 + 1.0e-9),
5387 "w={width:e}: the value range is {value_width:e} wide but this cell's own \
5388 derivative enclosure {:?} bounds the score's movement across it by \
5389 {mean_value_bound:e} — the natural extension is back",
5390 cell.derivative
5391 );
5392
5393 println!(
5394 "[GATE] w={width:e} value_width={value_width:e} point_width={point_width:e} \
5395 mvt={mean_value_bound:e} D={derivative_bound:e}"
5396 );
5397 assert!(
5406 value_width <= previous_width / 50.0 || value_width <= 2.0 * point_width,
5407 "w={width:e}: the value range fell only {previous_width:e} -> \
5408 {value_width:e}, and it is not at the point-enclosure floor \
5409 {point_width:e} — that is first-order behaviour"
5410 );
5411 previous_width = value_width;
5412 }
5413 }
5414
5415 #[test]
5434 fn the_centred_enclosure_holds_on_degenerate_adjacent_and_extreme_cells() {
5435 let grams = [1.0, 4.0, 1.0e-9, 2.5e7];
5436 let penalties = [1.0, 1.0, 1.0, 1.0];
5437 let projected = [0.5, 0.25, 1.0e-3, 3.0];
5438 let energies = [8.0];
5439 let profile =
5440 AffineRemlProfile::new(&grams, &penalties, &projected, &energies, 6.0, 4, 0.25)
5441 .expect("valid fixture");
5442
5443 for &x in &[-600.0_f64, -37.5, -1.0, 0.0, 2.75, 600.0] {
5444 let Ok((direct, _)) = profile.enclose_direct(x, x) else {
5445 continue;
5446 };
5447 let centred = profile.enclose(x, x).expect("a point cell must enclose");
5448 assert_eq!(
5449 centred, direct,
5450 "a point cell must return the natural extension untouched at x={x}"
5451 );
5452
5453 let up = next_up(x);
5455 let Ok(cell) = profile.enclose(x, up) else {
5456 continue;
5457 };
5458 let point = profile.enclose(x, x).expect("point cell");
5459 assert!(
5460 cell.score.value.lo <= point.score.value.lo
5461 && point.score.value.hi <= cell.score.value.hi,
5462 "adjacent-float cell at {x}: point value range {:?} escaped {:?}",
5463 point.score.value,
5464 cell.score.value
5465 );
5466 assert!(
5467 cell.derivative.lo <= point.derivative.lo
5468 && point.derivative.hi <= cell.derivative.hi,
5469 "adjacent-float cell at {x}: point derivative range {:?} escaped {:?}",
5470 point.derivative,
5471 cell.derivative
5472 );
5473 assert!(
5474 cell.score.value.is_valid() && cell.derivative.is_valid(),
5475 "adjacent-float cell at {x} produced an invalid enclosure: {cell:?}"
5476 );
5477
5478 let (wide, _) = profile.enclose_direct(x, up).expect("direct adjacent cell");
5480 assert!(
5481 cell.score.value.lo >= wide.score.value.lo
5482 && cell.score.value.hi <= wide.score.value.hi,
5483 "the centred value range {:?} is not inside the natural extension {:?} at {x}",
5484 cell.score.value,
5485 wide.score.value
5486 );
5487 assert!(
5488 cell.derivative.lo >= wide.derivative.lo
5489 && cell.derivative.hi <= wide.derivative.hi,
5490 "the centred derivative range {:?} is not inside the natural extension {:?} at {x}",
5491 cell.derivative,
5492 wide.derivative
5493 );
5494 }
5495 }
5496
5497 fn cascade_profile_parts() -> (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>) {
5516 let grams = vec![
5517 0.021513523027428847, 0.023421509558465926, 0.024477791743994424,
5518 0.03028760364561828, 0.03510108223379587, 0.040671848915996144,
5519 0.042394860646972565, 0.044208976267946384, 0.046980397477518414,
5520 0.051041787441650194, 0.053417305918114666, 0.05575657456312382,
5521 0.056982691606415704, 0.059623191536431024, 0.06072593823762461,
5522 0.061603808142128846, 0.0626306391548814, 0.06415989316153273, 0.06612727525342801,
5523 0.07201682707299777, 0.10499606046436369, 0.12037535776467499, 0.1486138626340859,
5524 0.1762399329554861, 0.19315924476245142, 0.26688703253550705, 0.2848266927054469,
5525 0.33232244706214037, 0.6015439556821448, 1.1406886269841172, 1.3973782387809837,
5526 1.8043547873076875, 2.0890420358314765,
5527 ];
5528 let penalties = vec![1.0_f64; 33];
5529 let projected = vec![
5530 0.0008447602450715568, 0.004744115853417025, 0.0013711877079256205,
5531 0.000556576229807026, 0.00032950514304538826, 0.00015869074743770514,
5532 0.004035749350652998, 0.002408288703125203, 0.0002161132863778849,
5533 0.0024599052556113317, 0.00028155268264135145, 9.068039769807838e-7,
5534 0.0004390033211936947, 0.004642257342083, 5.722227645019854e-6,
5535 0.003702111930202603, 0.003943553329808974, 0.0011808139994261783,
5536 1.490921408482301e-5, 0.001728436851442388, 0.00040290378245105683,
5537 0.0006710268119971442, 0.0032383572156905664, 0.00013742753101732549,
5538 6.681227329297447e-5, 0.054339495839186305, 0.018972176651153957,
5539 0.04535732957447296, 0.1129209190002305, 0.05428138627351111, 1.5501891913959478,
5540 0.14151749008562448, 0.11704548115908926,
5541 ];
5542 let energies = vec![2.7067510572921663_f64];
5543 (grams, penalties, projected, energies)
5544 }
5545
5546 #[test]
5575 fn the_centred_form_keeps_the_natural_extension_when_the_remainder_is_not_finite() {
5576 let direct = ClosedInterval::new(-10.0, 10.0);
5577 let point = ClosedInterval::new(-1.0, 1.0);
5578 let touching_zero = ClosedInterval::new(-0.5, 0.0);
5579 let straddling_zero = ClosedInterval::new(-0.5, 0.5);
5580
5581 let narrowed = ClosedInterval::new(f64::NAN, 1.0).mul(straddling_zero);
5584 assert!(
5585 narrowed.lo.is_finite() && narrowed.hi.is_finite(),
5586 "premise: a NaN endpoint must reduce to a finite-LOOKING range ({narrowed:?}); if \
5587 `mul` stops dropping it this gate is about nothing"
5588 );
5589 let infinite = ClosedInterval::new(f64::NEG_INFINITY, f64::NEG_INFINITY)
5592 .mul(ClosedInterval::new(-1.0, 0.0));
5593 assert!(
5594 infinite.lo <= 0.0 && infinite.hi.is_infinite(),
5595 "`inf * 0` must stay sound through `product_down`'s exact-zero mapping, got \
5596 {infinite:?}"
5597 );
5598
5599 for slope in [
5600 ClosedInterval::new(f64::NEG_INFINITY, 3.0),
5601 ClosedInterval::new(-3.0, f64::INFINITY),
5602 ClosedInterval::new(f64::NEG_INFINITY, f64::INFINITY),
5603 ClosedInterval::new(f64::NEG_INFINITY, f64::NEG_INFINITY),
5604 ClosedInterval::new(f64::NAN, 1.0),
5605 ClosedInterval::new(1.0, f64::NAN),
5606 ] {
5607 for offset in [touching_zero, straddling_zero, ClosedInterval::new(0.0, 0.5)] {
5608 assert_eq!(
5609 centred_or(direct, point, slope, offset),
5610 direct,
5611 "a non-finite slope {slope:?} over offset {offset:?} must leave the natural \
5612 extension in place"
5613 );
5614 }
5615 }
5616
5617 let tightened = centred_or(
5620 direct,
5621 point,
5622 ClosedInterval::new(-2.0, 2.0),
5623 straddling_zero,
5624 );
5625 assert!(
5626 tightened.lo > direct.lo && tightened.hi < direct.hi,
5627 "a finite remainder must still tighten: {tightened:?} against {direct:?}"
5628 );
5629 }
5630
5631 #[test]
5651 fn the_centred_ranges_contain_the_function_at_every_interior_point() {
5652 let (grams, penalties, projected, energies) = cascade_profile_parts();
5653 let profile = AffineRemlProfile::new(
5654 &grams,
5655 &penalties,
5656 &projected,
5657 &energies,
5658 33.0,
5659 33,
5660 9.226276711274537,
5661 )
5662 .expect("valid cascade profile");
5663
5664 let mut curvature_tightened = false;
5669 for centre in [-20.0_f64, -12.5, -6.0, -1.679, 3.0, 11.0, 17.5] {
5671 for exponent in [0_i32, -1, -2, -3, -4] {
5672 let half = 10.0_f64.powi(exponent);
5673 let (a, b) = (centre - half, centre + half);
5674 let cell = profile.enclose(a, b).expect("cell enclosure");
5675 let (natural, _) = profile.enclose_direct(a, b).expect("natural extension");
5676 assert!(
5677 cell.curvature.lo >= natural.curvature.lo
5678 && cell.curvature.hi <= natural.curvature.hi,
5679 "cell [{a}, {b}]: the centred curvature {:?} is not inside the natural \
5680 extension {:?}",
5681 cell.curvature,
5682 natural.curvature
5683 );
5684 if cell.curvature.hi - cell.curvature.lo
5685 < 0.5 * (natural.curvature.hi - natural.curvature.lo)
5686 {
5687 curvature_tightened = true;
5688 }
5689 for step in 0..=8 {
5690 let x = a + (b - a) * (step as f64 / 8.0);
5691 let point = profile.enclose(x, x).expect("point enclosure");
5692 assert!(
5693 cell.score.value.lo <= point.score.value.lo
5694 && point.score.value.hi <= cell.score.value.hi,
5695 "cell [{a}, {b}] value range {:?} does not contain the exact value at \
5696 x={x}, {:?}",
5697 cell.score.value,
5698 point.score.value
5699 );
5700 assert!(
5701 cell.derivative.lo <= point.derivative.lo
5702 && point.derivative.hi <= cell.derivative.hi,
5703 "cell [{a}, {b}] derivative range {:?} does not contain the exact \
5704 derivative at x={x}, {:?}",
5705 cell.derivative,
5706 point.derivative
5707 );
5708 assert!(
5709 cell.curvature.lo <= point.curvature.lo
5710 && point.curvature.hi <= cell.curvature.hi,
5711 "cell [{a}, {b}] curvature range {:?} does not contain the exact \
5712 curvature at x={x}, {:?} — the third-derivative kernel the curvature \
5713 is centred on is wrong",
5714 cell.curvature,
5715 point.curvature
5716 );
5717 }
5718 }
5719 }
5720 assert!(
5721 curvature_tightened,
5722 "the centred curvature never halved the natural extension's range anywhere in this \
5723 sweep, so the containment checks above would pass for a WRONG third-derivative \
5724 kernel too — this gate has gone vacuous"
5725 );
5726 }
5727
5728 #[test]
5752 fn the_located_optimum_is_enclosure_independent_and_accurate_to_the_contract() {
5753 let grams = [1.0_f64; 3];
5757 let penalties = [1.0_f64; 3];
5758 let projected = [4.0 / 3.0; 3];
5759 let energies = [10.0_f64];
5760 let profile =
5761 AffineRemlProfile::new(&grams, &penalties, &projected, &energies, 15.0, 3, 0.0)
5762 .expect("valid ridge profile");
5763 let lo = certified_ln_positive(f64::MIN_POSITIVE).expect("lo").lo;
5764 let hi = certified_ln_positive(f64::MAX / 2.0).expect("hi").hi;
5765 let resolution = f64::EPSILON.sqrt();
5766 let truth = 0.6_f64;
5769
5770 let natural = maximize_score_1d(
5771 lo,
5772 hi,
5773 resolution,
5774 |x| profile.evaluate(x),
5775 |a, b| profile.enclose_direct(a.x, b.x).map(|(e, _)| e),
5776 )
5777 .expect("the natural extension decomposes this domain");
5778 let centred = maximize_score_1d(lo, hi, resolution, |x| profile.evaluate(x), |a, b| {
5779 profile.enclose(a.x, b.x)
5780 })
5781 .expect("the centred form decomposes this domain");
5782
5783 assert_eq!(
5784 natural.optimum.x, centred.optimum.x,
5785 "the two enclosure forms located different optima ({} against {}); tightening may \
5786 change which cells are visited but must not move the certified root",
5787 natural.optimum.x, centred.optimum.x
5788 );
5789 for (label, search) in [("natural", &natural), ("centred", ¢red)] {
5790 assert!(
5791 matches!(search.location, ScoreOptimumLocation::Stationary(_)),
5792 "{label}: this fixture has an interior stationary optimum, got {:?}",
5793 search.location
5794 );
5795 let offset = (search.optimum.x - truth.ln()).abs();
5796 assert!(
5797 offset <= resolution,
5798 "{label}: the located root is {offset:e} from the closed form in rho, outside \
5799 the requested resolution {resolution:e} — that is a location-contract failure"
5800 );
5801 assert!(
5804 offset > 0.0,
5805 "{label}: an exactly-attained root would mean this gate has stopped measuring \
5806 what it claims"
5807 );
5808 }
5809 }
5810
5811 #[test]
5822 fn zz_measure_centred_enclosure_search_cost() {
5823 let (grams, penalties, projected, energies) = cascade_profile_parts();
5824 let cascade = AffineRemlProfile::new(
5825 &grams,
5826 &penalties,
5827 &projected,
5828 &energies,
5829 33.0,
5830 33,
5831 9.226276711274537,
5832 )
5833 .expect("valid cascade profile");
5834
5835 let full_lo = certified_ln_positive(f64::MIN_POSITIVE).expect("domain lo").lo;
5836 let full_hi = certified_ln_positive(f64::MAX / 2.0).expect("domain hi").hi;
5837 let cases: [(&str, f64, f64); 3] = [
5838 ("cascade/40.6-wide", -21.860900258111, 18.75853229939662),
5841 ("cascade/narrow-around-the-optimum", -3.0, 0.0),
5846 ("cascade/full-representable-domain", full_lo, full_hi),
5849 ];
5850
5851 for (label, lo, hi) in cases {
5852 let profile = &cascade;
5853 let resolution = f64::EPSILON.sqrt();
5854 let started = std::time::Instant::now();
5855 let natural = maximize_score_1d(
5856 lo,
5857 hi,
5858 resolution,
5859 |x| profile.evaluate(x),
5860 |a, b| profile.enclose_direct(a.x, b.x).map(|(e, _)| e),
5861 );
5862 let natural_seconds = started.elapsed().as_secs_f64();
5863 let started = std::time::Instant::now();
5864 let centred = maximize_score_1d(
5865 lo,
5866 hi,
5867 resolution,
5868 |x| profile.evaluate(x),
5869 |a, b| profile.enclose(a.x, b.x),
5870 );
5871 let centred_seconds = started.elapsed().as_secs_f64();
5872 println!(
5873 "#COST {label}: natural {:.4}s ({}) centred {:.4}s ({}) speedup {:.2}x",
5874 natural_seconds,
5875 natural.as_ref().map_or("REFUSED", |_| "ok"),
5876 centred_seconds,
5877 centred.as_ref().map_or("REFUSED", |_| "ok"),
5878 natural_seconds / centred_seconds.max(f64::MIN_POSITIVE),
5879 );
5880 assert!(
5885 centred.is_ok() || natural.is_err(),
5886 "{label}: the centred oracle refused ({centred:?}) where the natural extension \
5887 succeeded — an intersection can only tighten, so this is impossible unless the \
5888 centred form is unsound"
5889 );
5890 if natural.is_ok() {
5894 assert!(
5895 centred_seconds <= natural_seconds * 2.5 + 1.0e-3,
5896 "{label}: centring cost {centred_seconds:.4}s against the natural \
5897 extension's {natural_seconds:.4}s — more than the doubled per-cell work \
5898 can explain"
5899 );
5900 }
5901 }
5902 }
5903
5904 #[test]
5921 fn the_natural_extension_cannot_decompose_a_domain_the_centred_form_certifies() {
5922 let (grams, penalties, projected, energies) = cascade_profile_parts();
5923 let profile = AffineRemlProfile::new(
5924 &grams,
5925 &penalties,
5926 &projected,
5927 &energies,
5928 33.0,
5929 33,
5930 9.226276711274537,
5931 )
5932 .expect("valid cascade profile");
5933
5934 let (lo, hi) = (-21.860900258111_f64, 18.75853229939662);
5936 let resolution = f64::EPSILON.sqrt();
5937
5938 let natural = maximize_score_1d(
5939 lo,
5940 hi,
5941 resolution,
5942 |x| profile.evaluate(x),
5943 |a, b| profile.enclose_direct(a.x, b.x).map(|(enclosure, _)| enclosure),
5944 );
5945 let centred = maximize_score_1d(
5946 lo,
5947 hi,
5948 resolution,
5949 |x| profile.evaluate(x),
5950 |a, b| profile.enclose(a.x, b.x),
5951 );
5952
5953 let centred = centred.unwrap_or_else(|error| {
5954 panic!(
5955 "the centred enclosure must decompose this 33-mode cascade domain: {error}"
5956 )
5957 });
5958 assert!(
5959 matches!(
5960 natural,
5961 Err(ScoreSearchError::SubdivisionBudget { .. } | ScoreSearchError::Unresolved { .. })
5962 ),
5963 "PREMISE LOST: the natural extension now decomposes this domain \
5964 ({natural:?}), so this fixture no longer exercises the defect and the \
5965 comparison below proves nothing — widen the mode spread or the domain \
5966 until it refuses again",
5967 );
5968
5969 assert!(
5972 !matches!(centred.location, ScoreOptimumLocation::ResolutionFlat(_)),
5973 "the centred search must decide a location, got {:?}",
5974 centred.location
5975 );
5976 assert!(
5977 centred.value_certificate.maximum_excess
5978 <= centred.value_certificate.comparison_resolution,
5979 "the centred search's value ordering must close: excess {} against {}",
5980 centred.value_certificate.maximum_excess,
5981 centred.value_certificate.comparison_resolution
5982 );
5983 assert!(
5984 centred.optimum.x >= lo && centred.optimum.x <= hi && centred.optimum.x.is_finite(),
5985 "the selected log lambda must lie in the domain, got {}",
5986 centred.optimum.x
5987 );
5988 }
5989
5990 #[test]
5991 fn affine_reml_zero_smoothing_schur_residual_keeps_division_low_parts() {
5992 let grams = [3.0; 3];
5999 let penalties = [1.0; 3];
6000 let projected = [1.0; 3];
6001 let energies = [1.0];
6002 let profile =
6003 AffineRemlProfile::new(&grams, &penalties, &projected, &energies, 3.0, 3, 0.0)
6004 .expect("valid nonrepresentable-quotient fixture");
6005
6006 let zero_residual = profile.zero_lambda_residual[0];
6007 assert!(
6008 zero_residual.contains_zero(),
6009 "the exact identity 1 - 3*(1/3) = 0 must be retained: {zero_residual:?}"
6010 );
6011 assert!(
6012 zero_residual.hi - zero_residual.lo < 1.0e-28,
6013 "division corrections must live below ordinary binary64 cancellation scale: \
6014 {zero_residual:?}"
6015 );
6016
6017 let rho = -23.025850929940457_f64;
6018 let enclosure = profile
6019 .enclose(rho, rho)
6020 .expect("the small positive smoothing residual must remain resolved");
6021 assert!(
6022 enclosure.derivative.contains_zero(),
6023 "determinant and residual derivatives cancel analytically: {:?}",
6024 enclosure.derivative
6025 );
6026 assert!(
6027 enclosure.derivative.hi - enclosure.derivative.lo < 1.0e-6,
6028 "the exact Schur residual must control the profiled derivative: {:?}",
6029 enclosure.derivative
6030 );
6031 }
6032
6033 #[test]
6034 fn affine_reml_saturated_tail_preserves_complement_signs() {
6035 let profile = AffineRemlProfile::new(&[1.0], &[1.0], &[0.0], &[1.0], 4.0, 1, 0.0)
6036 .expect("valid saturated-tail fixture");
6037 let log_lambda = 700.0;
6038 let jet = profile.evaluate(log_lambda).expect("point jet");
6039 let enclosure = profile
6040 .enclose(log_lambda, log_lambda)
6041 .expect("point enclosure");
6042
6043 assert!(
6044 jet.derivative > 0.0,
6045 "the point derivative must preserve +0.5/(1+exp(rho)), got {}",
6046 jet.derivative
6047 );
6048 assert!(
6049 jet.curvature < 0.0,
6050 "the point curvature must preserve its negative u*c sign, got {}",
6051 jet.curvature
6052 );
6053 assert!(
6054 enclosure.curvature.hi <= 0.0,
6055 "the exact saturated curvature remains nonpositive: {:?}",
6056 enclosure.curvature
6057 );
6058 assert!(
6059 enclosure.derivative.lo >= 0.0,
6060 "the exact saturated derivative remains nonnegative: {:?}",
6061 enclosure.derivative
6062 );
6063 let score = enclosure.score;
6064 assert!(score.evaluation_error.is_finite());
6065 assert!(
6066 score
6067 .value
6068 .widen(score.evaluation_error)
6069 .contains(jet.value),
6070 "the stable score evaluator must lie inside its exact value range plus forward error"
6071 );
6072 }
6073
6074 #[test]
6075 fn affine_reml_extreme_domain_one_direction_encloses_and_maximizes_repeatably() {
6076 let gram_modes = [1.0, 1.0, 1.0];
6086 let penalty_modes = [1.0, 1.0, 1.0];
6087 let projected_rhs_squared = [4.0 / 3.0, 4.0 / 3.0, 4.0 / 3.0];
6088 let response_energy = [10.0];
6089 let profile = AffineRemlProfile::new(
6090 &gram_modes,
6091 &penalty_modes,
6092 &projected_rhs_squared,
6093 &response_energy,
6094 15.0,
6095 3,
6096 0.0,
6097 )
6098 .expect("valid normalized one-direction ridge profile");
6099 let rho_lo = certified_ln_positive(f64::MIN_POSITIVE)
6100 .expect("finite-domain lower log bound")
6101 .lo;
6102 let rho_hi = certified_ln_positive(f64::MAX / 2.0)
6103 .expect("finite-domain upper log bound")
6104 .hi;
6105
6106 let whole_domain = profile
6107 .enclose(rho_lo, rho_hi)
6108 .expect("scale-safe relative exp error keeps the full-domain residual finite");
6109 assert!(
6110 whole_domain.score.value.is_valid()
6111 && whole_domain.score.value.lo.is_finite()
6112 && whole_domain.score.value.hi.is_finite()
6113 );
6114 assert!(whole_domain.score.evaluation_error.is_finite());
6115 assert!(whole_domain.derivative.contains_zero());
6116
6117 let resolution = f64::EPSILON.sqrt();
6118 let first = profile
6119 .maximize_value_ordered(rho_lo, rho_hi, resolution)
6120 .expect("finite subdivision must certify the planted stationary optimum");
6121 let repeated = profile
6122 .maximize_value_ordered(rho_lo, rho_hi, resolution)
6123 .expect("the same exact search must be repeatable");
6124 assert_eq!(first, repeated);
6125 let ScoreOptimumLocation::Stationary(index) = first.location else {
6126 panic!(
6127 "the planted one-direction optimum must be stationary, got {:?}",
6128 first.location
6129 );
6130 };
6131 let stationary = first
6132 .stationary_points
6133 .get(index)
6134 .expect("stationary result index");
6135 let expected = certified_ln_positive(0.6).expect("analytic stationary log");
6136 assert!(
6137 stationary.bracket.lo <= expected.lo && stationary.bracket.hi >= expected.hi,
6138 "certified bracket {:?} must contain analytic log(0.6) {:?}",
6139 stationary.bracket,
6140 expected
6141 );
6142 assert!(
6143 first.value_certificate.maximum_excess <= first.value_certificate.comparison_resolution,
6144 "an isolated stationary root is not yet a globally ordered score candidate: \
6145 maximum excess {}, comparison resolution {}, bracket {:?}",
6146 first.value_certificate.maximum_excess,
6147 first.value_certificate.comparison_resolution,
6148 stationary.bracket,
6149 );
6150 }
6151
6152 #[test]
6153 fn affine_reml_gram_zero_subnormal_zero_projection_is_structural() {
6154 let minimum_subnormal = f64::from_bits(1);
6155 let log_lambda = -740.0;
6156 let lambda = exp_interval(log_lambda, log_lambda)
6157 .expect("the fixture needs a certified subnormal lambda");
6158 assert!(lambda.lo > 0.0 && lambda.hi < f64::MIN_POSITIVE);
6159 let raw_h = lambda.mul(ClosedInterval::point(minimum_subnormal));
6160 assert!(
6161 raw_h.lo < 0.0 && raw_h.hi > 0.0,
6162 "the raw outward product must cross rounded zero: {raw_h:?}"
6163 );
6164 let h = raw_h.nonnegative();
6165 assert_eq!(
6166 h.lo, 0.0,
6167 "known nonnegative product must clamp its outward lower bound to zero"
6168 );
6169
6170 let ranges = mode_ranges(0.0, minimum_subnormal, 0.0, lambda)
6171 .expect("the zero projection cancels before any residual division");
6172 assert_eq!(ranges.c, ClosedInterval::point(0.0));
6173 assert_eq!(ranges.w, ClosedInterval::point(0.0));
6174 assert_eq!(ranges.v, ClosedInterval::point(0.0));
6175 assert_eq!(ranges.p, ClosedInterval::point(0.0));
6176 assert_eq!(ranges.q, ClosedInterval::point(0.0));
6177
6178 let gram_modes = [0.0];
6179 let penalty_modes = [minimum_subnormal];
6180 let projected_rhs_squared = [0.0];
6181 let response_energy = [1.0];
6182 let profile = AffineRemlProfile::new(
6183 &gram_modes,
6184 &penalty_modes,
6185 &projected_rhs_squared,
6186 &response_energy,
6187 1.0,
6188 1,
6189 0.0,
6190 )
6191 .expect("valid gram-zero structural fixture");
6192 let jet = profile
6193 .evaluate(log_lambda)
6194 .expect("normalized determinant and zero residual projection stay finite");
6195 let enclosure = profile
6196 .enclose(log_lambda, log_lambda)
6197 .expect("the proof path must not divide by a zero-containing h interval");
6198 assert_eq!(jet.derivative, 0.0);
6199 assert_eq!(jet.curvature, 0.0);
6200 assert!(is_exact_zero(enclosure.derivative));
6201 assert!(is_exact_zero(enclosure.curvature));
6202 assert!(
6203 enclosure
6204 .score
6205 .value
6206 .widen(enclosure.score.evaluation_error)
6207 .contains(jet.value)
6208 );
6209 }
6210
6211 #[test]
6212 fn affine_reml_gram_zero_subnormal_nonzero_projection_stays_finite() {
6213 let minimum_subnormal = f64::from_bits(1);
6214 let log_lambda = -740.0;
6215 let lambda = exp_interval(log_lambda, log_lambda)
6216 .expect("the fixture needs a certified subnormal lambda");
6217 let penalty = 0.01;
6218 let h = lambda.mul(ClosedInterval::point(penalty)).nonnegative();
6219 assert_eq!(
6220 h.lo, 0.0,
6221 "the fixture must enter the structural quotient path"
6222 );
6223
6224 let ranges = mode_ranges(0.0, penalty, minimum_subnormal, lambda)
6225 .expect("the scaled quotient has a finite representable range");
6226 assert_eq!(ranges.c, ClosedInterval::point(0.0));
6227 assert_eq!(ranges.w, ClosedInterval::point(0.0));
6228 assert!(ranges.v.lo > 0.0 && ranges.v.hi.is_finite());
6229 assert_eq!(ranges.p, ranges.v);
6230 assert_eq!(ranges.q, ranges.v.neg());
6231
6232 let gram_modes = [0.0];
6233 let penalty_modes = [penalty];
6234 let projected_rhs_squared = [minimum_subnormal];
6235 let response_energy = [10.0];
6236 let profile = AffineRemlProfile::new(
6237 &gram_modes,
6238 &penalty_modes,
6239 &projected_rhs_squared,
6240 &response_energy,
6241 1.0,
6242 1,
6243 0.0,
6244 )
6245 .expect("valid gram-zero finite-ratio fixture");
6246 let jet = profile
6247 .evaluate(log_lambda)
6248 .expect("the point ratio must avoid the underflowing product");
6249 let enclosure = profile
6250 .enclose(log_lambda, log_lambda)
6251 .expect("the interval ratio must remain finite without a reciprocal overflow");
6252 assert!(
6253 enclosure
6254 .score
6255 .value
6256 .widen(enclosure.score.evaluation_error)
6257 .contains(jet.value)
6258 );
6259 }
6260
6261 #[test]
6262 fn affine_reml_gram_zero_unrepresentable_projection_is_typed() {
6263 let minimum_subnormal = f64::from_bits(1);
6264 let log_lambda = -740.0;
6265 let gram_modes = [0.0];
6266 let penalty_modes = [minimum_subnormal];
6267 let projected_rhs_squared = [1.0];
6268 let response_energy = [10.0];
6269 let profile = AffineRemlProfile::new(
6270 &gram_modes,
6271 &penalty_modes,
6272 &projected_rhs_squared,
6273 &response_energy,
6274 1.0,
6275 1,
6276 0.0,
6277 )
6278 .expect("valid gram-zero refusal fixture");
6279 assert!(matches!(
6280 profile.evaluate(log_lambda),
6281 Err(AffineRemlError::ElementaryEnclosureUnavailable {
6282 function: "gram-zero residual quotient",
6283 ..
6284 })
6285 ));
6286 assert!(matches!(
6287 profile.enclose(log_lambda, log_lambda),
6288 Err(AffineRemlError::ElementaryEnclosureUnavailable {
6289 function: "gram-zero residual quotient",
6290 ..
6291 })
6292 ));
6293 }
6294
6295 #[test]
6296 fn affine_reml_rejects_nonpositive_profile_residual() {
6297 let profile = AffineRemlProfile::new(&[1.0], &[1.0], &[2.0], &[1.0], 4.0, 1, 0.0)
6298 .expect("statically valid");
6299 assert!(matches!(
6300 profile.evaluate(-2.0),
6301 Err(AffineRemlError::NonPositiveResidual { .. })
6302 ));
6303 }
6304}