GBOED method

#11
by odunbar - opened
.cache/known_methods_snapshot.json CHANGED
@@ -125,6 +125,18 @@
125
  "levenberg-marquardt",
126
  "gradient_descent"
127
  ]
 
 
 
 
 
 
 
 
 
 
 
 
128
  }
129
  },
130
  "observed_methods": [
 
125
  "levenberg-marquardt",
126
  "gradient_descent"
127
  ]
128
+ },
129
+ "gboed": {
130
+ "abbreviation": "GBOED",
131
+ "Method": "Goal-oriented Bayesian Optimal Experimental Design",
132
+ "parallelism": "parallel-interacting",
133
+ "update_type": "general",
134
+ "method_goal": "uq",
135
+ "emulator_use": "within-optimize",
136
+ "aliases": [
137
+ "gboed",
138
+ "boed"
139
+ ]
140
  }
141
  },
142
  "observed_methods": [
CLAUDE.md CHANGED
@@ -61,21 +61,11 @@ calibration_benchmark/
61
  β”‚ β”œβ”€β”€ *.nc Kalman results (TEKI, ETKI, IEKF)
62
  β”‚ β”œβ”€β”€ bayesian/ ABC + HM results
63
  β”‚ β”œβ”€β”€ UKI_results/ UKI results
64
- β”‚ β”œβ”€β”€ adam_results/ ADAM gradient optimizer results
65
- β”‚ β”‚ filenames follow `leaderboard_adam_<benchmark-tag>_<date>.nc`;
66
- β”‚ β”‚ benchmark tags: `l63` β†’ L63, `l96_const-force` β†’ L96,
67
- β”‚ β”‚ `l96_vec-force` β†’ L96_SPATIAL_FORCING (no L96_NN_FORCING yet)
68
- β”‚ β”œβ”€β”€ levenberg_marquardt_results/ LM gradient optimizer results
69
- β”‚ β”‚ filenames follow `leaderboard_lm_<benchmark-tag>_<date>.nc`;
70
- β”‚ β”‚ same benchmark tags as adam_results/ (no L96_NN_FORCING yet)
71
  β”‚ β”œβ”€β”€ ces-eki-dmc_results/ CES-EKI-DMC ensemble-results files (UQ source)
72
- β”‚ β”‚ filenames follow `ces-eki-dmc_<benchmark-tag>_ensemble_results_<date>[_minimal].nc`;
73
- β”‚ β”‚ benchmark tags: `l63` β†’ L63, `l96` (no suffix) β†’ L96,
74
- β”‚ β”‚ `l96_nn_forcing` β†’ L96_NN_FORCING, `l96_spatial_forcing` β†’ L96_SPATIAL_FORCING
75
  β”‚ └── gnki-uq_results/ IEKF (a.k.a GNKI) ensemble-results files (UQ source)
76
- β”‚ filenames follow `leaderboard_gnki_<benchmark-tag>_<date>[_minimal].nc`;
77
- β”‚ benchmark tags: `l63` β†’ L63, `l96_const-force` β†’ L96,
78
- β”‚ `l96_flux-force` β†’ L96_NN_FORCING, `l96_vec-force` β†’ L96_SPATIAL_FORCING
79
  β”œβ”€β”€ examples/ lorenz_demo.ipynb + lorenz.py (reference, not app code)
80
  β”œβ”€β”€ From_Rob_6-10/ Raw collaborator data drop β€” NOT read by the app
81
  β”œβ”€β”€ .cache/ known_methods_snapshot.json (auto-generated, safe to delete)
@@ -191,6 +181,7 @@ A quick taxonomy tree is rendered on the home page (`src/streamlit_app.py`).
191
  | `ces-eki-dmc` | CES-EKI-DMC | parallel-interacting | kalman | uq | after-optimize | β€” |
192
  | `adam` | ADAM | serial | gradient | optimization | none | β€” |
193
  | `lm` | LM | serial | gradient | optimization | none | gradient_descent |
 
194
 
195
  ## How to extend
196
 
 
61
  β”‚ β”œβ”€β”€ *.nc Kalman results (TEKI, ETKI, IEKF)
62
  β”‚ β”œβ”€β”€ bayesian/ ABC + HM results
63
  β”‚ β”œβ”€β”€ UKI_results/ UKI results
64
+ β”‚ β”œβ”€β”€ adam_results/ ADAM gradient optimizer results (per-benchmark files)
65
+ β”‚ β”œβ”€β”€ levenberg_marquardt_results/ LM gradient optimizer results (per-benchmark files)
 
 
 
 
 
66
  β”‚ β”œβ”€β”€ ces-eki-dmc_results/ CES-EKI-DMC ensemble-results files (UQ source)
67
+ β”‚ β”œβ”€β”€ gboed-uq_results/ GBOED ensemble-results files (UQ source)
 
 
68
  β”‚ └── gnki-uq_results/ IEKF (a.k.a GNKI) ensemble-results files (UQ source)
 
 
 
69
  β”œβ”€β”€ examples/ lorenz_demo.ipynb + lorenz.py (reference, not app code)
70
  β”œβ”€β”€ From_Rob_6-10/ Raw collaborator data drop β€” NOT read by the app
71
  β”œβ”€β”€ .cache/ known_methods_snapshot.json (auto-generated, safe to delete)
 
181
  | `ces-eki-dmc` | CES-EKI-DMC | parallel-interacting | kalman | uq | after-optimize | β€” |
182
  | `adam` | ADAM | serial | gradient | optimization | none | β€” |
183
  | `lm` | LM | serial | gradient | optimization | none | gradient_descent |
184
+ | `gboed` | GBOED | parallel-interacting | general | uq | within-optimize | boed |
185
 
186
  ## How to extend
187
 
data/gboed-uq_results/boed_l63_ensemble_results_2026-07-27_minimal.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ca32e5609af36cfd99cd14ec73ae1f2254f24da96bbf65a16dce023b5a803f76
3
+ size 673057
data/gboed-uq_results/boed_l63_ensemble_results_2026-07-30_minimal.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:52616c824f068f6de1eb9ccf67f56a1000b56d1cd54c911daa817df4b600e26b
3
+ size 673057
data/gboed-uq_results/boed_l96_const-force_2026-07-28_minimal.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:50c1939536077245886497fab8b74ffe8382d95b62a55ac7d7ccd66c0815703c
3
+ size 809857
data/gboed-uq_results/boed_l96_const-force_2026-07-30_minimal.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:84062bc4afda563eba840efcf06c70143037660e99f258d6ecba84538a308b78
3
+ size 809857
data/gboed-uq_results/boed_l96_vec-force_2026-08-06_minimal.nc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8cd67af4f7a347aa156718ba4a759b2ed11a6047813ed6446cef6ed6922df851
3
+ size 536257
src/common/method_registry.py CHANGED
@@ -134,6 +134,15 @@ KNOWN_METHODS = {
134
  "emulator_use": "none",
135
  "aliases": ["lm", "levenberg_marquardt", "levenberg-marquardt", "gradient_descent"],
136
  },
 
 
 
 
 
 
 
 
 
137
  }
