gam_models/transformation_normal/error.rs
1// ---------------------------------------------------------------------------
2// Typed errors
3// ---------------------------------------------------------------------------
4
5/// Typed errors emitted by the transformation-normal family pipeline.
6///
7/// Each variant carries a pre-formatted `reason` so `Display` is
8/// byte-equivalent to the original `format!(...)` strings the module used
9/// before the typed-error migration. The category split lets callers
10/// pattern-match on the failure kind (e.g. distinguish a degenerate
11/// covariate design from a non-finite intermediate) without parsing text.
12///
13/// Public/trait boundaries (e.g. `CustomFamily::evaluate`) still return
14/// `Result<_, String>`; the `From<TransformationNormalError> for String`
15/// impl below provides the shim so every typed error flushes through `?`
16/// or `.into()` at the boundary without per-callsite `.map_err`.
17#[derive(Debug, Clone)]
18pub enum TransformationNormalError {
19 /// Shape/length/dimension/contract violations on inputs to a routine
20 /// (e.g. response/covariate row mismatch, beta length mismatch,
21 /// wrong number of blocks, malformed configuration parameters).
22 InvalidInput { reason: String },
23 /// A required covariate design or weight configuration cannot support
24 /// the routine — empty design, zero total weight, residual variance
25 /// not representable, warm-start coefficients all non-finite.
26 DesignDegenerate { reason: String },
27 /// A numeric intermediate (response transform, derivative,
28 /// log-likelihood, weight, offset, gradient component, calibration
29 /// quantity) came out non-finite or non-positive where positive
30 /// finite is required.
31 NonFinite { reason: String },
32 /// The fitted monotone transform's derivative dropped to or below
33 /// zero, or the response endpoint ordering required by the latent
34 /// score (lower < h < upper) was not satisfied at evaluation time.
35 MonotonicityViolated { reason: String },
36 /// A numerical step that maps through the standard-normal CDF
37 /// (endpoint mass, log-difference, PIT probability, derivative
38 /// ratio) underflowed or became non-representable at the requested
39 /// arguments.
40 NumericalFailure { reason: String },
41 /// A prediction evaluated the transform at a response/covariate point
42 /// whose transformed value falls outside the certified positivity
43 /// domain `[lower, upper]` by more than the boundary-roundoff floor.
44 /// Under the direct-α cutover (gam#2306) monotonicity is certified only
45 /// on the fitted rows (via the factored Khatri-Rao cone) and the
46 /// persisted domain certificate; extrapolation past that support is a
47 /// typed refusal, never a clamped/fabricated tail quantile.
48 OutsideCertifiedDomain { reason: String },
49}
50
51impl_reason_error_boilerplate! {
52 TransformationNormalError {
53 InvalidInput,
54 DesignDegenerate,
55 NonFinite,
56 MonotonicityViolated,
57 NumericalFailure,
58 OutsideCertifiedDomain,
59 }
60}
61
62impl From<crate::block_layout::block_count::BlockCountMismatch> for TransformationNormalError {
63 fn from(
64 err: crate::block_layout::block_count::BlockCountMismatch,
65 ) -> TransformationNormalError {
66 TransformationNormalError::InvalidInput {
67 reason: err.message(),
68 }
69 }
70}