GWDI eenvoudige berekening

Dit notebook laat een minimale GWDI-run zien voor één rastercel (fid) via de toolbox-moduleklasse GwdiInference.

Het notebook laat het volgende zien: 1. Inlezen van een lokale GWDI-configuratie (yaml) voor DataAdapters. 2. Uitvoeren van GwdiInference.run(...) met de smoke single-fid dataset. 3. Vergelijken van de uitkomst met de bestaande smoke-baseline uit de unit test.

import os
from pathlib import Path

import matplotlib.pyplot as plt
import pandas as pd

from toolbox_continu_inzicht import Config, DataAdapter
from toolbox_continu_inzicht.gwdi import (
    GwdiInference,
    GwdiKnmiRetrieval,
    GwdiWiwbRetrieval,
)
data_dir = Path.cwd() / "data_sets" / "10.gwdi_simple_calculation"
config_path = data_dir / "gwdi_simple_calculation.yaml"

De configuratie ziet er als volgt uit:

GlobalVariables:
    rootdir: "data_sets/10.gwdi_simple_calculation"
    calc_time: "2024-02-15 08:00:00"

    GwdiWiwbRetrieval:
        lag_days: 0
        publish_days: 35
        target_date: "2024-02-14"
        wiwb_precipitation_source_code: "Knmi.International.Radar.Composite.Combined"

    GwdiKnmiRetrieval:
        lag_days: 0
        publish_days: 35
        target_date: "2024-02-14"

DataAdapter:
    gwdi_input_precipitation_static:
        type: csv
        file: "precipitation_single_fid.csv"
    gwdi_input_evaporation_static:
        type: csv
        file: "evaporation_single_fid.csv"

    gwdi_input_precipitation_dynamic:
        type: python
    gwdi_input_evaporation_dynamic:
        type: python

    gwdi_input_climate_sampling_locations:
        type: csv
        file: "gwdi_climate_sampling_locations_single.csv"

    gwdi_input_pastas_mapping:
        type: csv
        file: "gwdi_pastas_mapping.csv"
        dtype:
            location: object
            position: object
    gwdi_input_pastas_models:
        type: pastas_models
        path: "pastas_models"
    gwdi_input_stats_minima:
        type: csv
        file: "df_stats_minima.csv"
        index_col: 0
    gwdi_output:
        type: python

Eerste voorbeeld: statische klimaatreeks

In dit eerste voorbeeld gebruiken we statische neerslag en verdamping (uit een CSV) om een GWDI-run met GwdiInference uit te voeren.

config = Config(config_path=config_path)
config.lees_config()

data_adapter = DataAdapter(config=config)
module = GwdiInference(data_adapter=data_adapter)

module.run(
    input=[
        "gwdi_input_precipitation_static",
        "gwdi_input_evaporation_static",
        "gwdi_input_pastas_mapping",
        "gwdi_input_pastas_models",
        "gwdi_input_stats_minima",
    ],
    output="gwdi_output",
)

df_gwdi = module.df_out.copy()
df_gwdi
2026-05-18 11:53:07 WARNING - base: The solver object is stored in the model.solver attribute since Pastas 1.3. Please update your pas-file to the new format by loading and saving the file with Pastas 1.3.
2026-05-18 11:53:07 WARNING - base: The solver object is stored in the model.solver attribute since Pastas 1.3. Please update your pas-file to the new format by loading and saving the file with Pastas 1.3.
2026-05-18 11:53:07 WARNING - base: The solver object is stored in the model.solver attribute since Pastas 1.3. Please update your pas-file to the new format by loading and saving the file with Pastas 1.3.
2026-05-18 11:53:07 WARNING - base: The solver object is stored in the model.solver attribute since Pastas 1.3. Please update your pas-file to the new format by loading and saving the file with Pastas 1.3.
2026-05-18 11:53:07 WARNING - base: The solver object is stored in the model.solver attribute since Pastas 1.3. Please update your pas-file to the new format by loading and saving the file with Pastas 1.3.
2026-05-18 11:53:07 WARNING - model: The stress of the stressmodel has no overlap with ml.oseries.
2026-05-18 11:53:07 WARNING - model: The stress of the stressmodel has no overlap with ml.oseries.
2026-05-18 11:53:08 WARNING - model: The stress of the stressmodel has no overlap with ml.oseries.
2026-05-18 11:53:08 WARNING - model: The stress of the stressmodel has no overlap with ml.oseries.
2026-05-18 11:53:08 WARNING - model: The stress of the stressmodel has no overlap with ml.oseries.
fid parameterid methodid datetime value peilbuisid
0 300268 4 1 1721865600000 0.476382 0
1 300268 4 1 1721952000000 0.480692 0
2 300268 4 1 1722038400000 0.511826 0
3 300268 4 1 1722124800000 0.547531 0
4 300268 4 1 1722211200000 0.589434 0
... ... ... ... ... ... ...
170 300268 4 1 1724457600000 0.460507 4
171 300268 4 1 1724544000000 0.505857 4
172 300268 4 1 1724630400000 0.542583 4
173 300268 4 1 1724716800000 0.604032 4
174 300268 4 1 1724803200000 0.686086 4

175 rows × 6 columns

Tweede voorbeeld: dynamische klimaatreeks via WIWB + KNMI

In dit tweede voorbeeld halen we neerslag en verdamping dynamisch op voor dezelfde GWDI-run: - GwdiWiwbRetrieval levert neerslag (P) op. - GwdiKnmiRetrieval levert verdamping (evaporation) op.

