1use std::fmt;
59use std::sync::OnceLock;
60
61#[derive(Clone, Copy, Debug, PartialEq)]
67pub struct ClosedInterval {
68 pub lo: f64,
69 pub hi: f64,
70}
71
72impl ClosedInterval {
73 #[inline]
74 pub const fn new(lo: f64, hi: f64) -> Self {
75 Self { lo, hi }
76 }
77
78 #[inline]
79 pub const fn point(value: f64) -> Self {
80 Self {
81 lo: value,
82 hi: value,
83 }
84 }
85
86 #[inline]
87 pub const fn entire() -> Self {
88 Self {
89 lo: f64::NEG_INFINITY,
90 hi: f64::INFINITY,
91 }
92 }
93
94 #[inline]
95 pub fn contains(self, value: f64) -> bool {
96 self.lo <= value && value <= self.hi
97 }
98
99 #[inline]
100 pub fn contains_zero(self) -> bool {
101 self.contains(0.0)
102 }
103
104 #[inline]
105 fn is_valid(self) -> bool {
106 !self.lo.is_nan() && !self.hi.is_nan() && self.lo <= self.hi
107 }
108
109 #[inline]
110 fn hull(self, other: Self) -> Self {
111 Self {
112 lo: self.lo.min(other.lo),
113 hi: self.hi.max(other.hi),
114 }
115 }
116
117 #[inline]
118 fn intersection(self, other: Self) -> Option<Self> {
119 let intersection = Self {
120 lo: self.lo.max(other.lo),
121 hi: self.hi.min(other.hi),
122 };
123 (intersection.lo <= intersection.hi).then_some(intersection)
124 }
125
126 #[inline]
127 fn max_abs(self) -> f64 {
128 self.lo.abs().max(self.hi.abs())
129 }
130
131 #[inline]
132 fn widen(self, radius: f64) -> Self {
133 if radius == 0.0 {
134 return self;
135 }
136 if radius == f64::INFINITY {
137 return Self::entire();
138 }
139 Self {
140 lo: next_down(self.lo - radius),
141 hi: next_up(self.hi + radius),
142 }
143 }
144
145 #[inline]
146 pub fn add(self, other: Self) -> Self {
148 Self {
149 lo: sum_down(self.lo, other.lo),
150 hi: sum_up(self.hi, other.hi),
151 }
152 }
153
154 #[inline]
155 pub fn sub(self, other: Self) -> Self {
157 Self {
158 lo: sum_down(self.lo, -other.hi),
159 hi: sum_up(self.hi, -other.lo),
160 }
161 }
162
163 #[inline]
164 pub fn neg(self) -> Self {
166 Self {
167 lo: -self.hi,
168 hi: -self.lo,
169 }
170 }
171
172 pub fn mul(self, other: Self) -> Self {
174 let pairs = [
175 (self.lo, other.lo),
176 (self.lo, other.hi),
177 (self.hi, other.lo),
178 (self.hi, other.hi),
179 ];
180 let mut lo = f64::INFINITY;
181 let mut hi = f64::NEG_INFINITY;
182 for (left, right) in pairs {
183 lo = lo.min(product_down(left, right));
184 hi = hi.max(product_up(left, right));
185 }
186 Self { lo, hi }
187 }
188
189 #[inline]
190 pub fn scale(self, value: f64) -> Self {
193 self.mul(Self::point(value))
194 }
195
196 fn square(self) -> Self {
197 if self.lo >= 0.0 {
198 Self {
199 lo: product_down(self.lo, self.lo).max(0.0),
200 hi: product_up(self.hi, self.hi),
201 }
202 } else if self.hi <= 0.0 {
203 Self {
204 lo: product_down(self.hi, self.hi).max(0.0),
205 hi: product_up(self.lo, self.lo),
206 }
207 } else {
208 Self {
209 lo: 0.0,
210 hi: product_up(self.lo, self.lo).max(product_up(self.hi, self.hi)),
211 }
212 }
213 }
214
215 fn ln_positive(self) -> Self {
217 assert!(
218 self.lo > 0.0,
219 "ln_positive requires a strictly positive interval, got lo={}",
220 self.lo
221 );
222 let lo = certified_ln_positive(self.lo)
223 .expect("ln_positive lower endpoint is finite and positive");
224 let hi = certified_ln_positive(self.hi)
225 .expect("ln_positive upper endpoint is finite and positive");
226 Self::new(lo.lo, hi.hi)
227 }
228
229 fn div_positive(self, denominator: Self) -> Self {
231 assert!(
232 denominator.lo > 0.0,
233 "div_positive requires a strictly positive denominator interval, got lo={}",
234 denominator.lo
235 );
236 let reciprocal = Self {
237 lo: quotient_down(1.0, denominator.hi).max(0.0),
238 hi: quotient_up(1.0, denominator.lo),
239 };
240 self.mul(reciprocal)
241 }
242
243 fn div_nonzero(self, denominator: Self) -> Self {
245 if denominator.lo > 0.0 {
246 self.div_positive(denominator)
247 } else {
248 assert!(
249 denominator.hi < 0.0,
250 "div_nonzero requires a denominator interval excluding zero, got {denominator:?}"
251 );
252 self.div_positive(denominator.neg()).neg()
253 }
254 }
255
256 #[inline]
257 fn nonnegative(self) -> Self {
258 Self {
259 lo: self.lo.max(0.0),
260 hi: self.hi.max(0.0),
261 }
262 }
263}
264
265#[derive(Clone, Copy, Debug, PartialEq)]
276pub struct ScoreJet {
277 pub value: f64,
278 pub derivative: f64,
279 pub curvature: f64,
280 pub third: f64,
281}
282
283#[derive(Clone, Copy, Debug, PartialEq)]
285pub struct ScoreSample {
286 pub x: f64,
287 pub value: f64,
288 pub derivative: f64,
289 pub curvature: f64,
290 pub third: f64,
291}
292
293#[derive(Clone, Copy, Debug, PartialEq)]
306pub struct ScoreValueEnclosure {
307 pub value: ClosedInterval,
308 pub evaluation_error: f64,
309}
310
311#[derive(Clone, Copy, Debug, PartialEq)]
317pub struct DerivativeEnclosure {
318 pub score: ScoreValueEnclosure,
319 pub derivative: ClosedInterval,
320 pub curvature: ClosedInterval,
321}
322
323#[derive(Clone, Copy, Debug, PartialEq)]
331pub struct ResolutionFlatRegion {
332 pub sample: ScoreSample,
333 pub bracket: ClosedInterval,
334 pub score: ClosedInterval,
336 pub max_score_gap: f64,
337 pub score_resolution: f64,
338}
339
340#[derive(Clone, Copy, Debug, PartialEq)]
344pub struct StationaryPoint {
345 pub sample: ScoreSample,
346 pub bracket: ClosedInterval,
347 pub score: ScoreValueEnclosure,
349 pub curvature: ClosedInterval,
355}
356
357#[derive(Clone, Copy, Debug, PartialEq)]
360pub struct GlobalScoreCertificate {
361 pub selected: ClosedInterval,
363 pub maximum: ClosedInterval,
365 pub maximum_excess: f64,
370 pub comparison_resolution: f64,
374}
375
376#[derive(Clone, Copy, Debug, PartialEq, Eq)]
377pub enum ScoreOptimumLocation {
378 LowerBoundary,
379 UpperBoundary,
380 Stationary(usize),
381 ResolutionFlat(usize),
382}
383
384#[derive(Clone, Copy, Debug, PartialEq)]
391pub struct DominatedRegion {
392 pub bracket: ClosedInterval,
393 pub score: ScoreValueEnclosure,
394 pub incumbent_lower: f64,
395}
396
397#[derive(Clone, Debug, PartialEq)]
402pub struct ScoreSearchResult {
403 pub optimum: ScoreSample,
404 pub location: ScoreOptimumLocation,
405 pub lower_boundary: ScoreSample,
406 pub upper_boundary: ScoreSample,
407 pub stationary_points: Vec<StationaryPoint>,
408 pub resolution_flat_regions: Vec<ResolutionFlatRegion>,
409 pub dominated_regions: Vec<DominatedRegion>,
413 pub value_certificate: GlobalScoreCertificate,
414}
415
416#[derive(Debug)]
418pub enum ScoreSearchError<E> {
419 InvalidDomain {
420 lo: f64,
421 hi: f64,
422 },
423 InvalidResolution {
424 resolution: f64,
425 },
426 PointEvaluation {
427 x: f64,
428 source: E,
429 },
430 EnclosureEvaluation {
431 lo: f64,
432 hi: f64,
433 source: E,
434 },
435 NonFiniteSample {
436 sample: ScoreSample,
437 },
438 InvalidEnclosure {
439 lo: f64,
440 hi: f64,
441 enclosure: DerivativeEnclosure,
442 },
443 ScoreValueEnclosureMissesEndpoint {
444 lo: f64,
445 hi: f64,
446 endpoint: ScoreSample,
447 score: ScoreValueEnclosure,
448 },
449 DisjointEndpointEnclosure {
450 lo: f64,
451 hi: f64,
452 endpoint: ScoreSample,
453 endpoint_derivative: ClosedInterval,
454 enclosure: DerivativeEnclosure,
455 },
456 InconsistentRootEnclosure {
460 lo: f64,
461 hi: f64,
462 left_derivative: ClosedInterval,
463 right_derivative: ClosedInterval,
464 curvature: ClosedInterval,
465 left_newton: ClosedInterval,
466 right_newton: ClosedInterval,
467 point_newton: ClosedInterval,
468 },
469 Unresolved {
473 lo: f64,
474 hi: f64,
475 requested_resolution: f64,
476 enclosure: DerivativeEnclosure,
477 },
478 SubdivisionBudget {
487 lo: f64,
488 hi: f64,
489 cell_lo: f64,
490 cell_hi: f64,
491 requested_resolution: f64,
492 subdivisions: usize,
493 budget: usize,
494 depth_bound: u32,
495 enclosure: DerivativeEnclosure,
496 },
497}
498
499pub fn subdivision_budget(lo: f64, hi: f64, resolution: f64) -> (usize, u32) {
614 let width = hi - lo;
615 if !(width.is_finite() && width > 0.0 && resolution.is_finite() && resolution > 0.0) {
616 return (1, 0);
617 }
618 let levels = (width / resolution).log2().ceil();
619 let depth_bound = if levels.is_finite() && levels >= 1.0 {
620 levels.min(u32::MAX as f64) as u32
623 } else {
624 1
625 };
626 let depth = depth_bound as usize;
627 (8 * depth * depth, depth_bound)
628}
629
630impl<E: fmt::Display> fmt::Display for ScoreSearchError<E> {
631 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
632 match self {
633 Self::InvalidDomain { lo, hi } => {
634 write!(f, "score search: invalid domain [{lo}, {hi}]")
635 }
636 Self::InvalidResolution { resolution } => {
637 write!(f, "score search: invalid resolution {resolution}")
638 }
639 Self::PointEvaluation { x, source } => {
640 write!(f, "score search: evaluation failed at {x}: {source}")
641 }
642 Self::EnclosureEvaluation { lo, hi, source } => write!(
643 f,
644 "score search: score/derivative enclosure failed on [{lo}, {hi}]: {source}"
645 ),
646 Self::NonFiniteSample { sample } => write!(
647 f,
648 "score search: non-finite jet at {} (value {}, derivative {}, curvature {}, third {})",
649 sample.x, sample.value, sample.derivative, sample.curvature, sample.third
650 ),
651 Self::InvalidEnclosure { lo, hi, enclosure } => write!(
652 f,
653 "score search: invalid score/derivative enclosure on [{lo}, {hi}]: {enclosure:?}"
654 ),
655 Self::ScoreValueEnclosureMissesEndpoint {
656 lo,
657 hi,
658 endpoint,
659 score,
660 } => write!(
661 f,
662 "score search: exact score range {:?} plus evaluator error {} on [{lo}, {hi}] misses the rounded endpoint value {} at {}",
663 score.value, score.evaluation_error, endpoint.value, endpoint.x
664 ),
665 Self::DisjointEndpointEnclosure {
666 lo,
667 hi,
668 endpoint,
669 endpoint_derivative,
670 enclosure,
671 } => write!(
672 f,
673 "score search: derivative enclosures on [{lo}, {hi}] and its endpoint {} are disjoint: endpoint range {endpoint_derivative:?}, cell {enclosure:?}; point estimate {endpoint:?}",
674 endpoint.x
675 ),
676 Self::InconsistentRootEnclosure {
677 lo,
678 hi,
679 left_derivative,
680 right_derivative,
681 curvature,
682 left_newton,
683 right_newton,
684 point_newton,
685 } => write!(
686 f,
687 "score search: interval-Newton certificates for the unique root on [{lo}, {hi}] \
688 are inconsistent: left derivative {left_derivative:?}, right derivative \
689 {right_derivative:?}, curvature {curvature:?}, left image {left_newton:?}, \
690 right image {right_newton:?}, point image {point_newton:?}"
691 ),
692 Self::Unresolved {
693 lo,
694 hi,
695 requested_resolution,
696 enclosure,
697 } => {
698 let evaluation_error = enclosure.score.evaluation_error;
703 let verdict = if evaluation_error >= *requested_resolution {
704 " -- the REQUEST is unsatisfiable: the certified evaluation error at this cell \
705 already reaches the requested resolution, so no bracket narrower than about \
706 twice that error is decidable and no additional subdivision can close it"
707 } else {
708 ""
709 };
710 write!(
711 f,
712 "score search: stationary structure unresolved on [{lo}, {hi}] at requested \
713 resolution {requested_resolution} (certified evaluation error \
714 {evaluation_error:e}){verdict}: {enclosure:?}"
715 )
716 }
717 Self::SubdivisionBudget {
718 lo,
719 hi,
720 cell_lo,
721 cell_hi,
722 requested_resolution,
723 subdivisions,
724 budget,
725 depth_bound,
726 enclosure,
727 } => {
728 let evaluation_error = enclosure.score.evaluation_error;
732 let verdict = if evaluation_error >= *requested_resolution {
733 "a LARGER BUDGET CANNOT HELP -- the certified evaluation error already reaches \
734 the requested resolution, so no subdivision separates stationary structure at \
735 this tolerance; the resolution asked for is finer than the evaluator delivers"
736 } else {
737 "the evaluation error is below the requested resolution, so this cell was still \
738 separable and a larger budget may resolve it"
739 };
740 write!(
741 f,
742 "score search: {subdivisions} cell subdivisions on [{lo}, {hi}] at requested \
743 resolution {requested_resolution} exceed the budget {budget} derived from this \
744 domain's subdivision depth bound {depth_bound}; the criterion is still \
745 undecomposable at [{cell_lo}, {cell_hi}], so it neither excludes nor isolates \
746 stationary structure over a region the search can only enumerate. Certified \
747 evaluation error at this cell is {evaluation_error:e} against requested \
748 resolution {requested_resolution:e}: {verdict}"
749 )
750 }
751 }
752 }
753}
754
755impl<E: std::error::Error + 'static> std::error::Error for ScoreSearchError<E> {}
756
757#[derive(Clone, Copy)]
758struct SearchSample {
759 sample: ScoreSample,
760 point_enclosure: Option<DerivativeEnclosure>,
761}
762
763#[derive(Clone, Copy)]
764struct SearchNode {
765 left: SearchSample,
766 right: SearchSample,
767}
768
769#[derive(Clone, Copy)]
770struct TerminalScoreCandidate {
771 score: ScoreValueEnclosure,
772 comparison_error: f64,
778 point_x: Option<f64>,
782}
783
784impl TerminalScoreCandidate {
785 #[inline]
786 fn point(x: f64, score: ScoreValueEnclosure) -> Self {
787 Self {
788 score,
789 comparison_error: score.evaluation_error,
790 point_x: Some(x),
791 }
792 }
793
794 #[inline]
795 fn region(score: ScoreValueEnclosure, comparison_error: f64) -> Self {
796 Self {
797 score,
798 comparison_error,
799 point_x: None,
800 }
801 }
802}
803
804fn evaluate_sample<E, F>(x: f64, evaluate: &mut F) -> Result<SearchSample, ScoreSearchError<E>>
805where
806 F: FnMut(f64) -> Result<ScoreJet, E>,
807{
808 let jet = evaluate(x).map_err(|source| ScoreSearchError::PointEvaluation { x, source })?;
809 let sample = ScoreSample {
810 x,
811 value: jet.value,
812 derivative: jet.derivative,
813 curvature: jet.curvature,
814 third: jet.third,
815 };
816 if sample.value.is_finite()
817 && sample.derivative.is_finite()
818 && sample.curvature.is_finite()
819 && sample.third.is_finite()
820 {
821 Ok(SearchSample {
822 sample,
823 point_enclosure: None,
824 })
825 } else {
826 Err(ScoreSearchError::NonFiniteSample { sample })
827 }
828}
829
830fn checked_enclosure<E, F>(
831 left: ScoreSample,
832 right: ScoreSample,
833 enclose: &mut F,
834) -> Result<DerivativeEnclosure, ScoreSearchError<E>>
835where
836 F: FnMut(ScoreSample, ScoreSample) -> Result<DerivativeEnclosure, E>,
837{
838 let lo = left.x;
839 let hi = right.x;
840 let enclosure = enclose(left, right)
846 .map_err(|source| ScoreSearchError::EnclosureEvaluation { lo, hi, source })?;
847 if !(enclosure.derivative.is_valid()
848 && enclosure.curvature.is_valid()
849 && enclosure.score.value.is_valid()
850 && enclosure.score.evaluation_error.is_finite()
851 && enclosure.score.evaluation_error >= 0.0)
852 {
853 return Err(ScoreSearchError::InvalidEnclosure { lo, hi, enclosure });
854 }
855 let score = enclosure.score;
856 let resolved_score = score.value.widen(score.evaluation_error);
857 for endpoint in [left, right] {
858 if !resolved_score.contains(endpoint.value) {
859 return Err(ScoreSearchError::ScoreValueEnclosureMissesEndpoint {
860 lo,
861 hi,
862 endpoint,
863 score,
864 });
865 }
866 }
867 Ok(enclosure)
868}
869
870fn certify_point<E, F>(
877 point: &mut SearchSample,
878 enclose: &mut F,
879) -> Result<DerivativeEnclosure, ScoreSearchError<E>>
880where
881 F: FnMut(ScoreSample, ScoreSample) -> Result<DerivativeEnclosure, E>,
882{
883 let enclosure = match point.point_enclosure {
884 Some(enclosure) => enclosure,
885 None => {
886 let enclosure = checked_enclosure(point.sample, point.sample, enclose)?;
887 point.point_enclosure = Some(enclosure);
888 enclosure
889 }
890 };
891 Ok(enclosure)
892}
893
894fn certify_endpoint_derivative<E, F>(
895 point: &mut SearchSample,
896 cell_lo: f64,
897 cell_hi: f64,
898 cell: DerivativeEnclosure,
899 enclose: &mut F,
900) -> Result<ClosedInterval, ScoreSearchError<E>>
901where
902 F: FnMut(ScoreSample, ScoreSample) -> Result<DerivativeEnclosure, E>,
903{
904 let endpoint_derivative = certify_point(point, enclose)?.derivative;
905 endpoint_derivative.intersection(cell.derivative).ok_or(
906 ScoreSearchError::DisjointEndpointEnclosure {
907 lo: cell_lo,
908 hi: cell_hi,
909 endpoint: point.sample,
910 endpoint_derivative,
911 enclosure: cell,
912 },
913 )
914}
915
916#[derive(Clone, Copy, PartialEq, Eq)]
917enum StrictSign {
918 Negative,
919 Positive,
920}
921
922#[inline]
923fn strict_sign(interval: ClosedInterval) -> Option<StrictSign> {
924 if interval.hi < 0.0 {
925 Some(StrictSign::Negative)
926 } else if interval.lo > 0.0 {
927 Some(StrictSign::Positive)
928 } else {
929 None
930 }
931}
932
933#[inline]
934fn is_exact_zero(interval: ClosedInterval) -> bool {
935 interval.lo == 0.0 && interval.hi == 0.0
936}
937
938fn certify_bracket_score<E, Eval, Enclose>(
939 bracket: ClosedInterval,
940 representative: SearchSample,
941 evaluate: &mut Eval,
942 enclose: &mut Enclose,
943) -> Result<ScoreValueEnclosure, ScoreSearchError<E>>
944where
945 Eval: FnMut(f64) -> Result<ScoreJet, E>,
946 Enclose: FnMut(ScoreSample, ScoreSample) -> Result<DerivativeEnclosure, E>,
947{
948 if bracket.lo == bracket.hi {
949 let mut representative = representative;
950 return Ok(certify_point(&mut representative, enclose)?.score);
951 }
952 let left = if representative.sample.x == bracket.lo {
953 representative
954 } else {
955 evaluate_sample(bracket.lo, evaluate)?
956 };
957 let right = if representative.sample.x == bracket.hi {
958 representative
959 } else {
960 evaluate_sample(bracket.hi, evaluate)?
961 };
962 Ok(checked_enclosure(left.sample, right.sample, enclose)?.score)
963}
964
965enum UniqueRootRefinement {
966 Stationary(StationaryPoint),
967 ResolutionFlat {
968 region: ResolutionFlatRegion,
969 score: ScoreValueEnclosure,
970 },
971}
972
973fn refine_unique_root<E, Eval, Enclose>(
977 mut left: SearchSample,
978 mut right: SearchSample,
979 resolution: f64,
980 enclosure: DerivativeEnclosure,
981 evaluate: &mut Eval,
982 enclose: &mut Enclose,
983) -> Result<UniqueRootRefinement, ScoreSearchError<E>>
984where
985 Eval: FnMut(f64) -> Result<ScoreJet, E>,
986 Enclose: FnMut(ScoreSample, ScoreSample) -> Result<DerivativeEnclosure, E>,
987{
988 let bracket_lo = left.sample.x;
989 let bracket_hi = right.sample.x;
990 let mut left_derivative =
991 certify_endpoint_derivative(&mut left, bracket_lo, bracket_hi, enclosure, enclose)?;
992 let mut right_derivative =
993 certify_endpoint_derivative(&mut right, bracket_lo, bracket_hi, enclosure, enclose)?;
994 let curvature_sign =
995 strict_sign(enclosure.curvature).ok_or(ScoreSearchError::InvalidEnclosure {
996 lo: left.sample.x,
997 hi: right.sample.x,
998 enclosure,
999 })?;
1000 let increasing = curvature_sign == StrictSign::Positive;
1001 let expected_left_sign = if increasing {
1002 StrictSign::Negative
1003 } else {
1004 StrictSign::Positive
1005 };
1006 let expected_right_sign = if increasing {
1007 StrictSign::Positive
1008 } else {
1009 StrictSign::Negative
1010 };
1011 if strict_sign(left_derivative) != Some(expected_left_sign)
1012 || strict_sign(right_derivative) != Some(expected_right_sign)
1013 {
1014 return Err(ScoreSearchError::InvalidEnclosure {
1015 lo: left.sample.x,
1016 hi: right.sample.x,
1017 enclosure,
1018 });
1019 }
1020
1021 let mut force_midpoint = false;
1022 while right.sample.x - left.sample.x > resolution {
1023 let width = right.sample.x - left.sample.x;
1024 let midpoint = left.sample.x + 0.5 * width;
1025 if !(midpoint > left.sample.x && midpoint < right.sample.x) {
1026 return Err(ScoreSearchError::Unresolved {
1027 lo: left.sample.x,
1028 hi: right.sample.x,
1029 requested_resolution: resolution,
1030 enclosure,
1031 });
1032 }
1033
1034 let base = if left_derivative.max_abs() <= right_derivative.max_abs() {
1044 left.sample
1045 } else {
1046 right.sample
1047 };
1048 let newton = if base.curvature != 0.0 {
1049 base.x - base.derivative / base.curvature
1050 } else {
1051 f64::NAN
1052 };
1053 let guard = 0.25 * width;
1054 let x = if !force_midpoint
1055 && newton.is_finite()
1056 && newton >= left.sample.x + guard
1057 && newton <= right.sample.x - guard
1058 {
1059 newton
1060 } else {
1061 midpoint
1062 };
1063 force_midpoint = false;
1064 if !(x > left.sample.x && x < right.sample.x) {
1065 return Err(ScoreSearchError::Unresolved {
1066 lo: left.sample.x,
1067 hi: right.sample.x,
1068 requested_resolution: resolution,
1069 enclosure,
1070 });
1071 }
1072 let mut sample = evaluate_sample(x, evaluate)?;
1073 let probe_x = sample.sample.x;
1074 let mut point_derivative = certify_endpoint_derivative(
1075 &mut sample,
1076 left.sample.x,
1077 right.sample.x,
1078 enclosure,
1079 enclose,
1080 )?;
1081 let mut root_curvature = enclosure.curvature;
1082 if !is_exact_zero(point_derivative) && strict_sign(point_derivative).is_none() {
1083 let left_cell = checked_enclosure(left.sample, sample.sample, enclose)?;
1089 let right_cell = checked_enclosure(sample.sample, right.sample, enclose)?;
1090 let left_probe_derivative = certify_endpoint_derivative(
1091 &mut sample,
1092 left.sample.x,
1093 probe_x,
1094 left_cell,
1095 enclose,
1096 )?;
1097 let right_probe_derivative = certify_endpoint_derivative(
1098 &mut sample,
1099 probe_x,
1100 right.sample.x,
1101 right_cell,
1102 enclose,
1103 )?;
1104 point_derivative = left_probe_derivative
1105 .intersection(right_probe_derivative)
1106 .ok_or(ScoreSearchError::DisjointEndpointEnclosure {
1107 lo: left.sample.x,
1108 hi: right.sample.x,
1109 endpoint: sample.sample,
1110 endpoint_derivative: left_probe_derivative,
1111 enclosure: right_cell,
1112 })?;
1113 let child_curvature = left_cell.curvature.hull(right_cell.curvature);
1114 root_curvature = enclosure.curvature.intersection(child_curvature).ok_or(
1115 ScoreSearchError::InvalidEnclosure {
1116 lo: left.sample.x,
1117 hi: right.sample.x,
1118 enclosure: right_cell,
1119 },
1120 )?;
1121 }
1122 if is_exact_zero(point_derivative) {
1123 let bracket = ClosedInterval::point(x);
1124 let score = certify_bracket_score(bracket, sample, evaluate, enclose)?;
1125 return Ok(UniqueRootRefinement::Stationary(StationaryPoint {
1126 sample: sample.sample,
1127 bracket,
1128 score,
1129 curvature: root_curvature,
1130 }));
1131 }
1132 if let Some(sign) = strict_sign(point_derivative) {
1133 match (increasing, sign) {
1134 (true, StrictSign::Negative) | (false, StrictSign::Positive) => {
1135 left = sample;
1136 left_derivative = point_derivative;
1137 }
1138 (true, StrictSign::Positive) | (false, StrictSign::Negative) => {
1139 right = sample;
1140 right_derivative = point_derivative;
1141 }
1142 }
1143 continue;
1144 }
1145
1146 let bracket = ClosedInterval::new(left.sample.x, right.sample.x);
1155 let point_newton =
1156 ClosedInterval::point(x).sub(point_derivative.div_nonzero(root_curvature));
1157 let left_newton = ClosedInterval::point(left.sample.x)
1158 .sub(left_derivative.div_nonzero(enclosure.curvature));
1159 let right_newton = ClosedInterval::point(right.sample.x)
1160 .sub(right_derivative.div_nonzero(enclosure.curvature));
1161 let root = bracket
1162 .intersection(point_newton)
1163 .and_then(|root| root.intersection(left_newton))
1164 .and_then(|root| root.intersection(right_newton))
1165 .ok_or(ScoreSearchError::InconsistentRootEnclosure {
1166 lo: left.sample.x,
1167 hi: right.sample.x,
1168 left_derivative,
1169 right_derivative,
1170 curvature: enclosure.curvature,
1171 left_newton,
1172 right_newton,
1173 point_newton,
1174 })?;
1175 if root.hi - root.lo <= resolution {
1176 let score = certify_bracket_score(root, sample, evaluate, enclose)?;
1177 return Ok(UniqueRootRefinement::Stationary(StationaryPoint {
1178 sample: sample.sample,
1179 bracket: root,
1180 score,
1181 curvature: root_curvature,
1182 }));
1183 }
1184 if root.lo > left.sample.x || root.hi < right.sample.x {
1185 let mut new_left = if root.lo == sample.sample.x {
1186 sample
1187 } else {
1188 evaluate_sample(root.lo, evaluate)?
1189 };
1190 let mut new_right = if root.hi == sample.sample.x {
1191 sample
1192 } else {
1193 evaluate_sample(root.hi, evaluate)?
1194 };
1195 let contracted_enclosure =
1196 checked_enclosure(new_left.sample, new_right.sample, enclose)?;
1197 let point_score = certify_point(&mut sample, enclose)?.score;
1208 let displacement = ClosedInterval::new(root.lo - x, root.hi - x);
1209 let taylor_score = point_score
1210 .value
1211 .add(point_derivative.mul(displacement))
1212 .add(root_curvature.mul(displacement.square()).scale(0.5));
1213 let tightened_score = contracted_enclosure
1214 .score
1215 .value
1216 .intersection(taylor_score)
1217 .ok_or(ScoreSearchError::InvalidEnclosure {
1218 lo: root.lo,
1219 hi: root.hi,
1220 enclosure: contracted_enclosure,
1221 })?;
1222 let contracted_enclosure = DerivativeEnclosure {
1223 score: ScoreValueEnclosure {
1224 value: tightened_score,
1225 evaluation_error: contracted_enclosure.score.evaluation_error,
1226 },
1227 ..contracted_enclosure
1228 };
1229 if let Some(region) = resolution_flat_region(
1230 SearchNode {
1231 left: new_left,
1232 right: new_right,
1233 },
1234 contracted_enclosure,
1235 ) {
1236 return Ok(UniqueRootRefinement::ResolutionFlat {
1237 region,
1238 score: contracted_enclosure.score,
1239 });
1240 }
1241 let new_left_derivative = if new_left.sample.x == sample.sample.x {
1242 point_derivative
1243 } else {
1244 certify_endpoint_derivative(
1245 &mut new_left,
1246 root.lo,
1247 root.hi,
1248 contracted_enclosure,
1249 enclose,
1250 )?
1251 };
1252 let new_right_derivative = if new_right.sample.x == sample.sample.x {
1253 point_derivative
1254 } else {
1255 certify_endpoint_derivative(
1256 &mut new_right,
1257 root.lo,
1258 root.hi,
1259 contracted_enclosure,
1260 enclose,
1261 )?
1262 };
1263
1264 let mut preserved_sign_contraction = false;
1270 if root.lo > left.sample.x {
1271 match strict_sign(new_left_derivative) {
1272 Some(sign) if sign == expected_left_sign => {
1273 left = new_left;
1274 left_derivative = new_left_derivative;
1275 preserved_sign_contraction = true;
1276 }
1277 Some(_) => {
1278 return Err(ScoreSearchError::InvalidEnclosure {
1279 lo: root.lo,
1280 hi: root.hi,
1281 enclosure: contracted_enclosure,
1282 });
1283 }
1284 None => {}
1285 }
1286 }
1287 if root.hi < right.sample.x {
1288 match strict_sign(new_right_derivative) {
1289 Some(sign) if sign == expected_right_sign => {
1290 right = new_right;
1291 right_derivative = new_right_derivative;
1292 preserved_sign_contraction = true;
1293 }
1294 Some(_) => {
1295 return Err(ScoreSearchError::InvalidEnclosure {
1296 lo: root.lo,
1297 hi: root.hi,
1298 enclosure: contracted_enclosure,
1299 });
1300 }
1301 None => {}
1302 }
1303 }
1304 if preserved_sign_contraction {
1305 continue;
1306 }
1307 }
1308 if x != midpoint {
1309 force_midpoint = true;
1310 continue;
1311 }
1312 return Err(ScoreSearchError::Unresolved {
1313 lo: left.sample.x,
1314 hi: right.sample.x,
1315 requested_resolution: resolution,
1316 enclosure,
1317 });
1318 }
1319
1320 let midpoint = left.sample.x + 0.5 * (right.sample.x - left.sample.x);
1321 let sample = if midpoint > left.sample.x && midpoint < right.sample.x {
1322 evaluate_sample(midpoint, evaluate)?.sample
1323 } else if left_derivative.max_abs() <= right_derivative.max_abs() {
1324 left.sample
1325 } else {
1326 right.sample
1327 };
1328 let bracket = ClosedInterval::new(left.sample.x, right.sample.x);
1329 let representative = SearchSample {
1330 sample,
1331 point_enclosure: None,
1332 };
1333 let score = certify_bracket_score(bracket, representative, evaluate, enclose)?;
1334 Ok(UniqueRootRefinement::Stationary(StationaryPoint {
1335 sample,
1336 bracket,
1337 score,
1338 curvature: enclosure.curvature,
1339 }))
1340}
1341
1342fn isolate_shared_endpoint_root<E, Eval, Enclose>(
1348 endpoint: SearchSample,
1349 domain_lo: f64,
1350 domain_hi: f64,
1351 resolution: f64,
1352 evaluate: &mut Eval,
1353 enclose: &mut Enclose,
1354) -> Result<Option<StationaryPoint>, ScoreSearchError<E>>
1355where
1356 Eval: FnMut(f64) -> Result<ScoreJet, E>,
1357 Enclose: FnMut(ScoreSample, ScoreSample) -> Result<DerivativeEnclosure, E>,
1358{
1359 let radius = 0.5 * resolution;
1360 let left_x = endpoint.sample.x - radius;
1361 let mut right_x = endpoint.sample.x + radius;
1362 if !(left_x >= domain_lo
1363 && right_x <= domain_hi
1364 && left_x < endpoint.sample.x
1365 && right_x > endpoint.sample.x)
1366 {
1367 return Ok(None);
1368 }
1369 while right_x - left_x > resolution {
1370 right_x = next_down(right_x);
1371 }
1372 if !(right_x > endpoint.sample.x && right_x - left_x <= resolution) {
1373 return Ok(None);
1374 }
1375
1376 let mut left = evaluate_sample(left_x, evaluate)?;
1377 let mut right = evaluate_sample(right_x, evaluate)?;
1378 let probe_enclosure = checked_enclosure(left.sample, right.sample, enclose)?;
1379 if probe_enclosure.curvature.contains_zero() {
1380 return Ok(None);
1381 }
1382 let left_derivative =
1383 certify_endpoint_derivative(&mut left, left_x, right_x, probe_enclosure, enclose)?;
1384 let right_derivative =
1385 certify_endpoint_derivative(&mut right, left_x, right_x, probe_enclosure, enclose)?;
1386 if strict_sign(left_derivative)
1387 .zip(strict_sign(right_derivative))
1388 .is_some_and(|(left_sign, right_sign)| left_sign != right_sign)
1389 {
1390 Ok(Some(StationaryPoint {
1391 sample: endpoint.sample,
1392 bracket: ClosedInterval::new(left_x, right_x),
1393 score: probe_enclosure.score,
1394 curvature: probe_enclosure.curvature,
1395 }))
1396 } else {
1397 Ok(None)
1398 }
1399}
1400
1401fn resolution_flat_region(
1414 node: SearchNode,
1415 enclosure: DerivativeEnclosure,
1416) -> Option<ResolutionFlatRegion> {
1417 let score = enclosure.score;
1418 let max_score_gap = if score.value.lo == score.value.hi {
1419 0.0
1420 } else {
1421 next_up(score.value.hi - score.value.lo)
1422 };
1423 let score_resolution = if score.evaluation_error == 0.0 {
1424 0.0
1425 } else {
1426 next_up(2.0 * score.evaluation_error)
1427 };
1428 if !(max_score_gap.is_finite() && score_resolution.is_finite()) {
1429 return None;
1430 }
1431 let sample = if node.right.sample.value > node.left.sample.value {
1432 node.right.sample
1433 } else {
1434 node.left.sample
1435 };
1436 (max_score_gap <= score_resolution).then_some(ResolutionFlatRegion {
1437 sample,
1438 bracket: ClosedInterval::new(node.left.sample.x, node.right.sample.x),
1439 score: score.value,
1440 max_score_gap,
1441 score_resolution,
1442 })
1443}
1444
1445fn certified_domain_boundary(
1453 node: &SearchNode,
1454 derivative_sign: StrictSign,
1455 domain_lo: f64,
1456 domain_hi: f64,
1457) -> Option<(ScoreSample, ScoreOptimumLocation)> {
1458 if node.left.sample.x != domain_lo || node.right.sample.x != domain_hi {
1459 return None;
1460 }
1461 Some(match derivative_sign {
1462 StrictSign::Positive => (node.right.sample, ScoreOptimumLocation::UpperBoundary),
1463 StrictSign::Negative => (node.left.sample, ScoreOptimumLocation::LowerBoundary),
1464 })
1465}
1466
1467pub fn maximize_score_1d<E, Eval, Enclose>(
1509 lo: f64,
1510 hi: f64,
1511 resolution: f64,
1512 mut evaluate: Eval,
1513 mut enclose: Enclose,
1514) -> Result<ScoreSearchResult, ScoreSearchError<E>>
1515where
1516 Eval: FnMut(f64) -> Result<ScoreJet, E>,
1517 Enclose: FnMut(ScoreSample, ScoreSample) -> Result<DerivativeEnclosure, E>,
1518{
1519 if !(lo.is_finite() && hi.is_finite() && lo <= hi && (hi - lo).is_finite()) {
1520 return Err(ScoreSearchError::InvalidDomain { lo, hi });
1521 }
1522 if !(resolution.is_finite() && resolution > 0.0) {
1523 return Err(ScoreSearchError::InvalidResolution { resolution });
1524 }
1525
1526 let mut lower_boundary = evaluate_sample(lo, &mut evaluate)?;
1527 if lo == hi {
1528 let score =
1529 checked_enclosure(lower_boundary.sample, lower_boundary.sample, &mut enclose)?.score;
1530 return Ok(ScoreSearchResult {
1531 optimum: lower_boundary.sample,
1532 location: ScoreOptimumLocation::LowerBoundary,
1533 lower_boundary: lower_boundary.sample,
1534 upper_boundary: lower_boundary.sample,
1535 stationary_points: Vec::new(),
1536 resolution_flat_regions: Vec::new(),
1537 dominated_regions: Vec::new(),
1538 value_certificate: GlobalScoreCertificate {
1539 selected: score.value,
1540 maximum: score.value,
1541 maximum_excess: 0.0,
1542 comparison_resolution: 0.0,
1543 },
1544 });
1545 }
1546 let mut upper_boundary = evaluate_sample(hi, &mut evaluate)?;
1547 let lower_boundary_score = certify_point(&mut lower_boundary, &mut enclose)?.score;
1548 let upper_boundary_score = certify_point(&mut upper_boundary, &mut enclose)?.score;
1549 let mut incumbent_lower = lower_boundary_score
1550 .value
1551 .lo
1552 .max(upper_boundary_score.value.lo);
1553 let (mut optimum, mut location) = if upper_boundary.sample.value > lower_boundary.sample.value {
1554 (upper_boundary.sample, ScoreOptimumLocation::UpperBoundary)
1555 } else {
1556 (lower_boundary.sample, ScoreOptimumLocation::LowerBoundary)
1557 };
1558
1559 let (budget, depth_bound) = subdivision_budget(lo, hi, resolution);
1560 let mut subdivisions = 0usize;
1561 let mut stationary_points = Vec::<StationaryPoint>::new();
1562 let mut resolution_flat_regions = Vec::<ResolutionFlatRegion>::new();
1563 let mut dominated_regions = Vec::<DominatedRegion>::new();
1564 let mut terminal_maxima = vec![
1568 TerminalScoreCandidate::point(lower_boundary.sample.x, lower_boundary_score),
1569 TerminalScoreCandidate::point(upper_boundary.sample.x, upper_boundary_score),
1570 ];
1571 let mut stack = vec![SearchNode {
1572 left: lower_boundary,
1573 right: upper_boundary,
1574 }];
1575 while let Some(mut node) = stack.pop() {
1576 let mathematical_enclosure =
1577 checked_enclosure(node.left.sample, node.right.sample, &mut enclose)?;
1578 let enclosure = mathematical_enclosure;
1579 if enclosure.score.value.hi < incumbent_lower {
1580 dominated_regions.push(DominatedRegion {
1581 bracket: ClosedInterval::new(node.left.sample.x, node.right.sample.x),
1582 score: enclosure.score,
1583 incumbent_lower,
1584 });
1585 continue;
1586 }
1587 if !enclosure.derivative.contains_zero() {
1588 let derivative_sign = if enclosure.derivative.lo > 0.0 {
1589 StrictSign::Positive
1590 } else {
1591 StrictSign::Negative
1592 };
1593 if let Some((proven_optimum, proven_location)) =
1594 certified_domain_boundary(&node, derivative_sign, lo, hi)
1595 {
1596 optimum = proven_optimum;
1597 location = proven_location;
1598 }
1599 let endpoint = match derivative_sign {
1600 StrictSign::Positive => &mut node.right,
1601 StrictSign::Negative => &mut node.left,
1602 };
1603 let endpoint_score = certify_point(endpoint, &mut enclose)?.score;
1604 incumbent_lower = incumbent_lower.max(endpoint_score.value.lo);
1605 terminal_maxima.push(TerminalScoreCandidate::point(
1606 endpoint.sample.x,
1607 endpoint_score,
1608 ));
1609 continue;
1610 }
1611
1612 let monotone = !enclosure.curvature.contains_zero();
1613 if monotone {
1614 let node_lo = node.left.sample.x;
1615 let node_hi = node.right.sample.x;
1616 let left_derivative = certify_endpoint_derivative(
1617 &mut node.left,
1618 node_lo,
1619 node_hi,
1620 enclosure,
1621 &mut enclose,
1622 )?;
1623 let right_derivative = certify_endpoint_derivative(
1624 &mut node.right,
1625 node_lo,
1626 node_hi,
1627 enclosure,
1628 &mut enclose,
1629 )?;
1630 let left_sign = strict_sign(left_derivative);
1631 let right_sign = strict_sign(right_derivative);
1632 let mut root_flat = None;
1633 let stationary = if is_exact_zero(left_derivative) {
1634 let score = certify_point(&mut node.left, &mut enclose)?.score;
1635 Some(StationaryPoint {
1636 sample: node.left.sample,
1637 bracket: ClosedInterval::point(node.left.sample.x),
1638 score,
1639 curvature: enclosure.curvature,
1640 })
1641 } else if is_exact_zero(right_derivative) {
1642 let score = certify_point(&mut node.right, &mut enclose)?.score;
1643 Some(StationaryPoint {
1644 sample: node.right.sample,
1645 bracket: ClosedInterval::point(node.right.sample.x),
1646 score,
1647 curvature: enclosure.curvature,
1648 })
1649 } else if left_sign
1650 .zip(right_sign)
1651 .is_some_and(|(left_sign, right_sign)| left_sign != right_sign)
1652 {
1653 match refine_unique_root(
1654 node.left,
1655 node.right,
1656 resolution,
1657 enclosure,
1658 &mut evaluate,
1659 &mut enclose,
1660 )? {
1661 UniqueRootRefinement::Stationary(stationary) => Some(stationary),
1662 UniqueRootRefinement::ResolutionFlat { region, score } => {
1663 root_flat = Some((region, score));
1664 None
1665 }
1666 }
1667 } else if left_sign.is_none() {
1668 isolate_shared_endpoint_root(
1669 node.left,
1670 lo,
1671 hi,
1672 resolution,
1673 &mut evaluate,
1674 &mut enclose,
1675 )?
1676 } else if right_sign.is_none() {
1677 isolate_shared_endpoint_root(
1678 node.right,
1679 lo,
1680 hi,
1681 resolution,
1682 &mut evaluate,
1683 &mut enclose,
1684 )?
1685 } else {
1686 None
1687 };
1688
1689 if let Some((flat, score)) = root_flat {
1690 let index = resolution_flat_regions.len();
1691 if flat.sample.value > optimum.value {
1692 optimum = flat.sample;
1693 location = ScoreOptimumLocation::ResolutionFlat(index);
1694 }
1695 let mut representative = SearchSample {
1696 sample: flat.sample,
1697 point_enclosure: None,
1698 };
1699 let representative_score = certify_point(&mut representative, &mut enclose)?.score;
1700 incumbent_lower = incumbent_lower.max(representative_score.value.lo);
1701 terminal_maxima.push(TerminalScoreCandidate::region(
1702 score,
1703 representative_score
1704 .evaluation_error
1705 .max(score.evaluation_error),
1706 ));
1707 resolution_flat_regions.push(flat);
1708 continue;
1709 }
1710
1711 if let Some(stationary) = stationary {
1712 let mut representative = SearchSample {
1713 sample: stationary.sample,
1714 point_enclosure: None,
1715 };
1716 let representative_score = certify_point(&mut representative, &mut enclose)?.score;
1717 incumbent_lower = incumbent_lower.max(representative_score.value.lo);
1718 let duplicate = stationary_points
1721 .last()
1722 .is_some_and(|previous| previous.sample.x == stationary.sample.x);
1723 if !duplicate {
1724 let index = stationary_points.len();
1725 if stationary.sample.value > optimum.value {
1726 optimum = stationary.sample;
1727 location = ScoreOptimumLocation::Stationary(index);
1728 }
1729 stationary_points.push(stationary);
1730 }
1731 if enclosure.curvature.hi < 0.0 {
1732 let score = stationary.score;
1733 terminal_maxima.push(if stationary.bracket.lo == stationary.bracket.hi {
1734 TerminalScoreCandidate::point(stationary.sample.x, score)
1735 } else {
1736 TerminalScoreCandidate::region(
1737 score,
1738 representative_score
1739 .evaluation_error
1740 .max(score.evaluation_error),
1741 )
1742 });
1743 } else {
1744 let left_score = certify_point(&mut node.left, &mut enclose)?.score;
1745 let right_score = certify_point(&mut node.right, &mut enclose)?.score;
1746 incumbent_lower = incumbent_lower
1747 .max(left_score.value.lo)
1748 .max(right_score.value.lo);
1749 terminal_maxima.push(TerminalScoreCandidate::point(
1750 node.left.sample.x,
1751 left_score,
1752 ));
1753 terminal_maxima.push(TerminalScoreCandidate::point(
1754 node.right.sample.x,
1755 right_score,
1756 ));
1757 }
1758 continue;
1759 }
1760
1761 if let Some((left_sign, right_sign)) = left_sign.zip(right_sign)
1766 && left_sign == right_sign
1767 {
1768 if let Some((proven_optimum, proven_location)) =
1769 certified_domain_boundary(&node, left_sign, lo, hi)
1770 {
1771 optimum = proven_optimum;
1772 location = proven_location;
1773 }
1774 let endpoint = match left_sign {
1775 StrictSign::Positive => &mut node.right,
1776 StrictSign::Negative => &mut node.left,
1777 };
1778 let endpoint_score = certify_point(endpoint, &mut enclose)?.score;
1779 incumbent_lower = incumbent_lower.max(endpoint_score.value.lo);
1780 terminal_maxima.push(TerminalScoreCandidate::point(
1781 endpoint.sample.x,
1782 endpoint_score,
1783 ));
1784 continue;
1785 }
1786 }
1787
1788 if let Some(flat) = resolution_flat_region(node, mathematical_enclosure) {
1789 let index = resolution_flat_regions.len();
1790 if flat.sample.value > optimum.value {
1791 optimum = flat.sample;
1792 location = ScoreOptimumLocation::ResolutionFlat(index);
1793 }
1794 let mut representative = SearchSample {
1795 sample: flat.sample,
1796 point_enclosure: None,
1797 };
1798 let representative_score = certify_point(&mut representative, &mut enclose)?.score;
1799 incumbent_lower = incumbent_lower.max(representative_score.value.lo);
1800 terminal_maxima.push(TerminalScoreCandidate::region(
1801 enclosure.score,
1802 representative_score
1803 .evaluation_error
1804 .max(enclosure.score.evaluation_error),
1805 ));
1806 resolution_flat_regions.push(flat);
1807 continue;
1808 }
1809
1810 let width = node.right.sample.x - node.left.sample.x;
1811 let midpoint = node.left.sample.x + 0.5 * width;
1812 if width <= resolution || !(midpoint > node.left.sample.x && midpoint < node.right.sample.x)
1813 {
1814 return Err(ScoreSearchError::Unresolved {
1815 lo: node.left.sample.x,
1816 hi: node.right.sample.x,
1817 requested_resolution: resolution,
1818 enclosure,
1819 });
1820 }
1821 subdivisions += 1;
1822 if subdivisions > budget {
1823 return Err(ScoreSearchError::SubdivisionBudget {
1824 lo,
1825 hi,
1826 cell_lo: node.left.sample.x,
1827 cell_hi: node.right.sample.x,
1828 requested_resolution: resolution,
1829 subdivisions,
1830 budget,
1831 depth_bound,
1832 enclosure,
1833 });
1834 }
1835 let middle = evaluate_sample(midpoint, &mut evaluate)?;
1836 stack.push(SearchNode {
1839 left: middle,
1840 right: node.right,
1841 });
1842 stack.push(SearchNode {
1843 left: node.left,
1844 right: middle,
1845 });
1846 }
1847
1848 let mut selected_sample = SearchSample {
1849 sample: optimum,
1850 point_enclosure: None,
1851 };
1852 let selected_score = certify_point(&mut selected_sample, &mut enclose)?.score;
1853 let global_lower = terminal_maxima
1854 .iter()
1855 .map(|candidate| candidate.score.value.lo)
1856 .fold(selected_score.value.lo, f64::max);
1857 let global_upper = terminal_maxima
1858 .iter()
1859 .map(|candidate| candidate.score.value.hi)
1860 .fold(selected_score.value.hi, f64::max);
1861 let candidate_evaluation_error = terminal_maxima
1862 .iter()
1863 .filter(|candidate| candidate.point_x != Some(optimum.x))
1864 .map(|candidate| candidate.comparison_error)
1865 .fold(0.0_f64, f64::max);
1866 let maximum_excess = terminal_maxima
1867 .iter()
1868 .filter(|candidate| candidate.point_x != Some(optimum.x))
1869 .map(|candidate| {
1870 if candidate.score.value.hi <= selected_score.value.lo {
1871 0.0
1872 } else {
1873 next_up(candidate.score.value.hi - selected_score.value.lo)
1874 }
1875 })
1876 .fold(0.0_f64, f64::max);
1877 let comparison_resolution =
1878 add_nonnegative_upward(selected_score.evaluation_error, candidate_evaluation_error);
1879
1880 Ok(ScoreSearchResult {
1881 optimum,
1882 location,
1883 lower_boundary: lower_boundary.sample,
1884 upper_boundary: upper_boundary.sample,
1885 stationary_points,
1886 resolution_flat_regions,
1887 dominated_regions,
1888 value_certificate: GlobalScoreCertificate {
1889 selected: selected_score.value,
1890 maximum: ClosedInterval::new(global_lower, global_upper),
1891 maximum_excess,
1892 comparison_resolution,
1893 },
1894 })
1895}
1896
1897pub fn maximize_score_1d_value_ordered<E, Eval, Enclose>(
1915 lo: f64,
1916 hi: f64,
1917 initial_resolution: f64,
1918 mut evaluate: Eval,
1919 mut enclose: Enclose,
1920) -> Result<ScoreSearchResult, ScoreSearchError<E>>
1921where
1922 Eval: FnMut(f64) -> Result<ScoreJet, E>,
1923 Enclose: FnMut(ScoreSample, ScoreSample) -> Result<DerivativeEnclosure, E>,
1924{
1925 let mut resolution = initial_resolution;
1926 let mut search = maximize_score_1d(lo, hi, resolution, &mut evaluate, &mut enclose)?;
1927 loop {
1928 let certificate = search.value_certificate;
1929 if certificate.maximum_excess <= certificate.comparison_resolution {
1930 return Ok(search);
1931 }
1932 let binary_refinement = 0.5 * resolution;
1933 let value_directed_refinement = if certificate.comparison_resolution > 0.0 {
1934 resolution * (certificate.comparison_resolution / certificate.maximum_excess)
1935 } else {
1936 binary_refinement
1937 };
1938 let next_resolution = binary_refinement.min(value_directed_refinement);
1939 if !(next_resolution.is_finite() && next_resolution > 0.0 && next_resolution < resolution) {
1940 return Ok(search);
1941 }
1942 match maximize_score_1d(lo, hi, next_resolution, &mut evaluate, &mut enclose) {
1943 Ok(refined) => {
1944 search = refined;
1945 resolution = next_resolution;
1946 }
1947 Err(
1958 ScoreSearchError::Unresolved { .. } | ScoreSearchError::SubdivisionBudget { .. },
1959 ) => return Ok(search),
1960 Err(error) => return Err(error),
1961 }
1962 }
1963}
1964
1965#[derive(Clone, Copy, Debug, PartialEq)]
1967pub enum AffineRemlError {
1968 EmptyModes,
1969 EmptyResponses,
1970 ShapeMismatch {
1971 gram_modes: usize,
1972 penalty_modes: usize,
1973 projected_rhs_squared: usize,
1974 responses: usize,
1975 },
1976 InvalidMode {
1977 index: usize,
1978 gram: f64,
1979 penalty: f64,
1980 },
1981 InvalidProjectedSquare {
1982 index: usize,
1983 value: f64,
1984 },
1985 InvalidResponseEnergy {
1986 output: usize,
1987 value: f64,
1988 },
1989 ZeroLambdaResidualUnavailable {
1990 output: usize,
1991 },
1992 InvalidResidualDof {
1993 value: f64,
1994 },
1995 InvalidLogdetConstant {
1996 value: f64,
1997 },
1998 RankMismatch {
1999 supplied: usize,
2000 inferred: usize,
2001 },
2002 InvalidLogLambda {
2003 value: f64,
2004 },
2005 InvalidLogLambdaInterval {
2006 lo: f64,
2007 hi: f64,
2008 },
2009 ElementaryEnclosureUnavailable {
2010 function: &'static str,
2011 lo: f64,
2012 hi: f64,
2013 },
2014 NonPositiveMode {
2015 index: usize,
2016 log_lambda: f64,
2017 value: f64,
2018 },
2019 NonPositiveResidual {
2020 output: usize,
2021 log_lambda: f64,
2022 value: f64,
2023 },
2024 NonPositiveResidualInterval {
2025 output: usize,
2026 lo: f64,
2027 hi: f64,
2028 lower_bound: f64,
2029 },
2030 InconsistentResidualEnclosures {
2031 output: usize,
2032 lo: f64,
2033 hi: f64,
2034 direct: ClosedInterval,
2035 complement: ClosedInterval,
2036 },
2037 UnboundedScoreEvaluationError {
2038 lo: f64,
2039 hi: f64,
2040 error: f64,
2041 },
2042}
2043
2044impl fmt::Display for AffineRemlError {
2045 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2046 match self {
2047 Self::EmptyModes => write!(f, "affine REML profile has no modes"),
2048 Self::EmptyResponses => write!(f, "affine REML profile has no responses"),
2049 Self::ShapeMismatch {
2050 gram_modes,
2051 penalty_modes,
2052 projected_rhs_squared,
2053 responses,
2054 } => write!(
2055 f,
2056 "affine REML profile shape mismatch: gram {gram_modes}, penalty {penalty_modes}, projected squares {projected_rhs_squared}, responses {responses}"
2057 ),
2058 Self::InvalidMode {
2059 index,
2060 gram,
2061 penalty,
2062 } => write!(
2063 f,
2064 "affine REML mode {index} must have finite nonnegative (g,s), not both zero; got ({gram}, {penalty})"
2065 ),
2066 Self::InvalidProjectedSquare { index, value } => write!(
2067 f,
2068 "affine REML projected square {index} must be finite and nonnegative, got {value}"
2069 ),
2070 Self::InvalidResponseEnergy { output, value } => write!(
2071 f,
2072 "affine REML response energy {output} must be finite and nonnegative, got {value}"
2073 ),
2074 Self::ZeroLambdaResidualUnavailable { output } => write!(
2075 f,
2076 "affine REML could not certify the zero-smoothing residual for response {output}"
2077 ),
2078 Self::InvalidResidualDof { value } => {
2079 write!(
2080 f,
2081 "affine REML residual dof must be finite and positive, got {value}"
2082 )
2083 }
2084 Self::InvalidLogdetConstant { value } => write!(
2085 f,
2086 "affine REML log-determinant constant must be finite, got {value}"
2087 ),
2088 Self::RankMismatch { supplied, inferred } => write!(
2089 f,
2090 "affine REML determinant rank {supplied} disagrees with {inferred} positive penalty modes"
2091 ),
2092 Self::InvalidLogLambda { value } => {
2093 write!(f, "affine REML invalid log lambda {value}")
2094 }
2095 Self::InvalidLogLambdaInterval { lo, hi } => {
2096 write!(f, "affine REML invalid log-lambda interval [{lo}, {hi}]")
2097 }
2098 Self::ElementaryEnclosureUnavailable { function, lo, hi } => write!(
2099 f,
2100 "affine REML has no finite source-derived {function} enclosure on [{lo}, {hi}]"
2101 ),
2102 Self::NonPositiveMode {
2103 index,
2104 log_lambda,
2105 value,
2106 } => write!(
2107 f,
2108 "affine REML mode {index} is nonpositive at log lambda {log_lambda}: {value}"
2109 ),
2110 Self::NonPositiveResidual {
2111 output,
2112 log_lambda,
2113 value,
2114 } => write!(
2115 f,
2116 "affine REML residual {output} is nonpositive at log lambda {log_lambda}: {value}"
2117 ),
2118 Self::NonPositiveResidualInterval {
2119 output,
2120 lo,
2121 hi,
2122 lower_bound,
2123 } => write!(
2124 f,
2125 "affine REML residual {output} is not certified positive on [{lo}, {hi}] (lower bound {lower_bound})"
2126 ),
2127 Self::InconsistentResidualEnclosures {
2128 output,
2129 lo,
2130 hi,
2131 direct,
2132 complement,
2133 } => write!(
2134 f,
2135 "affine REML residual {output} has disjoint direct {direct:?} and zero-smoothing-complement {complement:?} enclosures on [{lo}, {hi}]"
2136 ),
2137 Self::UnboundedScoreEvaluationError { lo, hi, error } => write!(
2138 f,
2139 "affine REML score evaluator has no finite forward-error bound on [{lo}, {hi}] (bound {error})"
2140 ),
2141 }
2142 }
2143}
2144
2145impl std::error::Error for AffineRemlError {}
2146
2147#[derive(Clone, Debug)]
2158pub struct AffineRemlProfile<'a> {
2159 gram_modes: &'a [f64],
2160 penalty_modes: &'a [f64],
2161 projected_rhs_squared: &'a [f64],
2162 response_energy: &'a [f64],
2163 zero_lambda_residual: Vec<ClosedInterval>,
2172 residual_dof: f64,
2173 logdet_constant: f64,
2174}
2175
2176struct CertifiedCompensatedSum {
2186 leading: f64,
2187 correction: ClosedInterval,
2188}
2189
2190impl CertifiedCompensatedSum {
2191 fn new(value: f64) -> Self {
2192 Self {
2193 leading: value,
2194 correction: ClosedInterval::point(0.0),
2195 }
2196 }
2197
2198 fn add_exact(&mut self, value: f64) -> bool {
2200 let sum = self.leading + value;
2201 if !sum.is_finite() {
2202 return false;
2203 }
2204 let virtual_value = sum - self.leading;
2205 let virtual_leading = sum - virtual_value;
2206 let value_residual = value - virtual_value;
2207 let leading_residual = self.leading - virtual_leading;
2208 let error = leading_residual + value_residual;
2211 self.leading = sum;
2212 self.correction = self.correction.add(ClosedInterval::point(error));
2213 self.correction.is_valid()
2214 }
2215
2216 fn subtract_interval(&mut self, value: ClosedInterval) -> bool {
2217 self.correction = self.correction.sub(value);
2218 self.correction.is_valid()
2219 }
2220
2221 fn enclosure(self) -> Option<ClosedInterval> {
2222 let enclosure = ClosedInterval::point(self.leading).add(self.correction);
2223 (enclosure.is_valid() && enclosure.lo.is_finite() && enclosure.hi.is_finite())
2224 .then_some(enclosure)
2225 }
2226}
2227
2228fn quotient_leading_and_correction(
2239 numerator: f64,
2240 denominator: f64,
2241) -> Option<(f64, ClosedInterval)> {
2242 if numerator == 0.0 {
2243 return Some((0.0, ClosedInterval::point(0.0)));
2244 }
2245 if !(numerator.is_finite() && numerator > 0.0 && denominator.is_finite() && denominator > 0.0) {
2246 return None;
2247 }
2248 let leading = numerator / denominator;
2249 if !(leading.is_finite() && leading >= 0.0) {
2250 return None;
2251 }
2252 if denominator == 1.0 {
2253 return Some((leading, ClosedInterval::point(0.0)));
2254 }
2255 let fused_residual = (-leading).mul_add(denominator, numerator);
2256 if !fused_residual.is_finite() {
2257 return None;
2258 }
2259 let exact_residual = ClosedInterval::new(next_down(fused_residual), next_up(fused_residual));
2260 let correction = exact_residual.div_positive(ClosedInterval::point(denominator));
2261 (correction.is_valid() && correction.lo.is_finite() && correction.hi.is_finite())
2262 .then_some((leading, correction))
2263}
2264
2265fn certified_zero_lambda_residual(
2266 energy: f64,
2267 gram_modes: &[f64],
2268 projected_squares: &[f64],
2269) -> Option<ClosedInterval> {
2270 let mut residual = CertifiedCompensatedSum::new(energy);
2271 for (&gram, &projected_square) in gram_modes.iter().zip(projected_squares) {
2272 if gram == 0.0 || projected_square == 0.0 {
2273 continue;
2274 }
2275 let (leading, correction) = quotient_leading_and_correction(projected_square, gram)?;
2276 if !(residual.add_exact(-leading) && residual.subtract_interval(correction)) {
2277 return None;
2278 }
2279 }
2280 residual.enclosure()
2281}
2282
2283const DETERMINANT_VALUE_OPS_PER_MODE: usize = 8;
2293const RESIDUAL_VALUE_OPS_PER_MODE: usize = 4;
2294const RESIDUAL_LOG_OPS_PER_RESPONSE: usize = 3;
2295const SCORE_COMBINE_OPS: usize = 4;
2296
2297impl<'a> AffineRemlProfile<'a> {
2298 pub fn new(
2299 gram_modes: &'a [f64],
2300 penalty_modes: &'a [f64],
2301 projected_rhs_squared: &'a [f64],
2302 response_energy: &'a [f64],
2303 residual_dof: f64,
2304 determinant_rank: usize,
2305 logdet_constant: f64,
2306 ) -> Result<Self, AffineRemlError> {
2307 let modes = gram_modes.len();
2308 let responses = response_energy.len();
2309 if modes == 0 {
2310 return Err(AffineRemlError::EmptyModes);
2311 }
2312 if responses == 0 {
2313 return Err(AffineRemlError::EmptyResponses);
2314 }
2315 if penalty_modes.len() != modes
2316 || projected_rhs_squared.len() != modes.saturating_mul(responses)
2317 {
2318 return Err(AffineRemlError::ShapeMismatch {
2319 gram_modes: modes,
2320 penalty_modes: penalty_modes.len(),
2321 projected_rhs_squared: projected_rhs_squared.len(),
2322 responses,
2323 });
2324 }
2325 for (index, (&gram, &penalty)) in gram_modes.iter().zip(penalty_modes).enumerate() {
2326 if !(gram.is_finite()
2327 && penalty.is_finite()
2328 && gram >= 0.0
2329 && penalty >= 0.0
2330 && (gram > 0.0 || penalty > 0.0))
2331 {
2332 return Err(AffineRemlError::InvalidMode {
2333 index,
2334 gram,
2335 penalty,
2336 });
2337 }
2338 }
2339 for (index, &value) in projected_rhs_squared.iter().enumerate() {
2340 if !(value.is_finite() && value >= 0.0) {
2341 return Err(AffineRemlError::InvalidProjectedSquare { index, value });
2342 }
2343 }
2344 for (output, &value) in response_energy.iter().enumerate() {
2345 if !(value.is_finite() && value >= 0.0) {
2346 return Err(AffineRemlError::InvalidResponseEnergy { output, value });
2347 }
2348 }
2349 if !(residual_dof.is_finite() && residual_dof > 0.0) {
2350 return Err(AffineRemlError::InvalidResidualDof {
2351 value: residual_dof,
2352 });
2353 }
2354 if !logdet_constant.is_finite() {
2355 return Err(AffineRemlError::InvalidLogdetConstant {
2356 value: logdet_constant,
2357 });
2358 }
2359 let inferred_rank = penalty_modes.iter().filter(|&&value| value > 0.0).count();
2360 if determinant_rank != inferred_rank {
2361 return Err(AffineRemlError::RankMismatch {
2362 supplied: determinant_rank,
2363 inferred: inferred_rank,
2364 });
2365 }
2366 let mut zero_lambda_residual = Vec::with_capacity(responses);
2367 for (output, &energy) in response_energy.iter().enumerate() {
2368 let start = output * modes;
2369 let end = start + modes;
2370 zero_lambda_residual.push(
2371 certified_zero_lambda_residual(
2372 energy,
2373 gram_modes,
2374 &projected_rhs_squared[start..end],
2375 )
2376 .ok_or(AffineRemlError::ZeroLambdaResidualUnavailable { output })?,
2377 );
2378 }
2379 Ok(Self {
2380 gram_modes,
2381 penalty_modes,
2382 projected_rhs_squared,
2383 response_energy,
2384 zero_lambda_residual,
2385 residual_dof,
2386 logdet_constant,
2387 })
2388 }
2389
2390 #[inline]
2391 pub fn num_modes(&self) -> usize {
2392 self.gram_modes.len()
2393 }
2394
2395 #[inline]
2396 pub fn num_responses(&self) -> usize {
2397 self.response_energy.len()
2398 }
2399
2400 pub fn evaluate(&self, log_lambda: f64) -> Result<ScoreJet, AffineRemlError> {
2403 if !log_lambda.is_finite() {
2404 return Err(AffineRemlError::InvalidLogLambda { value: log_lambda });
2405 }
2406 let lambda = certified_exp_representative(log_lambda)
2407 .ok_or(AffineRemlError::InvalidLogLambda { value: log_lambda })?;
2408 if !(lambda.is_finite() && lambda > 0.0) {
2409 return Err(AffineRemlError::InvalidLogLambda { value: log_lambda });
2410 }
2411
2412 let mut normalized_logdet = self.logdet_constant;
2413 let mut determinant_derivative = 0.0;
2414 let mut determinant_curvature = 0.0;
2415 let exp_neg_log_lambda = if log_lambda >= 0.0 {
2416 certified_exp_representative(-log_lambda)
2417 } else {
2418 None
2419 };
2420 for (index, (&gram, &penalty)) in self.gram_modes.iter().zip(self.penalty_modes).enumerate()
2421 {
2422 if gram == 0.0 {
2430 normalized_logdet +=
2431 certified_ln_value(penalty).ok_or(AffineRemlError::NonPositiveMode {
2432 index,
2433 log_lambda,
2434 value: penalty,
2435 })?;
2436 continue;
2437 }
2438 let h = lambda.mul_add(penalty, gram);
2439 if !(h.is_finite() && h > 0.0) {
2440 return Err(AffineRemlError::NonPositiveMode {
2441 index,
2442 log_lambda,
2443 value: h,
2444 });
2445 }
2446 let u = lambda * penalty / h;
2447 let determinant_complement = if penalty == 0.0 { 0.0 } else { gram / h };
2458 let normalized_mode = if penalty == 0.0 {
2470 certified_ln_value(gram)
2471 } else if log_lambda >= 0.0 {
2472 exp_neg_log_lambda
2473 .and_then(|exp_neg_rho| certified_ln_value(penalty + gram * exp_neg_rho))
2474 } else if gram >= penalty * lambda {
2475 certified_ln_value(gram)
2476 .zip(certified_ln_1p_value(penalty * lambda / gram))
2477 .map(|(log_gram, correction)| log_gram - log_lambda + correction)
2478 } else {
2479 certified_ln_value(penalty)
2480 .zip(certified_ln_1p_value(gram / (penalty * lambda)))
2481 .map(|(log_penalty, correction)| log_penalty + correction)
2482 }
2483 .ok_or(AffineRemlError::NonPositiveMode {
2484 index,
2485 log_lambda,
2486 value: h,
2487 })?;
2488 normalized_logdet += normalized_mode;
2489 determinant_derivative -= determinant_complement;
2490 determinant_curvature += u * determinant_complement;
2491 }
2492
2493 let modes = self.num_modes();
2494 let mut residual_log_sum = 0.0;
2495 let mut residual_derivative_sum = 0.0;
2496 let mut residual_curvature_sum = 0.0;
2497 for (output, &energy) in self.response_energy.iter().enumerate() {
2498 let mut residual = energy;
2499 let mut first = 0.0;
2500 let mut second = 0.0;
2501 for i in 0..modes {
2502 let projected_square = self.projected_rhs_squared[output * modes + i];
2503 if projected_square == 0.0 {
2504 continue;
2505 }
2506 if self.gram_modes[i] == 0.0 {
2507 let fitted = positive_ratio_over_product(
2508 projected_square,
2509 self.penalty_modes[i],
2510 lambda,
2511 )
2512 .ok_or(
2513 AffineRemlError::ElementaryEnclosureUnavailable {
2514 function: "gram-zero residual quotient",
2515 lo: log_lambda,
2516 hi: log_lambda,
2517 },
2518 )?;
2519 residual -= fitted;
2520 first += fitted;
2521 second -= fitted;
2522 continue;
2523 }
2524 let h = lambda.mul_add(self.penalty_modes[i], self.gram_modes[i]);
2525 let u = lambda * self.penalty_modes[i] / h;
2526 residual -= projected_square / h;
2527 first += projected_square * u / h;
2528 second += projected_square * u * (1.0 - 2.0 * u) / h;
2529 }
2530 if !(residual.is_finite() && residual > 0.0) {
2531 return Err(AffineRemlError::NonPositiveResidual {
2532 output,
2533 log_lambda,
2534 value: residual,
2535 });
2536 }
2537 let log_derivative = first / residual;
2538 residual_log_sum += certified_ln_value(residual / self.residual_dof).ok_or(
2539 AffineRemlError::NonPositiveResidual {
2540 output,
2541 log_lambda,
2542 value: residual,
2543 },
2544 )?;
2545 residual_derivative_sum += log_derivative;
2546 residual_curvature_sum += second / residual - log_derivative * log_derivative;
2547 }
2548
2549 let outputs = self.num_responses() as f64;
2550 Ok(ScoreJet {
2551 value: -0.5 * (outputs * normalized_logdet + self.residual_dof * residual_log_sum),
2552 derivative: -0.5
2553 * (outputs * determinant_derivative + self.residual_dof * residual_derivative_sum),
2554 curvature: -0.5
2555 * (outputs * determinant_curvature + self.residual_dof * residual_curvature_sum),
2556 third: 0.0,
2565 })
2566 }
2567
2568 pub fn enclose(&self, lo: f64, hi: f64) -> Result<DerivativeEnclosure, AffineRemlError> {
2650 let (direct, direct_third) = self.enclose_direct(lo, hi)?;
2651 if lo == hi {
2652 return Ok(direct);
2653 }
2654 let centre_point = (0.5 * (lo + hi)).clamp(lo, hi);
2660 let (centre, _) = self.enclose_direct(centre_point, centre_point)?;
2661 let offset = ClosedInterval::new(
2666 next_down(lo - centre_point).min(0.0),
2667 next_up(hi - centre_point).max(0.0),
2668 );
2669 let curvature = centred_or(direct.curvature, centre.curvature, direct_third, offset);
2679 let derivative = centred_or(direct.derivative, centre.derivative, curvature, offset);
2680 let value = centred_or(direct.score.value, centre.score.value, derivative, offset);
2681 Ok(DerivativeEnclosure {
2682 score: ScoreValueEnclosure {
2683 value,
2684 evaluation_error: direct.score.evaluation_error,
2685 },
2686 derivative,
2687 curvature,
2688 })
2689 }
2690
2691 fn enclose_direct(
2704 &self,
2705 lo: f64,
2706 hi: f64,
2707 ) -> Result<(DerivativeEnclosure, ClosedInterval), AffineRemlError> {
2708 if !(lo.is_finite() && hi.is_finite() && lo <= hi) {
2709 return Err(AffineRemlError::InvalidLogLambdaInterval { lo, hi });
2710 }
2711 let lambda = exp_interval(lo, hi)?;
2712 if !(lambda.lo.is_finite() && lambda.lo > 0.0 && lambda.hi.is_finite()) {
2713 return Err(AffineRemlError::InvalidLogLambdaInterval { lo, hi });
2714 }
2715 let lambda_relative_error =
2721 certified_exp_relative_forward_error(ClosedInterval::new(lo, hi), lambda);
2722 if !lambda_relative_error.is_finite() {
2723 return Err(AffineRemlError::UnboundedScoreEvaluationError {
2724 lo,
2725 hi,
2726 error: lambda_relative_error,
2727 });
2728 }
2729
2730 let mut normalized_logdet = ClosedInterval::point(self.logdet_constant);
2731 let mut normalized_logdet_magnitude = self.logdet_constant.abs();
2732 let mut normalized_logdet_error = 0.0;
2733 let mut determinant_first = ClosedInterval::point(0.0);
2734 let mut determinant_second = ClosedInterval::point(0.0);
2735 let mut determinant_third = ClosedInterval::point(0.0);
2736 for i in 0..self.num_modes() {
2737 let (normalized_mode, normalized_mode_error) =
2738 normalized_log_mode_enclosure(self.gram_modes[i], self.penalty_modes[i], lo, hi)?;
2739 normalized_logdet = normalized_logdet.add(normalized_mode);
2740 normalized_logdet_magnitude = add_nonnegative_upward(
2741 normalized_logdet_magnitude,
2742 add_nonnegative_upward(normalized_mode.max_abs(), normalized_mode_error),
2743 );
2744 normalized_logdet_error =
2745 add_nonnegative_upward(normalized_logdet_error, normalized_mode_error);
2746
2747 let ranges = mode_ranges(self.gram_modes[i], self.penalty_modes[i], 0.0, lambda)?;
2748 determinant_first = determinant_first.sub(ranges.c);
2749 determinant_second = determinant_second.add(ranges.w);
2750 determinant_third = determinant_third.add(ranges.determinant_third);
2751 }
2752
2753 let mut residual_first_sum = ClosedInterval::point(0.0);
2754 let mut residual_second_sum = ClosedInterval::point(0.0);
2755 let mut residual_third_sum = ClosedInterval::point(0.0);
2756 let mut residual_log_sum = ClosedInterval::point(0.0);
2757 let mut residual_log_magnitude = 0.0;
2758 let mut residual_log_error = 0.0;
2759 let modes = self.num_modes();
2760 for (output, &energy) in self.response_energy.iter().enumerate() {
2761 let mut fitted_quadratic = ClosedInterval::point(0.0);
2762 let mut smoothing_increment = ClosedInterval::point(0.0);
2763 let mut singular_fitted = ClosedInterval::point(0.0);
2764 let mut first = ClosedInterval::point(0.0);
2765 let mut second = ClosedInterval::point(0.0);
2766 let mut third = ClosedInterval::point(0.0);
2767 let mut fitted_magnitude = energy;
2768 for i in 0..modes {
2769 let ranges = mode_ranges(
2770 self.gram_modes[i],
2771 self.penalty_modes[i],
2772 self.projected_rhs_squared[output * modes + i],
2773 lambda,
2774 )?;
2775 fitted_quadratic = fitted_quadratic.add(ranges.v);
2776 smoothing_increment = smoothing_increment.add(ranges.smoothing_increment);
2777 singular_fitted = singular_fitted.add(ranges.singular_fitted);
2778 first = first.add(ranges.p);
2779 second = second.add(ranges.q);
2780 third = third.add(ranges.residual_third);
2781 fitted_magnitude = add_nonnegative_upward(fitted_magnitude, ranges.v.max_abs());
2782 }
2783 let direct_residual = ClosedInterval::point(energy).sub(fitted_quadratic);
2801 let complement_residual = self.zero_lambda_residual[output]
2802 .add(smoothing_increment)
2803 .sub(singular_fitted);
2804 let residual = direct_residual.intersection(complement_residual).ok_or(
2805 AffineRemlError::InconsistentResidualEnclosures {
2806 output,
2807 lo,
2808 hi,
2809 direct: direct_residual,
2810 complement: complement_residual,
2811 },
2812 )?;
2813 if !(residual.lo > 0.0 && residual.is_valid()) {
2814 return Err(AffineRemlError::NonPositiveResidualInterval {
2815 output,
2816 lo,
2817 hi,
2818 lower_bound: residual.lo,
2819 });
2820 }
2821 let first_ratio = first.div_positive(residual).nonnegative();
2822 let second_ratio = second.div_positive(residual);
2823 let third_ratio = third.div_positive(residual);
2824 residual_first_sum = residual_first_sum.add(first_ratio);
2825 residual_second_sum = residual_second_sum.add(second_ratio.sub(first_ratio.square()));
2826 residual_third_sum = residual_third_sum.add(
2829 third_ratio
2830 .sub(second_ratio.mul(first_ratio).scale(3.0))
2831 .add(first_ratio.square().mul(first_ratio).scale(2.0)),
2832 );
2833
2834 let fitted_arithmetic_error = wilkinson_roundoff(
2835 fitted_magnitude,
2836 modes.saturating_mul(RESIDUAL_VALUE_OPS_PER_MODE),
2837 );
2838 let fitted_exp_error = next_up(first.max_abs() * lambda_relative_error);
2841 let resolved_fitted_quadratic = fitted_quadratic.widen(add_nonnegative_upward(
2842 fitted_arithmetic_error,
2843 fitted_exp_error,
2844 ));
2845 let resolved_residual = ClosedInterval::point(energy).sub(resolved_fitted_quadratic);
2846 if !(resolved_residual.lo > 0.0 && resolved_residual.is_valid()) {
2847 return Err(AffineRemlError::NonPositiveResidualInterval {
2848 output,
2849 lo,
2850 hi,
2851 lower_bound: resolved_residual.lo,
2852 });
2853 }
2854 let residual_over_dof = residual.div_positive(ClosedInterval::point(self.residual_dof));
2855 if !(residual_over_dof.lo > 0.0 && residual_over_dof.hi.is_finite()) {
2856 return Err(AffineRemlError::ElementaryEnclosureUnavailable {
2857 function: "ln",
2858 lo: residual_over_dof.lo,
2859 hi: residual_over_dof.hi,
2860 });
2861 }
2862 let residual_log = residual_over_dof.ln_positive();
2863 residual_log_sum = residual_log_sum.add(residual_log);
2864
2865 let residual_error = enclosure_excess(residual, resolved_residual);
2874 let propagated_residual_error = next_up(residual_error / resolved_residual.lo);
2875 let elementary_error = certified_log_forward_error(
2876 residual.div_positive(ClosedInterval::point(self.residual_dof)),
2877 );
2878 let local_log_error = add_nonnegative_upward(
2879 propagated_residual_error,
2880 add_nonnegative_upward(
2881 elementary_error,
2882 wilkinson_roundoff(
2883 add_nonnegative_upward(1.0, residual_log.max_abs()),
2884 RESIDUAL_LOG_OPS_PER_RESPONSE,
2885 ),
2886 ),
2887 );
2888 residual_log_error = add_nonnegative_upward(residual_log_error, local_log_error);
2889 residual_log_magnitude = add_nonnegative_upward(
2890 residual_log_magnitude,
2891 add_nonnegative_upward(residual_log.max_abs(), local_log_error),
2892 );
2893 }
2894
2895 let outputs = self.num_responses() as f64;
2896 let first_bracket = determinant_first
2897 .scale(outputs)
2898 .add(residual_first_sum.scale(self.residual_dof));
2899 let second_bracket = determinant_second
2900 .scale(outputs)
2901 .add(residual_second_sum.scale(self.residual_dof));
2902 let third_bracket = determinant_third
2903 .scale(outputs)
2904 .add(residual_third_sum.scale(self.residual_dof));
2905 let derivative = first_bracket.scale(-0.5);
2906 let curvature = second_bracket.scale(-0.5);
2907 let third = third_bracket.scale(-0.5);
2908 let score_value = normalized_logdet
2909 .scale(outputs)
2910 .add(residual_log_sum.scale(self.residual_dof))
2911 .scale(-0.5);
2912 let score_magnitude = add_nonnegative_upward(
2913 next_up(outputs * normalized_logdet_magnitude),
2914 next_up(self.residual_dof * residual_log_magnitude),
2915 );
2916 normalized_logdet_error = add_nonnegative_upward(
2917 normalized_logdet_error,
2918 wilkinson_roundoff(normalized_logdet_magnitude, self.num_modes()),
2919 );
2920 residual_log_error = add_nonnegative_upward(
2921 residual_log_error,
2922 wilkinson_roundoff(residual_log_magnitude, self.num_responses()),
2923 );
2924 let final_arithmetic_error = wilkinson_roundoff(score_magnitude, SCORE_COMBINE_OPS);
2925 let weighted_component_error = add_nonnegative_upward(
2926 next_up(outputs * normalized_logdet_error),
2927 next_up(self.residual_dof * residual_log_error),
2928 );
2929 let value_evaluation_error =
2930 next_up(0.5 * add_nonnegative_upward(weighted_component_error, final_arithmetic_error));
2931 if !(score_value.is_valid() && value_evaluation_error.is_finite()) {
2932 return Err(AffineRemlError::UnboundedScoreEvaluationError {
2933 lo,
2934 hi,
2935 error: value_evaluation_error,
2936 });
2937 }
2938 let score = ScoreValueEnclosure {
2939 value: score_value,
2940 evaluation_error: value_evaluation_error,
2941 };
2942 Ok((
2943 DerivativeEnclosure {
2944 score,
2945 derivative,
2946 curvature,
2947 },
2948 third,
2949 ))
2950 }
2951
2952 pub fn maximize(
2953 &self,
2954 lo: f64,
2955 hi: f64,
2956 resolution: f64,
2957 ) -> Result<ScoreSearchResult, ScoreSearchError<AffineRemlError>> {
2958 maximize_score_1d(
2959 lo,
2960 hi,
2961 resolution,
2962 |x| self.evaluate(x),
2963 |a, b| self.enclose(a.x, b.x),
2964 )
2965 }
2966
2967 pub fn maximize_value_ordered(
2986 &self,
2987 lo: f64,
2988 hi: f64,
2989 initial_resolution: f64,
2990 ) -> Result<ScoreSearchResult, ScoreSearchError<AffineRemlError>> {
2991 maximize_score_1d_value_ordered(
2992 lo,
2993 hi,
2994 initial_resolution,
2995 |x| self.evaluate(x),
2996 |a, b| self.enclose(a.x, b.x),
2997 )
2998 }
2999}
3000
3001#[derive(Clone, Copy)]
3002struct ModeRanges {
3003 c: ClosedInterval,
3007 w: ClosedInterval,
3009 v: ClosedInterval,
3011 smoothing_increment: ClosedInterval,
3015 singular_fitted: ClosedInterval,
3018 p: ClosedInterval,
3021 q: ClosedInterval,
3024 determinant_third: ClosedInterval,
3031 residual_third: ClosedInterval,
3033}
3034
3035fn normalized_log_mode_enclosure(
3047 gram: f64,
3048 penalty: f64,
3049 lo: f64,
3050 hi: f64,
3051) -> Result<(ClosedInterval, f64), AffineRemlError> {
3052 if penalty == 0.0 {
3053 let range = ClosedInterval::point(gram).ln_positive();
3054 return Ok((
3055 range,
3056 certified_log_forward_error(ClosedInterval::point(gram)),
3057 ));
3058 }
3059 if gram == 0.0 {
3060 let range = ClosedInterval::point(penalty).ln_positive();
3061 return Ok((
3062 range,
3063 certified_log_forward_error(ClosedInterval::point(penalty)),
3064 ));
3065 }
3066
3067 let at_lo = normalized_log_mode_at(gram, penalty, lo)?;
3068 let at_hi = normalized_log_mode_at(gram, penalty, hi)?;
3069 let range = ClosedInterval::new(at_hi.lo, at_lo.hi);
3071 let negative_rho_abs = if lo < 0.0 { -lo } else { 0.0 };
3076 let arithmetic_scale = add_nonnegative_upward(
3077 add_nonnegative_upward(1.0, range.max_abs()),
3078 next_up(2.0 * negative_rho_abs),
3079 );
3080 let arithmetic_error = wilkinson_roundoff(arithmetic_scale, DETERMINANT_VALUE_OPS_PER_MODE);
3081 let mut exp_input_error = 0.0_f64;
3082 if hi >= 0.0 {
3083 let positive_lo = lo.max(0.0);
3084 let exp_neg_rho = exp_interval(-hi, -positive_lo)?;
3085 let argument_lo = ClosedInterval::point(penalty)
3086 .add(ClosedInterval::point(gram).mul(exp_neg_rho))
3087 .lo;
3088 if argument_lo > 0.0 {
3089 exp_input_error = exp_input_error.max(next_up(
3090 gram * certified_exp_forward_error(
3091 ClosedInterval::new(-hi, -positive_lo),
3092 exp_neg_rho,
3093 ) / argument_lo,
3094 ));
3095 } else {
3096 exp_input_error = f64::INFINITY;
3097 }
3098 }
3099 if lo < 0.0 {
3100 let negative_hi = hi.min(0.0);
3101 let exp_rho = exp_interval(lo, negative_hi)?;
3102 if exp_rho.lo > 0.0 {
3103 exp_input_error = exp_input_error.max(certified_exp_relative_forward_error(
3108 ClosedInterval::new(lo, negative_hi),
3109 exp_rho,
3110 ));
3111 } else {
3112 exp_input_error = f64::INFINITY;
3113 }
3114 }
3115 let log_output_error =
3116 certified_log_error_from_output(at_lo).max(certified_log_error_from_output(at_hi));
3117 let log_gram_error = certified_log_forward_error(ClosedInterval::point(gram));
3118 let log_penalty_error = certified_log_forward_error(ClosedInterval::point(penalty));
3119 let log1p_error = certified_ln1p_forward_error();
3120 let elementary_error = add_nonnegative_upward(
3121 exp_input_error,
3122 add_nonnegative_upward(
3123 log_output_error,
3124 add_nonnegative_upward(
3125 log_gram_error,
3126 add_nonnegative_upward(log_penalty_error, log1p_error),
3127 ),
3128 ),
3129 );
3130 Ok((
3131 range,
3132 add_nonnegative_upward(arithmetic_error, elementary_error),
3133 ))
3134}
3135
3136fn normalized_log_mode_at(
3137 gram: f64,
3138 penalty: f64,
3139 rho: f64,
3140) -> Result<ClosedInterval, AffineRemlError> {
3141 if rho >= 0.0 {
3142 let exp_neg_rho = exp_interval(-rho, -rho)?;
3143 let argument =
3144 ClosedInterval::point(penalty).add(ClosedInterval::point(gram).mul(exp_neg_rho));
3145 if !(argument.lo > 0.0 && argument.hi.is_finite()) {
3146 return Err(AffineRemlError::ElementaryEnclosureUnavailable {
3147 function: "ln",
3148 lo: argument.lo,
3149 hi: argument.hi,
3150 });
3151 }
3152 Ok(argument.ln_positive())
3153 } else {
3154 let exp_rho = exp_interval(rho, rho)?;
3155 let argument = ClosedInterval::point(gram).add(ClosedInterval::point(penalty).mul(exp_rho));
3156 if !(argument.lo > 0.0 && argument.hi.is_finite()) {
3157 return Err(AffineRemlError::ElementaryEnclosureUnavailable {
3158 function: "ln",
3159 lo: argument.lo,
3160 hi: argument.hi,
3161 });
3162 }
3163 Ok(argument.ln_positive().sub(ClosedInterval::point(rho)))
3164 }
3165}
3166
3167fn exp_interval(lo: f64, hi: f64) -> Result<ClosedInterval, AffineRemlError> {
3168 let unavailable = || AffineRemlError::ElementaryEnclosureUnavailable {
3169 function: "exp",
3170 lo,
3171 hi,
3172 };
3173 if !(lo.is_finite() && hi.is_finite() && lo <= hi) {
3174 return Err(unavailable());
3175 }
3176 let lower = certified_exp(lo).ok_or_else(unavailable)?;
3177 let upper = certified_exp(hi).ok_or_else(unavailable)?;
3178 let enclosure = ClosedInterval::new(lower.lo.max(0.0), upper.hi).nonnegative();
3179 if !enclosure.is_valid() {
3180 return Err(unavailable());
3181 }
3182 Ok(enclosure)
3183}
3184
3185fn finite_nonnegative_quotient(
3193 numerator: ClosedInterval,
3194 denominator: ClosedInterval,
3195 function: &'static str,
3196) -> Result<ClosedInterval, AffineRemlError> {
3197 if !(numerator.is_valid()
3198 && numerator.lo >= 0.0
3199 && denominator.is_valid()
3200 && denominator.lo > 0.0)
3201 {
3202 return Err(AffineRemlError::ElementaryEnclosureUnavailable {
3203 function,
3204 lo: denominator.lo,
3205 hi: denominator.hi,
3206 });
3207 }
3208 let quotient = ClosedInterval::new(
3209 quotient_down(numerator.lo, denominator.hi).max(0.0),
3210 quotient_up(numerator.hi, denominator.lo),
3211 );
3212 if !(quotient.is_valid() && quotient.hi.is_finite()) {
3213 return Err(AffineRemlError::ElementaryEnclosureUnavailable {
3214 function,
3215 lo: quotient.lo,
3216 hi: quotient.hi,
3217 });
3218 }
3219 Ok(quotient.nonnegative())
3220}
3221
3222fn centred_or(
3254 direct: ClosedInterval,
3255 point: ClosedInterval,
3256 slope: ClosedInterval,
3257 offset: ClosedInterval,
3258) -> ClosedInterval {
3259 if !(slope.is_valid() && slope.lo.is_finite() && slope.hi.is_finite()) {
3260 return direct;
3261 }
3262 let remainder = slope.mul(offset);
3263 if !(remainder.is_valid() && remainder.lo.is_finite() && remainder.hi.is_finite()) {
3264 return direct;
3265 }
3266 let centred = point.add(remainder);
3267 if !centred.is_valid() {
3268 return direct;
3269 }
3270 direct.intersection(centred).unwrap_or(direct)
3275}
3276
3277fn mode_ranges(
3278 gram: f64,
3279 penalty: f64,
3280 projected_square: f64,
3281 lambda: ClosedInterval,
3282) -> Result<ModeRanges, AffineRemlError> {
3283 if penalty == 0.0 {
3284 let v = ClosedInterval::point(projected_square)
3285 .div_positive(ClosedInterval::point(gram))
3286 .nonnegative();
3287 return Ok(ModeRanges {
3288 c: ClosedInterval::point(0.0),
3289 w: ClosedInterval::point(0.0),
3290 v,
3291 smoothing_increment: ClosedInterval::point(0.0),
3292 singular_fitted: ClosedInterval::point(0.0),
3293 p: ClosedInterval::point(0.0),
3294 q: ClosedInterval::point(0.0),
3295 determinant_third: ClosedInterval::point(0.0),
3296 residual_third: ClosedInterval::point(0.0),
3297 });
3298 }
3299 if gram == 0.0 {
3300 let zero = ClosedInterval::point(0.0);
3301 if projected_square == 0.0 {
3302 return Ok(ModeRanges {
3303 c: zero,
3304 w: zero,
3305 v: zero,
3306 smoothing_increment: zero,
3307 singular_fitted: zero,
3308 p: zero,
3309 q: zero,
3310 determinant_third: zero,
3311 residual_third: zero,
3312 });
3313 }
3314
3315 let h = lambda.mul(ClosedInterval::point(penalty)).nonnegative();
3323 let projected = ClosedInterval::point(projected_square);
3324 let v = if h.lo > 0.0 {
3325 finite_nonnegative_quotient(projected, h, "gram-zero residual quotient")?
3326 } else {
3327 let scaled = finite_nonnegative_quotient(
3328 projected,
3329 ClosedInterval::point(penalty),
3330 "gram-zero residual quotient",
3331 )?;
3332 finite_nonnegative_quotient(scaled, lambda, "gram-zero residual quotient")?
3333 };
3334 return Ok(ModeRanges {
3335 c: ClosedInterval::point(0.0),
3336 w: ClosedInterval::point(0.0),
3337 v,
3338 smoothing_increment: zero,
3339 singular_fitted: v,
3340 p: v,
3341 q: v.neg(),
3342 determinant_third: zero,
3346 residual_third: v,
3347 });
3348 }
3349
3350 let t = lambda
3355 .mul(ClosedInterval::point(penalty))
3356 .div_positive(ClosedInterval::point(gram))
3357 .nonnegative();
3358 let scale = ClosedInterval::point(projected_square)
3359 .div_positive(ClosedInterval::point(gram))
3360 .nonnegative();
3361 let kernels = kernel_ranges(t);
3362 Ok(ModeRanges {
3363 c: kernels.v,
3364 w: kernels.w,
3365 v: scale.mul(kernels.v).nonnegative(),
3366 smoothing_increment: scale.mul(kernels.u).nonnegative(),
3367 singular_fitted: ClosedInterval::point(0.0),
3368 p: scale.mul(kernels.w).nonnegative(),
3369 q: scale.mul(kernels.k),
3370 determinant_third: kernels.k,
3371 residual_third: scale.mul(kernels.third),
3372 })
3373}
3374
3375#[derive(Clone, Copy)]
3376struct KernelRanges {
3377 v: ClosedInterval,
3379 u: ClosedInterval,
3381 w: ClosedInterval,
3383 k: ClosedInterval,
3385 third: ClosedInterval,
3399}
3400
3401fn kernel_at(t: ClosedInterval) -> KernelRanges {
3402 let one = ClosedInterval::point(1.0);
3403 let denom = one.add(t);
3404 let v = one.div_positive(denom).nonnegative();
3405 let u = t.mul(v).nonnegative();
3406 let w = u.mul(v).nonnegative();
3407 let k = w.mul(one.sub(t)).div_positive(denom);
3408 let third = w
3412 .mul(one.sub(t.scale(4.0)).add(t.square()))
3413 .div_positive(denom.square());
3414 KernelRanges { v, u, w, k, third }
3415}
3416
3417fn kernel_ranges(t: ClosedInterval) -> KernelRanges {
3418 let left = kernel_at(ClosedInterval::point(t.lo));
3419 let right = kernel_at(ClosedInterval::point(t.hi));
3420 let mut v = ClosedInterval::new(right.v.lo, left.v.hi).nonnegative();
3421 let u = ClosedInterval::new(left.u.lo, right.u.hi).nonnegative();
3422 let mut w = left.w.hull(right.w).nonnegative();
3423 let mut k = left.k.hull(right.k);
3424 let mut third = left.third.hull(right.third);
3425
3426 if t.contains(1.0) {
3427 let critical = kernel_at(ClosedInterval::point(1.0));
3428 w = w.hull(critical.w).nonnegative();
3429 third = third.hull(critical.third);
3433 }
3434
3435 let sqrt_three =
3439 certified_sqrt_positive(3.0).expect("three is a finite positive square-root argument");
3440 let critical_points = [
3441 ClosedInterval::point(2.0).sub(sqrt_three),
3442 ClosedInterval::point(2.0).add(sqrt_three),
3443 ];
3444 for critical in critical_points {
3445 if critical.hi >= t.lo && critical.lo <= t.hi {
3446 k = k.hull(kernel_at(critical).k);
3447 }
3448 }
3449
3450 let sqrt_six =
3453 certified_sqrt_positive(6.0).expect("six is a finite positive square-root argument");
3454 let two_sqrt_six = sqrt_six.scale(2.0);
3455 for critical in [
3456 ClosedInterval::point(5.0).sub(two_sqrt_six),
3457 ClosedInterval::point(5.0).add(two_sqrt_six),
3458 ] {
3459 if critical.hi >= t.lo && critical.lo <= t.hi {
3460 third = third.hull(kernel_at(critical).third);
3461 }
3462 }
3463
3464 v.lo = v.lo.max(0.0);
3467 v.hi = v.hi.min(next_up(1.0));
3468 KernelRanges {
3469 v,
3470 u,
3471 w,
3472 k,
3473 third,
3474 }
3475}
3476
3477const LOG_SERIES_TERMS: usize = 18;
3478const EXP_SERIES_TERMS: usize = 18;
3479const EXP_RANGE_SQUARINGS: usize = 6;
3480
3481fn certified_sqrt_positive(value: f64) -> Option<ClosedInterval> {
3482 if !(value.is_finite() && value > 0.0) {
3483 return None;
3484 }
3485 let guess = value.sqrt();
3489 if !(guess.is_finite() && guess > 0.0) {
3490 return None;
3491 }
3492 let mut lo = next_down(guess);
3493 for _ in 0..8 {
3494 if ClosedInterval::point(lo).square().hi <= value {
3495 break;
3496 }
3497 lo = next_down(lo);
3498 }
3499 let mut hi = next_up(guess);
3500 for _ in 0..8 {
3501 if ClosedInterval::point(hi).square().lo >= value {
3502 break;
3503 }
3504 hi = next_up(hi);
3505 }
3506 (ClosedInterval::point(lo).square().hi <= value
3507 && ClosedInterval::point(hi).square().lo >= value)
3508 .then(|| ClosedInterval::new(lo, hi))
3509}
3510
3511fn certified_log_from_atanh(z: ClosedInterval) -> ClosedInterval {
3514 let z_abs = z.max_abs();
3515 assert!(z_abs <= 1.0 / 3.0 + f64::EPSILON);
3516 let z2 = z.square();
3517 let mut power = z;
3518 let mut sum = z;
3519 for term in 1..LOG_SERIES_TERMS {
3520 power = power.mul(z2);
3521 sum = sum.add(power.div_positive(ClosedInterval::point((2 * term + 1) as f64)));
3522 }
3523 let next_power = power.mul(z2).max_abs();
3524 let first_denominator = (2 * LOG_SERIES_TERMS + 1) as f64;
3525 let geometric_denominator = next_down(1.0 - next_up(z_abs * z_abs));
3526 let tail = if geometric_denominator > 0.0 {
3527 next_up(next_up(2.0 * next_power) / next_down(first_denominator * geometric_denominator))
3528 } else {
3529 f64::INFINITY
3530 };
3531 sum.scale(2.0).widen(tail)
3532}
3533
3534fn certified_ln_two() -> ClosedInterval {
3535 static LN_TWO: OnceLock<ClosedInterval> = OnceLock::new();
3536 *LN_TWO.get_or_init(|| {
3537 let third = ClosedInterval::point(1.0).div_positive(ClosedInterval::point(3.0));
3541 certified_log_from_atanh(third)
3542 })
3543}
3544
3545fn positive_binary64_parts(value: f64) -> Option<(f64, i32)> {
3548 if !(value.is_finite() && value > 0.0) {
3549 return None;
3550 }
3551 let bits = value.to_bits();
3552 let exponent_bits = ((bits >> 52) & 0x7ff) as i32;
3553 let fraction = bits & ((1_u64 << 52) - 1);
3554 if exponent_bits == 0 {
3555 let highest = 63_i32 - fraction.leading_zeros() as i32;
3558 let normalized = fraction << (52 - highest);
3559 let mantissa_bits = (1023_u64 << 52) | (normalized - (1_u64 << 52));
3560 Some((f64::from_bits(mantissa_bits), highest - 1074))
3561 } else {
3562 let mantissa_bits = (1023_u64 << 52) | fraction;
3563 Some((f64::from_bits(mantissa_bits), exponent_bits - 1023))
3564 }
3565}
3566
3567pub fn certified_ln_positive(value: f64) -> Option<ClosedInterval> {
3575 if !(value.is_finite() && value > 0.0) {
3576 return None;
3577 }
3578 if value == 1.0 {
3579 return Some(ClosedInterval::point(0.0));
3580 }
3581 let (mantissa, exponent) = positive_binary64_parts(value)?;
3582 let m = ClosedInterval::point(mantissa);
3583 let z = m
3584 .sub(ClosedInterval::point(1.0))
3585 .div_positive(m.add(ClosedInterval::point(1.0)));
3586 Some(certified_log_from_atanh(z).add(certified_ln_two().scale(exponent as f64)))
3587}
3588
3589pub fn certified_ln_1p(value: f64) -> Option<ClosedInterval> {
3598 if !(value.is_finite() && value > -1.0) {
3599 return None;
3600 }
3601 if value == 0.0 {
3602 return Some(ClosedInterval::point(0.0));
3603 }
3604 if (0.0..=1.0).contains(&value) {
3605 let x = ClosedInterval::point(value);
3606 let z = x.div_positive(ClosedInterval::point(2.0).add(x));
3607 return Some(certified_log_from_atanh(z));
3608 }
3609 if value > 1.0 {
3610 let reciprocal = ClosedInterval::point(1.0)
3611 .div_positive(ClosedInterval::point(value))
3612 .nonnegative();
3613 let z = reciprocal
3614 .div_positive(ClosedInterval::point(2.0).add(reciprocal))
3615 .nonnegative();
3616 return Some(certified_ln_positive(value)?.add(certified_log_from_atanh(z)));
3617 }
3618 let argument = ClosedInterval::point(1.0).add(ClosedInterval::point(value));
3619 if !(argument.lo > 0.0) {
3620 return None;
3621 }
3622 let lo = certified_ln_positive(argument.lo)?;
3623 let hi = certified_ln_positive(argument.hi)?;
3624 Some(ClosedInterval::new(lo.lo, hi.hi))
3625}
3626
3627fn exact_power_of_two(exponent: i32) -> Option<f64> {
3628 match exponent {
3629 -1074..=-1023 => {
3630 let bit = (exponent + 1074) as u32;
3631 Some(f64::from_bits(1_u64 << bit))
3632 }
3633 -1022..=1023 => Some(f64::from_bits(((exponent + 1023) as u64) << 52)),
3634 _ => None,
3635 }
3636}
3637
3638fn positive_ratio_over_product(
3644 numerator: f64,
3645 first_denominator: f64,
3646 second_denominator: f64,
3647) -> Option<f64> {
3648 if numerator == 0.0 {
3649 return Some(0.0);
3650 }
3651 let (numerator_mantissa, numerator_exponent) = positive_binary64_parts(numerator)?;
3652 let (first_mantissa, first_exponent) = positive_binary64_parts(first_denominator)?;
3653 let (second_mantissa, second_exponent) = positive_binary64_parts(second_denominator)?;
3654 let mut mantissa = numerator_mantissa / first_mantissa / second_mantissa;
3655 let mut exponent = numerator_exponent - first_exponent - second_exponent;
3656 if !(mantissa.is_finite() && mantissa > 0.0) {
3657 return None;
3658 }
3659 while mantissa < 1.0 {
3660 mantissa *= 2.0;
3661 exponent -= 1;
3662 }
3663 while mantissa >= 2.0 {
3664 mantissa *= 0.5;
3665 exponent += 1;
3666 }
3667 if exponent < -1075 {
3668 return Some(0.0);
3669 }
3670 if exponent > 1023 {
3671 return None;
3672 }
3673 let value = if exponent == -1075 {
3674 (0.5 * mantissa) * exact_power_of_two(-1074)?
3675 } else {
3676 mantissa * exact_power_of_two(exponent)?
3677 };
3678 (value.is_finite() && value >= 0.0).then_some(value)
3679}
3680
3681pub fn certified_exp(value: f64) -> Option<ClosedInterval> {
3691 if !value.is_finite() {
3692 return None;
3693 }
3694 if value == 0.0 {
3695 return Some(ClosedInterval::point(1.0));
3696 }
3697 let mut exponent = (value / std::f64::consts::LN_2).round() as i32;
3701 exponent = exponent.clamp(-1074, 1023);
3702 let remainder = ClosedInterval::point(value).sub(certified_ln_two().scale(exponent as f64));
3703 if !(remainder.is_valid() && remainder.max_abs() < 4.0) {
3704 return None;
3705 }
3706 let reduction = (1_u64 << EXP_RANGE_SQUARINGS) as f64;
3707 let reduced = remainder.scale(1.0 / reduction);
3708 if !(reduced.max_abs() < 1.0 / 16.0) {
3709 return None;
3710 }
3711 let mut term = ClosedInterval::point(1.0);
3712 let mut sum = term;
3713 for degree in 1..=EXP_SERIES_TERMS {
3714 term = term
3715 .mul(reduced)
3716 .div_positive(ClosedInterval::point(degree as f64));
3717 sum = sum.add(term);
3718 }
3719 let z = reduced.max_abs();
3720 let first_omitted = next_up(term.max_abs() * z / (EXP_SERIES_TERMS + 1) as f64);
3721 let tail = next_up(first_omitted / next_down(1.0 - z));
3723 let mut result = sum.widen(tail);
3724 for _ in 0..EXP_RANGE_SQUARINGS {
3725 result = result.square();
3726 }
3727 result = result.mul(ClosedInterval::point(exact_power_of_two(exponent)?));
3728 Some(result.nonnegative())
3729}
3730
3731#[inline]
3732fn certified_midpoint(interval: ClosedInterval) -> f64 {
3733 let midpoint = interval.lo + 0.5 * (interval.hi - interval.lo);
3734 midpoint.max(interval.lo).min(interval.hi)
3735}
3736
3737#[inline]
3743pub fn certified_exp_representative(value: f64) -> Option<f64> {
3744 certified_exp(value).map(certified_midpoint)
3745}
3746
3747#[inline]
3748fn certified_ln_value(value: f64) -> Option<f64> {
3749 certified_ln_positive(value).map(certified_midpoint)
3750}
3751
3752#[inline]
3753fn certified_ln_1p_value(value: f64) -> Option<f64> {
3754 certified_ln_1p(value).map(certified_midpoint)
3755}
3756
3757fn interval_diameter(interval: ClosedInterval) -> f64 {
3758 if interval.lo == interval.hi {
3759 0.0
3760 } else {
3761 next_up(interval.hi - interval.lo)
3762 }
3763}
3764
3765fn log_series_tail_max() -> f64 {
3766 let z = next_up(1.0 / 3.0);
3767 let z2 = next_up(z * z);
3768 let mut power = z;
3769 for _ in 1..LOG_SERIES_TERMS {
3770 power = next_up(power * z2);
3771 }
3772 power = next_up(power * z2);
3773 let denominator = next_down((2 * LOG_SERIES_TERMS + 1) as f64 * next_down(1.0 - z2));
3774 next_up(next_up(2.0 * power) / denominator)
3775}
3776
3777fn exp_series_relative_tail_max() -> f64 {
3781 let z = next_up(1.0 / 16.0);
3782 let mut term = 1.0;
3783 for degree in 1..=EXP_SERIES_TERMS {
3784 term = next_up(next_up(term * z) / degree as f64);
3785 }
3786 let first_omitted = next_up(next_up(term * z) / (EXP_SERIES_TERMS + 1) as f64);
3787 let absolute_tail = next_up(first_omitted / next_down(1.0 - z));
3788 let mut factor =
3792 ClosedInterval::point(1.0).add(ClosedInterval::point(next_up(2.0 * absolute_tail)));
3793 for _ in 0..EXP_RANGE_SQUARINGS {
3794 factor = factor.square();
3795 }
3796 next_up(factor.hi - 1.0).max(0.0)
3797}
3798
3799fn certified_log_forward_error(input: ClosedInterval) -> f64 {
3802 if !(input.lo > 0.0 && input.hi.is_finite()) {
3803 return f64::INFINITY;
3804 }
3805 let exponent_abs = [input.lo, input.hi]
3806 .into_iter()
3807 .map(|value| {
3808 let bits = value.to_bits();
3809 let exponent_bits = ((bits >> 52) & 0x7ff) as i32;
3810 if exponent_bits == 0 {
3811 let fraction = bits & ((1_u64 << 52) - 1);
3812 let highest = 63_i32 - fraction.leading_zeros() as i32;
3813 (highest - 1074).unsigned_abs() as f64
3814 } else {
3815 (exponent_bits - 1023).unsigned_abs() as f64
3816 }
3817 })
3818 .fold(0.0_f64, f64::max);
3819 let ln_two_uncertainty = next_up(exponent_abs * interval_diameter(certified_ln_two()));
3820 let mantissa_ops = 6 * LOG_SERIES_TERMS + 32;
3823 let mantissa_error =
3824 add_nonnegative_upward(wilkinson_roundoff(1.0, mantissa_ops), log_series_tail_max());
3825 add_nonnegative_upward(ln_two_uncertainty, mantissa_error)
3826}
3827
3828fn certified_log_error_from_output(output: ClosedInterval) -> f64 {
3829 if !output.is_valid() {
3830 return f64::INFINITY;
3831 }
3832 let exponent_abs = next_up(output.max_abs() / certified_ln_two().lo.abs()).ceil() + 1.0;
3834 let ln_two_uncertainty = next_up(exponent_abs * interval_diameter(certified_ln_two()));
3835 let mantissa_ops = 6 * LOG_SERIES_TERMS + 32;
3836 add_nonnegative_upward(
3837 ln_two_uncertainty,
3838 add_nonnegative_upward(wilkinson_roundoff(1.0, mantissa_ops), log_series_tail_max()),
3839 )
3840}
3841
3842fn certified_ln1p_forward_error() -> f64 {
3843 let operations = 6 * LOG_SERIES_TERMS + 36;
3844 add_nonnegative_upward(wilkinson_roundoff(1.0, operations), log_series_tail_max())
3845}
3846
3847fn certified_exp_forward_error(input: ClosedInterval, output: ClosedInterval) -> f64 {
3851 if !(input.is_valid() && output.is_valid() && output.lo >= 0.0) {
3852 return f64::INFINITY;
3853 }
3854 let exponent_abs = next_up(input.max_abs() / certified_ln_two().lo).ceil() + 1.0;
3855 let reduction_error = next_up(exponent_abs * interval_diameter(certified_ln_two()));
3856 if !(reduction_error < 1.0) {
3857 return f64::INFINITY;
3858 }
3859 let propagated_reduction =
3861 next_up(output.max_abs() * reduction_error / next_down(1.0 - reduction_error));
3862 let operations = 6 * EXP_SERIES_TERMS + 4 * EXP_RANGE_SQUARINGS + 40;
3865 let arithmetic = wilkinson_roundoff(output.max_abs(), operations);
3866 let truncation = next_up(output.max_abs() * exp_series_relative_tail_max());
3867 add_nonnegative_upward(
3868 propagated_reduction,
3869 add_nonnegative_upward(arithmetic, truncation),
3870 )
3871}
3872
3873fn certified_exp_relative_forward_error(input: ClosedInterval, output: ClosedInterval) -> f64 {
3885 if !(input.is_valid() && output.is_valid() && output.lo > 0.0 && output.hi.is_finite()) {
3886 return f64::INFINITY;
3887 }
3888 let exponent_abs = next_up(input.max_abs() / certified_ln_two().lo).ceil() + 1.0;
3889 let reduction_error = next_up(exponent_abs * interval_diameter(certified_ln_two()));
3890 if !(reduction_error < 1.0) {
3891 return f64::INFINITY;
3892 }
3893 let relative_reduction = next_up(reduction_error / next_down(1.0 - reduction_error));
3894 let operations = 6 * EXP_SERIES_TERMS + 4 * EXP_RANGE_SQUARINGS + 40;
3895 let relative_arithmetic = wilkinson_roundoff(1.0, operations);
3896 let relative_underflow = next_up(wilkinson_roundoff(0.0, operations) / output.lo);
3897 add_nonnegative_upward(
3898 relative_reduction,
3899 add_nonnegative_upward(
3900 relative_arithmetic,
3901 add_nonnegative_upward(exp_series_relative_tail_max(), relative_underflow),
3902 ),
3903 )
3904}
3905
3906fn add_nonnegative_upward(accumulator: f64, term: f64) -> f64 {
3908 if accumulator == f64::INFINITY || term == f64::INFINITY {
3909 f64::INFINITY
3910 } else if term == 0.0 {
3911 accumulator
3912 } else {
3913 next_up(accumulator + term)
3914 }
3915}
3916
3917fn enclosure_excess(mathematical: ClosedInterval, resolved: ClosedInterval) -> f64 {
3920 let lower = if mathematical.lo == resolved.lo {
3921 0.0
3922 } else {
3923 next_up(mathematical.lo - resolved.lo)
3924 };
3925 let upper = if mathematical.hi == resolved.hi {
3926 0.0
3927 } else {
3928 next_up(resolved.hi - mathematical.hi)
3929 };
3930 lower.max(upper).max(0.0)
3931}
3932
3933fn wilkinson_roundoff(magnitude: f64, operations: usize) -> f64 {
3938 if operations == 0 {
3939 return 0.0;
3940 }
3941 if !(magnitude.is_finite() && magnitude >= 0.0) {
3942 return f64::INFINITY;
3943 }
3944 let operation_count = next_up(operations as f64);
3949 let underflow = next_up(operation_count * f64::from_bits(1));
3950 if magnitude == 0.0 {
3951 return underflow;
3952 }
3953 let unit_roundoff = 0.5 * f64::EPSILON;
3955 let ku = next_up(operation_count * unit_roundoff);
3956 if !(ku < 1.0) {
3957 return f64::INFINITY;
3958 }
3959 let denominator = next_down(1.0 - ku);
3960 if !(denominator > 0.0) {
3961 return f64::INFINITY;
3962 }
3963 let gamma = next_up(ku / denominator);
3964 add_nonnegative_upward(next_up(gamma * magnitude), underflow)
3965}
3966
3967#[inline]
3968fn sum_down(left: f64, right: f64) -> f64 {
3969 let value = left + right;
3970 if sum_is_exact(left, right, value) {
3971 value
3972 } else {
3973 next_down(value)
3974 }
3975}
3976
3977#[inline]
3978fn sum_up(left: f64, right: f64) -> f64 {
3979 let value = left + right;
3980 if sum_is_exact(left, right, value) {
3981 value
3982 } else {
3983 next_up(value)
3984 }
3985}
3986
3987#[inline]
3995fn sum_is_exact(left: f64, right: f64, value: f64) -> bool {
3996 if left == 0.0 || right == 0.0 {
3997 return true;
3998 }
3999 if !(left.is_finite() && right.is_finite() && value.is_finite()) {
4000 return value == left || value == right;
4001 }
4002 let virtual_right = value - left;
4003 let virtual_left = value - virtual_right;
4004 let right_residual = right - virtual_right;
4005 let left_residual = left - virtual_left;
4006 left_residual + right_residual == 0.0
4007}
4008
4009#[inline]
4010fn product_is_exact(left: f64, right: f64) -> bool {
4011 left == 0.0 || right == 0.0 || left.abs() == 1.0 || right.abs() == 1.0
4012}
4013
4014#[inline]
4015fn product_down(left: f64, right: f64) -> f64 {
4016 let value = left * right;
4017 if product_is_exact(left, right) {
4018 if value.is_nan() { 0.0 } else { value }
4019 } else {
4020 next_down(value)
4021 }
4022}
4023
4024#[inline]
4025fn product_up(left: f64, right: f64) -> f64 {
4026 let value = left * right;
4027 if product_is_exact(left, right) {
4028 if value.is_nan() { 0.0 } else { value }
4029 } else {
4030 next_up(value)
4031 }
4032}
4033
4034#[inline]
4035fn quotient_down(numerator: f64, denominator: f64) -> f64 {
4036 let value = numerator / denominator;
4037 if numerator == 0.0 || denominator.abs() == 1.0 {
4038 value
4039 } else {
4040 next_down(value)
4041 }
4042}
4043
4044#[inline]
4045fn quotient_up(numerator: f64, denominator: f64) -> f64 {
4046 let value = numerator / denominator;
4047 if numerator == 0.0 || denominator.abs() == 1.0 {
4048 value
4049 } else {
4050 next_up(value)
4051 }
4052}
4053
4054fn next_down(value: f64) -> f64 {
4057 if value.is_nan() || value == f64::NEG_INFINITY {
4058 return value;
4059 }
4060 if value == 0.0 {
4061 return -f64::from_bits(1);
4062 }
4063 let bits = value.to_bits();
4064 f64::from_bits(if value > 0.0 { bits - 1 } else { bits + 1 })
4065}
4066
4067fn next_up(value: f64) -> f64 {
4070 if value.is_nan() || value == f64::INFINITY {
4071 return value;
4072 }
4073 if value == 0.0 {
4074 return f64::from_bits(1);
4075 }
4076 let bits = value.to_bits();
4077 f64::from_bits(if value > 0.0 { bits + 1 } else { bits - 1 })
4078}
4079
4080#[cfg(test)]
4081mod tests {
4082 use super::*;
4083
4084 fn polynomial_hidden_bump_jet(x: f64) -> ScoreJet {
4085 let p = x * (x - 0.5) * (x - 1.0);
4086 let dp = 3.0 * x * x - 3.0 * x + 0.5;
4087 let ddp = 6.0 * x - 3.0;
4088 ScoreJet {
4089 value: x + 1000.0 * p * p,
4090 derivative: 1.0 + 2000.0 * p * dp,
4091 curvature: 2000.0 * (dp * dp + p * ddp),
4092 third: 2000.0 * (3.0 * dp * ddp + p * 6.0),
4093 }
4094 }
4095
4096 fn polynomial_hidden_bump_enclosure(lo: f64, hi: f64) -> DerivativeEnclosure {
4097 let x = ClosedInterval::new(lo, hi);
4098 let p = x
4099 .mul(x.sub(ClosedInterval::point(0.5)))
4100 .mul(x.sub(ClosedInterval::point(1.0)));
4101 let dp = x
4102 .square()
4103 .scale(3.0)
4104 .sub(x.scale(3.0))
4105 .add(ClosedInterval::point(0.5));
4106 let ddp = x.scale(6.0).sub(ClosedInterval::point(3.0));
4107 let value = x.add(p.square().scale(1000.0));
4108 DerivativeEnclosure {
4109 score: ScoreValueEnclosure {
4110 value,
4111 evaluation_error: wilkinson_roundoff(value.max_abs(), 7),
4112 },
4113 derivative: ClosedInterval::point(1.0).add(p.mul(dp).scale(2000.0)),
4114 curvature: dp.square().add(p.mul(ddp)).scale(2000.0),
4115 }
4116 }
4117
4118 #[test]
4119 fn hidden_between_endpoint_and_midpoint_samples_is_found() {
4120 let result = maximize_score_1d(
4121 0.0,
4122 1.0,
4123 1.0e-9,
4124 |x| -> Result<_, String> { Ok(polynomial_hidden_bump_jet(x)) },
4125 |lo, hi| -> Result<_, String> { Ok(polynomial_hidden_bump_enclosure(lo.x, hi.x)) },
4126 )
4127 .expect("certified search");
4128
4129 assert_eq!(polynomial_hidden_bump_jet(0.0).derivative, 1.0);
4132 assert_eq!(polynomial_hidden_bump_jet(0.5).derivative, 1.0);
4133 assert_eq!(polynomial_hidden_bump_jet(1.0).derivative, 1.0);
4134 assert!(result.optimum.x > 0.5 && result.optimum.x < 1.0);
4135 assert!(result.optimum.value > 2.9);
4136 assert!(
4137 result
4138 .stationary_points
4139 .iter()
4140 .any(|point| point.bracket.contains(result.optimum.x)),
4141 "the hidden global maximizer must have a retained root certificate"
4142 );
4143 assert!(
4144 result
4145 .dominated_regions
4146 .iter()
4147 .all(|region| region.score.value.hi < region.incumbent_lower),
4148 "every skipped stationary branch must carry a strict exact dominance proof"
4149 );
4150 }
4151
4152 fn quartic_jet(x: f64) -> ScoreJet {
4153 ScoreJet {
4154 value: -(x * x - 1.0).powi(2),
4155 derivative: 4.0 * x - 4.0 * x * x * x,
4156 curvature: 4.0 - 12.0 * x * x,
4157 third: -24.0 * x,
4158 }
4159 }
4160
4161 fn quartic_enclosure(lo: f64, hi: f64) -> DerivativeEnclosure {
4162 let x = ClosedInterval::new(lo, hi);
4163 let shifted_square = x.square().sub(ClosedInterval::point(1.0));
4164 let value = shifted_square.square().neg();
4165 if lo == hi && (lo == -1.0 || lo == 0.0 || lo == 1.0) {
4166 return DerivativeEnclosure {
4167 score: ScoreValueEnclosure {
4168 value,
4169 evaluation_error: wilkinson_roundoff(value.max_abs(), 4),
4170 },
4171 derivative: ClosedInterval::point(0.0),
4172 curvature: ClosedInterval::point(quartic_jet(lo).curvature),
4173 };
4174 }
4175 DerivativeEnclosure {
4176 score: ScoreValueEnclosure {
4177 value,
4178 evaluation_error: wilkinson_roundoff(value.max_abs(), 4),
4179 },
4180 derivative: x.scale(4.0).sub(x.mul(x).mul(x).scale(4.0)),
4181 curvature: ClosedInterval::point(4.0).sub(x.square().scale(12.0)),
4182 }
4183 }
4184
4185 #[test]
4186 fn globally_relevant_roots_are_isolated_and_dominated_structure_is_audited() {
4187 let result = maximize_score_1d(
4188 -2.0,
4189 2.0,
4190 1.0e-10,
4191 |x| -> Result<_, String> { Ok(quartic_jet(x)) },
4192 |lo, hi| -> Result<_, String> { Ok(quartic_enclosure(lo.x, hi.x)) },
4193 )
4194 .expect("certified search");
4195 assert_eq!(
4196 result.stationary_points.len(),
4197 2,
4198 "both equal global maxima must survive strict dominance"
4199 );
4200 for expected in [-1.0_f64, 1.0] {
4201 let point = result
4202 .stationary_points
4203 .iter()
4204 .find(|point| (point.sample.x - expected).abs() <= 1.0e-9)
4205 .unwrap_or_else(|| panic!("missing global maximum at {expected}"));
4206 assert!(point.bracket.hi - point.bracket.lo <= 1.0e-10);
4207 }
4208 assert!(
4209 result
4210 .dominated_regions
4211 .iter()
4212 .any(|region| region.bracket.contains(0.0)),
4213 "the strictly inferior stationary minimum must remain auditable as dominated"
4214 );
4215 assert!((result.optimum.x.abs() - 1.0).abs() <= 1.0e-9);
4216 }
4217
4218 #[test]
4219 fn exact_dominance_prunes_an_uninformative_saturated_tail() {
4220 let mut evaluations = 0_usize;
4221 let result = maximize_score_1d(
4222 -1.0,
4223 10.0,
4224 1.0e-9,
4225 |x| -> Result<_, String> {
4226 evaluations += 1;
4227 Ok(ScoreJet {
4228 value: 1.0 - x * x,
4229 derivative: -2.0 * x,
4230 curvature: -2.0,
4231 third: 0.0,
4232 })
4233 },
4234 |left, right| -> Result<_, String> {
4235 let x = ClosedInterval::new(left.x, right.x);
4236 let value = ClosedInterval::point(1.0).sub(x.square());
4237 let root_side_cell = right.x <= 1.0;
4238 Ok(DerivativeEnclosure {
4239 score: ScoreValueEnclosure {
4240 value,
4241 evaluation_error: 1.0e-12,
4242 },
4243 derivative: if root_side_cell || left.x == right.x {
4244 x.scale(-2.0)
4245 } else {
4246 ClosedInterval::new(-100.0, 100.0)
4249 },
4250 curvature: if root_side_cell || left.x == right.x {
4251 ClosedInterval::point(-2.0)
4252 } else {
4253 ClosedInterval::new(-100.0, 100.0)
4254 },
4255 })
4256 },
4257 )
4258 .expect("the exact score incumbent must dominate the uninformative tail");
4259
4260 assert_eq!(result.optimum.x, 0.0);
4261 assert!(result.value_certificate.maximum.contains(1.0));
4262 assert!(
4263 !result.dominated_regions.is_empty(),
4264 "the fixture's saturated tail must be terminated by exact dominance"
4265 );
4266 assert!(
4267 result
4268 .dominated_regions
4269 .iter()
4270 .all(|region| region.score.value.hi < region.incumbent_lower),
4271 "every retained dominance decision must expose its strict exact ordering"
4272 );
4273 assert!(
4274 evaluations < 16,
4275 "the low-score tail was enumerated instead of pruned ({evaluations} evaluations)"
4276 );
4277 }
4278
4279 const ROUNDED_ZERO_ABSCISSA: f64 = 1.5;
4282
4283 #[test]
4299 fn a_rounded_zero_at_a_cell_endpoint_does_not_close_the_cell() {
4300 let mut rounded_zeros = 0_usize;
4301 let result = maximize_score_1d(
4302 0.0,
4303 3.0,
4304 1.0e-9,
4305 |x| -> Result<_, String> {
4306 let shifted = x - 2.5;
4307 let derivative = if x == ROUNDED_ZERO_ABSCISSA {
4308 rounded_zeros += 1;
4309 0.0
4310 } else {
4311 -2.0 * shifted
4312 };
4313 Ok(ScoreJet {
4314 value: 1.0 - shifted * shifted,
4315 derivative,
4316 curvature: -2.0,
4317 third: 0.0,
4318 })
4319 },
4320 |left, right| -> Result<_, String> {
4321 let x = ClosedInterval::new(left.x, right.x);
4322 let shifted = x.sub(ClosedInterval::point(2.5));
4323 let value = ClosedInterval::point(1.0).sub(shifted.square());
4324 Ok(DerivativeEnclosure {
4325 score: ScoreValueEnclosure {
4326 value,
4327 evaluation_error: wilkinson_roundoff(value.max_abs(), 3),
4328 },
4329 derivative: shifted.scale(-2.0),
4332 curvature: ClosedInterval::point(-2.0),
4333 })
4334 },
4335 )
4336 .expect("certified search");
4337
4338 assert!(
4339 rounded_zeros > 0,
4340 "fixture premise unmet: the search never evaluated x = {ROUNDED_ZERO_ABSCISSA}"
4341 );
4342 assert!(
4343 (result.optimum.x - 2.5).abs() <= 1.0e-9,
4344 "reported the maximum at x={} (value {}) instead of x=2.5",
4345 result.optimum.x,
4346 result.optimum.value,
4347 );
4348 assert!(
4349 result.value_certificate.maximum.contains(1.0),
4350 "the exact maximum escaped the global score certificate: {:?}",
4351 result.value_certificate,
4352 );
4353 assert!(
4354 result
4355 .stationary_points
4356 .iter()
4357 .all(|point| point.sample.x != ROUNDED_ZERO_ABSCISSA),
4358 "a derivative that rounded to zero was reported as a stationary point",
4359 );
4360 let root = result
4361 .stationary_points
4362 .iter()
4363 .find(|point| point.bracket.contains(2.5))
4364 .expect("the exact quadratic root must be isolated");
4365 assert_eq!(
4366 root.bracket,
4367 ClosedInterval::point(2.5),
4368 "the cancellation-free point enclosure must preserve the exact dyadic root"
4369 );
4370 }
4371
4372 #[test]
4373 fn adjacent_cell_evidence_is_retained_when_point_derivative_is_uninformative() {
4374 let planted = 0.7_f64;
4375 let result = maximize_score_1d(
4376 0.0,
4377 1.0,
4378 1.0e-9,
4379 |x| -> Result<_, String> {
4380 let shifted = x - planted;
4381 Ok(ScoreJet {
4382 value: 1.0 - shifted * shifted,
4383 derivative: -2.0 * shifted,
4384 curvature: -2.0,
4385 third: 0.0,
4386 })
4387 },
4388 |left, right| -> Result<_, String> {
4389 let x = ClosedInterval::new(left.x, right.x);
4390 let shifted = x.sub(ClosedInterval::point(planted));
4391 let value = ClosedInterval::point(1.0).sub(shifted.square());
4392 let interior_point = left.x == right.x && left.x > 0.0 && left.x < 1.0;
4393 Ok(DerivativeEnclosure {
4394 score: ScoreValueEnclosure {
4395 value,
4396 evaluation_error: wilkinson_roundoff(value.max_abs(), 3),
4397 },
4398 derivative: if interior_point {
4403 ClosedInterval::new(-2.0, 2.0)
4404 } else {
4405 shifted.scale(-2.0)
4406 },
4407 curvature: ClosedInterval::point(-2.0),
4408 })
4409 },
4410 )
4411 .expect("adjacent exact cell evidence must isolate the unique root");
4412
4413 assert!(
4414 (result.optimum.x - planted).abs() <= 1.0e-9,
4415 "selected {}, expected {planted}",
4416 result.optimum.x
4417 );
4418 let stationary = result
4419 .stationary_points
4420 .iter()
4421 .find(|point| point.bracket.contains(planted))
4422 .expect("the planted stationary point must be certified");
4423 assert!(stationary.bracket.hi - stationary.bracket.lo <= 1.0e-9);
4424 }
4425
4426 #[test]
4427 fn signed_endpoint_newton_reaches_the_existing_score_resolution_floor() {
4428 let planted = 0.8_f64;
4429 let ambiguous_probe = 0.5_f64;
4430 let mut ambiguous_probe_calls = 0_usize;
4431 let result = maximize_score_1d(
4432 0.0,
4433 1.0,
4434 1.0e-9,
4435 |x| -> Result<_, String> {
4436 let shifted = x - planted;
4437 Ok(ScoreJet {
4438 value: 1.0 - shifted * shifted,
4439 derivative: -2.0 * shifted,
4440 curvature: -2.0,
4441 third: 0.0,
4442 })
4443 },
4444 |left, right| -> Result<_, String> {
4445 let x = ClosedInterval::new(left.x, right.x);
4446 let shifted = x.sub(ClosedInterval::point(planted));
4447 let value = ClosedInterval::point(1.0).sub(shifted.square());
4448 let derivative = if left.x == right.x {
4449 if left.x == ambiguous_probe {
4450 ambiguous_probe_calls += 1;
4451 ClosedInterval::new(-2.0, 2.0)
4452 } else {
4453 ClosedInterval::point(-2.0 * (left.x - planted))
4454 }
4455 } else {
4456 ClosedInterval::new(-2.0, 2.0)
4460 };
4461 Ok(DerivativeEnclosure {
4462 score: ScoreValueEnclosure {
4463 value,
4464 evaluation_error: 0.021,
4469 },
4470 derivative,
4471 curvature: ClosedInterval::new(-4.0, -1.0),
4474 })
4475 },
4476 )
4477 .expect("signed endpoint Newton images must reach a typed score-resolution proof");
4478
4479 assert!(
4480 ambiguous_probe_calls > 0,
4481 "fixture premise unmet: the cancellation-heavy midpoint was never certified"
4482 );
4483 let ScoreOptimumLocation::ResolutionFlat(index) = result.location else {
4484 panic!(
4485 "the unique root's location is below the declared information floor: {:?}",
4486 result.location
4487 );
4488 };
4489 let flat = result.resolution_flat_regions[index];
4490 assert!(
4491 flat.bracket.contains(planted),
4492 "contracted flat bracket {:?} lost the unique root",
4493 flat.bracket
4494 );
4495 assert!(
4496 flat.max_score_gap <= flat.score_resolution,
4497 "typed flat proof exceeded its existing evaluator floor: {flat:?}"
4498 );
4499 assert!(result.stationary_points.is_empty());
4500 }
4501
4502 #[test]
4503 fn monotone_score_selects_exact_boundary() {
4504 let result = maximize_score_1d(
4505 -4.0,
4506 9.0,
4507 1.0e-9,
4508 |x| -> Result<_, String> {
4509 Ok(ScoreJet {
4510 value: 0.3 * x,
4511 derivative: 0.3,
4512 curvature: 0.0,
4513 third: 0.0,
4514 })
4515 },
4516 |left, right| -> Result<_, String> {
4517 let value = ClosedInterval::new(left.x, right.x).scale(0.3);
4518 Ok(DerivativeEnclosure {
4519 score: ScoreValueEnclosure {
4520 value,
4521 evaluation_error: wilkinson_roundoff(value.max_abs(), 1),
4522 },
4523 derivative: ClosedInterval::point(0.3),
4524 curvature: ClosedInterval::point(0.0),
4525 })
4526 },
4527 )
4528 .expect("certified search");
4529 assert_eq!(result.location, ScoreOptimumLocation::UpperBoundary);
4530 assert_eq!(result.optimum.x, 9.0);
4531 assert!(result.stationary_points.is_empty());
4532 assert_eq!(
4533 result.value_certificate.maximum_excess, 0.0,
4534 "the exact same terminal point is not a competing uncertain value"
4535 );
4536 }
4537
4538 #[test]
4539 fn certified_increase_selects_upper_boundary_when_rounded_values_tie() {
4540 let result = maximize_score_1d(
4541 -1.0,
4542 1.0,
4543 1.0e-9,
4544 |_| -> Result<_, String> {
4545 Ok(ScoreJet {
4546 value: 0.0,
4547 derivative: 1.0,
4548 curvature: 0.0,
4549 third: 0.0,
4550 })
4551 },
4552 |left, right| -> Result<_, String> {
4553 Ok(DerivativeEnclosure {
4554 score: ScoreValueEnclosure {
4555 value: ClosedInterval::new(left.x, right.x),
4556 evaluation_error: 1.0,
4557 },
4558 derivative: ClosedInterval::point(1.0),
4559 curvature: ClosedInterval::point(0.0),
4560 })
4561 },
4562 )
4563 .expect("a whole-domain positive derivative orders tied rounded endpoints");
4564 assert_eq!(result.lower_boundary.value, result.upper_boundary.value);
4565 assert_eq!(result.location, ScoreOptimumLocation::UpperBoundary);
4566 assert_eq!(result.optimum.x, 1.0);
4567 assert_eq!(result.value_certificate.maximum_excess, 0.0);
4568 }
4569
4570 #[test]
4571 fn certified_decrease_selects_lower_boundary_when_rounded_values_tie() {
4572 let result = maximize_score_1d(
4573 -1.0,
4574 1.0,
4575 1.0e-9,
4576 |_| -> Result<_, String> {
4577 Ok(ScoreJet {
4578 value: 0.0,
4579 derivative: -1.0,
4580 curvature: 0.0,
4581 third: 0.0,
4582 })
4583 },
4584 |left, right| -> Result<_, String> {
4585 Ok(DerivativeEnclosure {
4586 score: ScoreValueEnclosure {
4587 value: ClosedInterval::new(-right.x, -left.x),
4588 evaluation_error: 1.0,
4589 },
4590 derivative: ClosedInterval::point(-1.0),
4591 curvature: ClosedInterval::point(0.0),
4592 })
4593 },
4594 )
4595 .expect("a whole-domain negative derivative orders tied rounded endpoints");
4596 assert_eq!(result.lower_boundary.value, result.upper_boundary.value);
4597 assert_eq!(result.location, ScoreOptimumLocation::LowerBoundary);
4598 assert_eq!(result.optimum.x, -1.0);
4599 assert_eq!(result.value_certificate.maximum_excess, 0.0);
4600 }
4601
4602 #[test]
4603 fn tangential_nonmaximum_structure_is_closed_by_exact_dominance() {
4604 let result = maximize_score_1d(
4605 -1.0,
4606 1.0,
4607 1.0e-8,
4608 |x| -> Result<_, String> {
4609 Ok(ScoreJet {
4610 value: x * x * x,
4611 derivative: 3.0 * x * x,
4612 curvature: 6.0 * x,
4613 third: 6.0,
4614 })
4615 },
4616 |lo, hi| -> Result<_, String> {
4617 let x = ClosedInterval::new(lo.x, hi.x);
4618 Ok(DerivativeEnclosure {
4619 score: ScoreValueEnclosure {
4620 value: x.mul(x).mul(x),
4621 evaluation_error: f64::EPSILON,
4622 },
4623 derivative: x.square().scale(3.0),
4624 curvature: x.scale(6.0),
4625 })
4626 },
4627 )
4628 .expect("the inferior inflection is immaterial by exact score ordering");
4629 assert_eq!(result.location, ScoreOptimumLocation::UpperBoundary);
4630 assert!(
4631 !result.dominated_regions.is_empty(),
4632 "the search must record the exact dominance proof instead of silently dropping the cell"
4633 );
4634 for region in result.dominated_regions {
4635 assert!(region.score.value.hi < region.incumbent_lower);
4636 }
4637 }
4638
4639 #[test]
4640 fn unresolved_nonflat_cell_remains_typed() {
4641 let error = maximize_score_1d(
4642 0.0,
4643 1.0e-8,
4644 1.0e-8,
4645 |x| -> Result<_, String> {
4646 Ok(ScoreJet {
4647 value: x,
4648 derivative: 0.0,
4649 curvature: 0.0,
4650 third: 0.0,
4651 })
4652 },
4653 |lo, hi| -> Result<_, String> {
4654 Ok(DerivativeEnclosure {
4655 score: ScoreValueEnclosure {
4656 value: ClosedInterval::new(lo.x, hi.x),
4657 evaluation_error: 0.0,
4658 },
4659 derivative: ClosedInterval::new(-1.0, 1.0),
4660 curvature: ClosedInterval::new(-1.0, 1.0),
4661 })
4662 },
4663 )
4664 .expect_err("a derivative enclosure admitting visible score motion is not flat");
4665 assert!(matches!(error, ScoreSearchError::Unresolved { .. }));
4666 }
4667
4668 #[test]
4686 fn undecomposable_criterion_exhausts_the_budget_instead_of_enumerating_the_domain() {
4687 let lo = 0.0;
4688 let hi = 32.0;
4689 let resolution = f64::EPSILON.sqrt();
4690 let flat_error = 5.0e-4;
4691 let (budget, depth_bound) = subdivision_budget(lo, hi, resolution);
4692 assert_eq!(depth_bound, 31, "log2(32 / sqrt(eps)) rounds up to 31");
4693 assert_eq!(
4699 budget,
4700 8 * 31 * 31,
4701 "budget must track the 8 D^2 coefficient in subdivision_budget"
4702 );
4703 let error = maximize_score_1d(
4704 lo,
4705 hi,
4706 resolution,
4707 |_| -> Result<_, String> {
4708 Ok(ScoreJet {
4709 value: 0.0,
4710 derivative: 0.0,
4711 curvature: 0.0,
4712 third: 0.0,
4713 })
4714 },
4715 |left, right| -> Result<_, String> {
4716 let half_width = 0.5 * (right.x - left.x);
4717 Ok(DerivativeEnclosure {
4718 score: ScoreValueEnclosure {
4719 value: ClosedInterval::new(-half_width, half_width),
4720 evaluation_error: flat_error,
4721 },
4722 derivative: ClosedInterval::new(-1.0, 1.0),
4723 curvature: ClosedInterval::new(-1.0, 1.0),
4724 })
4725 },
4726 )
4727 .expect_err("a decomposition this large must refuse, not enumerate");
4728 let ScoreSearchError::SubdivisionBudget {
4729 subdivisions,
4730 budget: reported_budget,
4731 depth_bound: reported_depth,
4732 cell_lo,
4733 cell_hi,
4734 ..
4735 } = error
4736 else {
4737 panic!("expected a subdivision-budget refusal, got {error}");
4738 };
4739 assert_eq!(
4740 subdivisions,
4741 budget + 1,
4742 "the budget stops the split that exceeds it"
4743 );
4744 assert_eq!(reported_budget, budget);
4745 assert_eq!(reported_depth, depth_bound);
4746 assert!(
4747 cell_hi - cell_lo > 2.0 * flat_error,
4748 "the reported cell must be one the search could still have split and \
4749 had not yet certified ({cell_lo}, {cell_hi}); a narrower cell would \
4750 mean the depth floor, not the breadth budget, was binding"
4751 );
4752 }
4753
4754 #[test]
4759 fn a_converging_search_stays_far_under_the_subdivision_budget() {
4760 let lo = 0.0;
4761 let hi = 32.0;
4762 let resolution = f64::EPSILON.sqrt();
4763 let (budget, depth_bound) = subdivision_budget(lo, hi, resolution);
4764 let evaluations = std::cell::Cell::new(0usize);
4765 let result = maximize_score_1d(
4766 lo,
4767 hi,
4768 resolution,
4769 |x| -> Result<_, String> {
4770 evaluations.set(evaluations.get() + 1);
4771 let shifted = x - 7.0;
4772 Ok(ScoreJet {
4773 value: -shifted * shifted,
4774 derivative: -2.0 * shifted,
4775 curvature: -2.0,
4776 third: 0.0,
4777 })
4778 },
4779 |left, right| -> Result<_, String> {
4780 let x = ClosedInterval::new(left.x, right.x);
4781 let shifted = x.sub(ClosedInterval::point(7.0));
4782 Ok(DerivativeEnclosure {
4783 score: ScoreValueEnclosure {
4784 value: shifted.square().scale(-1.0),
4785 evaluation_error: f64::EPSILON * 1024.0,
4786 },
4787 derivative: shifted.scale(-2.0),
4788 curvature: ClosedInterval::point(-2.0),
4789 })
4790 },
4791 )
4792 .expect("a strictly concave criterion is decomposable");
4793 let ScoreOptimumLocation::Stationary(index) = result.location else {
4794 panic!("expected the interior maximum, got {:?}", result.location);
4795 };
4796 let bracket = result.stationary_points[index].bracket;
4797 assert!(
4798 bracket.lo <= 7.0 && bracket.hi >= 7.0,
4799 "certified bracket {bracket:?} must contain the planted maximum"
4800 );
4801 assert!(
4804 evaluations.get() < budget / 8,
4805 "a converging search used {} evaluations against budget {budget} at depth \
4806 bound {depth_bound}; a budget within 8x of a converging search is a \
4807 tuning parameter, not a backstop",
4808 evaluations.get()
4809 );
4810 }
4811
4812 #[test]
4813 fn resolution_flatness_is_exactly_value_diameter_vs_pairwise_error() {
4814 let sample = SearchSample {
4815 sample: ScoreSample {
4816 x: 0.0,
4817 value: 7.0,
4818 derivative: 0.0,
4819 curvature: 0.0,
4820 third: 0.0,
4821 },
4822 point_enclosure: None,
4823 };
4824 let node = SearchNode {
4825 left: sample,
4826 right: SearchSample {
4827 sample: ScoreSample {
4828 x: 1.0,
4829 ..sample.sample
4830 },
4831 point_enclosure: None,
4832 },
4833 };
4834 let error = 0.125;
4835 for (upper, expected) in [(1024.25, true), (next_up(1024.25), false)] {
4836 let enclosure = DerivativeEnclosure {
4837 score: ScoreValueEnclosure {
4838 value: ClosedInterval::new(1024.0, upper),
4841 evaluation_error: error,
4842 },
4843 derivative: ClosedInterval::new(-1.0, 1.0),
4844 curvature: ClosedInterval::new(-1.0, 1.0),
4845 };
4846 assert_eq!(
4847 resolution_flat_region(node, enclosure).is_some(),
4848 expected,
4849 "flatness must be equivalent to outward diameter <= outward 2*value error"
4850 );
4851 }
4852 }
4853
4854 #[test]
4855 fn resolution_flat_cells_remain_regions_instead_of_fake_points() {
4856 let resolution = 0.25;
4857 let result = maximize_score_1d(
4858 0.0,
4859 1.0,
4860 resolution,
4861 |_| -> Result<_, String> {
4862 Ok(ScoreJet {
4863 value: 3.0,
4864 derivative: 0.0,
4865 curvature: 0.0,
4866 third: 0.0,
4867 })
4868 },
4869 |_, _| -> Result<_, String> {
4870 Ok(DerivativeEnclosure {
4871 score: ScoreValueEnclosure {
4872 value: ClosedInterval::point(3.0),
4873 evaluation_error: 0.0,
4874 },
4875 derivative: ClosedInterval::new(-1.0, 1.0),
4876 curvature: ClosedInterval::new(-1.0, 1.0),
4877 })
4878 },
4879 )
4880 .expect("an exactly constant score is resolution-flat");
4881 assert_eq!(result.resolution_flat_regions.len(), 1);
4882 assert!(
4883 result.resolution_flat_regions[0].bracket.hi
4884 - result.resolution_flat_regions[0].bracket.lo
4885 > resolution,
4886 "value resolution may close a wide cell, so callers must not reinterpret it \
4887 as an abscissa-resolved stationary point"
4888 );
4889 }
4890
4891 #[test]
4892 fn directed_arithmetic_preserves_cancellation_and_subnormal_error() {
4893 assert_eq!(
4894 ClosedInterval::point(1.0).sub(ClosedInterval::point(1.0)),
4895 ClosedInterval::point(0.0),
4896 "an exact structural zero must not acquire artificial uncertainty"
4897 );
4898 let minimum_subnormal = f64::from_bits(1);
4899 let underflowing_product =
4900 ClosedInterval::point(minimum_subnormal).mul(ClosedInterval::point(0.5));
4901 assert!(
4902 underflowing_product.lo <= 0.5 * minimum_subnormal
4903 && underflowing_product.hi >= 0.5 * minimum_subnormal
4904 && underflowing_product.lo < 0.0
4905 && underflowing_product.hi > 0.0,
4906 "a nonzero exact product that rounds to zero needs additive subnormal width"
4907 );
4908 assert!(
4909 wilkinson_roundoff(0.0, 1) >= minimum_subnormal,
4910 "a zero-magnitude relative model must still charge additive underflow"
4911 );
4912 }
4913
4914 #[test]
4915 fn certified_elementary_intervals_cover_normal_and_subnormal_lanes() {
4916 for value in [
4917 f64::from_bits(1),
4918 f64::MIN_POSITIVE,
4919 0.5,
4920 1.0,
4921 2.0,
4922 f64::MAX,
4923 ] {
4924 let enclosure = certified_ln_positive(value).expect("certified positive log");
4925 assert!(enclosure.is_valid() && enclosure.lo.is_finite() && enclosure.hi.is_finite());
4926 assert!(
4927 enclosure.contains(value.ln()),
4928 "independent platform log sanity value {} escaped {:?}",
4929 value.ln(),
4930 enclosure
4931 );
4932 }
4933 for value in [-744.0_f64, -708.0, -1.0, 0.0, 1.0, 709.0] {
4934 let enclosure = certified_exp(value).expect("certified exponential");
4935 assert!(enclosure.is_valid() && enclosure.lo >= 0.0);
4936 assert!(
4937 enclosure.contains(value.exp()),
4938 "independent platform exp sanity value {} escaped {:?}",
4939 value.exp(),
4940 enclosure
4941 );
4942 }
4943 for value in [f64::from_bits(1), 1.0e-12, 0.25, 1.0] {
4944 let enclosure = certified_ln_1p(value).expect("certified log1p");
4945 assert!(
4946 enclosure.contains(value.ln_1p()),
4947 "independent platform log1p sanity value {} escaped {:?}",
4948 value.ln_1p(),
4949 enclosure
4950 );
4951 }
4952 }
4953
4954 #[test]
4955 fn exact_range_is_not_compared_to_a_separately_rounded_curvature() {
4956 let denormal = f64::from_bits(1);
4957 let result = maximize_score_1d(
4958 0.0,
4959 1.0,
4960 1.0e-8,
4961 |x| -> Result<_, String> {
4962 Ok(ScoreJet {
4963 value: x,
4964 derivative: 1.0,
4965 curvature: -0.0,
4968 third: 0.0,
4969 })
4970 },
4971 |left, right| -> Result<_, String> {
4972 Ok(DerivativeEnclosure {
4973 score: ScoreValueEnclosure {
4974 value: ClosedInterval::new(left.x, right.x),
4975 evaluation_error: 0.0,
4976 },
4977 derivative: ClosedInterval::point(1.0),
4978 curvature: ClosedInterval::point(-denormal),
4979 })
4980 },
4981 )
4982 .expect("an exact-real enclosure need not contain a separately rounded scalar jet");
4983 assert_eq!(result.location, ScoreOptimumLocation::UpperBoundary);
4984 }
4985
4986 fn affine_fixture() -> AffineRemlProfile<'static> {
4987 const G: &[f64] = &[2.0, 0.5, 0.0, 3.0];
4988 const S: &[f64] = &[1.0, 0.0, 2.0, 0.25];
4989 const Q: &[f64] = &[
4990 0.6, 0.1, 0.02, 0.3, 0.2, 0.4, 0.01, 0.5, ];
4993 const Y2: &[f64] = &[8.0, 10.0];
4994 AffineRemlProfile::new(G, S, Q, Y2, 12.0, 3, 0.7).expect("valid fixture")
4995 }
4996
4997 #[test]
4998 fn affine_reml_jet_matches_test_only_differences() {
4999 let profile = affine_fixture();
5000 for x in [-2.0_f64, -0.4, 0.7, 2.0] {
5001 let h = 1.0e-5;
5002 let center = profile.evaluate(x).unwrap();
5003 let left = profile.evaluate(x - h).unwrap();
5004 let right = profile.evaluate(x + h).unwrap();
5005 let derivative = (right.value - left.value) / (2.0 * h);
5006 let curvature = (right.derivative - left.derivative) / (2.0 * h);
5007 assert!(
5008 (center.derivative - derivative).abs() <= 2.0e-8 * (1.0 + derivative.abs()),
5009 "first derivative mismatch at {x}: analytic {}, difference {derivative}",
5010 center.derivative
5011 );
5012 assert!(
5013 (center.curvature - curvature).abs() <= 2.0e-8 * (1.0 + curvature.abs()),
5014 "curvature mismatch at {x}: analytic {}, difference {curvature}",
5015 center.curvature
5016 );
5017 }
5018 }
5019
5020 #[test]
5021 fn affine_reml_enclosure_contains_value_jets() {
5022 let profile = affine_fixture();
5023 let enclosure = profile.enclose(-2.5, 1.75).expect("enclosure");
5024 let score = enclosure.score;
5025 let resolved_score = score.value.widen(score.evaluation_error);
5026 for x in [-2.5_f64, -1.7, -0.3, 0.0, 0.9, 1.75] {
5027 let jet = profile.evaluate(x).unwrap();
5028 let point = profile.enclose(x, x).expect("point enclosure");
5029 assert!(
5030 resolved_score.contains(jet.value),
5031 "score {} at {x} outside {:?} ± {}",
5032 jet.value,
5033 score.value,
5034 score.evaluation_error
5035 );
5036 assert!(
5037 enclosure
5038 .derivative
5039 .intersection(point.derivative)
5040 .is_some(),
5041 "exact point gradient {:?} at {x} is disjoint from {:?}",
5042 point.derivative,
5043 enclosure.derivative
5044 );
5045 assert!(
5046 enclosure.curvature.intersection(point.curvature).is_some(),
5047 "exact point curvature {:?} at {x} is disjoint from {:?}",
5048 point.curvature,
5049 enclosure.curvature
5050 );
5051 }
5052 }
5053
5054 #[test]
5055 fn affine_reml_zero_smoothing_complement_retains_residual_correlation() {
5056 const MODES: usize = 64;
5069 let grams = [1.0; MODES];
5070 let penalties = [1.0; MODES];
5071 let projected = [1.0; MODES];
5072 let energies = [MODES as f64];
5073 let profile = AffineRemlProfile::new(
5074 &grams,
5075 &penalties,
5076 &projected,
5077 &energies,
5078 MODES as f64,
5079 MODES,
5080 0.0,
5081 )
5082 .expect("valid cancellation fixture");
5083 let rho = -23.025850929940457_f64; let enclosure = profile
5085 .enclose(rho, rho)
5086 .expect("equivalent residual forms must retain their intersection");
5087
5088 assert!(
5089 enclosure.derivative.contains_zero(),
5090 "the analytically constant profile must contain zero derivative: {:?}",
5091 enclosure.derivative
5092 );
5093 assert!(
5094 enclosure.derivative.hi - enclosure.derivative.lo < 1.0e-6,
5095 "the residual complement must remove the independent near-one dependency: {:?}",
5096 enclosure.derivative
5097 );
5098 }
5099
5100 #[test]
5133 fn the_value_enclosure_never_exceeds_the_bound_its_own_derivative_certifies() {
5134 const MODES: usize = 33;
5135 let grams = [1.0; MODES];
5136 let penalties = [1.0; MODES];
5137 let projected = [1.0; MODES];
5138 let energies = [MODES as f64];
5139 let profile = AffineRemlProfile::new(
5140 &grams,
5141 &penalties,
5142 &projected,
5143 &energies,
5144 MODES as f64,
5145 MODES,
5146 0.0,
5147 )
5148 .expect("valid cancellation fixture");
5149
5150 let centre = -12.0_f64;
5151 let mut previous_width = f64::INFINITY;
5152 for exponent in [-1_i32, -2, -3, -4, -5, -6] {
5153 let half = 10.0_f64.powi(exponent);
5154 let (a, b) = (centre - half, centre + half);
5155 let width = b - a;
5156 let cell = profile.enclose(a, b).expect("cell enclosure");
5157 let point = profile.enclose(centre, centre).expect("point enclosure");
5158
5159 assert!(
5163 cell.score.value.lo <= point.score.value.lo
5164 && point.score.value.hi <= cell.score.value.hi,
5165 "w={width:e}: the midpoint value range {:?} escaped the cell range {:?}",
5166 point.score.value,
5167 cell.score.value
5168 );
5169 assert!(
5170 cell.derivative.lo <= point.derivative.lo
5171 && point.derivative.hi <= cell.derivative.hi,
5172 "w={width:e}: the midpoint derivative range {:?} escaped the cell range {:?}",
5173 point.derivative,
5174 cell.derivative
5175 );
5176
5177 let value_width = cell.score.value.hi - cell.score.value.lo;
5178 let point_width = point.score.value.hi - point.score.value.lo;
5179 let derivative_bound = cell.derivative.hi.abs().max(cell.derivative.lo.abs());
5180 let mean_value_bound = point_width + derivative_bound * width;
5181 assert!(
5182 value_width <= mean_value_bound * (1.0 + 1.0e-9),
5183 "w={width:e}: the value range is {value_width:e} wide but this cell's own \
5184 derivative enclosure {:?} bounds the score's movement across it by \
5185 {mean_value_bound:e} — the natural extension is back",
5186 cell.derivative
5187 );
5188
5189 println!(
5190 "[GATE] w={width:e} value_width={value_width:e} point_width={point_width:e} \
5191 mvt={mean_value_bound:e} D={derivative_bound:e}"
5192 );
5193 assert!(
5202 value_width <= previous_width / 50.0 || value_width <= 2.0 * point_width,
5203 "w={width:e}: the value range fell only {previous_width:e} -> \
5204 {value_width:e}, and it is not at the point-enclosure floor \
5205 {point_width:e} — that is first-order behaviour"
5206 );
5207 previous_width = value_width;
5208 }
5209 }
5210
5211 #[test]
5230 fn the_centred_enclosure_holds_on_degenerate_adjacent_and_extreme_cells() {
5231 let grams = [1.0, 4.0, 1.0e-9, 2.5e7];
5232 let penalties = [1.0, 1.0, 1.0, 1.0];
5233 let projected = [0.5, 0.25, 1.0e-3, 3.0];
5234 let energies = [8.0];
5235 let profile =
5236 AffineRemlProfile::new(&grams, &penalties, &projected, &energies, 6.0, 4, 0.25)
5237 .expect("valid fixture");
5238
5239 for &x in &[-600.0_f64, -37.5, -1.0, 0.0, 2.75, 600.0] {
5240 let Ok((direct, _)) = profile.enclose_direct(x, x) else {
5241 continue;
5242 };
5243 let centred = profile.enclose(x, x).expect("a point cell must enclose");
5244 assert_eq!(
5245 centred, direct,
5246 "a point cell must return the natural extension untouched at x={x}"
5247 );
5248
5249 let up = next_up(x);
5251 let Ok(cell) = profile.enclose(x, up) else {
5252 continue;
5253 };
5254 let point = profile.enclose(x, x).expect("point cell");
5255 assert!(
5256 cell.score.value.lo <= point.score.value.lo
5257 && point.score.value.hi <= cell.score.value.hi,
5258 "adjacent-float cell at {x}: point value range {:?} escaped {:?}",
5259 point.score.value,
5260 cell.score.value
5261 );
5262 assert!(
5263 cell.derivative.lo <= point.derivative.lo
5264 && point.derivative.hi <= cell.derivative.hi,
5265 "adjacent-float cell at {x}: point derivative range {:?} escaped {:?}",
5266 point.derivative,
5267 cell.derivative
5268 );
5269 assert!(
5270 cell.score.value.is_valid() && cell.derivative.is_valid(),
5271 "adjacent-float cell at {x} produced an invalid enclosure: {cell:?}"
5272 );
5273
5274 let (wide, _) = profile.enclose_direct(x, up).expect("direct adjacent cell");
5276 assert!(
5277 cell.score.value.lo >= wide.score.value.lo
5278 && cell.score.value.hi <= wide.score.value.hi,
5279 "the centred value range {:?} is not inside the natural extension {:?} at {x}",
5280 cell.score.value,
5281 wide.score.value
5282 );
5283 assert!(
5284 cell.derivative.lo >= wide.derivative.lo
5285 && cell.derivative.hi <= wide.derivative.hi,
5286 "the centred derivative range {:?} is not inside the natural extension {:?} at {x}",
5287 cell.derivative,
5288 wide.derivative
5289 );
5290 }
5291 }
5292
5293 fn cascade_profile_parts() -> (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>) {
5312 let grams = vec![
5313 0.021513523027428847, 0.023421509558465926, 0.024477791743994424,
5314 0.03028760364561828, 0.03510108223379587, 0.040671848915996144,
5315 0.042394860646972565, 0.044208976267946384, 0.046980397477518414,
5316 0.051041787441650194, 0.053417305918114666, 0.05575657456312382,
5317 0.056982691606415704, 0.059623191536431024, 0.06072593823762461,
5318 0.061603808142128846, 0.0626306391548814, 0.06415989316153273, 0.06612727525342801,
5319 0.07201682707299777, 0.10499606046436369, 0.12037535776467499, 0.1486138626340859,
5320 0.1762399329554861, 0.19315924476245142, 0.26688703253550705, 0.2848266927054469,
5321 0.33232244706214037, 0.6015439556821448, 1.1406886269841172, 1.3973782387809837,
5322 1.8043547873076875, 2.0890420358314765,
5323 ];
5324 let penalties = vec![1.0_f64; 33];
5325 let projected = vec![
5326 0.0008447602450715568, 0.004744115853417025, 0.0013711877079256205,
5327 0.000556576229807026, 0.00032950514304538826, 0.00015869074743770514,
5328 0.004035749350652998, 0.002408288703125203, 0.0002161132863778849,
5329 0.0024599052556113317, 0.00028155268264135145, 9.068039769807838e-7,
5330 0.0004390033211936947, 0.004642257342083, 5.722227645019854e-6,
5331 0.003702111930202603, 0.003943553329808974, 0.0011808139994261783,
5332 1.490921408482301e-5, 0.001728436851442388, 0.00040290378245105683,
5333 0.0006710268119971442, 0.0032383572156905664, 0.00013742753101732549,
5334 6.681227329297447e-5, 0.054339495839186305, 0.018972176651153957,
5335 0.04535732957447296, 0.1129209190002305, 0.05428138627351111, 1.5501891913959478,
5336 0.14151749008562448, 0.11704548115908926,
5337 ];
5338 let energies = vec![2.7067510572921663_f64];
5339 (grams, penalties, projected, energies)
5340 }
5341
5342 #[test]
5371 fn the_centred_form_keeps_the_natural_extension_when_the_remainder_is_not_finite() {
5372 let direct = ClosedInterval::new(-10.0, 10.0);
5373 let point = ClosedInterval::new(-1.0, 1.0);
5374 let touching_zero = ClosedInterval::new(-0.5, 0.0);
5375 let straddling_zero = ClosedInterval::new(-0.5, 0.5);
5376
5377 let narrowed = ClosedInterval::new(f64::NAN, 1.0).mul(straddling_zero);
5380 assert!(
5381 narrowed.lo.is_finite() && narrowed.hi.is_finite(),
5382 "premise: a NaN endpoint must reduce to a finite-LOOKING range ({narrowed:?}); if \
5383 `mul` stops dropping it this gate is about nothing"
5384 );
5385 let infinite = ClosedInterval::new(f64::NEG_INFINITY, f64::NEG_INFINITY)
5388 .mul(ClosedInterval::new(-1.0, 0.0));
5389 assert!(
5390 infinite.lo <= 0.0 && infinite.hi.is_infinite(),
5391 "`inf * 0` must stay sound through `product_down`'s exact-zero mapping, got \
5392 {infinite:?}"
5393 );
5394
5395 for slope in [
5396 ClosedInterval::new(f64::NEG_INFINITY, 3.0),
5397 ClosedInterval::new(-3.0, f64::INFINITY),
5398 ClosedInterval::new(f64::NEG_INFINITY, f64::INFINITY),
5399 ClosedInterval::new(f64::NEG_INFINITY, f64::NEG_INFINITY),
5400 ClosedInterval::new(f64::NAN, 1.0),
5401 ClosedInterval::new(1.0, f64::NAN),
5402 ] {
5403 for offset in [touching_zero, straddling_zero, ClosedInterval::new(0.0, 0.5)] {
5404 assert_eq!(
5405 centred_or(direct, point, slope, offset),
5406 direct,
5407 "a non-finite slope {slope:?} over offset {offset:?} must leave the natural \
5408 extension in place"
5409 );
5410 }
5411 }
5412
5413 let tightened = centred_or(
5416 direct,
5417 point,
5418 ClosedInterval::new(-2.0, 2.0),
5419 straddling_zero,
5420 );
5421 assert!(
5422 tightened.lo > direct.lo && tightened.hi < direct.hi,
5423 "a finite remainder must still tighten: {tightened:?} against {direct:?}"
5424 );
5425 }
5426
5427 #[test]
5447 fn the_centred_ranges_contain_the_function_at_every_interior_point() {
5448 let (grams, penalties, projected, energies) = cascade_profile_parts();
5449 let profile = AffineRemlProfile::new(
5450 &grams,
5451 &penalties,
5452 &projected,
5453 &energies,
5454 33.0,
5455 33,
5456 9.226276711274537,
5457 )
5458 .expect("valid cascade profile");
5459
5460 let mut curvature_tightened = false;
5465 for centre in [-20.0_f64, -12.5, -6.0, -1.679, 3.0, 11.0, 17.5] {
5467 for exponent in [0_i32, -1, -2, -3, -4] {
5468 let half = 10.0_f64.powi(exponent);
5469 let (a, b) = (centre - half, centre + half);
5470 let cell = profile.enclose(a, b).expect("cell enclosure");
5471 let (natural, _) = profile.enclose_direct(a, b).expect("natural extension");
5472 assert!(
5473 cell.curvature.lo >= natural.curvature.lo
5474 && cell.curvature.hi <= natural.curvature.hi,
5475 "cell [{a}, {b}]: the centred curvature {:?} is not inside the natural \
5476 extension {:?}",
5477 cell.curvature,
5478 natural.curvature
5479 );
5480 if cell.curvature.hi - cell.curvature.lo
5481 < 0.5 * (natural.curvature.hi - natural.curvature.lo)
5482 {
5483 curvature_tightened = true;
5484 }
5485 for step in 0..=8 {
5486 let x = a + (b - a) * (step as f64 / 8.0);
5487 let point = profile.enclose(x, x).expect("point enclosure");
5488 assert!(
5489 cell.score.value.lo <= point.score.value.lo
5490 && point.score.value.hi <= cell.score.value.hi,
5491 "cell [{a}, {b}] value range {:?} does not contain the exact value at \
5492 x={x}, {:?}",
5493 cell.score.value,
5494 point.score.value
5495 );
5496 assert!(
5497 cell.derivative.lo <= point.derivative.lo
5498 && point.derivative.hi <= cell.derivative.hi,
5499 "cell [{a}, {b}] derivative range {:?} does not contain the exact \
5500 derivative at x={x}, {:?}",
5501 cell.derivative,
5502 point.derivative
5503 );
5504 assert!(
5505 cell.curvature.lo <= point.curvature.lo
5506 && point.curvature.hi <= cell.curvature.hi,
5507 "cell [{a}, {b}] curvature range {:?} does not contain the exact \
5508 curvature at x={x}, {:?} — the third-derivative kernel the curvature \
5509 is centred on is wrong",
5510 cell.curvature,
5511 point.curvature
5512 );
5513 }
5514 }
5515 }
5516 assert!(
5517 curvature_tightened,
5518 "the centred curvature never halved the natural extension's range anywhere in this \
5519 sweep, so the containment checks above would pass for a WRONG third-derivative \
5520 kernel too — this gate has gone vacuous"
5521 );
5522 }
5523
5524 #[test]
5548 fn the_located_optimum_is_enclosure_independent_and_accurate_to_the_contract() {
5549 let grams = [1.0_f64; 3];
5553 let penalties = [1.0_f64; 3];
5554 let projected = [4.0 / 3.0; 3];
5555 let energies = [10.0_f64];
5556 let profile =
5557 AffineRemlProfile::new(&grams, &penalties, &projected, &energies, 15.0, 3, 0.0)
5558 .expect("valid ridge profile");
5559 let lo = certified_ln_positive(f64::MIN_POSITIVE).expect("lo").lo;
5560 let hi = certified_ln_positive(f64::MAX / 2.0).expect("hi").hi;
5561 let resolution = f64::EPSILON.sqrt();
5562 let truth = 0.6_f64;
5565
5566 let natural = maximize_score_1d(
5567 lo,
5568 hi,
5569 resolution,
5570 |x| profile.evaluate(x),
5571 |a, b| profile.enclose_direct(a.x, b.x).map(|(e, _)| e),
5572 )
5573 .expect("the natural extension decomposes this domain");
5574 let centred = maximize_score_1d(lo, hi, resolution, |x| profile.evaluate(x), |a, b| {
5575 profile.enclose(a.x, b.x)
5576 })
5577 .expect("the centred form decomposes this domain");
5578
5579 assert_eq!(
5580 natural.optimum.x, centred.optimum.x,
5581 "the two enclosure forms located different optima ({} against {}); tightening may \
5582 change which cells are visited but must not move the certified root",
5583 natural.optimum.x, centred.optimum.x
5584 );
5585 for (label, search) in [("natural", &natural), ("centred", ¢red)] {
5586 assert!(
5587 matches!(search.location, ScoreOptimumLocation::Stationary(_)),
5588 "{label}: this fixture has an interior stationary optimum, got {:?}",
5589 search.location
5590 );
5591 let offset = (search.optimum.x - truth.ln()).abs();
5592 assert!(
5593 offset <= resolution,
5594 "{label}: the located root is {offset:e} from the closed form in rho, outside \
5595 the requested resolution {resolution:e} — that is a location-contract failure"
5596 );
5597 assert!(
5600 offset > 0.0,
5601 "{label}: an exactly-attained root would mean this gate has stopped measuring \
5602 what it claims"
5603 );
5604 }
5605 }
5606
5607 #[test]
5618 fn zz_measure_centred_enclosure_search_cost() {
5619 let (grams, penalties, projected, energies) = cascade_profile_parts();
5620 let cascade = AffineRemlProfile::new(
5621 &grams,
5622 &penalties,
5623 &projected,
5624 &energies,
5625 33.0,
5626 33,
5627 9.226276711274537,
5628 )
5629 .expect("valid cascade profile");
5630
5631 let full_lo = certified_ln_positive(f64::MIN_POSITIVE).expect("domain lo").lo;
5632 let full_hi = certified_ln_positive(f64::MAX / 2.0).expect("domain hi").hi;
5633 let cases: [(&str, f64, f64); 3] = [
5634 ("cascade/40.6-wide", -21.860900258111, 18.75853229939662),
5637 ("cascade/narrow-around-the-optimum", -3.0, 0.0),
5642 ("cascade/full-representable-domain", full_lo, full_hi),
5645 ];
5646
5647 for (label, lo, hi) in cases {
5648 let profile = &cascade;
5649 let resolution = f64::EPSILON.sqrt();
5650 let started = std::time::Instant::now();
5651 let natural = maximize_score_1d(
5652 lo,
5653 hi,
5654 resolution,
5655 |x| profile.evaluate(x),
5656 |a, b| profile.enclose_direct(a.x, b.x).map(|(e, _)| e),
5657 );
5658 let natural_seconds = started.elapsed().as_secs_f64();
5659 let started = std::time::Instant::now();
5660 let centred = maximize_score_1d(
5661 lo,
5662 hi,
5663 resolution,
5664 |x| profile.evaluate(x),
5665 |a, b| profile.enclose(a.x, b.x),
5666 );
5667 let centred_seconds = started.elapsed().as_secs_f64();
5668 println!(
5669 "#COST {label}: natural {:.4}s ({}) centred {:.4}s ({}) speedup {:.2}x",
5670 natural_seconds,
5671 natural.as_ref().map_or("REFUSED", |_| "ok"),
5672 centred_seconds,
5673 centred.as_ref().map_or("REFUSED", |_| "ok"),
5674 natural_seconds / centred_seconds.max(f64::MIN_POSITIVE),
5675 );
5676 assert!(
5681 centred.is_ok() || natural.is_err(),
5682 "{label}: the centred oracle refused ({centred:?}) where the natural extension \
5683 succeeded — an intersection can only tighten, so this is impossible unless the \
5684 centred form is unsound"
5685 );
5686 if natural.is_ok() {
5690 assert!(
5691 centred_seconds <= natural_seconds * 2.5 + 1.0e-3,
5692 "{label}: centring cost {centred_seconds:.4}s against the natural \
5693 extension's {natural_seconds:.4}s — more than the doubled per-cell work \
5694 can explain"
5695 );
5696 }
5697 }
5698 }
5699
5700 #[test]
5717 fn the_natural_extension_cannot_decompose_a_domain_the_centred_form_certifies() {
5718 let (grams, penalties, projected, energies) = cascade_profile_parts();
5719 let profile = AffineRemlProfile::new(
5720 &grams,
5721 &penalties,
5722 &projected,
5723 &energies,
5724 33.0,
5725 33,
5726 9.226276711274537,
5727 )
5728 .expect("valid cascade profile");
5729
5730 let (lo, hi) = (-21.860900258111_f64, 18.75853229939662);
5732 let resolution = f64::EPSILON.sqrt();
5733
5734 let natural = maximize_score_1d(
5735 lo,
5736 hi,
5737 resolution,
5738 |x| profile.evaluate(x),
5739 |a, b| profile.enclose_direct(a.x, b.x).map(|(enclosure, _)| enclosure),
5740 );
5741 let centred = maximize_score_1d(
5742 lo,
5743 hi,
5744 resolution,
5745 |x| profile.evaluate(x),
5746 |a, b| profile.enclose(a.x, b.x),
5747 );
5748
5749 let centred = centred.unwrap_or_else(|error| {
5750 panic!(
5751 "the centred enclosure must decompose this 33-mode cascade domain: {error}"
5752 )
5753 });
5754 assert!(
5755 matches!(
5756 natural,
5757 Err(ScoreSearchError::SubdivisionBudget { .. } | ScoreSearchError::Unresolved { .. })
5758 ),
5759 "PREMISE LOST: the natural extension now decomposes this domain \
5760 ({natural:?}), so this fixture no longer exercises the defect and the \
5761 comparison below proves nothing — widen the mode spread or the domain \
5762 until it refuses again",
5763 );
5764
5765 assert!(
5768 !matches!(centred.location, ScoreOptimumLocation::ResolutionFlat(_)),
5769 "the centred search must decide a location, got {:?}",
5770 centred.location
5771 );
5772 assert!(
5773 centred.value_certificate.maximum_excess
5774 <= centred.value_certificate.comparison_resolution,
5775 "the centred search's value ordering must close: excess {} against {}",
5776 centred.value_certificate.maximum_excess,
5777 centred.value_certificate.comparison_resolution
5778 );
5779 assert!(
5780 centred.optimum.x >= lo && centred.optimum.x <= hi && centred.optimum.x.is_finite(),
5781 "the selected log lambda must lie in the domain, got {}",
5782 centred.optimum.x
5783 );
5784 }
5785
5786 #[test]
5787 fn affine_reml_zero_smoothing_schur_residual_keeps_division_low_parts() {
5788 let grams = [3.0; 3];
5795 let penalties = [1.0; 3];
5796 let projected = [1.0; 3];
5797 let energies = [1.0];
5798 let profile =
5799 AffineRemlProfile::new(&grams, &penalties, &projected, &energies, 3.0, 3, 0.0)
5800 .expect("valid nonrepresentable-quotient fixture");
5801
5802 let zero_residual = profile.zero_lambda_residual[0];
5803 assert!(
5804 zero_residual.contains_zero(),
5805 "the exact identity 1 - 3*(1/3) = 0 must be retained: {zero_residual:?}"
5806 );
5807 assert!(
5808 zero_residual.hi - zero_residual.lo < 1.0e-28,
5809 "division corrections must live below ordinary binary64 cancellation scale: \
5810 {zero_residual:?}"
5811 );
5812
5813 let rho = -23.025850929940457_f64;
5814 let enclosure = profile
5815 .enclose(rho, rho)
5816 .expect("the small positive smoothing residual must remain resolved");
5817 assert!(
5818 enclosure.derivative.contains_zero(),
5819 "determinant and residual derivatives cancel analytically: {:?}",
5820 enclosure.derivative
5821 );
5822 assert!(
5823 enclosure.derivative.hi - enclosure.derivative.lo < 1.0e-6,
5824 "the exact Schur residual must control the profiled derivative: {:?}",
5825 enclosure.derivative
5826 );
5827 }
5828
5829 #[test]
5830 fn affine_reml_saturated_tail_preserves_complement_signs() {
5831 let profile = AffineRemlProfile::new(&[1.0], &[1.0], &[0.0], &[1.0], 4.0, 1, 0.0)
5832 .expect("valid saturated-tail fixture");
5833 let log_lambda = 700.0;
5834 let jet = profile.evaluate(log_lambda).expect("point jet");
5835 let enclosure = profile
5836 .enclose(log_lambda, log_lambda)
5837 .expect("point enclosure");
5838
5839 assert!(
5840 jet.derivative > 0.0,
5841 "the point derivative must preserve +0.5/(1+exp(rho)), got {}",
5842 jet.derivative
5843 );
5844 assert!(
5845 jet.curvature < 0.0,
5846 "the point curvature must preserve its negative u*c sign, got {}",
5847 jet.curvature
5848 );
5849 assert!(
5850 enclosure.curvature.hi <= 0.0,
5851 "the exact saturated curvature remains nonpositive: {:?}",
5852 enclosure.curvature
5853 );
5854 assert!(
5855 enclosure.derivative.lo >= 0.0,
5856 "the exact saturated derivative remains nonnegative: {:?}",
5857 enclosure.derivative
5858 );
5859 let score = enclosure.score;
5860 assert!(score.evaluation_error.is_finite());
5861 assert!(
5862 score
5863 .value
5864 .widen(score.evaluation_error)
5865 .contains(jet.value),
5866 "the stable score evaluator must lie inside its exact value range plus forward error"
5867 );
5868 }
5869
5870 #[test]
5871 fn affine_reml_saturated_tail_uses_complement_sign_before_value_flatness() {
5872 let profile = AffineRemlProfile::new(&[1.0], &[1.0], &[0.0], &[1.0], 4.0, 1, 0.0)
5873 .expect("valid saturated-tail fixture");
5874 let result = profile
5875 .maximize(600.0, 700.0, f64::EPSILON.sqrt())
5876 .expect("the cancellation-free derivative proves the tail monotone");
5877 assert_eq!(result.location, ScoreOptimumLocation::UpperBoundary);
5878 assert_eq!(result.optimum.x, 700.0);
5879 assert!(
5880 result.resolution_flat_regions.is_empty(),
5881 "a strictly positive derivative should resolve before value-flat fallback"
5882 );
5883 }
5884
5885 #[test]
5886 fn affine_reml_extreme_domain_one_direction_encloses_and_maximizes_repeatably() {
5887 let gram_modes = [1.0, 1.0, 1.0];
5897 let penalty_modes = [1.0, 1.0, 1.0];
5898 let projected_rhs_squared = [4.0 / 3.0, 4.0 / 3.0, 4.0 / 3.0];
5899 let response_energy = [10.0];
5900 let profile = AffineRemlProfile::new(
5901 &gram_modes,
5902 &penalty_modes,
5903 &projected_rhs_squared,
5904 &response_energy,
5905 15.0,
5906 3,
5907 0.0,
5908 )
5909 .expect("valid normalized one-direction ridge profile");
5910 let rho_lo = certified_ln_positive(f64::MIN_POSITIVE)
5911 .expect("finite-domain lower log bound")
5912 .lo;
5913 let rho_hi = certified_ln_positive(f64::MAX / 2.0)
5914 .expect("finite-domain upper log bound")
5915 .hi;
5916
5917 let whole_domain = profile
5918 .enclose(rho_lo, rho_hi)
5919 .expect("scale-safe relative exp error keeps the full-domain residual finite");
5920 assert!(
5921 whole_domain.score.value.is_valid()
5922 && whole_domain.score.value.lo.is_finite()
5923 && whole_domain.score.value.hi.is_finite()
5924 );
5925 assert!(whole_domain.score.evaluation_error.is_finite());
5926 assert!(whole_domain.derivative.contains_zero());
5927
5928 let resolution = f64::EPSILON.sqrt();
5929 let first = profile
5930 .maximize_value_ordered(rho_lo, rho_hi, resolution)
5931 .expect("finite subdivision must certify the planted stationary optimum");
5932 let repeated = profile
5933 .maximize_value_ordered(rho_lo, rho_hi, resolution)
5934 .expect("the same exact search must be repeatable");
5935 assert_eq!(first, repeated);
5936 let ScoreOptimumLocation::Stationary(index) = first.location else {
5937 panic!(
5938 "the planted one-direction optimum must be stationary, got {:?}",
5939 first.location
5940 );
5941 };
5942 let stationary = first
5943 .stationary_points
5944 .get(index)
5945 .expect("stationary result index");
5946 let expected = certified_ln_positive(0.6).expect("analytic stationary log");
5947 assert!(
5948 stationary.bracket.lo <= expected.lo && stationary.bracket.hi >= expected.hi,
5949 "certified bracket {:?} must contain analytic log(0.6) {:?}",
5950 stationary.bracket,
5951 expected
5952 );
5953 assert!(
5954 first.value_certificate.maximum_excess <= first.value_certificate.comparison_resolution,
5955 "an isolated stationary root is not yet a globally ordered score candidate: \
5956 maximum excess {}, comparison resolution {}, bracket {:?}",
5957 first.value_certificate.maximum_excess,
5958 first.value_certificate.comparison_resolution,
5959 stationary.bracket,
5960 );
5961 }
5962
5963 #[test]
5964 fn affine_reml_gram_zero_subnormal_zero_projection_is_structural() {
5965 let minimum_subnormal = f64::from_bits(1);
5966 let log_lambda = -740.0;
5967 let lambda = exp_interval(log_lambda, log_lambda)
5968 .expect("the fixture needs a certified subnormal lambda");
5969 assert!(lambda.lo > 0.0 && lambda.hi < f64::MIN_POSITIVE);
5970 let raw_h = lambda.mul(ClosedInterval::point(minimum_subnormal));
5971 assert!(
5972 raw_h.lo < 0.0 && raw_h.hi > 0.0,
5973 "the raw outward product must cross rounded zero: {raw_h:?}"
5974 );
5975 let h = raw_h.nonnegative();
5976 assert_eq!(
5977 h.lo, 0.0,
5978 "known nonnegative product must clamp its outward lower bound to zero"
5979 );
5980
5981 let ranges = mode_ranges(0.0, minimum_subnormal, 0.0, lambda)
5982 .expect("the zero projection cancels before any residual division");
5983 assert_eq!(ranges.c, ClosedInterval::point(0.0));
5984 assert_eq!(ranges.w, ClosedInterval::point(0.0));
5985 assert_eq!(ranges.v, ClosedInterval::point(0.0));
5986 assert_eq!(ranges.p, ClosedInterval::point(0.0));
5987 assert_eq!(ranges.q, ClosedInterval::point(0.0));
5988
5989 let gram_modes = [0.0];
5990 let penalty_modes = [minimum_subnormal];
5991 let projected_rhs_squared = [0.0];
5992 let response_energy = [1.0];
5993 let profile = AffineRemlProfile::new(
5994 &gram_modes,
5995 &penalty_modes,
5996 &projected_rhs_squared,
5997 &response_energy,
5998 1.0,
5999 1,
6000 0.0,
6001 )
6002 .expect("valid gram-zero structural fixture");
6003 let jet = profile
6004 .evaluate(log_lambda)
6005 .expect("normalized determinant and zero residual projection stay finite");
6006 let enclosure = profile
6007 .enclose(log_lambda, log_lambda)
6008 .expect("the proof path must not divide by a zero-containing h interval");
6009 assert_eq!(jet.derivative, 0.0);
6010 assert_eq!(jet.curvature, 0.0);
6011 assert!(is_exact_zero(enclosure.derivative));
6012 assert!(is_exact_zero(enclosure.curvature));
6013 assert!(
6014 enclosure
6015 .score
6016 .value
6017 .widen(enclosure.score.evaluation_error)
6018 .contains(jet.value)
6019 );
6020 }
6021
6022 #[test]
6023 fn affine_reml_gram_zero_subnormal_nonzero_projection_stays_finite() {
6024 let minimum_subnormal = f64::from_bits(1);
6025 let log_lambda = -740.0;
6026 let lambda = exp_interval(log_lambda, log_lambda)
6027 .expect("the fixture needs a certified subnormal lambda");
6028 let penalty = 0.01;
6029 let h = lambda.mul(ClosedInterval::point(penalty)).nonnegative();
6030 assert_eq!(
6031 h.lo, 0.0,
6032 "the fixture must enter the structural quotient path"
6033 );
6034
6035 let ranges = mode_ranges(0.0, penalty, minimum_subnormal, lambda)
6036 .expect("the scaled quotient has a finite representable range");
6037 assert_eq!(ranges.c, ClosedInterval::point(0.0));
6038 assert_eq!(ranges.w, ClosedInterval::point(0.0));
6039 assert!(ranges.v.lo > 0.0 && ranges.v.hi.is_finite());
6040 assert_eq!(ranges.p, ranges.v);
6041 assert_eq!(ranges.q, ranges.v.neg());
6042
6043 let gram_modes = [0.0];
6044 let penalty_modes = [penalty];
6045 let projected_rhs_squared = [minimum_subnormal];
6046 let response_energy = [10.0];
6047 let profile = AffineRemlProfile::new(
6048 &gram_modes,
6049 &penalty_modes,
6050 &projected_rhs_squared,
6051 &response_energy,
6052 1.0,
6053 1,
6054 0.0,
6055 )
6056 .expect("valid gram-zero finite-ratio fixture");
6057 let jet = profile
6058 .evaluate(log_lambda)
6059 .expect("the point ratio must avoid the underflowing product");
6060 let enclosure = profile
6061 .enclose(log_lambda, log_lambda)
6062 .expect("the interval ratio must remain finite without a reciprocal overflow");
6063 assert!(
6064 enclosure
6065 .score
6066 .value
6067 .widen(enclosure.score.evaluation_error)
6068 .contains(jet.value)
6069 );
6070 }
6071
6072 #[test]
6073 fn affine_reml_gram_zero_unrepresentable_projection_is_typed() {
6074 let minimum_subnormal = f64::from_bits(1);
6075 let log_lambda = -740.0;
6076 let gram_modes = [0.0];
6077 let penalty_modes = [minimum_subnormal];
6078 let projected_rhs_squared = [1.0];
6079 let response_energy = [10.0];
6080 let profile = AffineRemlProfile::new(
6081 &gram_modes,
6082 &penalty_modes,
6083 &projected_rhs_squared,
6084 &response_energy,
6085 1.0,
6086 1,
6087 0.0,
6088 )
6089 .expect("valid gram-zero refusal fixture");
6090 assert!(matches!(
6091 profile.evaluate(log_lambda),
6092 Err(AffineRemlError::ElementaryEnclosureUnavailable {
6093 function: "gram-zero residual quotient",
6094 ..
6095 })
6096 ));
6097 assert!(matches!(
6098 profile.enclose(log_lambda, log_lambda),
6099 Err(AffineRemlError::ElementaryEnclosureUnavailable {
6100 function: "gram-zero residual quotient",
6101 ..
6102 })
6103 ));
6104 }
6105
6106 #[test]
6107 fn affine_reml_rejects_nonpositive_profile_residual() {
6108 let profile = AffineRemlProfile::new(&[1.0], &[1.0], &[2.0], &[1.0], 4.0, 1, 0.0)
6109 .expect("statically valid");
6110 assert!(matches!(
6111 profile.evaluate(-2.0),
6112 Err(AffineRemlError::NonPositiveResidual { .. })
6113 ));
6114 }
6115}