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 %}