In gwdi_simple_calculation.yaml gebruiken beide retrievals dezelfde resample-configuratie (resample_frequency, resample_period_start, resample_period_end) zodat de tijdstappen op elkaar aansluiten. Daarna voeren we GwdiInference uit met deze dynamische invoer.

required_env = ("WIWB_CLIENT_ID", "WIWB_KEY", "KNMI_API_KEY")
missing_env = [name for name in required_env if not os.getenv(name)]
if missing_env:
    print(
        f"Ontbrekende omgevingsvariabelen voor dynamische GWDI-run: {', '.join(missing_env)}"
    )
else:
    print(f"OK: de benodigde omgevingsvariabelen zijn aanwezig: {required_env}")
Ontbrekende omgevingsvariabelen voor dynamische GWDI-run: WIWB_CLIENT_ID, WIWB_KEY, KNMI_API_KEY
# Gebruik dezelfde YAML-configuratie als in het statische voorbeeld.
data_adapter_dynamic = DataAdapter(config=config)

if missing_env:
    print(
        f"Ontbrekende omgevingsvariabelen voor dynamische GWDI-run: {', '.join(missing_env)}"
    )
else:
    wiwb_module = GwdiWiwbRetrieval(data_adapter=data_adapter_dynamic)
    wiwb_module.run(
        input="gwdi_input_climate_sampling_locations",
        output="gwdi_input_precipitation_dynamic",
    )

    knmi_module = GwdiKnmiRetrieval(data_adapter=data_adapter_dynamic)
    knmi_module.run(
        input="gwdi_input_climate_sampling_locations",
        output="gwdi_input_evaporation_dynamic",
    )
Ontbrekende omgevingsvariabelen voor dynamische GWDI-run: WIWB_CLIENT_ID, WIWB_KEY, KNMI_API_KEY

GwdiInference vereist exact dezelfde tijdreeksen voor verdamping en neerslag. In de praktijk kan bronbeschikbaarheid per provider toch verschillen. Daarom maken we hieronder expliciet een gezamenlijke (time, fid)-grid (intersection) voor beide reeksen.

if missing_env:
    print(
        f"Ontbrekende omgevingsvariabelen voor dynamische GWDI-run: {', '.join(missing_env)}"
    )
else:
    wiwb_grid = wiwb_module.df_out[["time", "fid"]].drop_duplicates()
    knmi_grid = knmi_module.df_out[["time", "fid"]].drop_duplicates()
    common_grid = wiwb_grid.merge(knmi_grid, on=["time", "fid"], how="inner")

    if len(common_grid) == 0:
        raise UserWarning(
            "Geen overlappende (`time`, `fid`)-grid tussen WIWB en KNMI. "
            "Controleer periode/resample-instellingen."
        )

    df_precip_dynamic = wiwb_module.df_out.merge(
        common_grid, on=["time", "fid"], how="inner"
    )
    df_evap_dynamic = knmi_module.df_out.merge(
        common_grid, on=["time", "fid"], how="inner"
    )
    df_precip_dynamic = df_precip_dynamic.sort_values(["time", "fid"]).reset_index(
        drop=True
    )
    df_evap_dynamic = df_evap_dynamic.sort_values(["time", "fid"]).reset_index(
        drop=True
    )

    print("Gezamenlijk grid:")
    display(df_precip_dynamic.merge(df_evap_dynamic, on=["time", "fid"]))
Ontbrekende omgevingsvariabelen voor dynamische GWDI-run: WIWB_CLIENT_ID, WIWB_KEY, KNMI_API_KEY

Tenslotte voeren we GwdiInference uit met deze dynamische invoer.

if missing_env:
    print(
        f"Ontbrekende omgevingsvariabelen voor dynamische GWDI-run: {', '.join(missing_env)}"
    )
else:
    data_adapter_dynamic.set_dataframe_adapter(
        "gwdi_input_precipitation_dynamic",
        df_precip_dynamic,
    )
    data_adapter_dynamic.set_dataframe_adapter(
        "gwdi_input_evaporation_dynamic",
        df_evap_dynamic,
    )

    module_dynamic = GwdiInference(data_adapter=data_adapter_dynamic)
    module_dynamic.run(
        input=[
            "gwdi_input_precipitation_dynamic",
            "gwdi_input_evaporation_dynamic",
            "gwdi_input_pastas_mapping",
            "gwdi_input_pastas_models",
            "gwdi_input_stats_minima",
        ],
        output="gwdi_output",
    )

    df_gwdi_dynamic = module_dynamic.df_out.copy()

    plt.close("all")
    fig, ax = plt.subplots()
    df_gwdi_dynamic["datetime"] = pd.to_datetime(
        df_gwdi_dynamic["datetime"], unit="ms", utc=True
    )
    df_gwdi_agg = df_gwdi_dynamic.groupby("datetime")["value"].agg(
        ["min", "mean", "max"]
    )
    ax.fill_between(
        df_gwdi_agg.index,
        df_gwdi_agg["min"],
        df_gwdi_agg["max"],
        alpha=0.2,
        color="blue",
    )
    ax.plot(df_gwdi_agg.index, df_gwdi_agg["mean"])
    ax.set_xticks(ax.get_xticks(), ax.get_xticklabels(), rotation=45, ha="right")
Ontbrekende omgevingsvariabelen voor dynamische GWDI-run: WIWB_CLIENT_ID, WIWB_KEY, KNMI_API_KEY