138
 
139
 
@@ -142,6 +151,7 @@ KNOWN_METHODS = {
142
  _METHOD_PALETTE = [
143
  "#4c78a8", "#f58518", "#e45756", "#72b7b2", "#54a24b",
144
  "#eeca3b", "#b279a2", "#ff9da6", "#9d755d", "#bab0ac",
 
145
  ]
146
 
147
  # Stable abbreviation β†’ hex color mapping. Import this wherever Altair charts are built
 
134
  "emulator_use": "none",
135
  "aliases": ["lm", "levenberg_marquardt", "levenberg-marquardt", "gradient_descent"],
136
  },
137
+ "gboed": {
138
+ "abbreviation": "GBOED",
139
+ "Method": "Goal-oriented Bayesian Optimal Experimental Design",
140
+ "parallelism": "parallel-interacting",
141
+ "update_type": "general",
142
+ "method_goal": "uq",
143
+ "emulator_use": "within-optimize",
144
+ "aliases": ["gboed", "boed"],
145
+ },
146
  }
147
 
148
 
 
151
  _METHOD_PALETTE = [
152
  "#4c78a8", "#f58518", "#e45756", "#72b7b2", "#54a24b",
153
  "#eeca3b", "#b279a2", "#ff9da6", "#9d755d", "#bab0ac",
154
+ "#5254a3", "#8ca252",
155
  ]
156
 
157
  # Stable abbreviation β†’ hex color mapping. Import this wherever Altair charts are built
src/data_store.py CHANGED
@@ -93,6 +93,7 @@ UQ_BUDGET_FILES: dict[str, list[tuple[str, str]]] = {
93
  ("ces-eki-const", "ces-eki-const_results/ces-eki-const_l63_ensemble_results_2026-07-09_minimal.nc"),
94
  ("ces-iekf-const", "ces-iekf-const_results/ces-iekf-const_l63_ensemble_results_2026-07-17_minimal.nc"),
95
  ("hm", "history-matching-uq_results/history-matching_l63_ensemble_results_2026-07-20_minimal.nc"),
 
96
  ],
97
  "L96": [
98
  ("ces-eki-dmc", "ces-eki-dmc_results/ces-eki-dmc_l96_ensemble_results_2026-06-15_minimal.nc"),
@@ -100,6 +101,7 @@ UQ_BUDGET_FILES: dict[str, list[tuple[str, str]]] = {
100
  ("ces-eki-const", "ces-eki-const_results/ces-eki-const_l96_ensemble_results_2026-07-09_minimal.nc"),
101
  ("ces-iekf-const", "ces-iekf-const_results/ces-iekf-const_l96_ensemble_results_2026-07-17_minimal.nc"),
102
  ("hm", "history-matching-uq_results/history-matching_l96_const-force_2026-07-20_minimal.nc"),
 
103
  ],
104
  "L96_NN_FORCING": [
105
  ("ces-eki-dmc", "ces-eki-dmc_results/ces-eki-dmc_l96_nn_forcing_ensemble_results_2026-06-15_minimal.nc"),
@@ -114,6 +116,7 @@ UQ_BUDGET_FILES: dict[str, list[tuple[str, str]]] = {
114
  ("ces-eki-const", "ces-eki-const_results/ces-eki-const_l96_spatial_forcing_ensemble_results_2026-07-09_minimal.nc"),
115
  ("ces-iekf-const", "ces-iekf-const_results/ces-iekf-const_l96_spatial_forcing_ensemble_results_2026-07-17_minimal.nc"),
116
  ("hm", "history-matching-uq_results/history-matching_l96_vec-force_2026-07-22_minimal.nc"),
 
117
  ],
118
  }
119
 
 
93
  ("ces-eki-const", "ces-eki-const_results/ces-eki-const_l63_ensemble_results_2026-07-09_minimal.nc"),
94
  ("ces-iekf-const", "ces-iekf-const_results/ces-iekf-const_l63_ensemble_results_2026-07-17_minimal.nc"),
95
  ("hm", "history-matching-uq_results/history-matching_l63_ensemble_results_2026-07-20_minimal.nc"),
96
+ ("gboed", "gboed-uq_results/boed_l63_ensemble_results_2026-07-30_minimal.nc"),
97
  ],
98
  "L96": [
99
  ("ces-eki-dmc", "ces-eki-dmc_results/ces-eki-dmc_l96_ensemble_results_2026-06-15_minimal.nc"),
 
101
  ("ces-eki-const", "ces-eki-const_results/ces-eki-const_l96_ensemble_results_2026-07-09_minimal.nc"),
102
  ("ces-iekf-const", "ces-iekf-const_results/ces-iekf-const_l96_ensemble_results_2026-07-17_minimal.nc"),
103
  ("hm", "history-matching-uq_results/history-matching_l96_const-force_2026-07-20_minimal.nc"),
104
+ ("gboed", "gboed-uq_results/boed_l96_const-force_2026-07-30_minimal.nc"),
105
  ],
106
  "L96_NN_FORCING": [
107
  ("ces-eki-dmc", "ces-eki-dmc_results/ces-eki-dmc_l96_nn_forcing_ensemble_results_2026-06-15_minimal.nc"),
 
116
  ("ces-eki-const", "ces-eki-const_results/ces-eki-const_l96_spatial_forcing_ensemble_results_2026-07-09_minimal.nc"),
117
  ("ces-iekf-const", "ces-iekf-const_results/ces-iekf-const_l96_spatial_forcing_ensemble_results_2026-07-17_minimal.nc"),
118
  ("hm", "history-matching-uq_results/history-matching_l96_vec-force_2026-07-22_minimal.nc"),
119
+ ("gboed", "gboed-uq_results/boed_l96_vec-force_2026-08-06_minimal.nc"),
120
  ],
121
  }
122
 
src/pages/MethodDetails.py CHANGED
@@ -94,6 +94,11 @@ method_meta = {
94
  "url": "https://doi.org/10.1090/qam/10666",
95
  "summary": "Levenberg-Marquardt β€” damped least-squares algorithm that interpolates between gradient descent and Gauss-Newton steps for efficient nonlinear least-squares minimization.",
96
  },
 
 
 
 
 
97
  }
98
 
99
  # Selection UI (defaults to query param if valid)
 
94
  "url": "https://doi.org/10.1090/qam/10666",
95
  "summary": "Levenberg-Marquardt β€” damped least-squares algorithm that interpolates between gradient descent and Gauss-Newton steps for efficient nonlinear least-squares minimization.",
96
  },
97
+ "GBOED": {
98
+ "citation": "Holthuijzen et al. 2026",
99
+ "url": "https://arxiv.org/abs/2508.13071",
100
+ "summary": "Goal-oriented Bayesian Optimal Experimental Design β€” selects each batch of forward-model evaluations by maximizing an expected-information-gain criterion targeted at the quantity of interest, refitting the surrogate used by the criterion at every iteration.",
101
+ },
102
  }
103
 
104
  # Selection UI (defaults to query param if valid)
src/streamlit_app.py CHANGED
@@ -1,6 +1,17 @@
1
- import streamlit as st
2
  from pathlib import Path
3
 
 
 
 
 
 
 
 
 
 
 
 
4
  st.set_page_config(page_title="Calibration Benchmark", page_icon="🏠", layout="wide")
5
 
6
  # Sidebar navigation
@@ -88,38 +99,51 @@ st.markdown(
88
  "a surrogate model. See the **πŸ“˜ Methods** page for citations and per-method "
89
  "performance charts."
90
  )
