Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
78adb47
chore(studio): studio/dev becomes the integration branch (#60)
aliakherati Aug 14, 2026
90419e1
studio: schema v0 — SciField, RunConfig, RunSet and the config hash (…
aliakherati Aug 14, 2026
39ca343
studio: science derivations — plume, size distribution, air density, …
aliakherati Aug 14, 2026
fb2e99a
studio: dependency graph and override semantics (#67)
aliakherati Aug 14, 2026
8c12422
studio: the model seam and RunSummary (#69)
aliakherati Aug 14, 2026
bd284b2
studio: the runner, the job lifecycle and the first end-to-end run (#72)
aliakherati Aug 14, 2026
d7bbbce
studio: refuse the temperature feedback (schema 0.2.0) (#75)
aliakherati Aug 14, 2026
a479dc8
studio: measure the archived-ensemble reproduction tolerance (#74)
aliakherati Aug 14, 2026
3e07874
studio: correct the dp_mid_um tolerance row — not exact for a Studio …
aliakherati Aug 14, 2026
d215653
studio: re-land the heating/buoyancy scope decision dropped by #75's …
aliakherati Aug 14, 2026
22f4428
chore(studio): merge main into studio/dev; stop condition takes the d…
aliakherati Aug 14, 2026
994666c
studio: the two-tier golden harness (task 0.7) (#79)
aliakherati Aug 14, 2026
1a864fe
studio: provenance records, written before the run (task 0.9a) (#81)
aliakherati Aug 14, 2026
0e06f55
studio: record what the archive comparison assumes, and correct two c…
aliakherati Aug 14, 2026
550b15f
studio: persistence — models, artefact store, and Alembic from the fi…
aliakherati Aug 14, 2026
6b113bb
studio: the CLI — run, sweep, status (task 0.9c) (#86)
aliakherati Aug 15, 2026
3f1a101
studio: the API, the page, and the service both front ends share (0.9…
aliakherati Aug 17, 2026
5d3bf79
Merge branch 'studio/dev' into chore/phase-0-to-main
aliakherati Aug 17, 2026
5f55a53
docs(studio): Phase 0 complete
aliakherati Aug 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .github/workflows/studio-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ name: studio-ci

on:
push:
branches: ["main"]
# `studio/dev` is the integration branch every Studio task PRs into; main sees Studio in
# reviewed batches. Running on pushes to both means a merge into either is verified, not just
# the PR that preceded it -- a PR is tested against its own head, and a stale one can go green
# and still break the branch it lands on.
branches: ["main", "studio/dev"]
paths:
- "studio/**"
- "docs/studio/**"
Expand Down Expand Up @@ -79,10 +83,10 @@ jobs:
- name: black
run: .venv/bin/black --check studio/

# --strict on the two packages that must stay pure; the pyproject baseline covers the rest.
# --strict on the three packages that must stay pure; the pyproject baseline covers the rest.
- name: mypy
run: |
.venv/bin/mypy --strict studio/schema studio/science
.venv/bin/mypy --strict studio/schema studio/science studio/resolve
.venv/bin/mypy

# Tier B (full-case golden reproduction against the archived ensemble) is NOT run here: the
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ gas_phase_chemistry/
# -- SQLite database and run artefacts, both regenerable and both machine-specific
.env
var/

# Subagent worktrees: separate checkouts, never part of this repo
.claude/worktrees/
15 changes: 13 additions & 2 deletions docs/studio/ASSUMPTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,17 @@ plus a conversion layer becomes the better trade.

## ASSUMPTION-2 — The archived `state.npz` files are the golden reference, at a tolerance yet to be measured

**Made:** 2026-08-13 · **Affects:** `studio/tests/golden/` ·
**Made:** 2026-08-13 · **Settled:** 2026-08-13 (#70) · **Affects:** `studio/tests/golden/` ·
**Recorded in:** [ADR-009](adr/ADR-009-golden-file-strategy.md)

> **Settled.** The measurement exists:
> [`studio/tests/golden/REFERENCE_TOLERANCES.md`](../../studio/tests/golden/REFERENCE_TOLERANCES.md).
> Reproduction is **close but not bit-for-bit** — every headline quantity within 2.1e-12, worst
> deviation anywhere 3.4e-12, but only ~31 % of gas state-vector elements bit-identical. The archive
> is usable as a golden reference at ~1e-12 (endpoints) / ~1e-10 (series and per-bin size
> distribution); exact equality is not. The paragraphs below stand as the reasoning that made the
> measurement necessary; the *consequence* below still holds and cannot be retrofitted.

Golden fixtures are derived from the existing `coupled/paper_ensemble/runs*/` outputs. Whether
re-running those cases **today** reproduces them bit-for-bit is *unverified*: the submodule commits
at which they were produced were never recorded (there is no provenance record for the existing
Expand All @@ -52,7 +60,10 @@ the measurement together with the SHAs it was taken at.
**Consequence.** Golden fixtures record the SHA at which the reference was *measured*, not the SHA at
which the data was originally produced. This is an honest limitation and cannot be retrofitted.

**What would settle it.** The measurement itself, in `studio/tests/golden/REFERENCE_TOLERANCES.md`.
**What settled it.** The measurement itself, in `studio/tests/golden/REFERENCE_TOLERANCES.md`
(2026-08-13, issue #70). It also produced a result nobody had asked for: the model is bit-for-bit
deterministic run-to-run *today*, so the residual is drift between the archive's toolchain and this
one — which is what makes a 1e-12 tolerance defensible rather than arbitrary.

---

Expand Down
20 changes: 20 additions & 0 deletions docs/studio/CAVEATS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,26 @@ ones that arise from *configuring and comparing* runs.

---

## The box does not heat and does not rise — and this model cannot answer whether it should

**Every run is isobaric and isothermal at the configured temperature.** There is no radiative
heating response and no buoyant rise. A result must not be read as containing a plume-warming
signal, a lofting signal, or an altitude change.

This is a **scope boundary, not a pending feature** (Ali, 2026-08-13). The model's heating term is
shortwave-only — longwave cooling is absent from the radiative calculation (AD-5.4) — so enabling it
would not make the thermodynamics more complete, it would make them one-sided, producing a
~+1.2 K / 10 d drift that is an artefact of the missing cooling. Buoyant rise follows from a heating
rate this model cannot compute, so a rise velocity would be a free parameter dressed as physics.

**Answering either question requires a different model**, with longwave radiation and plume
dynamics. Studio therefore refuses `switches.heating_to_t` outright (schema 0.2.0, `True` fails
validation) and exposes **no** buoyancy or heating-rate fields at all — a knob for a capability the
model does not have would advertise it.

Sedimentation is a separate question and remains genuinely open (SCIENCE-4, issue #56); it is a
particle-loss process, not a thermodynamic response, and this decision says nothing about it.

## Top-level caveats — shown on every results view

### The definition of t = 0 is unresolved, and it dominates particle number
Expand Down
14 changes: 11 additions & 3 deletions docs/studio/GLOSSARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,17 @@ uses, plus the repository-specific names that are otherwise unguessable.
**RunSet** — the primary user-facing object: a base `RunConfig` plus zero or more **axes**. A single
run is a RunSet with zero axes, so there is no separate code path for N = 1.

**Axis** — a schema path marked as varying, either `{path, values: [...]}` or
`{path, range: {start, stop, n, spacing}}`. Expanded by **GRID** (Cartesian product), **ZIP**
(paired), or **LIST** (explicit configs).
**Axis** — one dimension of a sweep: a name, a kind, and **points**. Each point is a short `label`
plus the field `assignments` it stands for. Kinds: **GRID** (crossed with the other GRID/LIST axes),
**ZIP** (advanced in lockstep with the other ZIP axes, the group then crossed with the rest), and
**LIST** (crossed, but each point sets *several* fields at once — a covarying group, e.g. the paper
ensemble's site axis, where latitude, T, p and H₂O move together). Expansion order is
`itertools.product`: the last axis varies fastest, which is what reproduces the existing ensemble's
case order. Assignments name **leaf** paths only; a whole group has no unit, provenance or DAG node.

**Axis point label** — the short token that becomes part of the run label, e.g. `sabr220`, `a1p0`.
Joined by `__` across axes to give the **case ID**, which is how the existing ensemble names its
directories.

**Derived field** — a value computed from other fields, declared via `derived_from` metadata. Each
carries a state: **auto** (recomputed silently when an upstream field changes) or **user_override**
Expand Down
51 changes: 47 additions & 4 deletions docs/studio/OPEN_QUESTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ material is **not** represented.

---

### SCIENCE-4 — Box thermodynamics · **OPEN** · blocks Phase 5 · [#56](https://github.com/reflective-org/SANDBOX/issues/56)
### SCIENCE-4 — Box thermodynamics · **ANSWERED for heating and buoyancy** (2026-08-13); sedimentation open · [#56](https://github.com/reflective-org/SANDBOX/issues/56)
*Is the box isobaric? isothermal? does it rise buoyantly? do particles sediment out?*

Absent from the original brief. Current behaviour, from the code:
Expand All @@ -179,8 +179,30 @@ Absent from the original brief. Current behaviour, from the code:
AD-5.4), producing a one-sided ≈ +1.2 K / 10 d warm drift. Every science script leaves it off.
- **No buoyant rise.** No sedimentation.

Each must become a schema field with a documented default, and the SW-only asymmetry must warn in
the UI when the switch is enabled rather than silently producing a drifting temperature.
**Answered (Ali, 2026-08-13): heating and buoyancy are out of scope for this model.**

Not "undecided" — **out of scope**, which is a different status and is why this row is closed rather
than left open. Longwave radiation is not in the radiative calculation, so the heating term cannot
represent the box's energy balance: enabling it does not make the thermodynamics more complete, it
makes them one-sided, and the ~+1.2 K / 10 d drift is an artefact of the missing cooling rather than
a result. Buoyant rise follows the same logic — a parcel rises in response to a heating rate this
model cannot compute, so a rise velocity here would be a free parameter dressed as physics.

**Answering either question needs a different model**, one with longwave radiation and plume
dynamics. It is not a gap to be filled in by a later Studio phase, and Studio must not present a
knob implying otherwise:

- `switches.heating_to_t` is `Literal[False]` from schema 0.2.0 — `True` fails validation rather
than being defaulted off, so it cannot be enabled by a form, a YAML file or a sweep axis.
- **No buoyancy or heating-rate fields are added to the schema at all.** A field for a capability
the model does not have would advertise it; the absence is the honest interface (ADR-005).
- Every run is therefore **isobaric and isothermal at the configured temperature**, and results
carry that as a top-level caveat rather than a footnote.

**Still open: sedimentation.** It is untouched by this decision — a particle-loss process, not a
thermodynamic response — and the model does not have it. It is deliberately left in this register
rather than swept in with the rest, because "we decided not to model heating" is not an argument
about gravitational settling.

---

Expand All @@ -204,6 +226,27 @@ required. Until then `background_evolves` is a schema field whose only accepted

---

### SCIENCE-6 — GCR ion-pair production rate has no derivation · **OPEN** · Phase 0/4 · [#63](https://github.com/reflective-org/SANDBOX/issues/63)
*What is the ion-pair production rate as a function of altitude, latitude and solar-cycle phase?*

Raised by task 0.5. The two values available in the repository are an **uncited constant** and a
value that **switches off a physical process**: the paper ensemble uses a bare `30.0` cm⁻³ s⁻¹
(`run_ensemble.py:102`, described in `TABLE_microphysics_parameters.md` as "galactic cosmic rays at
~20 km"), and the model defaults to `0.0`, which disables ion-induced nucleation entirely
(`coupled/coupled_scenario.py:117`).

It feeds the ion-induced channels of Dunne et al. (2016) nucleation — the most sensitive part of this
system. GCR ionisation varies by roughly a factor of two over the solar cycle and strongly with
latitude and altitude, so one number is wrong nearly everywhere except where it was read off.

`studio/science/gcr.py` therefore raises `NotImplementedError` rather than interpolating an uncited
number, and exposes `PAPER_ENSEMBLE_ION_PAIR_RATE = 30.0` as a constant with its provenance attached.

**Answered when** either a citable parameterisation is agreed and implemented with its reference, or
the decision is recorded that the fixed value stands, with its sensitivity quantified.

---

## Register of capabilities the spec assumes but the model does not have

Not open questions — settled facts, listed here because the spec's stage descriptions imply
Expand All @@ -216,7 +259,7 @@ issue at that point rather than sitting in a backlog now.
| `chemistry.rate_overrides[]` (general) | Only `so2_ho2_rate` is a knob (`coupled_scenario.py:130`). Arbitrary per-reaction overrides do not exist. |
| `chemistry.photolysis.tuvx_settings.{o3_column, albedo, aod}` | Not exposed. Only mode + lat/lon/doy/hour reach TUV-x (`model_bridge.py:46`). |
| `numerics.bin_scheme.{d_min, d_max, mass_doubling}` | Fixed by the TOMAS grid; only `tomas_nbins ∈ {40, 80, 160}` is selectable (`tomas_bridge.py:146`). Ratio = `2**(40/nbins)`; the top boundary is pinned. |
| `numerics.box_thermodynamics.*` | See SCIENCE-4. |
| `numerics.box_thermodynamics.*` | **Not exposed, by decision.** Heating and buoyancy are out of scope (SCIENCE-4): the model cannot compute them and a field would imply it can. Isobaric + isothermal is the only behaviour. |
| `dilution.entrainment.{entrains_background_gases, entrains_background_aerosol}` | Entrainment is unconditional when `switches.dilution` is on. Separate flags are new code. |
| `dilution.background_evolves` | See SCIENCE-5. Only `false` is accepted. |
| `background.aerosol` custom lognormal modes | Six named modes + tabulated `redcircles` only — but `_seed_lognormal` (`tomas_bridge.py:110`) already accepts arbitrary `(N, Dg, σg)` tuples, so this is a small, worthwhile early addition. |
Expand Down
Loading
Loading