Reinforcement Learning (Actor Critic)

#18
Files changed (25) hide show
  1. .cache/known_methods_snapshot.json +63 -13
  2. .claude/skills/leaderboard-manager/references/add-method.md +1 -1
  3. .claude/skills/leaderboard-manager/references/populate-metadata.md +1 -1
  4. CLAUDE.md +25 -14
  5. data/rl-ddpg/leaderboard_rl-ddpg-long_l63_2026-10-04.nc +3 -0
  6. data/rl-ddpg/leaderboard_rl-ddpg-long_l96_const-force_2026-10-04.nc +3 -0
  7. data/rl-ddpg/leaderboard_rl-ddpg-long_l96_flux-force_2026-10-04.nc +3 -0
  8. data/rl-ddpg/leaderboard_rl-ddpg-long_l96_vec-force_2026-10-04.nc +3 -0
  9. data/rl-ddpg/leaderboard_rl-ddpg-short_l63_2026-10-04.nc +3 -0
  10. data/rl-ddpg/leaderboard_rl-ddpg-short_l96_const-force_2026-10-04.nc +3 -0
  11. data/rl-ddpg/leaderboard_rl-ddpg-short_l96_flux-force_2026-10-04.nc +3 -0
  12. data/rl-ddpg/leaderboard_rl-ddpg-short_l96_vec-force_2026-10-04.nc +3 -0
  13. data/rl-td3/leaderboard_rl-td3-long_l63_2026-10-04.nc +3 -0
  14. data/rl-td3/leaderboard_rl-td3-long_l96_const-force_2026-10-04.nc +3 -0
  15. data/rl-td3/leaderboard_rl-td3-long_l96_flux-force_2026-10-04.nc +3 -0
  16. data/rl-td3/leaderboard_rl-td3-long_l96_vec-force_2026-10-04.nc +3 -0
  17. data/rl-td3/leaderboard_rl-td3-short_l63_2026-10-04.nc +3 -0
  18. data/rl-td3/leaderboard_rl-td3-short_l96_const-force_2026-10-04.nc +3 -0
  19. data/rl-td3/leaderboard_rl-td3-short_l96_flux-force_2026-10-04.nc +3 -0
  20. data/rl-td3/leaderboard_rl-td3-short_l96_vec-force_2026-10-04.nc +3 -0
  21. src/common/leaderboard.py +12 -5
  22. src/common/method_registry.py +58 -16
  23. src/data_store.py +16 -0
  24. src/pages/MethodDetails.py +172 -0
  25. src/streamlit_app.py +5 -4
.cache/known_methods_snapshot.json CHANGED
@@ -4,7 +4,7 @@
4
  "abbreviation": "TEKI",
5
  "Method": "Tikhonov Regularized Ensemble Kalman Inversion",
6
  "parallelism": "parallel-interacting",
7
- "update_type": "kalman",
8
  "method_goal": "optimization",
9
  "emulator_use": "none",
10
  "aliases": [
@@ -15,7 +15,7 @@
15
  "abbreviation": "ETKI",
16
  "Method": "Ensemble Transform Kalman Inversion",
17
  "parallelism": "parallel-interacting",
18
- "update_type": "kalman",
19
  "method_goal": "optimization",
20
  "emulator_use": "none",
21
  "aliases": [
@@ -26,7 +26,7 @@
26
  "abbreviation": "IEKF",
27
  "Method": "Iterative Ensemble Kalman Filter",
28
  "parallelism": "parallel-interacting",
29
- "update_type": "kalman",
30
  "method_goal": "uq",
31
  "emulator_use": "none",
32
  "aliases": [
@@ -39,7 +39,7 @@
39
  "abbreviation": "UKI",
40
  "Method": "Unscented Kalman Inversion",
41
  "parallelism": "parallel-interacting",
42
- "update_type": "kalman",
43
  "method_goal": "optimization",
44
  "emulator_use": "none",
45
  "aliases": [
@@ -72,7 +72,7 @@
72
  "abbreviation": "CES-EKI-DMC",
73
  "Method": "Calibrate Emulate Sample (EKI-DataMisfitController)",
74
  "parallelism": "parallel-interacting",
75
- "update_type": "kalman",
76
  "method_goal": "uq",
77
  "emulator_use": "after-optimize",
78
  "aliases": [
@@ -83,7 +83,7 @@
83
  "abbreviation": "CES-EKI-CONST",
84
  "Method": "Calibrate Emulate Sample (EKI-Constant Scheduler)",
85
  "parallelism": "parallel-interacting",
86
- "update_type": "kalman",
87
  "method_goal": "uq",
88
  "emulator_use": "after-optimize",
89
  "aliases": [
@@ -94,7 +94,7 @@
94
  "abbreviation": "CES-IEKF-CONST",
95
  "Method": "Calibrate Emulate Sample (IEKF-Constant Scheduler)",
96
  "parallelism": "parallel-interacting",
97
- "update_type": "kalman",
98
  "method_goal": "uq",
99
  "emulator_use": "after-optimize",
100
  "aliases": [
@@ -105,7 +105,7 @@
105
  "abbreviation": "ADAM",
106
  "Method": "Adaptive Moment Estimation",
107
  "parallelism": "serial",
108
- "update_type": "gradient",
109
  "method_goal": "optimization",
110
  "emulator_use": "none",
111
  "aliases": [
@@ -116,7 +116,7 @@
116
  "abbreviation": "LM",
117
  "Method": "Levenberg-Marquardt",
118
  "parallelism": "serial",
119
- "update_type": "gradient",
120
  "method_goal": "optimization",
121
  "emulator_use": "none",
122
  "aliases": [
@@ -142,7 +142,7 @@
142
  "abbreviation": "LM-KGMM",
143
  "Method": "Score-based Levenberg-Marquardt (KGMM)",
144
  "parallelism": "serial",
145
- "update_type": "gradient",
146
  "method_goal": "optimization",
147
  "emulator_use": "none",
148
  "aliases": [
@@ -155,7 +155,7 @@
155
  "abbreviation": "LM-DSM",
156
  "Method": "Score-based Levenberg-Marquardt (DSM)",
157
  "parallelism": "serial",
158
- "update_type": "gradient",
159
  "method_goal": "optimization",
160
  "emulator_use": "none",
161
  "aliases": [
@@ -168,7 +168,7 @@
168
  "abbreviation": "ADAM-KGMM",
169
  "Method": "Score-based Adam (KGMM)",
170
  "parallelism": "serial",
171
- "update_type": "gradient",
172
  "method_goal": "optimization",
173
  "emulator_use": "none",
174
  "aliases": [
@@ -181,7 +181,7 @@
181
  "abbreviation": "ADAM-DSM",
182
  "Method": "Score-based Adam (DSM)",
183
  "parallelism": "serial",
184
- "update_type": "gradient",
185
  "method_goal": "optimization",
186
  "emulator_use": "none",
187
  "aliases": [
@@ -189,6 +189,52 @@
189
  "score-based adam (gfdt, dsm) (extended window)",
190
  "score-based adam (gfdt, dsm) (parallel branches)"
191
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  }
193
  },
194
  "observed_methods": [
@@ -200,6 +246,10 @@
200
  "lm",
201
  "lm-dsm",
202
  "lm-kgmm",
 
 
 
 
203
  "teki",
204
  "uki"
205
  ],
 
4
  "abbreviation": "TEKI",
5
  "Method": "Tikhonov Regularized Ensemble Kalman Inversion",
6
  "parallelism": "parallel-interacting",
7
+ "update_type": "gradient (Kalman)",
8
  "method_goal": "optimization",
9
  "emulator_use": "none",
10
  "aliases": [
 
15
  "abbreviation": "ETKI",
16
  "Method": "Ensemble Transform Kalman Inversion",
17
  "parallelism": "parallel-interacting",
18
+ "update_type": "gradient (Kalman)",
19
  "method_goal": "optimization",
20
  "emulator_use": "none",
21
  "aliases": [
 
26
  "abbreviation": "IEKF",
27
  "Method": "Iterative Ensemble Kalman Filter",
28
  "parallelism": "parallel-interacting",
29
+ "update_type": "gradient (Kalman)",
30
  "method_goal": "uq",
31
  "emulator_use": "none",
32
  "aliases": [
 
39
  "abbreviation": "UKI",
40
  "Method": "Unscented Kalman Inversion",
41
  "parallelism": "parallel-interacting",
42
+ "update_type": "gradient (Kalman)",
43
  "method_goal": "optimization",
44
  "emulator_use": "none",
45
  "aliases": [
 
72
  "abbreviation": "CES-EKI-DMC",
73
  "Method": "Calibrate Emulate Sample (EKI-DataMisfitController)",
74
  "parallelism": "parallel-interacting",
75
+ "update_type": "gradient (Kalman)",
76
  "method_goal": "uq",
77
  "emulator_use": "after-optimize",
78
  "aliases": [
 
83
  "abbreviation": "CES-EKI-CONST",
84
  "Method": "Calibrate Emulate Sample (EKI-Constant Scheduler)",
85
  "parallelism": "parallel-interacting",
86
+ "update_type": "gradient (Kalman)",
87
  "method_goal": "uq",
88
  "emulator_use": "after-optimize",
89
  "aliases": [
 
94
  "abbreviation": "CES-IEKF-CONST",
95
  "Method": "Calibrate Emulate Sample (IEKF-Constant Scheduler)",
96
  "parallelism": "parallel-interacting",
97
+ "update_type": "gradient (Kalman)",
98
  "method_goal": "uq",
99
  "emulator_use": "after-optimize",
100
  "aliases": [
 
105
  "abbreviation": "ADAM",
106
  "Method": "Adaptive Moment Estimation",
107
  "parallelism": "serial",
108
+ "update_type": "gradient (exact)",
109
  "method_goal": "optimization",
110
  "emulator_use": "none",
111
  "aliases": [
 
116
  "abbreviation": "LM",
117
  "Method": "Levenberg-Marquardt",
118
  "parallelism": "serial",
119
+ "update_type": "gradient (exact)",
120
  "method_goal": "optimization",
121
  "emulator_use": "none",
122
  "aliases": [
 
142
  "abbreviation": "LM-KGMM",
143
  "Method": "Score-based Levenberg-Marquardt (KGMM)",
144
  "parallelism": "serial",
145
+ "update_type": "gradient (score)",
146
  "method_goal": "optimization",
147
  "emulator_use": "none",
148
  "aliases": [
 
155
  "abbreviation": "LM-DSM",
156
  "Method": "Score-based Levenberg-Marquardt (DSM)",
157
  "parallelism": "serial",
158
+ "update_type": "gradient (score)",
159
  "method_goal": "optimization",
160
  "emulator_use": "none",
161
  "aliases": [
 
168
  "abbreviation": "ADAM-KGMM",
169
  "Method": "Score-based Adam (KGMM)",
170
  "parallelism": "serial",
171
+ "update_type": "gradient (score)",
172
  "method_goal": "optimization",
173
  "emulator_use": "none",
174
  "aliases": [
 
181
  "abbreviation": "ADAM-DSM",
182
  "Method": "Score-based Adam (DSM)",
183
  "parallelism": "serial",
184
+ "update_type": "gradient (score)",
185
  "method_goal": "optimization",
186
  "emulator_use": "none",
187
  "aliases": [
 
189
  "score-based adam (gfdt, dsm) (extended window)",
190
  "score-based adam (gfdt, dsm) (parallel branches)"
191
  ]
192
+ },
193
+ "rl-ddpg-short": {
194
+ "abbreviation": "RL-DDPG-SHORT",
195
+ "Method": "Reinforcement Learning (DDPG, short RL step)",
196
+ "parallelism": "serial",
197
+ "update_type": "gradient (score)",
198
+ "method_goal": "optimization",
199
+ "emulator_use": "none",
200
+ "aliases": [
201
+ "rl-ddpg-short"
202
+ ]
203
+ },
204
+ "rl-ddpg": {
205
+ "abbreviation": "RL-DDPG",
206
+ "Method": "Reinforcement Learning (DDPG)",
207
+ "parallelism": "serial",
208
+ "update_type": "gradient (score)",
209
+ "method_goal": "optimization",
210
+ "emulator_use": "none",
211
+ "aliases": [
212
+ "rl-ddpg",
213
+ "rl-ddpg-long"
214
+ ]
215
+ },
216
+ "rl-td3-short": {
217
+ "abbreviation": "RL-TD3-SHORT",
218
+ "Method": "Reinforcement Learning (TD3, short RL step)",
219
+ "parallelism": "serial",
220
+ "update_type": "gradient (score)",
221
+ "method_goal": "optimization",
222
+ "emulator_use": "none",
223
+ "aliases": [
224
+ "rl-td3-short"
225
+ ]
226
+ },
227
+ "rl-td3": {
228
+ "abbreviation": "RL-TD3",
229
+ "Method": "Reinforcement Learning (TD3)",
230
+ "parallelism": "serial",
231
+ "update_type": "gradient (score)",
232
+ "method_goal": "optimization",
233
+ "emulator_use": "none",
234
+ "aliases": [
235
+ "rl-td3",
236
+ "rl-td3-long"
237
+ ]
238
  }
239
  },
240
  "observed_methods": [
 
246
  "lm",
247
  "lm-dsm",
248
  "lm-kgmm",
249
+ "rl-ddpg",
250
+ "rl-ddpg-short",
251
+ "rl-td3",
252
+ "rl-td3-short",
253
  "teki",
254
  "uki"
255
  ],
.claude/skills/leaderboard-manager/references/add-method.md CHANGED
@@ -45,7 +45,7 @@ at line 7). Follow the existing pattern exactly:
45
  "abbreviation": "NEWABBR", # Short uppercase display name
46
  "Method": "Full Method Name", # Human-readable name for tables
47
  "parallelism": "parallel-interacting", # serial | parallel-independent | parallel-interacting
48
- "update_type": "kalman", # gradient | kalman | general
49
  "method_goal": "optimization", # optimization | uq
50
  "emulator_use": "none", # none | within-optimize | after-optimize
51
  "aliases": ["new-key", "alias2"], # All raw strings that may appear in NetCDF algorithm_type
 
45
  "abbreviation": "NEWABBR", # Short uppercase display name
46
  "Method": "Full Method Name", # Human-readable name for tables
47
  "parallelism": "parallel-interacting", # serial | parallel-independent | parallel-interacting
48
+ "update_type": "gradient (Kalman)", # gradient (exact) | gradient (Kalman) | gradient (score) | general
49
  "method_goal": "optimization", # optimization | uq
50
  "emulator_use": "none", # none | within-optimize | after-optimize
51
  "aliases": ["new-key", "alias2"], # All raw strings that may appear in NetCDF algorithm_type
.claude/skills/leaderboard-manager/references/populate-metadata.md CHANGED
@@ -114,7 +114,7 @@ short label that appears in table columns), edit `KNOWN_METHODS` in
114
  "abbreviation": "UKI", # Short label in table columns
115
  "Method": "Unscented Kalman Inversion", # Full name in Method column
116
  "parallelism": "parallel-interacting", # serial | parallel-independent | parallel-interacting
117
- "update_type": "kalman", # gradient | kalman | general
118
  "method_goal": "optimization", # optimization | uq
119
  "emulator_use": "none", # none | within-optimize | after-optimize
120
  "aliases": ["uki"], # Raw algorithm_type strings from NetCDF
 
114
  "abbreviation": "UKI", # Short label in table columns
115
  "Method": "Unscented Kalman Inversion", # Full name in Method column
116
  "parallelism": "parallel-interacting", # serial | parallel-independent | parallel-interacting
117
+ "update_type": "gradient (Kalman)", # gradient (exact) | gradient (Kalman) | gradient (score) | general
118
  "method_goal": "optimization", # optimization | uq
119
  "emulator_use": "none", # none | within-optimize | after-optimize
120
  "aliases": ["uki"], # Raw algorithm_type strings from NetCDF
CLAUDE.md CHANGED
@@ -78,6 +78,8 @@ calibration_benchmark/
78
  │ ├── opt_dsm_score_lm/ Score-based LM (DSM score estimator) results (per-benchmark files)
79
  │ ├── opt_kgmm_score_adam/ Score-based ADAM (KGMM score estimator) results (per-benchmark files)
80
  │ ├── opt_dsm_score_adam/ Score-based ADAM (DSM score estimator) results (per-benchmark files)
 
 
81
  │ ├── ces-eki-dmc_results/ CES-EKI-DMC ensemble-results files (UQ source)
82
  │ ├── gboed-uq_results/ GBOED ensemble-results files (UQ source)
83
  │ ├── gnki-uq_results/ IEKF (a.k.a GNKI) ensemble-results files (UQ source)
@@ -161,13 +163,18 @@ Four independent tags on every `KNOWN_METHODS` entry, each scoped to its own axi
161
 
162
  - **`parallelism`** — how the search explores parameter space:
163
  `serial` (single point estimate advanced step by step, e.g. `ADAM`/`LM`) ·
 
 
164
  `parallel-independent` (population updated with no coupling between members,
165
  e.g. `ABC`'s accepted samples / `HM`'s per-wave resampling) ·
166
  `parallel-interacting` (ensemble members coupled through a shared update each
167
  iteration — any Kalman-based method).
168
  - **`update_type`** — the mechanism driving each update step:
169
- `gradient` (follows the loss gradient or a Gauss-Newton approximation) ·
170
- `kalman` (a possibly linearized/unscented Kalman-style ensemble update) ·
 
 
 
171
  `general` (anything else, e.g. `ABC`'s rejection sampling, `HM`'s implausibility cuts).
172
  - **`method_goal`** — what the method is built to report:
173
  `optimization` (single best-fit parameter estimate) or `uq` (full posterior /
@@ -179,7 +186,7 @@ Four independent tags on every `KNOWN_METHODS` entry, each scoped to its own axi
179
  `after-optimize` (fits a surrogate once, after calibration finishes, e.g. `CES-EKI-DMC`).
180
 
181
  `update_type` deliberately avoids a "Bayesian" label — Kalman methods are approximate
182
- Bayesian updates too, so the split is about mechanism (gradient vs. Kalman vs. general),
183
  not philosophy. `_UPDATE_TYPE_ORDER` in `src/common/leaderboard.py` controls
184
  table/suitability sort order and must stay in sync with `update_type` values.
185
  A quick taxonomy tree is rendered on the home page (`src/streamlit_app.py`).
@@ -188,20 +195,24 @@ A quick taxonomy tree is rendered on the home page (`src/streamlit_app.py`).
188
 
189
  | Canonical | Abbreviation | parallelism | update_type | method_goal | emulator_use | Aliases |
190
  |---|---|---|---|---|---|---|
191
- | `teki` | TEKI | parallel-interacting | kalman | optimization | none | — |
192
- | `etki` | ETKI | parallel-interacting | kalman | optimization | none | — |
193
- | `iekf` | IEKF | parallel-interacting | kalman | uq | none | gnsl, gnki |
194
- | `uki` | UKI | parallel-interacting | kalman | optimization | none | — |
195
  | `abc` | ABC | parallel-independent | general | uq | none | — |
196
  | `hm` | HM | parallel-independent | general | uq | within-optimize | — |
197
- | `ces-eki-dmc` | CES-EKI-DMC | parallel-interacting | kalman | uq | after-optimize | — |
198
- | `adam` | ADAM | serial | gradient | optimization | none | — |
199
- | `lm` | LM | serial | gradient | optimization | none | gradient_descent |
200
  | `gboed` | GBOED | parallel-interacting | general | uq | within-optimize | boed |
201
- | `lm-kgmm` | LM-KGMM | serial | gradient | optimization | none | — |
202
- | `lm-dsm` | LM-DSM | serial | gradient | optimization | none | — |
203
- | `adam-kgmm` | ADAM-KGMM | serial | gradient | optimization | none | — |
204
- | `adam-dsm` | ADAM-DSM | serial | gradient | optimization | none | — |
 
 
 
 
205
 
206
  ## How to extend
207
 
 
78
  │ ├── opt_dsm_score_lm/ Score-based LM (DSM score estimator) results (per-benchmark files)
79
  │ ├── opt_kgmm_score_adam/ Score-based ADAM (KGMM score estimator) results (per-benchmark files)
80
  │ ├── opt_dsm_score_adam/ Score-based ADAM (DSM score estimator) results (per-benchmark files)
81
+ │ ├── rl-td3/ RL-TD3-SHORT / RL-TD3 (TD3 variant of the RL experiments; RL-TD3 files are `rl-td3-long` in the NetCDF) results (per-benchmark files)
82
+ │ ├── rl-ddpg/ RL-DDPG-SHORT / RL-DDPG (DDPG actor-critic; short RL step / standard-window RL step; RL-DDPG files are `rl-ddpg-long` in the NetCDF) results (per-benchmark files)
83
  │ ├── ces-eki-dmc_results/ CES-EKI-DMC ensemble-results files (UQ source)
84
  │ ├── gboed-uq_results/ GBOED ensemble-results files (UQ source)
85
  │ ├── gnki-uq_results/ IEKF (a.k.a GNKI) ensemble-results files (UQ source)
 
163
 
164
  - **`parallelism`** — how the search explores parameter space:
165
  `serial` (single point estimate advanced step by step, e.g. `ADAM`/`LM`) ·
166
+ `serial (short window)` (serial, but each step advances a sliding statistics window by a
167
+ fraction of its length rather than a full window, e.g. `RL-DDPG-SHORT`/`RL-TD3-SHORT`) ·
168
  `parallel-independent` (population updated with no coupling between members,
169
  e.g. `ABC`'s accepted samples / `HM`'s per-wave resampling) ·
170
  `parallel-interacting` (ensemble members coupled through a shared update each
171
  iteration — any Kalman-based method).
172
  - **`update_type`** — the mechanism driving each update step:
173
+ `gradient (exact)` (loss gradient / Gauss-Newton step differentiated through the forward
174
+ model, e.g. `ADAM`/`LM`) ·
175
+ `gradient (Kalman)` (a possibly linearized/unscented Kalman-style ensemble update) ·
176
+ `gradient (score)` (gradient built from an estimated score or learned critic rather than a
177
+ simulator Jacobian, e.g. `*-KGMM`, `*-DSM`, `RL-*`) ·
178
  `general` (anything else, e.g. `ABC`'s rejection sampling, `HM`'s implausibility cuts).
179
  - **`method_goal`** — what the method is built to report:
180
  `optimization` (single best-fit parameter estimate) or `uq` (full posterior /
 
186
  `after-optimize` (fits a surrogate once, after calibration finishes, e.g. `CES-EKI-DMC`).
187
 
188
  `update_type` deliberately avoids a "Bayesian" label — Kalman methods are approximate
189
+ Bayesian updates too, so the split is about mechanism (exact vs. Kalman vs. score gradient vs. general),
190
  not philosophy. `_UPDATE_TYPE_ORDER` in `src/common/leaderboard.py` controls
191
  table/suitability sort order and must stay in sync with `update_type` values.
192
  A quick taxonomy tree is rendered on the home page (`src/streamlit_app.py`).
 
195
 
196
  | Canonical | Abbreviation | parallelism | update_type | method_goal | emulator_use | Aliases |
197
  |---|---|---|---|---|---|---|
198
+ | `teki` | TEKI | parallel-interacting | gradient (Kalman) | optimization | none | — |
199
+ | `etki` | ETKI | parallel-interacting | gradient (Kalman) | optimization | none | — |
200
+ | `iekf` | IEKF | parallel-interacting | gradient (Kalman) | uq | none | gnsl, gnki |
201
+ | `uki` | UKI | parallel-interacting | gradient (Kalman) | optimization | none | — |
202
  | `abc` | ABC | parallel-independent | general | uq | none | — |
203
  | `hm` | HM | parallel-independent | general | uq | within-optimize | — |
204
+ | `ces-eki-dmc` | CES-EKI-DMC | parallel-interacting | gradient (Kalman) | uq | after-optimize | — |
205
+ | `adam` | ADAM | serial | gradient (exact) | optimization | none | — |
206
+ | `lm` | LM | serial | gradient (exact) | optimization | none | gradient_descent |
207
  | `gboed` | GBOED | parallel-interacting | general | uq | within-optimize | boed |
208
+ | `lm-kgmm` | LM-KGMM | serial | gradient (score) | optimization | none | — |
209
+ | `lm-dsm` | LM-DSM | serial | gradient (score) | optimization | none | — |
210
+ | `adam-kgmm` | ADAM-KGMM | serial | gradient (score) | optimization | none | — |
211
+ | `adam-dsm` | ADAM-DSM | serial | gradient (score) | optimization | none | — |
212
+ | `rl-ddpg-short` | RL-DDPG-SHORT | serial (short window) | gradient (score) | optimization | none | — |
213
+ | `rl-ddpg` | RL-DDPG | serial | gradient (score) | optimization | none | rl-ddpg-long |
214
+ | `rl-td3-short` | RL-TD3-SHORT | serial (short window) | gradient (score) | optimization | none | — |
215
+ | `rl-td3` | RL-TD3 | serial | gradient (score) | optimization | none | rl-td3-long |
216
 
217
  ## How to extend
218
 
data/rl-ddpg/leaderboard_rl-ddpg-long_l63_2026-10-04.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:75420eb82ea3b59b30f803fc289a85af15869a782bc1d74ff8ed899d6997abce
3
+ size 9200
data/rl-ddpg/leaderboard_rl-ddpg-long_l96_const-force_2026-10-04.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:85660a09db96d4486bdfec01c8310b2fa8e888903e930437334f678c2a24f693
3
+ size 9200
data/rl-ddpg/leaderboard_rl-ddpg-long_l96_flux-force_2026-10-04.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d541f2a8bb5ed357bd6a47183546f95f243202afc6a47ac64a4c78d5b1ae1b0b
3
+ size 9200
data/rl-ddpg/leaderboard_rl-ddpg-long_l96_vec-force_2026-10-04.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d541f2a8bb5ed357bd6a47183546f95f243202afc6a47ac64a4c78d5b1ae1b0b
3
+ size 9200
data/rl-ddpg/leaderboard_rl-ddpg-short_l63_2026-10-04.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:68189692e5ecab696cd87030be1fcd94a4c018f6035c2e361924dfa1b1904859
3
+ size 9200
data/rl-ddpg/leaderboard_rl-ddpg-short_l96_const-force_2026-10-04.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7f461cf02963eccb7fefb4d41c0451dd823232b93ae1b5cfa971df9e2ffbd8d9
3
+ size 9200
data/rl-ddpg/leaderboard_rl-ddpg-short_l96_flux-force_2026-10-04.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7cec229bfc4948a5c2ef382fba6b733221f8ef5ea3143e5b72be9c93f833e512
3
+ size 9200
data/rl-ddpg/leaderboard_rl-ddpg-short_l96_vec-force_2026-10-04.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ce5cff4a54bcff60d862a0f8f29d956cc9d6eba230f3469992a71fd6e44a7efa
3
+ size 9200
data/rl-td3/leaderboard_rl-td3-long_l63_2026-10-04.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f546837f79d3f020fa8e8c6f1f97f4aa9543dda0631d8a5b462687ff49793697
3
+ size 9200
data/rl-td3/leaderboard_rl-td3-long_l96_const-force_2026-10-04.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:27956dd2b55fe624f191cd1759701d247f6efc09cd833daa5adc1851c6c0c42d
3
+ size 9200
data/rl-td3/leaderboard_rl-td3-long_l96_flux-force_2026-10-04.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0562fc2dc63bc374389772c9ebba5fa7118edb7217e60a04e02a246b671337b0
3
+ size 9200
data/rl-td3/leaderboard_rl-td3-long_l96_vec-force_2026-10-04.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8c51d0d65cdb3abf2f3f095d790a028132039eb2c7c161c5544fc962ba615196
3
+ size 9200
data/rl-td3/leaderboard_rl-td3-short_l63_2026-10-04.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0c33c3864a00e00e57baa9f8d08521c730da84695c0c1139d6ba60e3e2b48d0e
3
+ size 9200
data/rl-td3/leaderboard_rl-td3-short_l96_const-force_2026-10-04.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:abd015cc5749e5a2db779756192a4eb1521c01170a69fb551b52b65dedfb71a2
3
+ size 9200
data/rl-td3/leaderboard_rl-td3-short_l96_flux-force_2026-10-04.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:599281064f3f993989a9f93c1eb24bb6a1e30dba7c5b900da86ac90ba0d99a1b
3
+ size 9200
data/rl-td3/leaderboard_rl-td3-short_l96_vec-force_2026-10-04.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:80282ab8703b8addb2114fde70b5d30934b0e6f5b04260cf092d69eef515f8b9
3
+ size 9200
src/common/leaderboard.py CHANGED
@@ -53,9 +53,10 @@ def _method_color(present_abbrevs: list[str]) -> alt.Color:
53
  # Unknown/unmapped update types fall back to 99 and appear at the end.
54
  # See method_registry.py for the full four-axis taxonomy.
55
  _UPDATE_TYPE_ORDER: dict[str, int] = {
56
- "kalman": 0,
57
- "gradient": 1,
58
- "general": 2,
 
59
  }
60
 
61
  # Failure-rate threshold (%) below which a method's runs at a given target are
@@ -119,7 +120,11 @@ def _render_suitability_table(
119
  continue
120
  qualifying = sub[sub["failure_rate"] < failure_threshold].dropna(subset=["metric"])
121
  if qualifying.empty:
122
- cell_text[method][bm] = "failed"
 
 
 
 
123
  cell_color[method][bm] = _UNSUITABLE
124
  continue
125
  method_best = float(qualifying["metric"].min())
@@ -157,7 +162,9 @@ def _render_suitability_table(
157
  f"Evaluated at target = {suitability_target}. "
158
  f"**Green**: at some ensemble size, failure rate < {failure_threshold:.0f}% "
159
  f"and mean budget ≤ {ratio_threshold:.0f}× the best method (ratio shown). "
160
- "**Red** (failed): data present but all runs failed to reach the target. "
 
 
161
  "**Gray (—)**: no data for this benchmark."
162
  )
163
  st.dataframe(
 
53
  # Unknown/unmapped update types fall back to 99 and appear at the end.
54
  # See method_registry.py for the full four-axis taxonomy.
55
  _UPDATE_TYPE_ORDER: dict[str, int] = {
56
+ "gradient (Kalman)": 0,
57
+ "gradient (exact)": 1,
58
+ "gradient (score)": 2,
59
+ "general": 3,
60
  }
61
 
62
  # Failure-rate threshold (%) below which a method's runs at a given target are
 
120
  continue
121
  qualifying = sub[sub["failure_rate"] < failure_threshold].dropna(subset=["metric"])
122
  if qualifying.empty:
123
+ # Report the best (lowest) failure rate over ensemble sizes
124
+ best_fail = sub["failure_rate"].min()
125
+ cell_text[method][bm] = (
126
+ f"failed ({best_fail:.0f}%)" if pd.notna(best_fail) else "failed"
127
+ )
128
  cell_color[method][bm] = _UNSUITABLE
129
  continue
130
  method_best = float(qualifying["metric"].min())
 
162
  f"Evaluated at target = {suitability_target}. "
163
  f"**Green**: at some ensemble size, failure rate < {failure_threshold:.0f}% "
164
  f"and mean budget ≤ {ratio_threshold:.0f}× the best method (ratio shown). "
165
+ "**Red** (failed): data present but no ensemble size met the criteria; "
166
+ "the percentage is the lowest failure rate (share of runs that did not reach the target) "
167
+ "across ensemble sizes. "
168
  "**Gray (—)**: no data for this benchmark."
169
  )
170
  st.dataframe(
src/common/method_registry.py CHANGED
@@ -10,6 +10,8 @@ from pathlib import Path
10
  #
11
  # ``parallelism`` — how the search explores parameter space:
12
  # "serial" — a single point estimate advanced step by step (ADAM, LM).
 
 
13
  # "parallel-independent" — a population of candidates updated with no coupling
14
  # between members (ABC's accepted samples, HM's per-wave
15
  # resampling from the non-implausible region).
@@ -17,9 +19,13 @@ from pathlib import Path
17
  # update each iteration (any Kalman-based method).
18
  #
19
  # ``update_type`` — the mechanism driving each update step:
20
- # "gradient" — follows the loss gradient or a Gauss-Newton approximation of it.
21
- # "kalman" — a (possibly linearized/unscented) Kalman-style ensemble update.
22
- # "general" — anything else (e.g. ABC's rejection sampling, HM's implausibility cuts).
 
 
 
 
23
  #
24
  # ``method_goal`` — what the method is built to report:
25
  # "optimization" — a single best-fit parameter estimate.
@@ -39,7 +45,7 @@ KNOWN_METHODS = {
39
  "abbreviation": "TEKI",
40
  "Method": "Tikhonov Regularized Ensemble Kalman Inversion",
41
  "parallelism": "parallel-interacting",
42
- "update_type": "kalman",
43
  "method_goal": "optimization",
44
  "emulator_use": "none",
45
  "aliases": ["teki"],
@@ -48,7 +54,7 @@ KNOWN_METHODS = {
48
  "abbreviation": "ETKI",
49
  "Method": "Ensemble Transform Kalman Inversion",
50
  "parallelism": "parallel-interacting",
51
- "update_type": "kalman",
52
  "method_goal": "optimization",
53
  "emulator_use": "none",
54
  "aliases": ["etki"],
@@ -57,7 +63,7 @@ KNOWN_METHODS = {
57
  "abbreviation": "IEKF",
58
  "Method": "Iterative Ensemble Kalman Filter",
59
  "parallelism": "parallel-interacting",
60
- "update_type": "kalman",
61
  "method_goal": "uq",
62
  "emulator_use": "none",
63
  "aliases": ["iekf", "gnsl", "gnki"],
@@ -66,7 +72,7 @@ KNOWN_METHODS = {
66
  "abbreviation": "UKI",
67
  "Method": "Unscented Kalman Inversion",
68
  "parallelism": "parallel-interacting",
69
- "update_type": "kalman",
70
  "method_goal": "optimization",
71
  "emulator_use": "none",
72
  "aliases": ["uki"],
@@ -93,7 +99,7 @@ KNOWN_METHODS = {
93
  "abbreviation": "CES-EKI-DMC",
94
  "Method": "Calibrate Emulate Sample (EKI-DataMisfitController)",
95
  "parallelism": "parallel-interacting",
96
- "update_type": "kalman",
97
  "method_goal": "uq",
98
  "emulator_use": "after-optimize",
99
  "aliases": ["ces-eki-dmc"]
@@ -102,7 +108,7 @@ KNOWN_METHODS = {
102
  "abbreviation": "CES-EKI-CONST",
103
  "Method": "Calibrate Emulate Sample (EKI-Constant Scheduler)",
104
  "parallelism": "parallel-interacting",
105
- "update_type": "kalman",
106
  "method_goal": "uq",
107
  "emulator_use": "after-optimize",
108
  "aliases": ["ces-eki-const"]
@@ -111,7 +117,7 @@ KNOWN_METHODS = {
111
  "abbreviation": "CES-IEKF-CONST",
112
  "Method": "Calibrate Emulate Sample (IEKF-Constant Scheduler)",
113
  "parallelism": "parallel-interacting",
114
- "update_type": "kalman",
115
  "method_goal": "uq",
116
  "emulator_use": "after-optimize",
117
  "aliases": ["ces-iekf-const"]
@@ -120,7 +126,7 @@ KNOWN_METHODS = {
120
  "abbreviation": "ADAM",
121
  "Method": "Adaptive Moment Estimation",
122
  "parallelism": "serial",
123
- "update_type": "gradient",
124
  "method_goal": "optimization",
125
  "emulator_use": "none",
126
  "aliases": ["adam"],
@@ -129,7 +135,7 @@ KNOWN_METHODS = {
129
  "abbreviation": "LM",
130
  "Method": "Levenberg-Marquardt",
131
  "parallelism": "serial",
132
- "update_type": "gradient",
133
  "method_goal": "optimization",
134
  "emulator_use": "none",
135
  "aliases": ["lm", "levenberg_marquardt", "levenberg-marquardt", "gradient_descent"],
@@ -147,7 +153,7 @@ KNOWN_METHODS = {
147
  "abbreviation": "LM-KGMM",
148
  "Method": "Score-based Levenberg-Marquardt (KGMM)",
149
  "parallelism": "serial",
150
- "update_type": "gradient",
151
  "method_goal": "optimization",
152
  "emulator_use": "none",
153
  "aliases": [
@@ -160,7 +166,7 @@ KNOWN_METHODS = {
160
  "abbreviation": "LM-DSM",
161
  "Method": "Score-based Levenberg-Marquardt (DSM)",
162
  "parallelism": "serial",
163
- "update_type": "gradient",
164
  "method_goal": "optimization",
165
  "emulator_use": "none",
166
  "aliases": [
@@ -173,7 +179,7 @@ KNOWN_METHODS = {
173
  "abbreviation": "ADAM-KGMM",
174
  "Method": "Score-based Adam (KGMM)",
175
  "parallelism": "serial",
176
- "update_type": "gradient",
177
  "method_goal": "optimization",
178
  "emulator_use": "none",
179
  "aliases": [
@@ -186,7 +192,7 @@ KNOWN_METHODS = {
186
  "abbreviation": "ADAM-DSM",
187
  "Method": "Score-based Adam (DSM)",
188
  "parallelism": "serial",
189
- "update_type": "gradient",
190
  "method_goal": "optimization",
191
  "emulator_use": "none",
192
  "aliases": [
@@ -195,6 +201,42 @@ KNOWN_METHODS = {
195
  "score-based adam (gfdt, dsm) (parallel branches)",
196
  ],
197
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  }
199
 
200
 
 
10
  #
11
  # ``parallelism`` — how the search explores parameter space:
12
  # "serial" — a single point estimate advanced step by step (ADAM, LM).
13
+ # "serial (short window)" — serial, but each step advances a sliding statistics window by
14
+ # a fraction of its length (RL-*-SHORT) rather than a full window.
15
  # "parallel-independent" — a population of candidates updated with no coupling
16
  # between members (ABC's accepted samples, HM's per-wave
17
  # resampling from the non-implausible region).
 
19
  # update each iteration (any Kalman-based method).
20
  #
21
  # ``update_type`` — the mechanism driving each update step:
22
+ # "gradient (exact)" — follows the loss gradient or a Gauss-Newton approximation of it,
23
+ # differentiated through the forward model (ADAM, LM).
24
+ # "gradient (Kalman)" — a (possibly linearized/unscented) Kalman-style ensemble update,
25
+ # i.e. an ensemble-estimated gradient/Gauss-Newton step.
26
+ # "gradient (score)" — a gradient built from a learned/estimated score or critic rather
27
+ # than a simulator Jacobian (*-KGMM, *-DSM, RL-*).
28
+ # "general" — anything else (e.g. ABC's rejection sampling, HM's implausibility cuts).
29
  #
30
  # ``method_goal`` — what the method is built to report:
31
  # "optimization" — a single best-fit parameter estimate.
 
45
  "abbreviation": "TEKI",
46
  "Method": "Tikhonov Regularized Ensemble Kalman Inversion",
47
  "parallelism": "parallel-interacting",
48
+ "update_type": "gradient (Kalman)",
49
  "method_goal": "optimization",
50
  "emulator_use": "none",
51
  "aliases": ["teki"],
 
54
  "abbreviation": "ETKI",
55
  "Method": "Ensemble Transform Kalman Inversion",
56
  "parallelism": "parallel-interacting",
57
+ "update_type": "gradient (Kalman)",
58
  "method_goal": "optimization",
59
  "emulator_use": "none",
60
  "aliases": ["etki"],
 
63
  "abbreviation": "IEKF",
64
  "Method": "Iterative Ensemble Kalman Filter",
65
  "parallelism": "parallel-interacting",
66
+ "update_type": "gradient (Kalman)",
67
  "method_goal": "uq",
68
  "emulator_use": "none",
69
  "aliases": ["iekf", "gnsl", "gnki"],
 
72
  "abbreviation": "UKI",
73
  "Method": "Unscented Kalman Inversion",
74
  "parallelism": "parallel-interacting",
75
+ "update_type": "gradient (Kalman)",
76
  "method_goal": "optimization",
77
  "emulator_use": "none",
78
  "aliases": ["uki"],
 
99
  "abbreviation": "CES-EKI-DMC",
100
  "Method": "Calibrate Emulate Sample (EKI-DataMisfitController)",
101
  "parallelism": "parallel-interacting",
102
+ "update_type": "gradient (Kalman)",
103
  "method_goal": "uq",
104
  "emulator_use": "after-optimize",
105
  "aliases": ["ces-eki-dmc"]
 
108
  "abbreviation": "CES-EKI-CONST",
109
  "Method": "Calibrate Emulate Sample (EKI-Constant Scheduler)",
110
  "parallelism": "parallel-interacting",
111
+ "update_type": "gradient (Kalman)",
112
  "method_goal": "uq",
113
  "emulator_use": "after-optimize",
114
  "aliases": ["ces-eki-const"]
 
117
  "abbreviation": "CES-IEKF-CONST",
118
  "Method": "Calibrate Emulate Sample (IEKF-Constant Scheduler)",
119
  "parallelism": "parallel-interacting",
120
+ "update_type": "gradient (Kalman)",
121
  "method_goal": "uq",
122
  "emulator_use": "after-optimize",
123
  "aliases": ["ces-iekf-const"]
 
126
  "abbreviation": "ADAM",
127
  "Method": "Adaptive Moment Estimation",
128
  "parallelism": "serial",
129
+ "update_type": "gradient (exact)",
130
  "method_goal": "optimization",
131
  "emulator_use": "none",
132
  "aliases": ["adam"],
 
135
  "abbreviation": "LM",
136
  "Method": "Levenberg-Marquardt",
137
  "parallelism": "serial",
138
+ "update_type": "gradient (exact)",
139
  "method_goal": "optimization",
140
  "emulator_use": "none",
141
  "aliases": ["lm", "levenberg_marquardt", "levenberg-marquardt", "gradient_descent"],
 
153
  "abbreviation": "LM-KGMM",
154
  "Method": "Score-based Levenberg-Marquardt (KGMM)",
155
  "parallelism": "serial",
156
+ "update_type": "gradient (score)",
157
  "method_goal": "optimization",
158
  "emulator_use": "none",
159
  "aliases": [
 
166
  "abbreviation": "LM-DSM",
167
  "Method": "Score-based Levenberg-Marquardt (DSM)",
168
  "parallelism": "serial",
169
+ "update_type": "gradient (score)",
170
  "method_goal": "optimization",
171
  "emulator_use": "none",
172
  "aliases": [
 
179
  "abbreviation": "ADAM-KGMM",
180
  "Method": "Score-based Adam (KGMM)",
181
  "parallelism": "serial",
182
+ "update_type": "gradient (score)",
183
  "method_goal": "optimization",
184
  "emulator_use": "none",
185
  "aliases": [
 
192
  "abbreviation": "ADAM-DSM",
193
  "Method": "Score-based Adam (DSM)",
194
  "parallelism": "serial",
195
+ "update_type": "gradient (score)",
196
  "method_goal": "optimization",
197
  "emulator_use": "none",
198
  "aliases": [
 
201
  "score-based adam (gfdt, dsm) (parallel branches)",
202
  ],
203
  },
204
+ "rl-ddpg-short": {
205
+ "abbreviation": "RL-DDPG-SHORT",
206
+ "Method": "Reinforcement Learning (DDPG, short RL step)",
207
+ "parallelism": "serial (short window)",
208
+ "update_type": "gradient (score)",
209
+ "method_goal": "optimization",
210
+ "emulator_use": "none",
211
+ "aliases": ["rl-ddpg-short"],
212
+ },
213
+ "rl-ddpg": {
214
+ "abbreviation": "RL-DDPG",
215
+ "Method": "Reinforcement Learning (DDPG)",
216
+ "parallelism": "serial",
217
+ "update_type": "gradient (score)",
218
+ "method_goal": "optimization",
219
+ "emulator_use": "none",
220
+ "aliases": ["rl-ddpg", "rl-ddpg-long"],
221
+ },
222
+ "rl-td3-short": {
223
+ "abbreviation": "RL-TD3-SHORT",
224
+ "Method": "Reinforcement Learning (TD3, short RL step)",
225
+ "parallelism": "serial (short window)",
226
+ "update_type": "gradient (score)",
227
+ "method_goal": "optimization",
228
+ "emulator_use": "none",
229
+ "aliases": ["rl-td3-short"],
230
+ },
231
+ "rl-td3": {
232
+ "abbreviation": "RL-TD3",
233
+ "Method": "Reinforcement Learning (TD3)",
234
+ "parallelism": "serial",
235
+ "update_type": "gradient (score)",
236
+ "method_goal": "optimization",
237
+ "emulator_use": "none",
238
+ "aliases": ["rl-td3", "rl-td3-long"],
239
+ },
240
  }
241
 
242
 
src/data_store.py CHANGED
@@ -39,6 +39,10 @@ DATASET_FILES = {
39
  "opt_dsm_score_lm/leaderboard_sblm_dsm_parallel_l63_2026-08-12.nc",
40
  "opt_kgmm_score_adam/leaderboard_sbadam_kgmm_parallel_l63_2026-09-11.nc",
41
  "opt_dsm_score_adam/leaderboard_sbadam_dsm_parallel_l63_2026-09-14.nc",
 
 
 
 
42
  ],
43
  "L96": [
44
  "teki_results/leaderboard_teki_l96_const-force_2026-09-10.nc",
@@ -51,6 +55,10 @@ DATASET_FILES = {
51
  "opt_dsm_score_lm/leaderboard_sblm_dsm_parallel_l96_const-force_2026-08-12.nc",
52
  "opt_kgmm_score_adam/leaderboard_sbadam_kgmm_parallel_l96_const-force_2026-09-11.nc",
53
  "opt_dsm_score_adam/leaderboard_sbadam_dsm_parallel_l96_const-force_2026-09-14.nc",
 
 
 
 
54
  ],
55
  "L96_NN_FORCING": [
56
  "teki_results/leaderboard_teki_l96_flux-force_2026-09-10.nc",
@@ -61,6 +69,10 @@ DATASET_FILES = {
61
  "opt_dsm_score_lm/leaderboard_sblm_dsm_parallel_l96_flux-force_2026-08-12.nc",
62
  "opt_kgmm_score_adam/leaderboard_sbadam_kgmm_parallel_l96_flux-force_2026-09-11.nc",
63
  "opt_dsm_score_adam/leaderboard_sbadam_dsm_parallel_l96_flux-force_2026-09-14.nc",
 
 
 
 
64
  ],
65
  "L96_SPATIAL_FORCING": [
66
  "teki_results/leaderboard_teki_l96_vec-force_2026-09-10.nc",
@@ -73,6 +85,10 @@ DATASET_FILES = {
73
  "opt_dsm_score_lm/leaderboard_sblm_dsm_parallel_l96_vec-force_2026-08-12.nc",
74
  "opt_kgmm_score_adam/leaderboard_sbadam_kgmm_parallel_l96_vec-force_2026-09-11.nc",
75
  "opt_dsm_score_adam/leaderboard_sbadam_dsm_parallel_l96_vec-force_2026-09-14.nc",
 
 
 
 
76
  ],
77
  }
78
 
 
39
  "opt_dsm_score_lm/leaderboard_sblm_dsm_parallel_l63_2026-08-12.nc",
40
  "opt_kgmm_score_adam/leaderboard_sbadam_kgmm_parallel_l63_2026-09-11.nc",
41
  "opt_dsm_score_adam/leaderboard_sbadam_dsm_parallel_l63_2026-09-14.nc",
42
+ "rl-ddpg/leaderboard_rl-ddpg-short_l63_2026-10-04.nc",
43
+ "rl-ddpg/leaderboard_rl-ddpg-long_l63_2026-10-04.nc",
44
+ "rl-td3/leaderboard_rl-td3-short_l63_2026-10-04.nc",
45
+ "rl-td3/leaderboard_rl-td3-long_l63_2026-10-04.nc",
46
  ],
47
  "L96": [
48
  "teki_results/leaderboard_teki_l96_const-force_2026-09-10.nc",
 
55
  "opt_dsm_score_lm/leaderboard_sblm_dsm_parallel_l96_const-force_2026-08-12.nc",
56
  "opt_kgmm_score_adam/leaderboard_sbadam_kgmm_parallel_l96_const-force_2026-09-11.nc",
57
  "opt_dsm_score_adam/leaderboard_sbadam_dsm_parallel_l96_const-force_2026-09-14.nc",
58
+ "rl-ddpg/leaderboard_rl-ddpg-short_l96_const-force_2026-10-04.nc",
59
+ "rl-ddpg/leaderboard_rl-ddpg-long_l96_const-force_2026-10-04.nc",
60
+ "rl-td3/leaderboard_rl-td3-short_l96_const-force_2026-10-04.nc",
61
+ "rl-td3/leaderboard_rl-td3-long_l96_const-force_2026-10-04.nc",
62
  ],
63
  "L96_NN_FORCING": [
64
  "teki_results/leaderboard_teki_l96_flux-force_2026-09-10.nc",
 
69
  "opt_dsm_score_lm/leaderboard_sblm_dsm_parallel_l96_flux-force_2026-08-12.nc",
70
  "opt_kgmm_score_adam/leaderboard_sbadam_kgmm_parallel_l96_flux-force_2026-09-11.nc",
71
  "opt_dsm_score_adam/leaderboard_sbadam_dsm_parallel_l96_flux-force_2026-09-14.nc",
72
+ "rl-ddpg/leaderboard_rl-ddpg-short_l96_flux-force_2026-10-04.nc",
73
+ "rl-ddpg/leaderboard_rl-ddpg-long_l96_flux-force_2026-10-04.nc",
74
+ "rl-td3/leaderboard_rl-td3-short_l96_flux-force_2026-10-04.nc",
75
+ "rl-td3/leaderboard_rl-td3-long_l96_flux-force_2026-10-04.nc",
76
  ],
77
  "L96_SPATIAL_FORCING": [
78
  "teki_results/leaderboard_teki_l96_vec-force_2026-09-10.nc",
 
85
  "opt_dsm_score_lm/leaderboard_sblm_dsm_parallel_l96_vec-force_2026-08-12.nc",
86
  "opt_kgmm_score_adam/leaderboard_sbadam_kgmm_parallel_l96_vec-force_2026-09-11.nc",
87
  "opt_dsm_score_adam/leaderboard_sbadam_dsm_parallel_l96_vec-force_2026-09-14.nc",
88
+ "rl-ddpg/leaderboard_rl-ddpg-short_l96_vec-force_2026-10-04.nc",
89
+ "rl-ddpg/leaderboard_rl-ddpg-long_l96_vec-force_2026-10-04.nc",
90
+ "rl-td3/leaderboard_rl-td3-short_l96_vec-force_2026-10-04.nc",
91
+ "rl-td3/leaderboard_rl-td3-long_l96_vec-force_2026-10-04.nc",
92
  ],
93
  }
94
 
src/pages/MethodDetails.py CHANGED
@@ -450,6 +450,178 @@ method_meta = {
450
  ],
451
  "notes": "**Inherits both LM-DSM's slow DSM-training cost and Adam's local-minimum susceptibility** — training the DSM score network is the binding wall-clock cost (as with LM-DSM), and Adam's lack of a trust region or ensemble diversity leaves it as prone to getting stuck in a bad basin as ADAM-KGMM.",
452
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
  }
454
 
455
  # KNOWN_METHODS is the source of truth for the abbreviation/full-name list,
 
450
  ],
451
  "notes": "**Inherits both LM-DSM's slow DSM-training cost and Adam's local-minimum susceptibility** — training the DSM score network is the binding wall-clock cost (as with LM-DSM), and Adam's lack of a trust region or ensemble diversity leaves it as prone to getting stuck in a bad basin as ADAM-KGMM.",
452
  },
453
+ "RL-DDPG-SHORT": {
454
+ "citation": "Lillicrap et al., ICLR, 2016",
455
+ "url": "https://arxiv.org/abs/1509.02971",
456
+ "source": "`opt_experiments/ReinforcementLearning` — DDPG actor-critic (`ddpg_core.jl`, `rl_env.jl`, `rl_run.jl`), `UPDATE_FREQUENCY=short`.",
457
+ "math": (
458
+ "The unknown forcing parameterization is the **policy** and the whitened statistics misfit "
459
+ "`Φ_k=‖y-G(x_{T_k})‖²_Γ` defines the **reward** `r_k=Φ_{k-1}-Φ_k`. A small critic MLP `q(x,F)` is "
460
+ "trained on replay-buffer Bellman targets `-(1-γ)Φ_k+γ·q(x_{k+1},π(x_{k+1}))` (the `Φ_{k-1}` "
461
+ "offset cancels exactly), and the policy parameters are ascended along `∇_F q` averaged over "
462
+ "buffer states with Adam. No Jacobian of the simulator is ever formed — the gradient comes "
463
+ "from differentiating the learned critic. **The prior enters in three ways, but not as a "
464
+ "penalty:** the initial policy is a random draw from the prior, the policy is stored in "
465
+ "prior-whitened coordinates `F=μ+Lθ̃` (`L` = Cholesky factor of the prior covariance) so the "
466
+ "Adam step and the Gaussian exploration noise are measured in prior standard deviations "
467
+ "(following the prior's correlations), and the critic sees the whitened action. The reward "
468
+ "is the misfit only. In the *short* variant one RL step advances a "
469
+ "sliding statistics window by 1/10 of its length, so there are ~10 RL updates per window of "
470
+ "simulated time, each costing a fraction of a forward evaluation."
471
+ ),
472
+ "math_eq": r"\theta \leftarrow \theta + \mathrm{Adam}\!\left(\mathbb{E}_{x}\left[\nabla_F\, q_\vartheta(x,F)\right]_{F=F_\theta}\right)",
473
+ "guardrails": (
474
+ "`γ=0.5<1` is essential: for a state-independent policy at `γ=1` the reward telescopes and "
475
+ "`∇_F Q` vanishes, so `γ=1` reproduces the no-learning control. A Polyak-averaged target critic "
476
+ "(`τ=0.05`) stabilizes the bootstrap; critic inputs/targets are standardized from warm-up data "
477
+ "and frozen; Gaussian exploration noise (0.1 prior std) is added to the applied action, with a "
478
+ "warm-up phase before the first update. The statistic is computed over a trailing window of "
479
+ "the full length `W` (not the short step) so it is not biased by a shortened estimation "
480
+ "window. Cost is counted as simulation only (1 initial full evaluation + stride/T per step); "
481
+ "per-cell budget is capped at 10000 units."
482
+ ),
483
+ "params": [
484
+ {"Parameter": "γ (discount)", "Value": "0.5"},
485
+ {"Parameter": "Polyak rate", "Value": "0.05"},
486
+ {"Parameter": "critic_lr / actor_lr", "Value": "1e-3 / 0.02 (prior-std units)"},
487
+ {"Parameter": "batch size / updates per step", "Value": "64 / 1"},
488
+ {"Parameter": "steps per window / warm-up steps / buffer size", "Value": "10 / 50 / 5000"},
489
+ {"Parameter": "exploration noise / warm-up fraction", "Value": "0.1 / 0.5"},
490
+ {"Parameter": "critic hidden width", "Value": "64 (L63, L96 const) / 128 (vec, flux)"},
491
+ ],
492
+ "notes": (
493
+ "**Cost advantage is partly structural** — the continuing RL trajectory pays spin-up once rather "
494
+ "than every step (for L63 `T_start/T=0.75`), and the convergence test runs at every stride on "
495
+ "overlapping windows, so scores are not directly comparable to methods that re-pay spin-up each "
496
+ "evaluation. **Not tuned per case.** Converges on every seed on "
497
+ "L63 and L96-const, but the higher-dimensional forcings are the hard case for a learned "
498
+ "critic: on L96 flux-force and vec-force it reaches the targets on only a few of 30 seeds "
499
+ "(flux: 1 at target 1.1, 5 at 1.2; vec: 2 at 1.1, 4 at 1.2) and on none at 1.0 within the budget."
500
+ ),
501
+ },
502
+ "RL-DDPG": {
503
+ "citation": "Lillicrap et al., ICLR, 2016",
504
+ "url": "https://arxiv.org/abs/1509.02971",
505
+ "source": "`opt_experiments/ReinforcementLearning` — DDPG actor-critic (`ddpg_core.jl`, `rl_env.jl`, `rl_run.jl`), `UPDATE_FREQUENCY=long` (leaderboard name `rl-ddpg-long`).",
506
+ "math": (
507
+ "Same DDPG actor-critic scheme as RL-DDPG-SHORT (see that entry's update equation): the unknown "
508
+ "forcing parameterization is the **policy**, the decrease in the whitened statistics misfit "
509
+ "`Φ_k` is the **reward**, and the policy parameters are ascended along `∇_F q` of a small learned "
510
+ "critic. The prior enters as in RL-DDPG-SHORT: a random prior draw as the initial policy, "
511
+ "prior-whitened policy coordinates (step and exploration noise in prior standard deviations), "
512
+ "and no prior penalty in the reward. Here one RL step is the whole statistics window — exactly one standard forward "
513
+ "evaluation (spin-up + window), the same statistical window as every other method on the "
514
+ "leaderboard — so there is one RL update per forward evaluation and each step costs 1 unit."
515
+ ),
516
+ "math_eq": r"\theta \leftarrow \theta + \mathrm{Adam}\!\left(\mathbb{E}_{x}\left[\nabla_F\, q_\vartheta(x,F)\right]_{F=F_\theta}\right)",
517
+ "guardrails": (
518
+ "Same `γ=0.5<1` (at `γ=1` the critic has no `F`-dependence for a state-independent policy), "
519
+ "Polyak target critic (`τ=0.05`), frozen input/target standardization, and Gaussian exploration "
520
+ "noise (0.1 prior std) as RL-DDPG-SHORT. The statistic `G_k` is exactly the standard forward map "
521
+ "used by every other method, and the spin-up is paid on every step; per-cell budget is capped "
522
+ "at 10000 units."
523
+ ),
524
+ "params": [
525
+ {"Parameter": "γ (discount)", "Value": "0.5"},
526
+ {"Parameter": "Polyak rate", "Value": "0.05"},
527
+ {"Parameter": "critic_lr / actor_lr", "Value": "1e-3 / 0.02 (prior-std units)"},
528
+ {"Parameter": "batch size / updates per step", "Value": "64 / 1"},
529
+ {"Parameter": "steps per window / warm-up steps / buffer size", "Value": "1 / 20 / 2000"},
530
+ {"Parameter": "exploration noise / warm-up fraction", "Value": "0.1 / 0.5"},
531
+ {"Parameter": "critic hidden width", "Value": "64 (L63, L96 const) / 128 (vec, flux)"},
532
+ ],
533
+ "notes": (
534
+ "**One RL update per forward evaluation, so far more expensive than RL-DDPG-SHORT** — the short "
535
+ "variant makes ~10× more updates per unit of cost and pays spin-up only once. **Not tuned per "
536
+ "case.** Converges reliably on L63 and L96-const, but never "
537
+ "reached the targets on L96 flux-force or vec-force within the budget."
538
+ ),
539
+ },
540
+ "RL-TD3-SHORT": {
541
+ "citation": "Fujimoto, van Hoof & Meger, ICML, 2018",
542
+ "url": "https://arxiv.org/abs/1802.09477",
543
+ "source": "`opt_experiments/ReinforcementLearning` — TD3 actor-critic (`ddpg_core.jl`, `rl_env.jl`, `rl_run.jl`), `ALGORITHM=td3`, `UPDATE_FREQUENCY=short`.",
544
+ "math": (
545
+ "Same actor-critic formulation as RL-DDPG-SHORT (see that entry): the unknown forcing "
546
+ "parameterization is the **policy**, the decrease in the whitened statistics misfit is the "
547
+ "**reward**, and the policy parameters are ascended along `∇_F q` of a learned critic. TD3 "
548
+ "replaces the single critic with **twin critics** whose minimum forms the Bellman target "
549
+ "`-(1-γ)Φ_k+γ·min_i q_i(x_{k+1},π(x_{k+1}))`, and the actor ascends critic 1 only. The prior "
550
+ "enters as in RL-DDPG-SHORT (random prior draw as the initial policy; prior-whitened policy "
551
+ "coordinates for the Adam step, exploration and target-smoothing noise; no prior penalty in "
552
+ "the reward). In the *short* variant one RL step advances a sliding statistics window by 1/10 of its length, so "
553
+ "there are ~10 RL updates per window of simulated time, each costing a fraction of a forward "
554
+ "evaluation."
555
+ ),
556
+ "math_eq": r"\theta \leftarrow \theta + \mathrm{Adam}\!\left(\mathbb{E}_{x}\left[\nabla_F\, q^{(1)}_\vartheta(x,F)\right]_{F=F_\theta}\right)",
557
+ "guardrails": (
558
+ "The three TD3 robustness heuristics are switched on relative to RL-DDPG: **twin critics** with a "
559
+ "min-target (reduces critic overestimation), **delayed policy updates** (the actor and the "
560
+ "Polyak target critics update once per 2 critic updates), and **target-policy smoothing** "
561
+ "(clipped Gaussian noise added to the target action, in prior-std units, since the actor is a "
562
+ "single vector). As for RL-DDPG there is no target actor. Same `γ=0.5<1`, Polyak rate, frozen "
563
+ "input/target standardization, 0.1 prior-std Gaussian exploration noise and trailing window of "
564
+ "full length `W` as RL-DDPG-SHORT; cost counts simulation only (1 initial evaluation + stride/T "
565
+ "per step), capped at 10000 units per cell."
566
+ ),
567
+ "params": [
568
+ {"Parameter": "γ (discount)", "Value": "0.5"},
569
+ {"Parameter": "Polyak rate", "Value": "0.05"},
570
+ {"Parameter": "critics / policy delay", "Value": "2 / 2"},
571
+ {"Parameter": "target smoothing noise (clip)", "Value": "0.2 (0.5), prior-std units"},
572
+ {"Parameter": "critic_lr / actor_lr", "Value": "1e-3 / 0.02 (prior-std units)"},
573
+ {"Parameter": "batch size / updates per step", "Value": "64 / 1"},
574
+ {"Parameter": "steps per window / warm-up steps / buffer size", "Value": "10 / 50 / 5000"},
575
+ {"Parameter": "exploration noise / warm-up fraction", "Value": "0.1 / 0.5"},
576
+ {"Parameter": "critic hidden width", "Value": "64 (L63, L96 const) / 128 (vec, flux)"},
577
+ ],
578
+ "notes": (
579
+ "**Same structural cost advantage as RL-DDPG-SHORT** — the continuing trajectory pays spin-up "
580
+ "once and the convergence test runs on overlapping windows, so scores are not directly "
581
+ "comparable to methods that re-pay spin-up every evaluation. **Not tuned per case.** "
582
+ "Converges on every seed on L63 and L96-const (slower than RL-DDPG-SHORT on L63 at targets "
583
+ "1.0–1.1, similar at 1.2); on the higher-dimensional forcings it is weaker than RL-DDPG-SHORT: "
584
+ "it never reaches the targets on L96 flux-force, and on vec-force only 1 of 30 seeds does "
585
+ "(target 1.2) within the budget."
586
+ ),
587
+ },
588
+ "RL-TD3": {
589
+ "citation": "Fujimoto, van Hoof & Meger, ICML, 2018",
590
+ "url": "https://arxiv.org/abs/1802.09477",
591
+ "source": "`opt_experiments/ReinforcementLearning` — TD3 actor-critic (`ddpg_core.jl`, `rl_env.jl`, `rl_run.jl`), `ALGORITHM=td3`, `UPDATE_FREQUENCY=long` (leaderboard name `rl-td3-long`).",
592
+ "math": (
593
+ "Same TD3 actor-critic scheme as RL-TD3-SHORT (twin critics with a min-target; the actor "
594
+ "ascends critic 1 only; see that entry and RL-DDPG-SHORT for the formulation, including how "
595
+ "the prior sets the initial policy and the whitened coordinates). Here one RL step "
596
+ "is the whole statistics window — exactly one standard forward evaluation, the same statistical "
597
+ "window as every other method on the leaderboard — so there is one RL update per forward "
598
+ "evaluation and each step costs 1 unit."
599
+ ),
600
+ "math_eq": r"\theta \leftarrow \theta + \mathrm{Adam}\!\left(\mathbb{E}_{x}\left[\nabla_F\, q^{(1)}_\vartheta(x,F)\right]_{F=F_\theta}\right)",
601
+ "guardrails": (
602
+ "Same TD3 heuristics as RL-TD3-SHORT: twin critics with a min-target, delayed policy/target "
603
+ "updates (every 2 critic updates), and clipped target-policy smoothing noise. Same `γ=0.5<1`, "
604
+ "Polyak rate, frozen standardization and 0.1 prior-std exploration noise; the spin-up is paid "
605
+ "on every step; per-cell budget is capped at 10000 units."
606
+ ),
607
+ "params": [
608
+ {"Parameter": "γ (discount)", "Value": "0.5"},
609
+ {"Parameter": "Polyak rate", "Value": "0.05"},
610
+ {"Parameter": "critics / policy delay", "Value": "2 / 2"},
611
+ {"Parameter": "target smoothing noise (clip)", "Value": "0.2 (0.5), prior-std units"},
612
+ {"Parameter": "critic_lr / actor_lr", "Value": "1e-3 / 0.02 (prior-std units)"},
613
+ {"Parameter": "batch size / updates per step", "Value": "64 / 1"},
614
+ {"Parameter": "steps per window / warm-up steps / buffer size", "Value": "1 / 20 / 2000"},
615
+ {"Parameter": "exploration noise / warm-up fraction", "Value": "0.1 / 0.5"},
616
+ {"Parameter": "critic hidden width", "Value": "64 (L63, L96 const) / 128 (vec, flux)"},
617
+ ],
618
+ "notes": (
619
+ "**One RL update per forward evaluation, so far more expensive than RL-TD3-SHORT.** **Not tuned "
620
+ "per case.** Converges on every seed on L63 and L96-const (slower than RL-DDPG "
621
+ "on L96-const, and on L63 faster at targets 1.0–1.1 but slower at 1.2), but never reached the "
622
+ "targets on L96 flux-force or vec-force within the budget."
623
+ ),
624
+ },
625
  }
626
 
627
  # KNOWN_METHODS is the source of truth for the abbreviation/full-name list,
src/streamlit_app.py CHANGED
@@ -121,9 +121,10 @@ _taxonomy_df = (
121
  )
122
 
123
  _UPDATE_TYPE_ROW_COLOR = {
124
- "kalman": "#d9f2e6", # pastel green
125
- "gradient": "#dbe9fa", # pastel blue
126
- "general": "#fde2c8", # pastel orange
 
127
  }
128
 
129
 
@@ -151,7 +152,7 @@ st.dataframe(
151
  )
152
  st.caption(
153
  "Note: Kalman methods are Bayesian in spirit too (they're approximate Gaussian "
154
- "posterior updates) — update type is about mechanism (gradient vs. Kalman vs. "
155
  "general), not whether a method is 'Bayesian'."
156
  )
157
 
 
121
  )
122
 
123
  _UPDATE_TYPE_ROW_COLOR = {
124
+ "gradient (Kalman)": "#d9f2e6", # pastel green
125
+ "gradient (exact)": "#dbe9fa", # pastel blue
126
+ "gradient (score)": "#e6dcf5", # pastel purple
127
+ "general": "#fde2c8", # pastel orange
128
  }
129
 
130
 
 
152
  )
153
  st.caption(
154
  "Note: Kalman methods are Bayesian in spirit too (they're approximate Gaussian "
155
+ "posterior updates) — update type is about mechanism (which kind of gradient — exact, Kalman or score — vs. "
156
  "general), not whether a method is 'Bayesian'."
157
  )
158