91
- st.markdown(
92
- """
93
- - **Parallelism** β€” how the search explores parameter space
94
- - **Serial** β€” `ADAM`, `LM` β€” a single point estimate advanced step by step.
95
- - **Parallel-independent** β€” `ABC`, `HM` β€” a population of candidates updated
96
- with no coupling between members (accepted samples / per-wave resampling).
97
- - **Parallel-interacting** β€” `TEKI`, `ETKI`, `IEKF`, `UKI`, `CES-EKI-DMC`, `CES-EKI-CONST`,
98
- `CES-IEKF-CONST` β€” an ensemble whose members are coupled through a shared update
99
- each iteration.
100
- - **Update type** β€” the mechanism driving each update step
101
- - **Gradient** β€” `ADAM`, `LM` β€” follow the loss gradient (or a Gauss-Newton
102
- approximation of it) directly.
103
- - **Kalman** β€” `TEKI`, `ETKI`, `IEKF`, `UKI`, `CES-EKI-DMC`, `CES-EKI-CONST`,
104
- `CES-IEKF-CONST` β€” a (possibly linearized or unscented) Kalman-style ensemble update.
105
- - **General** β€” `ABC`, `HM` β€” neither gradient- nor Kalman-based (rejection
106
- sampling, implausibility cuts).
107
- - **Method goal** β€” what the method is built to report
108
- - **Optimization** β€” `TEKI`, `ETKI`, `UKI`, `ADAM`, `LM` β€” a single best-fit
109
- parameter estimate.
110
- - **UQ** β€” `IEKF`, `ABC`, `HM`, `CES-EKI-DMC`, `CES-EKI-CONST`, `CES-IEKF-CONST` β€” the
111
- full posterior / parameter uncertainty. Can still be scored on the Optimization
112
- leaderboard, but tends to be less competitive there since it's not optimizing for
113
- speed-to-target.
114
- - **Emulator use** β€” when/whether a surrogate model of the forward model is used
115
- - **None** β€” `TEKI`, `ETKI`, `IEKF`, `UKI`, `ADAM`, `LM`, `ABC` β€” samples/evaluates
116
- the true forward model throughout.
117
- - **Within-optimize** β€” `HM` β€” refits a surrogate at each iteration (wave) of the
118
- search itself.
119
- - **After-optimize** β€” `CES-EKI-DMC`, `CES-EKI-CONST`, `CES-IEKF-CONST` β€” fits a
120
- surrogate (e.g. a GP) once, after calibration finishes, and samples the posterior
121
- through it.
122
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  )
124
  st.caption(
125
  "Note: Kalman methods are Bayesian in spirit too (they're approximate Gaussian "
 
1
+ import sys
2
  from pathlib import Path
3
 
4
+ import pandas as pd
5
+ import streamlit as st
6
+
7
+ try:
8
+ from common.method_registry import KNOWN_METHODS
9
+ from common.leaderboard import _UPDATE_TYPE_ORDER
10
+ except ModuleNotFoundError:
11
+ sys.path.append(str(Path(__file__).resolve().parent))
12
+ from common.method_registry import KNOWN_METHODS
13
+ from common.leaderboard import _UPDATE_TYPE_ORDER
14
+
15
  st.set_page_config(page_title="Calibration Benchmark", page_icon="🏠", layout="wide")
16
 
17
  # Sidebar navigation
 
99
  "a surrogate model. See the **πŸ“˜ Methods** page for citations and per-method "
100
  "performance charts."
101
  )
102
+ _taxonomy_rows = [
103
+ {
104
+ "Algorithm": info["abbreviation"],
105
+ "Method Goal": info["method_goal"],
106
+ "Update Type": info["update_type"],
107
+ "Parallelism": info["parallelism"],
108
+ "Emulator Use": info["emulator_use"],
109
+ "_update_type_order": _UPDATE_TYPE_ORDER.get(info["update_type"], 99),
110
+ }
111
+ for info in KNOWN_METHODS.values()
112
+ ]
113
+ _taxonomy_df = (
114
+ pd.DataFrame(_taxonomy_rows)
115
+ .sort_values(["_update_type_order", "Algorithm"])
116
+ .drop(columns="_update_type_order")
117
+ )
118
+
119
+ _UPDATE_TYPE_ROW_COLOR = {
120
+ "kalman": "#d9f2e6", # pastel green
121
+ "gradient": "#dbe9fa", # pastel blue
122
+ "general": "#fde2c8", # pastel orange
123
+ }
124
+
125
+
126
+ def _style_taxonomy_rows(df: pd.DataFrame) -> pd.DataFrame:
127
+ result = pd.DataFrame("", index=df.index, columns=df.columns)
128
+ for row in df.index:
129
+ bg = _UPDATE_TYPE_ROW_COLOR.get(df.loc[row, "Update Type"], "")
130
+ if bg:
131
+ result.loc[row, :] = f"background-color: {bg}; color: #212529"
132
+ return result
133
+
134
+
135
+ st.dataframe(
136
+ _taxonomy_df.style.apply(_style_taxonomy_rows, axis=None),
137
+ hide_index=True,
138
+ use_container_width=True,
139
+ height=int((len(_taxonomy_df) + 1) * 35.2 + 3),
140
+ column_config={
141
+ "Algorithm": st.column_config.TextColumn("Algorithm"),
142
+ "Method Goal": st.column_config.TextColumn("Method Goal"),
143
+ "Update Type": st.column_config.TextColumn("Update Type"),
144
+ "Parallelism": st.column_config.TextColumn("Parallelism"),
145
+ "Emulator Use": st.column_config.TextColumn("Emulator Use"),
146
+ },
147
  )
148
  st.caption(
149
  "Note: Kalman methods are Bayesian in spirit too (they're approximate Gaussian "