Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/config.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ load:
- 2030
- 2040
- 2050
weather_year_tyndp: {}
manual_adjustments: true
scaling_factor: 1.0
fixed_year: false
Expand Down
17 changes: 17 additions & 0 deletions config/config.tyndp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,23 @@ load:
- 2030
- 2040
- 2050
weather_year_tyndp:
2030:
- 3
- 21
- 29
2035:
- 32
- 37
- 59
2040:
- 65
- 71
- 77
2050:
- 91
- 92
- 106
manual_adjustments: false
supplement_synthetic: false
patch_demand_with_mm: true
Expand Down
20 changes: 20 additions & 0 deletions config/schema.default.json
Original file line number Diff line number Diff line change
Expand Up @@ -3422,6 +3422,16 @@
},
"type": "array"
},
"weather_year_tyndp": {
"additionalProperties": {
"items": {
"type": "integer"
},
"type": "array"
},
"description": "Mapping of planning horizons to the weather years (climate year column indices, e.g. 3 for `WS003`) that contain data in the TYNDP 2026 demand data, for that planning horizon. The first entry is used as the preferred weather year; if it's missing for a given demand type/file, the script falls back to another entry in the list (see `scripts._helpers.check_weather_year`).",
"type": "object"
},
"manual_adjustments": {
"default": true,
"description": "Whether to adjust the load data manually according to the function in `manual_adjustment`.",
Expand Down Expand Up @@ -11736,6 +11746,16 @@
},
"type": "array"
},
"weather_year_tyndp": {
"additionalProperties": {
"items": {
"type": "integer"
},
"type": "array"
},
"description": "Mapping of planning horizons to the weather years (climate year column indices, e.g. 3 for `WS003`) that contain data in the TYNDP 2026 demand data, for that planning horizon. The first entry is used as the preferred weather year; if it's missing for a given demand type/file, the script falls back to another entry in the list (see `scripts._helpers.check_weather_year`).",
"type": "object"
},
"manual_adjustments": {
"default": true,
"description": "Whether to adjust the load data manually according to the function in `manual_adjustment`.",
Expand Down
17 changes: 17 additions & 0 deletions config/test/config.tyndp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,23 @@ load:
- 2030
- 2040
- 2050
weather_year_tyndp:
2030:
- 3
- 21
- 29
2035:
- 32
- 37
- 59
2040:
- 65
- 71
- 77
2050:
- 91
- 92
- 106
manual_adjustments: false
supplement_synthetic: false
patch_demand_with_mm: true
Expand Down
205 changes: 205 additions & 0 deletions rules/sb.smk
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,211 @@ use rule build_electricity_demand as build_electricity_demand_tyndp with:
benchmarks("performances/build_electricity_demand_{planning_horizons}")


def get_weather_year_tyndp(w):
"""Get the preferred TYNDP 2026 weather year (climate year column index) for a given planning horizon."""
weather_years = config_provider("load", "weather_year_tyndp")(w)
pyear = safe_pyear(
w.planning_horizons,
available_years=sorted(weather_years),
source="TYNDP demand weather year",
verbose=False,
)
return weather_years[pyear][0]


# Generic rule: parameterized by the `demand_type` wildcard, so any demand
# type/file present under `data/tyndp_2026_bundle/Demand` can be requested
# directly by target filename. The concrete demand types below (electricity
# market, EV charging, hydrogen zones, ...) are defined as named aliases of
# this rule via `use rule ... as ...`, fixing `demand_type` and giving each a
# stable, readable output name instead of relying on the wildcard.
rule build_tyndp_demand:
input:
# TODO Replace with rules.retrieve_tyndp_2026.output.demand once available
demand="data/tyndp_2026_bundle/Demand",
output:
demand=resources("demand_tyndp_{demand_type}_{planning_horizons}.csv"),
log:
logs("build_tyndp_demand_{demand_type}_{planning_horizons}.log"),
benchmark:
benchmarks("performances/build_tyndp_demand_{demand_type}_{planning_horizons}")
threads: 1
resources:
mem_mb=4000,
params:
snapshots=config_provider("snapshots"),
drop_leap_day=config_provider("enable", "drop_leap_day"),
weather_year=get_weather_year_tyndp,
weather_years=config_provider("load", "weather_year_tyndp"),
demand_type=lambda w: w.demand_type,
script:
scripts("sb/build_tyndp_demand.py")


use rule build_tyndp_demand as build_tyndp_electricity_market_demand with:
output:
demand=resources("electricity_demand_tyndp_{planning_horizons}.csv"),
log:
logs("build_tyndp_electricity_market_demand_{planning_horizons}.log"),
benchmark:
benchmarks(
"performances/build_tyndp_electricity_market_demand_{planning_horizons}"
)
params:
snapshots=config_provider("snapshots"),
drop_leap_day=config_provider("enable", "drop_leap_day"),
weather_year=get_weather_year_tyndp,
weather_years=config_provider("load", "weather_year_tyndp"),
demand_type="ELECTRICITY_MARKET",


use rule build_tyndp_demand as build_tyndp_electricity_prosumer_demand with:
output:
demand=resources("electricity_prosumer_demand_tyndp_{planning_horizons}.csv"),
log:
logs("build_tyndp_electricity_prosumer_demand_{planning_horizons}.log"),
benchmark:
benchmarks(
"performances/build_tyndp_electricity_prosumer_demand_{planning_horizons}"
)
params:
snapshots=config_provider("snapshots"),
drop_leap_day=config_provider("enable", "drop_leap_day"),
weather_year=get_weather_year_tyndp,
weather_years=config_provider("load", "weather_year_tyndp"),
demand_type="ELECTRICITY_PROSUMER",


use rule build_tyndp_demand as build_tyndp_electricity_prosumer_btm_demand with:
output:
demand=resources(
"electricity_prosumer_btm_demand_tyndp_{planning_horizons}.csv"
),
log:
logs("build_tyndp_electricity_prosumer_btm_demand_{planning_horizons}.log"),
benchmark:
benchmarks(
"performances/build_tyndp_electricity_prosumer_btm_demand_{planning_horizons}"
)
params:
snapshots=config_provider("snapshots"),
drop_leap_day=config_provider("enable", "drop_leap_day"),
weather_year=get_weather_year_tyndp,
weather_years=config_provider("load", "weather_year_tyndp"),
demand_type="ELECTRICITY_PROSUMER_BEHIND_THE_METER_FIXED_LOAD",


use rule build_tyndp_demand as build_tyndp_ev_market_demand with:
output:
demand=resources("ev_market_demand_tyndp_{planning_horizons}.csv"),
log:
logs("build_tyndp_ev_market_demand_{planning_horizons}.log"),
benchmark:
benchmarks("performances/build_tyndp_ev_market_demand_{planning_horizons}")
params:
snapshots=config_provider("snapshots"),
drop_leap_day=config_provider("enable", "drop_leap_day"),
weather_year=get_weather_year_tyndp,
weather_years=config_provider("load", "weather_year_tyndp"),
demand_type="EV_FIXED_LOAD_PROFILES_ELECTRICITY_MARKET",


use rule build_tyndp_demand as build_tyndp_ev_prosumer_demand with:
output:
demand=resources("ev_prosumer_demand_tyndp_{planning_horizons}.csv"),
log:
logs("build_tyndp_ev_prosumer_demand_{planning_horizons}.log"),
benchmark:
benchmarks("performances/build_tyndp_ev_prosumer_demand_{planning_horizons}")
params:
snapshots=config_provider("snapshots"),
drop_leap_day=config_provider("enable", "drop_leap_day"),
weather_year=get_weather_year_tyndp,
weather_years=config_provider("load", "weather_year_tyndp"),
demand_type="EV_FIXED_LOAD_PROFILES_ELECTRICITY_PROSUMER",


use rule build_tyndp_demand as build_tyndp_h2_zone1_demand with:
output:
demand=resources("h2_zone1_demand_tyndp_{planning_horizons}.csv"),
log:
logs("build_tyndp_h2_zone1_demand_{planning_horizons}.log"),
benchmark:
benchmarks("performances/build_tyndp_h2_zone1_demand_{planning_horizons}")
params:
snapshots=config_provider("snapshots"),
drop_leap_day=config_provider("enable", "drop_leap_day"),
weather_year=get_weather_year_tyndp,
weather_years=config_provider("load", "weather_year_tyndp"),
demand_type="Hydrogen_Zone 1",


use rule build_tyndp_demand as build_tyndp_h2_zone2_demand with:
output:
demand=resources("h2_zone2_demand_tyndp_{planning_horizons}.csv"),
log:
logs("build_tyndp_h2_zone2_demand_{planning_horizons}.log"),
benchmark:
benchmarks("performances/build_tyndp_h2_zone2_demand_{planning_horizons}")
params:
snapshots=config_provider("snapshots"),
drop_leap_day=config_provider("enable", "drop_leap_day"),
weather_year=get_weather_year_tyndp,
weather_years=config_provider("load", "weather_year_tyndp"),
demand_type="Hydrogen_Zone 2",


use rule build_tyndp_demand as build_tyndp_synthetic_fuels_demand with:
output:
demand=resources("synthetic_fuels_demand_tyndp_{planning_horizons}.csv"),
log:
logs("build_tyndp_synthetic_fuels_demand_{planning_horizons}.log"),
benchmark:
benchmarks(
"performances/build_tyndp_synthetic_fuels_demand_{planning_horizons}"
)
params:
snapshots=config_provider("snapshots"),
drop_leap_day=config_provider("enable", "drop_leap_day"),
weather_year=get_weather_year_tyndp,
weather_years=config_provider("load", "weather_year_tyndp"),
demand_type="SYNTHETIC_FUELS",


use rule build_tyndp_demand as build_tyndp_thermal_hydrogen_demand with:
output:
demand=resources("thermal_hydrogen_demand_tyndp_{planning_horizons}.csv"),
log:
logs("build_tyndp_thermal_hydrogen_demand_{planning_horizons}.log"),
benchmark:
benchmarks(
"performances/build_tyndp_thermal_hydrogen_demand_{planning_horizons}"
)
params:
snapshots=config_provider("snapshots"),
drop_leap_day=config_provider("enable", "drop_leap_day"),
weather_year=get_weather_year_tyndp,
weather_years=config_provider("load", "weather_year_tyndp"),
demand_type="Thermal_energy_Hydrogen",


use rule build_tyndp_demand as build_tyndp_thermal_methane_demand with:
output:
demand=resources("thermal_methane_demand_tyndp_{planning_horizons}.csv"),
log:
logs("build_tyndp_thermal_methane_demand_{planning_horizons}.log"),
benchmark:
benchmarks(
"performances/build_tyndp_thermal_methane_demand_{planning_horizons}"
)
params:
snapshots=config_provider("snapshots"),
drop_leap_day=config_provider("enable", "drop_leap_day"),
weather_year=get_weather_year_tyndp,
weather_years=config_provider("load", "weather_year_tyndp"),
demand_type="Thermal_energy_Methane",


def get_pecd_prebuilt(w):
if "pre-built" in PECD_DATASET["version"]:
return rules.retrieve_tyndp_pecd.output.dir
Expand Down
42 changes: 42 additions & 0 deletions scripts/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,48 @@ def check_cyear(cyear: int, scenario: str) -> int:
return cyear


def check_weather_year(weather_year: int, valid_weather_years: list[int]) -> int:
"""
Check if the weather year is one of the known-valid ones, falling back if not.

TYNDP 2026 demand profiles provide 30 climate year columns per planning
horizon (labelled ``WSxxx``), but depending on the demand type, either all
of them contain data or only a handful of them do (the rest being empty
placeholders). Which weather years are valid therefore has to be
determined per file (see e.g. ``get_valid_weather_years`` in
`scripts/sb/build_tyndp_demand.py`) rather than assumed globally.

Parameters
----------
weather_year : int
Weather year (climate year column index, e.g. 3 for ``WS003``) to validate.
valid_weather_years : list[int]
Weather years known to contain data, for the file being processed.

Returns
-------
int
Valid weather year, falling back to the first entry of
`valid_weather_years` if the input is not among them.
"""

if not valid_weather_years:
raise ValueError(
"No `valid_weather_years` provided. Expected a non-empty list of weather years."
)

if weather_year not in valid_weather_years:
fallback = valid_weather_years[0]
logger.warning(
f"Weather year WS{weather_year:03d} doesn't contain data for this file "
f"(available: {[f'WS{y:03d}' for y in valid_weather_years]}). "
f"Falling back to WS{fallback:03d}."
)
weather_year = fallback

return weather_year


def get_tyndp_conventional_thermals(
mapping: pd.DataFrame,
tyndp_conventional_carriers: list[str],
Expand Down
4 changes: 4 additions & 0 deletions scripts/lib/validation/config/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ class LoadConfig(BaseModel):
default_factory=lambda: [2030, 2040, 2050],
description="List of years for which TYNDP demand data is available.",
)
weather_year_tyndp: dict[int, list[int]] = Field(
default_factory=dict,
description="Mapping of planning horizons to the weather years (climate year column indices, e.g. 3 for `WS003`) that contain data in the TYNDP 2026 demand data, for that planning horizon. The first entry is used as the preferred weather year; if it's missing for a given demand type/file, the script falls back to another entry in the list (see `scripts._helpers.check_weather_year`).",
)
manual_adjustments: bool = Field(
True,
description="Whether to adjust the load data manually according to the function in `manual_adjustment`.",
Expand Down
Loading