1/(1 + exp(t)), saturated instead of overflowing.
The two logistic terms of Eq. (4) reach |t| ~ 34 over the
ISOMIP+ box including ghost rows, but a caller with a much wider
y_len (or a tiny f_c) would drive exp(t) past the real64
overflow at t ~ 709. Saturating at +/-T_SAT is exact to the
last bit of r on both sides (1/(1+exp(500)) underflows to 0
and 1/(1+exp(-500)) rounds to 1 anyway), so this costs nothing
and removes an Inf that would propagate as a NaN.
t is built from grid positions and the Table-1 constants, all
finite by construction, so the CLAUDE.md “if/else clamps launder
NaN” trap does not apply: there is no path that feeds this a NaN.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | t |
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| real(kind=wp), | private, | parameter | :: | T_SAT | = | 500.0_wp |
pure function isomip_logistic(t) result(r) !! `1/(1 + exp(t))`, saturated instead of overflowing. !! !! The two logistic terms of Eq. (4) reach `|t| ~ 34` over the !! ISOMIP+ box including ghost rows, but a caller with a much wider !! `y_len` (or a tiny `f_c`) would drive `exp(t)` past the `real64` !! overflow at `t ~ 709`. Saturating at +/-`T_SAT` is exact to the !! last bit of `r` on both sides (`1/(1+exp(500))` underflows to 0 !! and `1/(1+exp(-500))` rounds to 1 anyway), so this costs nothing !! and removes an Inf that would propagate as a NaN. !! !! `t` is built from grid positions and the Table-1 constants, all !! finite by construction, so the CLAUDE.md "if/else clamps launder !! NaN" trap does not apply: there is no path that feeds this a NaN. real(wp), intent(in) :: t real(wp) :: r real(wp), parameter :: T_SAT = 500.0_wp if (t > T_SAT) then r = 0.0_wp else if (t < -T_SAT) then r = 1.0_wp else r = 1.0_wp/(1.0_wp + exp(t)) end if end function isomip_logistic