Skip to content

Mathematical analysis of the pressure-solver discontinuity (issue #77)#1

Open
maresb wants to merge 11 commits into
masterfrom
discontinuity-analysis
Open

Mathematical analysis of the pressure-solver discontinuity (issue #77)#1
maresb wants to merge 11 commits into
masterfrom
discontinuity-analysis

Conversation

@maresb

@maresb maresb commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

This PR adds discontinuity_analysis/ — a self-contained mathematical account (19-page PDF, LaTeX source, 13 figures, and all analysis scripts) of the period-2 convergence failure reported in dgilford#77, written for a reader with no atmospheric-physics background. The compiled PDF is included: discontinuity_analysis.pdf.

Key findings

  • The failing sounding is Hurricane Francine's pre-landfall environment (18 UTC 2024-09-11, 28.6°N 92.1°W, confirmed by the NHC best track) under a −1.6 K counterfactual SST.
  • No fixed point exists. The solver's map g jumps downward by 0.837 hPa at x_d ≈ 950.8289947515 hPa; the residual g(x) − x changes sign only across the jump. The bit-exact 2-cycle (950.653… ↔ 951.279…) is the canonical dynamics of a contraction with a diagonal-straddling gap.
  • The cause is one discontinuous selection rule in cape(): "integrate signed buoyancy to the topmost grid level with positive buoyancy, clamp at zero." A ±10⁻¹¹ K sign flicker at the 350 hPa level toggles 287 J/kg of negative area; eyewall CAPE flips between 224.23 J/kg and 0. The deciding feature is energetically negligible (~10⁻² J/kg) and dynamically unreachable (W = −61 J/kg there).
  • This is pcmin's own defect, not a porting bug: the selector, clamp, and all formulas are inherited line-for-line from pc_min.m ("Find maximum level of positive buoyancy, INB"), and neither Emanuel (1994) nor Bister & Emanuel (2002) specifies a rule for multiple buoyant layers.
  • Not a resolution/interpolation/quadrature problem (each tested and refuted; the discontinuity survives C^∞ data and 5 hPa smooth reconstruction).
  • Generic mechanism, rare alignment: 20% of 1,200 sampled real columns carry at least one jump of g; the fatal diagonal-in-gap placement occupies a ~0.04 K SST window for this column — exactly what attribution-style SST sweeps find.
  • Repair: define CAPE as the value function max_q W(q) over the same quadrature and candidates (continuous by Berge's maximum theorem; matches forced-eyewall-ascent physics) and solve with residual-checked bracketing. Exact corrected roots: 950.3136889226 hPa (VMAX 83.9065 m/s, PMIN 920.7517 hPa) for Add failing test to illustrate failure of convergence dgilford/tcpyPI#77 and 960.7687 hPa (VMAX 74.4149 m/s) for the 1995 regression case. On the 7,093-column 2024 benchmark: zero flag changes, 98.1% of VMAX within 0.1 m/s, and a 1.9% tail confined to configurations where the legacy value was fragile by construction. The period-2 "rescue" becomes dead code (its midpoint was never a root).

Contents

  • discontinuity_analysis.pdf / .tex — the document (rebuild with latexmk -pdf; final log is warning-clean)
  • figures/ — all figures (PDF + PNG)
  • scripts/ — the analysis scripts behind every number and figure (run against dgilford/tcpyPI branch modernize_eg, commit abc4b4e); every headline quantity was reproduced by a second, independently written implementation

Suggested follow-ups (not in this PR)

Staged implementation plan is in §7.5 of the document: diagnostics first, then the max-work kernel behind an explicit option, outflow/tie semantics (including flagging ptop-clipped LNBs), and a bracketed solver. The convention change should be coordinated upstream before becoming a default.

🤖 Generated with Claude Code

maresb added 11 commits July 12, 2026 18:05
…ford#77)

Merged synthesis of two independent, mutually-audited analyses
(discontinuity_analysis/ and reports/issue77/) of the period-2
convergence failure. The fixed-point map is discontinuous because
cape() integrates signed buoyancy to the topmost positive grid level
and clamps at zero; for the PR dgilford#77 sounding (Hurricane Francine's
pre-landfall environment under a -1.6 K counterfactual SST) the only
diagonal crossing falls inside a 0.84 hPa jump at x_d ~ 950.829 hPa,
so no fixed point exists. Smooth toy models separate genuine
variational discontinuities (argmax jumps with continuous value;
barrier transitions, which are free-convection rather than PI physics)
from this artificial one; refuted alternatives (quadrature, grid
resolution, interpolation smoothness, floating point) are dispatched
in one subsection.

Repair: CAPE = max_q W(q) (value function, matching forced eyewall
ascent semantics) plus residual-checked bracketed root finding with an
explicit non-hypercane endpoint hypothesis. Exact corrected roots
950.3136889226 hPa (dgilford#77) and 960.7687 hPa (1995), verified by both
implementations; 7,093-column benchmark: zero flag changes, 98.1%
of VMAX within 0.1 m/s.

The compiled PDF is gitignored (build with latexmk -pdf).

Generated-with: Claude Fable 5
The analysis stands on its own as the definitive writeup: drop the
synthesis framing, the companion citations and appendix, and keep a
neutral note that all headline quantities were reproduced by a second
independently written implementation.

Generated-with: Claude Fable 5
The previous 'generic column' was the sample's single most extreme case
(VMAX 97 m/s), whose saturated parcel is still +5 K buoyant at the last
retained level, so the plotted 'LNB' was the ptop-truncation clip at
70 hPa and buoyancy never changed sign. Replace it with a representative
column (January, 10N 108W, VMAX 72.5): buoyancy crosses zero between 100
and 70 hPa, interior interpolated LNB at 91.2 hPa, fixed point 944.6 hPa.

Add a remark quantifying the clipping (23/7093 columns, all VMAX > 86,
with competing TO-cold / CAPE-low biases, unquantifiable from the shipped
sample whose data stops at 50 hPa) and extend implementation phase C to
flag clipped LNBs.

Generated-with: Claude Fable 5
At the point where E_top / E_max / E_reach are defined, show concrete
profiles spanning every way they can relate: (a) single hump, all agree;
(b) dominated upper cell without barrier, E_top undercounts; (c) marginal
bump behind a barrier (the issue-77 shape), E_top clamps to 0; (d)
dominant upper cell behind a barrier, E_reach stalls. Each panel marks
the three terminal points and values. The toy-model sweeps are linked to
the archetypes they animate.

Generated-with: Claude Fable 5
- Author line: break manually (maketitle sets authors in an unbreakable
  tabular, causing a 109pt overfull).
- Convention 2/3: breakable set notation; replace the over-wide prose
  display for E_top with prose plus the compact formula.
- Eq. (2): stack g and CAT instead of side-by-side.
- Status table: ragged-right p-columns (array package) to kill
  underfulls; final-interpretation causal chain restacked as one
  five-row alignment.
- Appendix table: footnotesize plus row splits to fit the text width.
- Mute the spurious 'Command \showhyphens has changed' kernel warning
  (reproducible with bare \usepackage{microtype} on this TeX Live 2025
  install) around microtype's begindocument check only.
- Add \emergencystretch for residual tight lines.

Final log: zero warnings, zero over/underfull boxes.

Generated-with: Claude Fable 5
… bug

Verified against matlab_scripts/pc_min.m (identical INB loop, clamp, and
partial-area formulas; Emanuel's comment: 'Find maximum level of positive
buoyancy, INB') and the git history (the Python CAPE module and pc_min.m
enter together in the first pyPI commit, April 2020, with the INB block
translated verbatim and never altered). Neither Emanuel (1994) nor
Bister & Emanuel (2002) specifies a multi-crossing rule, so the selector
is an undocumented implementation choice of the reference code. State
this explicitly in Convention 3 and in the conclusions.

Generated-with: Claude Fable 5
Panel (a) compared the legacy and repaired maps in overlapping solid
blue/teal with terse legend labels. Restyle using the document's
established convention colors: legacy E_top map in solid red, repaired
E_max map in dashed teal drawn on top, so coincidence (red through the
dash gaps) and divergence are both visible; spell out the intent in the
legend and title ("same map, one changed ingredient").

The restyle also exposed that the old caption was wrong: the two maps
coincide only on the right branch (where INB already selects the
dominant cell); on the left branch they differ by the full effect of
the 222 J/kg clamp. Caption corrected.

Generated-with: Claude Fable 5
Include discontinuity_analysis.pdf in the repository so the analysis is
readable without a TeX toolchain; keep ignoring LaTeX intermediates.

Generated-with: Claude Fable 5
New subsection (sec:jumpmag): the 0.838 hPa jump decomposes as
|dg| ~ g * dE_M/(Rd*Tv) * (1 - k/2 * SST/T_O), with all factors verified
against the real cape()/pi(): partial cancellation c1 = 0.342 (E_M
enters CAT twice with opposite signs), thermal denominator Rd*Tv =
87.0 kJ/kg, level g ~ 951 hPa. Prediction 0.838 vs measured 0.8381; the
1995 column's own factors give 0.840 vs measured 0.839. Generic failure
scale: ~200 J/kg of clamped CAPE x 3.7e-3 hPa/(J/kg) = 0.7-0.9 hPa,
just above the 0.5 hPa tolerance - the silent-failure window - reached
by both known cases from different ingredients.

Correction: the clamp does NOT maximize (or cause) the jump. Disabling
max(.,0) leaves the discontinuity intact and increases the measured
jump to 1.0845 hPa (E_M flips -65.90 <-> 224.23); the clamp pins one
branch at exactly 0 and attenuates the jump by a quarter. Convention 3
and the mechanism section no longer overstate the clamp's role, and the
-60.9 (phase A) vs -65.90 (at x_d) raw integrals are now disambiguated.

Generated-with: Claude Fable 5
The table conflated the trigger with the deciding feature's energy and
presented the g-jump as a separate 'map discontinuity'. Restructured:
the perturbation that flips the outcome is infinitesimal (a sign
crossing - that is what discontinuity means); ~1e-2 J/kg is the
feature's own energy content; and the 0.838 hPa jump of g is the same
discontinuity as the 224 J/kg jump of E_M, attenuated through eq. (2),
not a third object.

Generated-with: Claude Fable 5
Generated-with: Claude Fable 5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant