Skip to content

ERA5 population study of buoyancy topologies and LNB-convention sensitivity#4

Open
maresb wants to merge 20 commits into
masterfrom
era5-topology-analysis
Open

ERA5 population study of buoyancy topologies and LNB-convention sensitivity#4
maresb wants to merge 20 commits into
masterfrom
era5-topology-analysis

Conversation

@maresb

@maresb maresb commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

Adds era5_analysis/: a reproducible pipeline measuring, over 569,478 random ERA5 ocean columns (1980–2024), how often multi-crossing buoyancy structures occur for the three tcpyPI parcels (A environmental, B eyewall, C saturated core) and how the LNB convention (legacy topmost-level "top" vs max-work "max" vs lifted-ballistic "reach") affects CAPE and PI. Companion to the discontinuity_analysis/ issue-dgilford#77 root-cause study (#1): that PR shows why the legacy convention destabilizes the pressure iteration; this one shows how often it matters at climatological scale.

Headline results (TC-relevant subset: SST ≥ 26 °C)

  • Parcel A topologies are a rich mixture (+-+- 32%, -+- 17%, +-+-+- 16%, +- 14%, …); parcel B is 84% single-hump +-; parcel C is ~98% +- — convention sensitivity is inversely proportional to parcel energy.
  • E_top clamps to 0 while E_max > 0 on 15.4% (A) / 1.75% (B) / 0.00% (C) of columns.
  • Full pi() under all conventions over 476k columns: legacy LNB fails to converge on 8 columns (~1 in 60,000; median max-work VMAX 64 m/s — real storm environments returned missing); max-work fails on 0. Where PI matters, top vs max agree to p99 = 0.53 m/s.
  • Minimal column ceiling observing every buoyancy crossing: retain levels down to 30 hPa (highest crossing 47.6 hPa, saturated core).

Contents

  • Pipeline scripts (convert.pyscan_final.pypi_conv.py → …), documented in era5_analysis/README.md with the full result summary.
  • build_tcpypi_era5_sample.py — builds the input sample from public ARCO-ERA5 (no credentials needed).
  • "Sounding Scope" interactive explorer (prep_artifact_data.py + sounding_scope_template.html): flip-book of profiles with map/seasonal/diurnal/year marginals, crossfilter selection, per-convention CAPE/LNB/PI readout, ΔVMAX scatters, and conditioning on convention disagreements.

Test plan

  • validate_scan.py cross-checks scan CAPEs against the real cape() kernels on random profiles.
  • pi_conv.py reproduces stock tcpyPI outputs under the legacy convention.

🤖 Generated with Claude Code

