Skip to content

Commit f3fc07c

Browse files
author
Micha
authored
Improve exogenous transport demand (#91)
* simplify indexing * determine charger capacities like in prepare_sector_network; remove land_transport_electric_share * add PHEV to number of electric cars because they provide charging capacity as well * syntax fixes * rename branch * add option to specify AGEB(+KBA) as source for transport demand in 2020 and 2025 * add warning * set transport shares to dummy values * add changelog * use mobility demand from uba projektionsbericht * have separate option for the 2020 data * renaming * rename script and changelog * transporte_shares matter outside of Germany! * rename mobility_demand -> mobility_data * more renaming * rename and refactor * small adjustments * explicitly define arrays as float
1 parent 39e72aa commit f3fc07c

File tree

6 files changed

+280
-188
lines changed

6 files changed

+280
-188
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# Changelog
2+
- Added an option to source mobility demand from UBA MWMS (Projektionsbericht 2025) for the years 2025-2035
3+
- Renamed functions and script for exogenous mobility demand
4+
- Improved the transport demand data, added an option to source 2020 and 2025 data from AGEB instead of Aladin
25
- Added a helper function to change the weather_year to build_scenario
36
- Longer lifetime (40 years) is only applied to existing gas CHPs, not new ones. Added a new config entry `existing_capacities:fill_value_gas_chp_lifetime`
47
- Bugfix: gas CHPs are extendable again

Snakefile

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -362,24 +362,29 @@ if config["enable"]["retrieve"] and config["enable"].get("retrieve_cost_data", T
362362
ruleorder: modify_cost_data > retrieve_cost_data
363363

364364

365-
rule build_mobility_demand:
365+
rule build_exogenous_mobility_data:
366366
params:
367367
reference_scenario=config_provider("iiasa_database", "reference_scenario"),
368368
planning_horizons=config_provider("scenario", "planning_horizons"),
369369
leitmodelle=config_provider("iiasa_database", "leitmodelle"),
370+
ageb_for_mobility=config_provider("iiasa_database", "ageb_for_mobility"),
371+
uba_for_mobility=config_provider("iiasa_database", "uba_for_mobility"),
372+
shipping_oil_share=config_provider("sector", "shipping_oil_share"),
373+
aviation_demand_factor=config_provider("sector", "aviation_demand_factor"),
374+
energy_totals_year=config_provider("energy", "energy_totals_year"),
370375
input:
371376
ariadne="resources/ariadne_database.csv",
372-
clustered_pop_layout=resources("pop_layout_base_s_{clusters}.csv"),
377+
energy_totals=resources("energy_totals.csv"),
373378
output:
374-
mobility_demand=resources(
375-
"mobility_demand_aladin_{clusters}_{planning_horizons}.csv"
379+
mobility_data=resources(
380+
"modified_mobility_data_{clusters}_{planning_horizons}.csv"
376381
),
377382
resources:
378383
mem_mb=1000,
379384
log:
380-
logs("build_mobility_demand_{clusters}_{planning_horizons}.log"),
385+
logs("build_exogenous_mobility_data_{clusters}_{planning_horizons}.log"),
381386
script:
382-
"scripts/pypsa-de/build_mobility_demand.py"
387+
"scripts/pypsa-de/build_exogenous_mobility_data.py"
383388

384389

385390
rule build_egon_data:
@@ -541,9 +546,6 @@ rule modify_prenetwork:
541546
must_run=config_provider("must_run"),
542547
clustering=config_provider("clustering", "temporal", "resolution_sector"),
543548
H2_plants=config_provider("electricity", "H2_plants_DE"),
544-
land_transport_electric_share=config_provider(
545-
"sector", "land_transport_electric_share"
546-
),
547549
onshore_nep_force=config_provider("onshore_nep_force"),
548550
offshore_nep_force=config_provider("offshore_nep_force"),
549551
shipping_methanol_efficiency=config_provider(
@@ -553,6 +555,9 @@ rule modify_prenetwork:
553555
shipping_methanol_share=config_provider("sector", "shipping_methanol_share"),
554556
mwh_meoh_per_tco2=config_provider("sector", "MWh_MeOH_per_tCO2"),
555557
scale_capacity=config_provider("scale_capacity"),
558+
bev_charge_rate=config_provider("sector", "bev_charge_rate"),
559+
bev_energy=config_provider("sector", "bev_energy"),
560+
bev_dsm_availability=config_provider("sector", "bev_dsm_availability"),
556561
input:
557562
costs_modifications="ariadne-data/costs_{planning_horizons}-modifications.csv",
558563
network=resources(
@@ -564,10 +569,9 @@ rule modify_prenetwork:
564569
else []
565570
),
566571
costs=resources("costs_{planning_horizons}.csv"),
567-
aladin_demand=resources(
568-
"mobility_demand_aladin_{clusters}_{planning_horizons}.csv"
572+
modified_mobility_data=resources(
573+
"modified_mobility_data_{clusters}_{planning_horizons}.csv"
569574
),
570-
transport_data=resources("transport_data_s_{clusters}.csv"),
571575
biomass_potentials=resources(
572576
"biomass_potentials_s_{clusters}_{planning_horizons}.csv"
573577
),

config/config.de.yaml

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#run
66
run:
7-
prefix: 20250721_weather_year_helper
7+
prefix: 20250717_improve_transport_demand
88
name:
99
# - ExPol
1010
- KN2045_Mix
@@ -43,6 +43,8 @@ iiasa_database:
4343
- KN2045_NFhoch
4444
reference_scenario: KN2045_Mix
4545
region: Deutschland
46+
ageb_for_mobility: true # In 2020 use AGEB data for final energy demand and KBA for vehicles
47+
uba_for_mobility: false # For 2025–2035 use MWMS scenario from UBA Projektionsbericht 2025
4648

4749
# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#foresight
4850
foresight: myopic
@@ -332,27 +334,28 @@ sector:
332334
2040: 0.29
333335
2045: 0.36
334336
2050: 0.43
337+
# For Germany these settings get overwritten in build_exogenous_mobility_data
335338
land_transport_fuel_cell_share:
336-
2020: 0.05
337-
2025: 0.05
338-
2030: 0.05
339-
2035: 0.05
340-
2040: 0.05
341-
2045: 0.05
342-
2050: 0.05
339+
2020: 0.01
340+
2025: 0.01
341+
2030: 0.02
342+
2035: 0.03
343+
2040: 0.03
344+
2045: 0.03
345+
2050: 0.03
343346
land_transport_electric_share:
344347
2020: 0.05
345348
2025: 0.15
346349
2030: 0.3
347350
2035: 0.45
348-
2040: 0.7
349-
2045: 0.85
350-
2050: 0.95
351+
2040: 0.72
352+
2045: 0.87
353+
2050: 0.97
351354
land_transport_ice_share:
352-
2020: 0.9
353-
2025: 0.8
354-
2030: 0.65
355-
2035: 0.5
355+
2020: 0.94
356+
2025: 0.84
357+
2030: 0.68
358+
2035: 0.52
356359
2040: 0.25
357360
2045: 0.1
358361
2050: 0.0
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import logging
2+
3+
import pandas as pd
4+
5+
from scripts._helpers import configure_logging, mock_snakemake
6+
7+
logger = logging.getLogger(__name__)
8+
9+
10+
def get_mobility_data(
11+
db,
12+
year,
13+
non_land_liquids,
14+
ageb_for_mobility=True,
15+
uba_for_mobility=False,
16+
):
17+
"""
18+
Retrieve the German mobility demand from the transport model.
19+
20+
Sum over the subsectors Bus, LDV, Rail, and Truck for the fuels
21+
electricity, hydrogen, and synthetic fuels.
22+
"""
23+
subsectors = ["Bus", "LDV", "Rail", "Truck"]
24+
fuels = ["Electricity", "Hydrogen", "Liquids"]
25+
26+
mobility_data = pd.Series(0.0, index=fuels)
27+
28+
if year == "2020":
29+
logger.info(
30+
"For 2020, using hard-coded transport data from the Ariadne2-internal database."
31+
)
32+
33+
mobility_data = pd.Series(
34+
{
35+
"Electricity": 0.0 + 17.0 + 35.82 + 0.0,
36+
"Hydrogen": 0.0 + 0.0 + 0.0 + 0.0,
37+
"Liquids": 41.81 + 1369.34 + 11.18 + 637.23,
38+
}
39+
)
40+
41+
mobility_data = mobility_data.div(3.6e-6) # convert PJ to MWh
42+
mobility_data["million_EVs"] = 0.658407 + 0.120261 # BEV + PHEV
43+
44+
if ageb_for_mobility or uba_for_mobility:
45+
if uba_for_mobility:
46+
logger.warning(
47+
"For 2020, using historical AGEB and KBA data instead of UBA projections."
48+
)
49+
# AGEB 2020, https://ag-energiebilanzen.de/daten-und-fakten/bilanzen-1990-bis-2030/?_jahresbereich-bilanz=2011-2020
50+
mobility_data = pd.Series(
51+
{
52+
"Electricity": 39129.0 + 2394.0, # Schiene + Straße
53+
"Hydrogen": 0.0,
54+
"Liquids": 140718.0
55+
+ 1261942.0
56+
+ 10782.0
57+
+ 638820.0, # Bio Strasse + Diesel Strasse + Diesel Schiene + Otto Strasse
58+
}
59+
)
60+
mobility_data = mobility_data.div(3.6e-3) # convert PJ to MWH
61+
# https://www.kba.de/DE/Statistik/Produktkatalog/produkte/Fahrzeuge/fz27_b_uebersicht.html
62+
# FZ27_202101, table FZ 27.2, 1. January 2021:
63+
mobility_data["million_EVs"] = 0.358498 + 0.280149
64+
65+
elif year == "2025" and uba_for_mobility:
66+
# https://www.umweltbundesamt.de/sites/default/files/medien/11850/publikationen/projektionsbericht_2025.pdf, Abbildung 64 & 59,
67+
mobility_data = pd.Series(
68+
{
69+
"Electricity": 21.0,
70+
"Hydrogen": 0.0,
71+
"Liquids": 524.0 + 51.0,
72+
}
73+
)
74+
mobility_data["Liquids"] -= non_land_liquids[
75+
int(year)
76+
] # remove domestic navigation and aviation from UBA data to avoid double counting
77+
mobility_data = mobility_data.mul(1e6) # convert TWh to MWh
78+
mobility_data["million_EVs"] = 2.7 + 1.2 # BEV + PHEV
79+
80+
elif year == "2030" and uba_for_mobility:
81+
mobility_data = pd.Series(
82+
{
83+
"Electricity": 57.0,
84+
"Hydrogen": 14.0,
85+
"Liquids": 418.0 + 34.0 + 1.0,
86+
}
87+
)
88+
mobility_data["Liquids"] -= non_land_liquids[int(year)]
89+
mobility_data = mobility_data.mul(1e6)
90+
mobility_data["million_EVs"] = 8.7 + 1.8
91+
92+
elif year == "2035" and uba_for_mobility:
93+
mobility_data = pd.Series(
94+
{
95+
"Electricity": 117.0,
96+
"Hydrogen": 36.0,
97+
"Liquids": 237.0 + 26.0 + 1.0,
98+
}
99+
)
100+
mobility_data["Liquids"] -= non_land_liquids[int(year)]
101+
mobility_data = mobility_data.mul(1e6)
102+
mobility_data["million_EVs"] = 18.9 + 1.8
103+
104+
else:
105+
if uba_for_mobility:
106+
logger.error(
107+
f"Year {year} is not supported for UBA mobility projections. Please use only 2020, 2025, 2030, 2035."
108+
)
109+
110+
df = db[year].loc[snakemake.params.leitmodelle["transport"]]
111+
112+
for fuel in fuels:
113+
for subsector in subsectors:
114+
key = f"Final Energy|Transportation|{subsector}|{fuel}"
115+
mobility_data.loc[fuel] += df.get((key, "TWh/yr"), 0.0)
116+
117+
mobility_data = mobility_data.mul(1e6) # convert TWh to MWh
118+
mobility_data["million_EVs"] = (
119+
df.loc["Stock|Transportation|LDV|BEV", "million"]
120+
+ df.loc["Stock|Transportation|LDV|PHEV", "million"]
121+
)
122+
123+
return mobility_data
124+
125+
126+
if __name__ == "__main__":
127+
if "snakemake" not in globals():
128+
snakemake = mock_snakemake(
129+
"build_exogenous_mobility_data",
130+
simpl="",
131+
clusters=27,
132+
opts="",
133+
ll="vopt",
134+
sector_opts="none",
135+
planning_horizons="2020",
136+
run="KN2045_Mix",
137+
)
138+
configure_logging(snakemake)
139+
140+
db = pd.read_csv(
141+
snakemake.input.ariadne,
142+
index_col=["model", "scenario", "region", "variable", "unit"],
143+
).loc[
144+
:,
145+
snakemake.params.reference_scenario,
146+
"Deutschland",
147+
:,
148+
:,
149+
]
150+
151+
energy_totals = (
152+
pd.read_csv(
153+
snakemake.input.energy_totals,
154+
index_col=[0, 1],
155+
)
156+
.xs(
157+
snakemake.params.energy_totals_year,
158+
level="year",
159+
)
160+
.loc["DE"]
161+
)
162+
163+
domestic_aviation = energy_totals.loc["total domestic aviation"] * pd.Series(
164+
snakemake.params.aviation_demand_factor
165+
)
166+
167+
domestic_navigation = energy_totals.loc["total domestic navigation"] * pd.Series(
168+
snakemake.params.shipping_oil_share
169+
)
170+
171+
non_land_liquids = domestic_aviation + domestic_navigation
172+
173+
logger.info(
174+
f"Retrieving German mobility demand from {snakemake.params.leitmodelle['transport']} transport model."
175+
)
176+
# get mobility_data data
177+
mobility_data = get_mobility_data(
178+
db,
179+
snakemake.wildcards.planning_horizons,
180+
non_land_liquids,
181+
ageb_for_mobility=snakemake.params.ageb_for_mobility,
182+
uba_for_mobility=snakemake.params.uba_for_mobility,
183+
)
184+
185+
mobility_data.to_csv(snakemake.output.mobility_data, header=False)

0 commit comments

Comments
 (0)