Skip to content

feat(studio): interactive dilution equation, curated backgrounds, custom bimodal modes (schema 0.5.0) - #98

Merged
aliakherati merged 3 commits into
studio/devfrom
feat/background-curation
Aug 27, 2026
Merged

feat(studio): interactive dilution equation, curated backgrounds, custom bimodal modes (schema 0.5.0)#98
aliakherati merged 3 commits into
studio/devfrom
feat/background-curation

Conversation

@aliakherati

Copy link
Copy Markdown
Collaborator

Interactive dilution, full-profile hover, curated backgrounds, custom modes

Four review requests in one message; two stacked PRs deliver them. This branch carries both commits
(the first is also PR #98's base): schema 0.4.0 → 0.5.0.

The dilution tab is now the equation (first commit)

The model's two-piece form is displayed with the selected regime's constant beside it:

V/V₀ = t^0.8              (t ≤ 10⁴ s)
V/V₀ = 1585 · exp(k · (t − 10⁴)^1.5)        k (s⁻¹·⁵) = 8.890e−9
  • Regimes are chips on the graph, each carrying its k — introspected from
    coupled/dilution.py's own segment tuples, never re-typed (a test compares against the same
    tuples). Clicking a chip sets dilution.regime for the run — choosing on the graph is
    choosing.
  • "try k" draws any k in [1e-10, 1e-6] as a dashed gold curve, computed by the model's own
    _two_piece/_eval_segment machinery. At a named regime's k the custom curve equals that
    regime's bit for bit — the test that proves it is not a lookalike formula. Clearly marked a
    picture: the model runs named regimes or a constant rate.
  • The ERA5 profile hover reads the whole level — p · z · T · H₂O (p 50 hPa · z 20.8 km · T 211.4 K · H₂O 4.20 ppmv), tracking along the pressure axis. Also retires a latent defect:
    the x-tracking hover assumed sorted xs, which a temperature profile does not have.
  • Sub-decade gridlines on log axes (2–9 per decade), faint and dashed.

Curated backgrounds — hidden is not removed (second commit)

Picker now offers: sabr_330, sabr_310, sabr_220, aer_geo (the spec's GEOENG reference — Pierce
et al. AER 2D), custom. Hidden: redcircles, cesm_g6, cesm_g6_amb — still valid, because Tier
B's archived cases use cesm_g6, and a schema that refused them would disconnect the archive from its
own configurations. SciField.hidden_choices is what the generator filters on; an archival value
still displays, marked "(archival)".

Custom bimodal distribution

