Add failing test to illustrate failure of convergence#77
Draft
maresb wants to merge 3 commits into
Draft
Conversation
This improves isolation for testing, and makes scoping explicit
maresb
marked this pull request as draft
March 12, 2025 19:20
maresb
added a commit
that referenced
this pull request
Jul 14, 2026
The argmax of the running CAPE can land on a level with TVRDIF <= 0 (the trapezoid into it nets positive whenever the level below is more buoyant than it is negative), and the PINB/PAT/TOB formulas then interpolate between two same-sign values, extrapolating outside their interval: wrong LNB/TOB and a spurious PAT term. Measured on 800 real columns: 1.25% of cape() calls. Back up one level in that case (the level below is provably positive, so the crossing formulas apply), and skip the residual-area block entirely when no sign change exists. With the guard, all 24 measured hazard cases agree with a crossing-level reference implementation to <= 1.3e-3 J/kg, and the 1995 marginal profile now matches it exactly (74.378263 m/s). Issue #77 (Francine) converges: VMAX=83.849841, IFL=1, map continuous at the former jump. Also adapt test_run_sample.py to the intentional LNB-selection change: the old rtol=1e-13 pins cannot hold (summation order moved, and ~1-2.5% of columns change on purpose, up to 14.4 m/s on this sample). Compare NaN/flag patterns exactly (small allowance for log-derived variables where vmax crosses 0), the bulk at per-variable noise scales, and bound the intentional tail. Re-pin the .nc outputs to retire this scaffolding. Generated-with: Claude Fable 5
maresb
added a commit
that referenced
this pull request
Jul 14, 2026
…vels The continuous running work integral peaks at buoyancy zero crossings, between grid levels. Ranking candidates by their level values alone can mis-rank two buoyant layers whose level values are within a partial area of each other, returning the smaller layer's CAPE and the wrong LNB, and leaving a small residual discontinuity (bounded by the partial area, ~few J/kg, i.e. ~0.02 hPa in the pressure map - far below the 0.5 hPa stopping tolerance, but avoidable). Rank each level by its running CAPE plus the residual partial area to the interpolated crossing above it. This supersedes the previous back-up-one-level guard: levels with TVRDIF <= 0 carry no partial area and are strictly dominated by the crossing candidate of the buoyant layer below, so the winner always admits the crossing formulas. Verified on 2,400 cape() calls over 800 real columns against the previous commit: the new value is never smaller (min diff exactly 0), only 5 calls change (max +2.06 J/kg), with LNB moving to the truly dominant layer on those; flags identical. Issue #77 and both 1995 profiles unchanged; full test suite and doctests pass. Generated-with: Claude Fable 5
maresb
added a commit
that referenced
this pull request
Jul 15, 2026
Fixes the solver non-convergence of issue #77 at its root. pcmin.m's CAPE rule -- integrate the signed buoyancy work to the HIGHEST grid level with positive buoyancy, then clamp at zero -- is a discontinuous function of the inputs: an arbitrarily small buoyancy sign change at a marginal level aloft toggles the intervening negative area all at once, jumping CAPE by O(100 J/kg) and the pressure map by ~0.8 hPa. When such a jump straddles the map's diagonal, no fixed point exists and the iteration locks into a bit-exact period-2 cycle (the Francine/issue-77 sounding and the historical 1995 marginal profile). cape() now takes CAPE as the maximum of the same running signed work integral (same trapezoid increments, same interpolated zero-crossing partial areas) over all candidate outflow levels, and the LNB as the level attaining it. The maximum is continuous in the inputs (Berge), needs no clamp (the launch level is a zero-work candidate), and equals the legacy value exactly on single-crossing buoyancy profiles. The period-2 rescue is removed: with a continuous map the cycles it patched over cannot occur, and both historical failing soundings now converge through the normal path in a handful of iterations (regression-pinned in tests/test_marginal_convergence.py, including the exact PR #77 Francine column). Deliberately NOT bit-identical to pcmin.m -- an intentional convention correction, validated in discontinuity_analysis/: on the 7,093-column 2024 sample, zero flag changes, 98.1% of VMAX within 0.1 m/s, and a 1.9% tail (up to 7.9 m/s) confined to marginal multi-layer profiles on which the legacy value was discontinuity-adjacent. AGENTS.md's bit-identity policy gains the corresponding carve-out. The fuzz test's false invariant (IFL != 1 implies NaN outputs) is also corrected: flags tripped by the environmental-CAPE call persist (issue #78) and may accompany finite outputs. Generated-with: Claude Fable 5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The data for this failure comes from climatological sensitivity analysis when looking at Francine with the following parameters: