Skip to main content

gam_problem/
monotone_root_error.rs

1/// Typed errors emitted by the monotone-root solver. `Display` preserves the
2/// exact pre-refactor error-string shapes so log expectations are unchanged.
3#[derive(Clone, Debug)]
4pub enum MonotoneRootError {
5    /// `eval(a)` returned an inner error message at the bracketing / refine step.
6    EvalFailed {
7        label: String,
8        a: f64,
9        source: String,
10    },
11    /// `eval(a)` returned a non-finite tuple component (f, f', or f'').
12    NonFiniteEval {
13        label: String,
14        a: f64,
15        f: f64,
16        fp: f64,
17        fpp: f64,
18    },
19    /// Derivative at the initial / current point is zero or non-finite —
20    /// monotonicity hypothesis violated.
21    DegenerateDerivative { label: String, a: f64, fp: f64 },
22    /// Bracketing failed to find a sign change within `max_bracket_iters`.
23    BracketingExhausted {
24        label: String,
25        iters: usize,
26        a_lo: f64,
27        a_hi: f64,
28    },
29    /// Newton refinement did not meet `convergence_tol` within `max_refine_iters`.
30    RefinementDidNotConverge {
31        label: String,
32        iters: usize,
33        last_residual: f64,
34    },
35}
36
37/// Internal: which exact textual shape a given error site emitted.
38/// These are folded into the enum variants above via Display so callers see
39/// byte-identical strings to the pre-refactor format!() output.
40impl MonotoneRootError {
41    pub fn exact_root_degenerate(label: &str, a: f64) -> Self {
42        // Tagged via `iters = usize::MAX` to select the "exact root" Display arm.
43        MonotoneRootError::RefinementDidNotConverge {
44            label: format!("__EXACT_ROOT__{label}"),
45            iters: usize::MAX,
46            last_residual: a,
47        }
48    }
49
50    pub fn converged_root_degenerate(label: &str, a: f64) -> Self {
51        MonotoneRootError::RefinementDidNotConverge {
52            label: format!("__CONVERGED__{label}"),
53            iters: 0,
54            last_residual: a,
55        }
56    }
57
58    pub fn analytic_bracket_invalid(label: &str, lo: f64, hi: f64) -> Self {
59        MonotoneRootError::BracketingExhausted {
60            label: format!("__ANALYTIC_INVALID__{label}"),
61            iters: 0,
62            a_lo: lo,
63            a_hi: hi,
64        }
65    }
66
67    pub fn analytic_bracket_no_straddle(label: &str, f_lo: f64, f_hi: f64) -> Self {
68        MonotoneRootError::BracketingExhausted {
69            label: format!("__ANALYTIC_NOSTRADDLE__{label}"),
70            iters: 0,
71            a_lo: f_lo,
72            a_hi: f_hi,
73        }
74    }
75
76    pub fn search_exhausted(label: &str, step_sign: f64, a_init: f64) -> Self {
77        MonotoneRootError::BracketingExhausted {
78            label: format!("__SEARCH__{label}"),
79            iters: 0,
80            a_lo: a_init,
81            a_hi: step_sign,
82        }
83    }
84}
85
86impl std::fmt::Display for MonotoneRootError {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        match self {
89            MonotoneRootError::EvalFailed { source, .. } => f.write_str(source),
90            MonotoneRootError::NonFiniteEval { label, a, .. } => {
91                write!(f, "{label}: non-finite evaluation at a={a:.6}")
92            }
93            MonotoneRootError::DegenerateDerivative { label, a, .. } => {
94                write!(
95                    f,
96                    "{label}: initial derivative is zero or non-finite at a={a:.6}"
97                )
98            }
99            MonotoneRootError::BracketingExhausted {
100                label, a_lo, a_hi, ..
101            } => {
102                if let Some(real_label) = label.strip_prefix("__ANALYTIC_INVALID__") {
103                    write!(
104                        f,
105                        "{real_label}: invalid analytic bracket [{a_lo:.6}, {a_hi:.6}]"
106                    )
107                } else if let Some(real_label) = label.strip_prefix("__ANALYTIC_NOSTRADDLE__") {
108                    let f_lo = a_lo;
109                    let f_hi = a_hi;
110                    write!(
111                        f,
112                        "{real_label}: analytic bracket does not straddle root (f_lo={f_lo:.3e}, f_hi={f_hi:.3e})"
113                    )
114                } else if let Some(real_label) = label.strip_prefix("__SEARCH__") {
115                    let step_sign = *a_hi;
116                    let a_init = *a_lo;
117                    write!(
118                        f,
119                        "{real_label}: failed to bracket root (searched {step_sign:+.0} from a={a_init:.6})"
120                    )
121                } else {
122                    write!(
123                        f,
124                        "{label}: failed to bracket root (a_lo={a_lo:.6}, a_hi={a_hi:.6})"
125                    )
126                }
127            }
128            MonotoneRootError::RefinementDidNotConverge {
129                label,
130                last_residual,
131                ..
132            } => {
133                if let Some(real_label) = label.strip_prefix("__EXACT_ROOT__") {
134                    let a = last_residual;
135                    write!(
136                        f,
137                        "{real_label}: zero or non-finite derivative at exact root a={a:.6}"
138                    )
139                } else if let Some(real_label) = label.strip_prefix("__CONVERGED__") {
140                    let a = last_residual;
141                    write!(
142                        f,
143                        "{real_label}: zero or non-finite derivative at converged root a={a:.6}"
144                    )
145                } else {
146                    write!(
147                        f,
148                        "{label}: refinement did not converge (last residual={last_residual:.3e})"
149                    )
150                }
151            }
152        }
153    }
154}
155
156impl std::error::Error for MonotoneRootError {}