diff --git a/.gitignore b/.gitignore
index 50b2ff7d..833a323f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,7 +14,7 @@ _dev
.vscode
.idea
.snakemake
-output
+output*
_sandbox/
experiment_report.html
diff --git a/config/varda-rapid-0.2.yaml b/config/varda-rapid-0.2.yaml
new file mode 100644
index 00000000..7461d434
--- /dev/null
+++ b/config/varda-rapid-0.2.yaml
@@ -0,0 +1,158 @@
+# yaml-language-server: $schema=../workflow/tools/config.schema.json
+description: |
+ Evaluate skill of Varda-rapid against ground observations.
+
+config_label: varda-rapid-0.2
+
+dates:
+ # start: 2025-01-01T06:00
+ # end: 2025-12-31T06:00
+ # frequency: 294h
+ - 2025-03-01T00:00
+ - 2025-04-02T06:00
+ - 2025-07-02T12:00
+ - 2025-11-02T18:00
+
+runs:
+ - temporal_downscaler:
+ checkpoint: https://service.meteoswiss.ch/mlstore#/models/sruc-m-2-interpolator/versions/3
+ label: Varda-rapid-0.2
+ steps: 0/12/1
+ config: resources/inference/configs/sgm-temporal-downscaler-global_trimedge_multi_with_nudging.yaml
+ extra_requirements:
+ - anemoi-datasets==0.5.35
+ - git+https://github.com/MeteoSwiss/anemoi-plugins-meteoswiss@0e69b5916166a7feb104f892cdf2a091bd45bd56
+ - git+https://github.com/ecmwf/anemoi-inference@88c1ec13632c570f246cc2580ce324e45f36a03b
+ forecaster:
+ checkpoint: https://service.meteoswiss.ch/mlstore#/models/sruc-m-1-forecaster/versions/4
+ config: resources/inference/configs/sgm-multidataset-forecaster-global-ich1-oper.yaml
+ steps: 0/12/6
+ extra_requirements:
+ - git+https://github.com/MeteoSwiss/anemoi-plugins-meteoswiss@0e69b5916166a7feb104f892cdf2a091bd45bd56
+ - git+https://github.com/ecmwf/anemoi-inference@88c1ec13632c570f246cc2580ce324e45f36a03b
+
+ - temporal_downscaler:
+ checkpoint: https://service.meteoswiss.ch/mlstore#/models/sruc-m-2-interpolator/versions/3
+ label: Varda-single-1.0
+ steps: 0/12/1
+ config: resources/inference/configs/sgm-temporal-downscaler-global_trimedge_multi.yaml
+ extra_requirements:
+ - anemoi-datasets==0.5.35
+ - git+https://github.com/MeteoSwiss/anemoi-plugins-meteoswiss@cc116a80c86346cb8de4449c1091bb2c6299a302
+ - git+https://github.com/ecmwf/anemoi-inference@88c1ec13632c570f246cc2580ce324e45f36a03b
+ forecaster:
+ checkpoint: https://service.meteoswiss.ch/mlstore#/models/sruc-m-1-forecaster/versions/4
+ config: resources/inference/configs/sgm-multidataset-forecaster-global-ich1-oper.yaml
+ steps: 0/12/6
+ extra_requirements:
+ - git+https://github.com/MeteoSwiss/anemoi-plugins-meteoswiss@cc116a80c86346cb8de4449c1091bb2c6299a302
+ - git+https://github.com/ecmwf/anemoi-inference@88c1ec13632c570f246cc2580ce324e45f36a03b
+
+ - baseline:
+ label: INCA
+ root: /store_new/mch/msclim/INCA
+ steps: 0/6/1
+ - baseline:
+ label: ICON-CH2-CTRL
+ root: /store_new/mch/msopr/osm/ICON-CH2-EPS
+ steps: 0/12/1
+ - baseline:
+ label: ICON-CH1-CTRL
+ root: /store_new/mch/msopr/osm/ICON-CH1-EPS
+ steps: 0/12/1
+
+truth:
+ label: SwissMetNet
+ root: jretrievedwh:bbox=45.7,48.0,5.8,10.8;use_limitation=40
+
+experiment:
+ params:
+ - T_2M
+ - TD_2M
+ - U_10M
+ - V_10M
+ - TOT_PREC
+ - PMSL
+ - PS
+ stratification:
+ regions:
+ - icon
+ - switzerland
+ - mittelland
+ - berge
+ - alpennordseite
+ - alpensuedseite
+ - jura
+ root: /store_new/mch/msopr/ml/regions/Prognoseregionen_LV95_20220517
+ thresholds:
+ TOT_PREC:
+ gt: [0.0, 1, 5]
+ U_10M:
+ gt: [2.5, 5.0, 10.0]
+ V_10M:
+ gt: [2.5, 5.0, 10.0]
+ T_2M:
+ lt: [273.15]
+ gt: [288.15, 298.15]
+ dashboard:
+ stratification:
+ - season
+ # - region
+ station_holdout:
+ - CHM
+ - FRE
+ - RUE
+ - BAS
+ - PLF
+ - CGI
+ - HOE
+ - PUY
+ - RAG
+ - EIN
+ - ALT
+ - GOS
+ - COV
+ - ULR
+ - BIN
+ - GUE
+ - ROE
+ - BIA
+ - CEV
+ - OTL
+ scoremaps:
+ enabled: false
+
+showcase:
+ params:
+ - T_2M
+ - TD_2M
+ - U_10M
+ - V_10M
+ meteograms:
+ enabled: false
+ stations: [JUN, KLO, LUG, GVE] #, COV, GOR, WFJ, SAE, SAM, DAV, ZER, ANT, VSBAS, BRT, LTB, GOS, CEV, BIA]
+ animations:
+ enabled: true
+ frames_per_second: 0.5
+ domains:
+ # - globe
+ # - europe
+ - alps
+ - icon-ch
+ - switzerland
+
+locations:
+ output_root: output/
+
+profile:
+ executor: slurm
+ global_resources:
+ gpus: 16
+ default_resources:
+ slurm_partition: "normal"
+ cpus_per_task: 1
+ mem_mb_per_cpu: 1800
+ runtime: "1h"
+ gpus: 0
+ slurm_account: s83
+ jobs: 50
diff --git a/resources/inference/configs/sgm-temporal-downscaler-global_trimedge_multi_with_nudging.yaml b/resources/inference/configs/sgm-temporal-downscaler-global_trimedge_multi_with_nudging.yaml
new file mode 100644
index 00000000..1936dd15
--- /dev/null
+++ b/resources/inference/configs/sgm-temporal-downscaler-global_trimedge_multi_with_nudging.yaml
@@ -0,0 +1,176 @@
+runner: temporal_downscaler
+
+input:
+ cutout:
+ - lam_0:
+ grib:
+ path: forecaster/20*
+ pre_processors:
+ - forward_transform_filter:
+ # does not have an effect if the temporal downscaler does not use tp as prognostic variable
+ rescale:
+ scale: 0.001 # convert from kg m-2 to m
+ offset: 0
+ param: tp
+ # nudge the initial condition toward station observations.
+ - forward_transform_filter:
+ retrieve_observation:
+ obs_path: observation/nudging_station_obs_raw.parquet
+ jretrieve_src_path: /scratch/mch/llanzila/sruc/evalml/src
+ retrieval_bbox: [40.5, 53.0, 0.0, 17.5]
+ # station_group: "1,2"
+ variables: [T_2M, TD_2M, U_10M, V_10M, TOT_PREC, PMSL, PS]
+ use_limitation: 40
+ run_mode: "devt"
+ station_filter_mode: domain
+ trim_bbox: [45.7, 48.0, 5.8, 10.8]
+ - forward_transform_filter:
+ clean_observation:
+ obs_path_in: observation/nudging_station_obs_raw.parquet
+ obs_path_out: observation/nudging_station_obs.parquet
+ - forward_transform_filter:
+ nudge_toward_observation:
+ obs_path: observation/nudging_station_obs.parquet
+ nudge_variables: [T_2M, TD_2M, U_10M, V_10M, PMSL, PS]
+ icon_grid_file: /scratch/mch/llanzila/sruc/aux_files/icon_grid_0001_R19B08_mch.nc
+ icon_orog_file: /scratch/mch/icontest/testing-input-data/c2sm/icon-1/external_parameter_icon_grid_0001_R19B08_mch.nc
+ d_eff_file: /scratch/mch/llanzila/sruc/aux_files/d_eff_cache_domain_maxdist50km_nbar50x3_bw1500m_elev50_elevdiff100_nsta1939.nc
+ weight_power: 2.0
+ max_dist: 50000.0
+ use_reliability_check: true
+ number_of_std: 5.0
+ reliability_min_dist_frac: 0.03
+ write_diagnostics: true
+ diagnostics_dir: nudging_diagnostics
+ run_mode: "devt"
+ namer: &namer
+ rules:
+ - - shortName: T
+ - t_{level}
+ - - shortName: U
+ - u_{level}
+ - - shortName: V
+ - v_{level}
+ - - shortName: W
+ - w_{level}
+ - - shortName: QV
+ - q_{level}
+ - - shortName: FI
+ - z_{level}
+ - - shortName: PMSL
+ - msl
+ - - shortName: FIS
+ - z
+ - - shortName: PS
+ - sp
+ - - shortName: T_2M
+ - 2t
+ - - shortName: TD_2M
+ - 2d
+ - - shortName: T_G
+ - skt
+ - - shortName: U_10M
+ - 10u
+ - - shortName: V_10M
+ - 10v
+ - - shortName: FR_LAND
+ - lsm
+ - - shortName: TOT_PREC
+ - tp
+ - global:
+ grib:
+ path: forecaster/ifs*
+ namer: *namer
+
+constant_forcings:
+ test:
+ use_original_paths: true
+
+patch_metadata: resources/sgm-temporal-downscaler-ich1-oper-patch.yaml
+
+post_processors:
+ - accumulate_from_start_of_forecast: # accumulate tp from start of forecast
+ accumulations:
+ - tp
+ - forward_transform_filter:
+ rescale:
+ scale: 1000 # convert units from m to kg m-2
+ offset: 0
+ param: tp
+
+output:
+ tee:
+ - grib:
+ path: grib/{dateTime}_{step:03}.grib
+ encoding:
+ typeOfGeneratingProcess: 2
+ templates:
+ samples: resources/templates_index_icon.yaml
+ post_processors:
+ - extract_mask: # removes global points
+ mask: "lam_0/cutout_mask"
+ as_slice: true
+ # here, the trimedge mask can be specified when available
+ # at overlap steps (multiples of the forecaster stride) replace the
+ # re-predicted prognostics with the forecaster's boundary values
+ - forward_transform_filter:
+ copy-prognostic-from-forecaster:
+ forecaster_path: forecaster/20*
+ common_leadtime: 6h
+ params_to_keep: [tp] # NOTE, if more diagnostics are added, update accordingly.
+ namer: *namer
+
+ - grib:
+ path: grib/ifs-{dateTime}_{step:03}.grib
+ encoding:
+ typeOfGeneratingProcess: 2
+ templates:
+ samples: resources/templates_index_ifs.yaml
+ post_processors:
+ - extract_mask: # removes lam points
+ mask: "lam_0/cutout_mask"
+ as_slice: true
+ inverse: true
+ - assign_mask: # fill local/global overlapping points with nan
+ mask: "global/cutout_mask"
+ - forward_transform_filter:
+ copy-prognostic-from-forecaster:
+ forecaster_path: forecaster/ifs*
+ common_leadtime: 6h
+ params_to_keep: [tp] # NOTE, if more diagnostics are added, update accordingly.
+ namer: *namer
+ modifiers:
+ - patches:
+ - variable:
+ U_10M: {"param": 165, "shortName": "10u"}
+ 10u: {"param": 165, "shortName": "10u"}
+ V_10M: {"param": 166, "shortName": "10v"}
+ 10v: {"param": 166, "shortName": "10v"}
+ TD_2M: {"param": 168, "shortName": "2d"}
+ 2d: {"param": 168, "shortName": "2d"}
+ T_2M: {"param": 167, "shortName": "2t"}
+ 2t: {"param": 167, "shortName": "2t"}
+ FR_LAND: {"param": 172, "shortName": "lsm"}
+ lsm: {"param": 172, "shortName": "lsm"}
+ PMSL: {"param": 151, "shortName": "msl"}
+ msl: {"param": 151, "shortName": "msl"}
+ PS: {"param": 134, "shortName": "sp"}
+ sp: {"param": 134, "shortName": "sp"}
+ SSO_SIGMA: {"param": 163, "shortName": "slor"}
+ slor: {"param": 163, "shortName": "slor"}
+ SSO_STDH: {"param": 160, "shortName": "sdor"}
+ sdor: {"param": 160, "shortName": "sdor"}
+ TOT_PREC: {"param": 228, "shortName": "tp"}
+ tp: {"param": 228, "shortName": "tp"}
+ z: {"param": 129, "shortName": "z"}
+ "^q_(\\d+)$": {"param": 133, "shortName": "q"}
+ "^t_(\\d+)$": {"param": 130, "shortName": "t"}
+ "^u_(\\d+)$": {"param": 131, "shortName": "u"}
+ "^v_(\\d+)$": {"param": 132, "shortName": "v"}
+ "^w_(\\d+)$": {"param": 135, "shortName": "w"}
+ "^z_(\\d+)$": {"param": 129, "shortName": "z"}
+
+# silenced due to bug in anemoi-inference for multi-step temporal downscalers, can be removed when fixed
+verbosity: 0
+allow_nans: true
+output_frequency: "1h"
diff --git a/resources/report/dashboard/script.js b/resources/report/dashboard/script.js
index 830324a0..d89af495 100644
--- a/resources/report/dashboard/script.js
+++ b/resources/report/dashboard/script.js
@@ -34,6 +34,7 @@ function initChoices(id) {
initChoices("region-select");
initChoices("season-select");
initChoices("init-select");
+initChoices("station-group-select");
initChoices("source-select");
initChoices("metric-select");
initChoices("param-select");
@@ -52,8 +53,9 @@ function getSelected(id) {
window.DATA = raw.data.map(row => {
const obj = {};
for (let i = 0; i < cols.length; i++) obj[cols[i]] = row[i];
+ const sgPart = obj.station_group !== "all" ? ", Group: " + obj.station_group : "";
obj.region_season_init =
- "Region: " + obj.region + ", Season: " + obj.season + ", Init: " + obj.init_hour;
+ "Region: " + obj.region + ", Season: " + obj.season + ", Init: " + obj.init_hour + sgPart;
return obj;
});
})();
@@ -154,7 +156,7 @@ async function renderLegend(filteredData) {
legend: {
orient: "bottom",
direction: "horizontal",
- title: "Region / Season / Init",
+ title: "Region / Season / Init / Group",
labelLimit: 400,
symbolType: "circle", symbolSize: 120,
},
@@ -216,13 +218,16 @@ async function updateChart() {
const selMetrics = getSelected("metric-select");
const selParams = getSelected("param-select");
- // Filter data by region / season / init / source
+ const selStationGroups = getSelected("station-group-select");
+
+ // Filter data by region / season / init / source / station_group
// (metric and param are handled per cell)
let filtered = DATA;
- if (selRegions.length) filtered = filtered.filter(d => selRegions.includes(d.region));
- if (selSeasons.length) filtered = filtered.filter(d => selSeasons.includes(d.season));
- if (selInits.length) filtered = filtered.filter(d => selInits.includes(d.init_hour));
- if (selSources.length) filtered = filtered.filter(d => selSources.includes(d.source));
+ if (selRegions.length) filtered = filtered.filter(d => selRegions.includes(d.region));
+ if (selSeasons.length) filtered = filtered.filter(d => selSeasons.includes(d.season));
+ if (selInits.length) filtered = filtered.filter(d => selInits.includes(d.init_hour));
+ if (selSources.length) filtered = filtered.filter(d => selSources.includes(d.source));
+ if (selStationGroups.length) filtered = filtered.filter(d => selStationGroups.includes(d.station_group));
// Show / hide table columns (params)
document.querySelectorAll("#chart-table thead th[data-param]").forEach(th => {
@@ -327,6 +332,7 @@ function resizeChartScroll() {
getSelected("source-select"),
getSelected("metric-select"),
getSelected("param-select"),
+ getSelected("station-group-select"),
].flatMap(v => v);
summary.textContent = parts.join(", ");
}
@@ -342,7 +348,7 @@ function resizeChartScroll() {
// Keep summary current when selections change (guard: some selects may be absent)
["region-select", "season-select", "init-select",
- "source-select", "metric-select", "param-select"].forEach(id => {
+ "source-select", "metric-select", "param-select", "station-group-select"].forEach(id => {
const el = document.getElementById(id);
if (el) el.addEventListener("change", () => {
if (panel.classList.contains("collapsed")) updateSummary();
diff --git a/resources/report/dashboard/template.html.jinja2 b/resources/report/dashboard/template.html.jinja2
index a6fe1f4a..0da748f0 100644
--- a/resources/report/dashboard/template.html.jinja2
+++ b/resources/report/dashboard/template.html.jinja2
@@ -154,6 +154,16 @@
{% endif %}
+ {% if station_holdout %}
+
+
+
+
+ {% endif %}