maresb added 20 commits July 19, 2026 16:36
Population study over 569,478 random ERA5 ocean columns (1980-2024) of
lifted-parcel buoyancy topologies and LNB-convention sensitivity for the
three parcels used by potential intensity, companion to
discontinuity_analysis/ (issue dgilford#77).

Contents: the sample builder (build_tcpypi_era5_sample.py, reads public
ARCO-ERA5, reproduces the published S3 parquets bit-for-bit from its
seeds), the scan pipeline (topology/crossings/partial sums under four LNB
conventions; full pi() under each convention including the wild-population
legacy non-convergence census of 8-in-476k; three-parcel scan at converged
P_M), figure generators, and the Sounding Scope interactive flipbook
template. Paths are portable via ERA5_SCRATCH; tcpyPI imports resolve
against this repo's src/. Headline numbers in era5_analysis/README.md.

Generated-with: Claude Fable 5
The artifact silently restricted all three parcels to the SST >= 26 C
subset and hardcoded the map to +/-38 latitude, clamping out-of-range
points into edge bins. Parcel A is defined on all 569,478 columns
(-78..+90 lat) and B/C on the 476,102 with a converged storm pressure;
the payload now carries per-parcel populations with sentinels where B/C
are undefined, the map spans the data, out-of-range points are dropped
rather than clamped, and the scope draws unaltered curves through a
clipped viewport instead of clamping values to the axis range.

Generated-with: Claude Fable 5
Highest observed buoyancy crossing across all 1.52M parcel evaluations
is 49.6 hPa (saturated core), so retaining levels with P > 10 hPa (top
20 hPa) leaves a 2.5x pressure buffer: zero clipped topologies remain,
21/569k parcel-A columns fail the entropy solve aloft and are flagged.
Buoyancy x-ranges now come from the deepest interior dip (most negative
value before a curve's last positive level) with 10% margin, so the
terminal stratospheric plunge no longer compresses the plots. Figures
and the Sounding Scope template/payload regenerated accordingly.

Generated-with: Claude Fable 5
Click any marginal to filter the flipbook samples: the map selects a
position (haversine, +/-1500 km), the day-of-year ring a +/-15-day
circular window, the new hour-of-day ring a +/-3 h UTC window, and the
year track a single year. Each marginal displays the density under all
the OTHER active filters (crossfilter convention) with its own
selection drawn dashed; active selections appear as dismissable chips
with a live matching-sample count, and Escape clears them. Payload
gains hour-of-day for the player pool and map layer.

Generated-with: Claude Fable 5
… filter

scan_final now records the LNB pressure under each of the four
conventions (columns 8-11). The artifact shows T/M/F/R lanes on the
scope marking each convention's LNB for the current profile in the
document's semantic colors (red topmost, aqua max-work, yellow first,
violet ballistic); the readout lists CAPE @ LNB per convention and
VMAX under legacy vs max-work (from pi_conv, ptop=50), flagging
disagreements. A condition chip row crossfilters everything to
disagreement populations: clamp-flips, |dE|>10 J/kg, |dVMAX|>1 m/s,
and the eight real legacy non-convergent columns, which are force-
included in the player pool and map layer.

Generated-with: Claude Fable 5
The highest buoyancy crossing anywhere in the sample is 47.6 hPa, so
retaining levels down to 30 hPa (ptop=20) observes every crossing with
zero clipping and, unlike the 20 hPa ceiling, zero entropy-solver
failures. Conditions are now top!=max and reach!=max (>1 J/kg) as
primaries, keeping clamp-flip, |dVMAX|>1 m/s, and legacy-non-convergent
as the mechanistically distinct cases; the first-crossing convention is
dropped from the display (materially wrong too often to be
interesting). Readout gives pos and SST their own cells with
figure-space-padded coordinates so the layout no longer thrashes with
coordinate width.

Generated-with: Claude Fable 5
The chip row is itself a marginal: with any position/season/hour/year
selection or disagreement condition active, each topology's share is
recomputed from the 40k map layer under all the OTHER filters (its own
selection excluded, crossfilter convention), so e.g. clicking the Gulf
of Mexico in September immediately shows that region-season's topology
mix. With no filters active the exact population shares are shown.

Generated-with: Claude Fable 5
Under active filters the topology chips re-sort by filtered frequency
and empty classes are masked off (the currently selected class stays
visible so it can be deselected); with no filters the canonical
global-frequency order is shown.

Generated-with: Claude Fable 5
The filter-status chip now shows in every state (not only with marginal
selections active): flip-sample count, matching map points, and the
retained share of the current parcel's population with adaptive
precision (>= 0.1% one decimal, below that two significant figures, so
e.g. the 8 legacy-failure columns read 0.020% instead of rounding to
zero). Also fixes a latent guard bug where hour-only selections showed
no count (selHour was missing from the visibility condition).

Generated-with: Claude Fable 5
The single top!=max condition mixed two populations with different
physics: genuine value disagreements (both conventions positive) and
the zero-flips where legacy returns exactly 0 (deep-valley clamps plus
launch-level surface slivers). Conditions are now 0!=top!=max and
0=top!=max (both at >1 J/kg), expressed from the existing bitmask
client-side with no payload change.

Generated-with: Claude Fable 5
New panel: scatter of VMAX under the legacy topmost-LNB convention (x)
against max-work and lifted-ballistic (y, aqua/violet), over every map
point passing the full crossfilter, with a light dashed identity
diagonal, cached offscreen and invalidated with the other densities.
The current flipbook profile is highlighted with rings per convention.
Map layer payload gains Vtop/Vmax/Vreach.

Generated-with: Claude Fable 5
Replace the combined PI scatter with two panels: dVMAX = max - top and
dVMAX = reach - top vs VMAX(top), each with a horizontal zero reference
instead of the diagonal and a symmetric y-axis computed from the
FILTERED deltas so points are never clipped (the axis rescales).
Convention naming is now uniform everywhere -- top / max / reach --
with the long definitions stated once in the footer; the condition chip
reads "top non-convergent".

Generated-with: Claude Fable 5
Clicking either scatter selects a window in (VMAX(top), dVMAX) space
(+/-5 m/s in x, +/-10% of the y-scale at click time in delta, min 0.5),
one PI selection at a time, shown as a dashed rectangle and a
dismissable chip. The selection filters the flipbook pool, map, rings,
year track, and chip shares; the scatters themselves skip their own
dimension per crossfilter convention. Clicking the panel padding or
pressing Escape clears it.

Generated-with: Claude Fable 5
VMAX^2 is proportional to (SST/T_O)(E_C - E_B), so when parcel B or C
is selected the other of the pair is drawn for the same profile as a
thin dashed curve in its own topology-class color: the PI winds live in
the gap between the two curves. Footer states the relation.

Generated-with: Claude Fable 5
…ters

The two-panel scatter refactor removed the scatKey variable but
invalidate() still assigned to it -- a ReferenceError under strict mode
that silently aborted selChanged() after the throw, so live chip shares,
matching counts, and the resample tick were skipped or only partially
updated on marginal clicks (some handlers ran buildChips before
invalidate, making the staleness look erratic). invalidate() now clears
the per-panel cache keys. Also: clicking inside the current dVMAX
selection rectangle clears it (documented in the footer), alongside the
existing chip-x / margin-click / Escape paths.

Generated-with: Claude Fable 5
The storm outflow level (parcel C's max-work LNB, the one that sets T_O
in the PI formulas) is now marked with a labeled arrow on the scope's
pressure axis for every profile, whichever parcel is selected -- keeping
parcel B (where the topological variety lives) as the default view
without hiding the LNB that actually bills into PI.

Generated-with: Claude Fable 5
Counts, class shares, densities and the dVMAX scatters previously ran on
a 40k map subsample, so marginal selections sparsified fast. The payload
now carries every column (569,478 rows) as a binary-packed statistics
table: little-endian int16/int8 columns (deci/centi quantization, -1
sentinels preserved exactly), gzipped and base64-embedded, decoded in
the browser via DecompressionStream -- 12.5 MB raw -> 7.0 MB in-page.
Player curves move to int16 centi-J/kg rectangles (same 0.01 precision
as the old JSON), letting the flip-book pool grow 5.5k -> 24.5k within
the 16 MB artifact cap. Dead "first"-convention player fields dropped.

To keep 569k rows interactive, the per-frame day/hour/year histogram
scans now sit behind selection-keyed caches (invalidated with the other
caches), and the scatter panels switch to a two-pass dense rendering
(low-alpha mass + opaque |dVMAX| > 1 m/s outliers) above 20k points.
Count chip now reads "N of TOTAL columns"; footer documents that only
the animated flip-book remains a sample.

Generated-with: Claude Fable 5
…nges

Switching parcel A/B/C or adding a marginal/topology/condition filter
used to force an immediate resample even when the displayed profile was
still inside the new subset. The per-index predicate is factored out of
pool() as passPlayer(), and every filter-mutation path now goes through
retick(): if the current profile still passes, only the scope and
readout redraw (so the same sounding can be compared across parcels);
resampling happens only when the profile falls outside the new subset.
The play timer's cadence is unchanged.

Generated-with: Claude Fable 5
The single |dVMAX| > 1 m/s condition becomes two signed chips on the gap
dv = VMAX(max) - VMAX(top): dv > +8 m/s (1,822 columns; the legacy clamp
suppressing real PI) and dv < -2 m/s (71 columns; top exceeding max).
gpi codes: 1 = top non-convergent, 2 = dv > 8, 3 = dv < -2. Up to 400
columns per extreme are force-included in the flip-book pool so both
conditions animate.

Generated-with: Claude Fable 5
Each condition chip now shows its share of the population passing all
OTHER active filters (parcel, topology, marginals, dVMAX selection),
computed in one full-table pass mirroring condPassMap, with adaptive
precision so rare conditions (e.g. the 8 non-convergent columns,
0.0014%) stay visible. Shares refresh on every chip/marginal/parcel/
theme change.

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