(N₁, Dp₁, σ₁) + (N₂, Dp₂, σ₂) with an explicit STP/ambient basis — required because the
bridge refuses a custom mode list without one (SCIENCE-3's per-dataset basis trap, made a field).
Defaults are SABR-220's mode with N₂ = 0: custom starts citable and unimodal, and with untouched
defaults it seeds the same bins as sabr_220, asserted bin-for-bin. The panel redraws live as you
type, through the same to_scenario → initial_tomas_state path the run uses.

Found by test at the seam: the model refuses zero-N modes (N must be > 0), so the seam filters
them — N₂ = 0 is a one-mode list, not a zero entry — and refuses the all-zero case with a message
naming the fields.

Also

max_wall_time_s no longer cites "BLOCKING-4" bare — raised in review as confusing, which it was.
It now says what the cap does and where the hour came from.

371 Python Tier-A, 60 vitest, both lanes green. Verified live in the browser: chip click changed
the run's regime; typing N₂ = 8 drew the bimodal distribution on the spot.

Stacked on #97#96. Merge bottom-up; rebase on request.

aliakherati and others added 3 commits August 26, 2026 18:24
…readout

Review requests, all three built:

Dilution is now the equation, not just curves. The model's two-piece form is shown --
V/V0 = t^0.8 (t <= 1e4 s), then 1585*exp(k*(t-1e4)^1.5) -- with the selected regime's k
beside it. The regimes are chips ON the graph carrying their k values (introspected from
coupled/dilution.py's segment tuples, never re-typed; a test compares against the same
tuples). Clicking a chip sets dilution.regime through the same server round-trip as any
field edit, so choosing on the graph IS choosing in the config. A "try k" box draws the
family at any k in [1e-10, 1e-6] as a dashed gold curve, computed by the model's OWN
_two_piece/_eval_segment machinery -- at a named regime's k the custom curve equals that
regime's curve bit for bit, which is the test that proves it is not a lookalike formula.
Marked clearly as a picture: the model runs named regimes or a constant rate.

The ERA5 profile hover now reads out the whole level: pressure, altitude, temperature AND
water vapour (p 50 hPa / z 20.8 km / T 211.4 K / H2O 4.20 ppmv), tracking the pointer
along the PRESSURE axis -- charts gain hoverAxis="y" with a horizontal crosshair and a
panel-supplied readout, since only the panel knows what lives at each level. This also
retires a latent defect: the old x-tracking hover assumed sorted xs, which a temperature
profile does not have.

Sub-decade gridlines on log axes (2..9 per decade), faint and dashed so they read as
scaffolding: requested for the pressure axis, and the V/V0 axis gets them for free.

Panel parameters travel as PreviewRequest.params -- exploration inputs for a picture,
deliberately not part of the config, the hash, or anything persistent.

363 Python Tier-A (+3), 60 vitest (+2).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EaDHnPdiacsybr8Tp5WnqR
…tion (schema 0.5.0)

Review requests: offer SABRE-220/310/330 and the geoengineered stratosphere only, and
allow a custom distribution from two (N, sigma, Dp) triplets.

Curated means HIDDEN, not removed. Tier B's archived cases use cesm_g6; a schema that
refused it would disconnect the archive from its own configurations. So the enum keeps
every member, SciField gains `hidden_choices`, and the form generator filters them from
the picker -- an archival value still displays when it IS the current value, marked
"(archival)". The picker now offers sabr_330, sabr_310, sabr_220, aer_geo (the spec's
GEOENG reference: Pierce et al. AER 2D), and custom.

CUSTOM is two lognormal modes -- (N1, Dp1, sigma1), (N2, Dp2, sigma2) -- plus an explicit
STP/ambient basis, because the bridge refuses a custom mode list without one (the
per-dataset basis trap SCIENCE-3 recorded). Defaults are SABR-220's mode with N2 = 0:
custom starts citable and unimodal, and with untouched defaults it seeds the SAME bins as
sabr_220, asserted bin for bin. The size-distribution panel redraws live as the modes are
typed, through the same to_scenario -> initial_tomas_state path the run uses.

Found by test at the seam: the model refuses zero-N modes outright ("N must be > 0"), so
the seam FILTERS them -- N2 = 0 means a one-mode list, not a zero entry -- and refuses the
all-zero case with a message naming the fields rather than the bridge internals.

Also from review: the max-wall-time description cited "BLOCKING-4" bare, which read as a
mystery label; it now says what the cap does and where the hour came from.

371 Python Tier-A, 60 vitest. Hash ...d35ba6 -> ...2c09c6 (0.5.0): default run unchanged,
seven more fields describing it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EaDHnPdiacsybr8Tp5WnqR
…ntrol that fixes it

Two review corrections.

The campaign is SABRE -- Stratospheric Aerosol processes, Budget and Radiative Effects.
Enum members and values are respelled (sabre_220/310/330, shown as such in the picker and
the legacy page), and studio/modelio translates them to the model's internal sabr_* keys:
the bridge's BACKGROUND_MODES naming is the model's own, and renaming model internals is
not the seam's call. Hash repinned within the still-unmerged 0.5.0.

The dilution explorer's error handling was a trap of my own making: a rejected k came back
as a panel-level 422 that REPLACED the panel body -- taking with it the chips, the chart,
and the very input needed to fix the value, while the bad k stayed pinned so every refetch
re-failed. Unrecoverable without a reload; reported from use with a screenshot. Two-part
fix: Frame renders errors ABOVE its content instead of instead of it (an error must never
remove the control that clears it), and the k box validates against the bounds the server
declares before any request, with the message inline at the input. Verified live: k=10
shows "k must be within 1e-10 ... 1e-6" beside the box with everything still interactive,
and 1.5e-8 recovers on the next keystroke.

371 Python Tier-A, 60 vitest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EaDHnPdiacsybr8Tp5WnqR
@aliakherati
aliakherati force-pushed the feat/background-curation branch from d06ae71 to c939b5f Compare August 27, 2026 01:26
@aliakherati
aliakherati merged commit d4c419f into studio/dev Aug 27, 2026
2 checks passed
@aliakherati
aliakherati deleted the feat/background-curation branch August 27, 2026 05:16
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