Skip to content

Commit

Permalink
Merge pull request #957 from PyPSA/land-transport-fix
Browse files Browse the repository at this point in the history
Land transport fix
  • Loading branch information
fneum committed May 15, 2024
2 parents be28996 + 438f651 commit 44fb8ca
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 162 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#
# SPDX-License-Identifier: CC0-1.0

master

.snakemake*
.ipynb_checkpoints
__pycache__
Expand Down
7 changes: 4 additions & 3 deletions config/config.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,6 @@ sector:
bev_availability: 0.5
bev_energy: 0.05
bev_charge_efficiency: 0.9
bev_plug_to_wheel_efficiency: 0.2
bev_charge_rate: 0.011
bev_avail_max: 0.95
bev_avail_mean: 0.8
Expand Down Expand Up @@ -460,8 +459,9 @@ sector:
2040: 0.3
2045: 0.15
2050: 0
transport_fuel_cell_efficiency: 0.5
transport_internal_combustion_efficiency: 0.3
transport_electric_efficiency: 53.19 # 1 MWh_el = 53.19*100 km
transport_fuel_cell_efficiency: 30.003 # 1 MWh_H2 = 30.003*100 km
transport_ice_efficiency: 16.0712 # 1 MWh_oil = 16.0712 * 100 km
agriculture_machinery_electric_share: 0
agriculture_machinery_oil_share: 1
agriculture_machinery_fuel_efficiency: 0.7
Expand Down Expand Up @@ -1041,6 +1041,7 @@ plotting:
BEV charger: '#baf238'
V2G: '#e5ffa8'
land transport EV: '#baf238'
land transport demand: '#38baf2'
Li ion: '#baf238'
# hot water storage
water tanks: '#e69487'
Expand Down
6 changes: 3 additions & 3 deletions doc/configtables/sector.csv
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ bev_dsm,--,"{true, false}",Add the option for battery electric vehicles (BEV) to
bev_availability,--,float,The share for battery electric vehicles (BEV) that are able to do demand side management (DSM)
bev_energy,--,float,The average size of battery electric vehicles (BEV) in MWh
bev_charge_efficiency,--,float,Battery electric vehicles (BEV) charge and discharge efficiency
bev_plug_to_wheel _efficiency,km/kWh,float,The distance battery electric vehicles (BEV) can travel in km per kWh of energy charge in battery. Base value comes from `Tesla Model S <https://www.fueleconomy.gov/feg/>`_
bev_charge_rate,MWh,float,The power consumption for one electric vehicle (EV) in MWh. Value derived from 3-phase charger with 11 kW.
bev_avail_max,--,float,The maximum share plugged-in availability for passenger electric vehicles.
bev_avail_mean,--,float,The average share plugged-in availability for passenger electric vehicles.
v2g,--,"{true, false}",Allows feed-in to grid from EV battery
land_transport_fuel_cell _share,--,Dictionary with planning horizons as keys.,The share of vehicles that uses fuel cells in a given year
land_transport_electric _share,--,Dictionary with planning horizons as keys.,The share of vehicles that uses electric vehicles (EV) in a given year
land_transport_ice _share,--,Dictionary with planning horizons as keys.,The share of vehicles that uses internal combustion engines (ICE) in a given year. What is not EV or FCEV is oil-fuelled ICE.
transport_fuel_cell _efficiency,--,float,The H2 conversion efficiencies of fuel cells in transport
transport_internal _combustion_efficiency,--,float,The oil conversion efficiencies of internal combustion engine (ICE) in transport
transport_electric_efficiency,MWh/100km,float,The conversion efficiencies of electric vehicles in transport
transport_fuel_cell_efficiency,MWh/100km,float,The H2 conversion efficiencies of fuel cells in transport
transport_ice_efficiency,MWh/100km,float,The oil conversion efficiencies of internal combustion engine (ICE) in transport
agriculture_machinery _electric_share,--,float,The share for agricultural machinery that uses electricity
agriculture_machinery _oil_share,--,float,The share for agricultural machinery that uses oil
agriculture_machinery _fuel_efficiency,--,float,The efficiency of electric-powered machinery in the conversion of electricity to meet agricultural needs.
Expand Down
3 changes: 3 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ Upcoming Release

* bugfix: convert Strings to pathlib.Path objects as input to ConfigSettings

* bugfix: fix distinction of temperature-dependent correction factors for the
energy demand of electric vehicles, ICES fuel cell cars.

* Allow the use of more solvers in clustering (Xpress, COPT, Gurobi, CPLEX, SCIP, MOSEK).

* Enhanced support for choosing different weather years
Expand Down
5 changes: 2 additions & 3 deletions scripts/build_energy_totals.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,13 +396,12 @@ def build_idees(countries):
names=["country", "year"],
)

# efficiency kgoe/100km -> ktoe/100km
totals.loc[:, "passenger car efficiency"] *= 1e3
# convert ktoe to TWh
exclude = totals.columns.str.fullmatch("passenger cars")
totals.loc[:, ~exclude] *= 11.63 / 1e3

# convert TWh/100km to kWh/km
totals.loc[:, "passenger car efficiency"] *= 10

return totals


Expand Down
47 changes: 21 additions & 26 deletions scripts/build_transport_demand.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@


def build_nodal_transport_data(fn, pop_layout, year):
# get numbers of car and fuel efficiency per country
transport_data = pd.read_csv(fn, index_col=[0, 1])
transport_data = transport_data.xs(min(2015, year), level="year")

# break number of cars down to nodal level based on population density
nodal_transport_data = transport_data.loc[pop_layout.ct].fillna(0.0)
nodal_transport_data.index = pop_layout.index
nodal_transport_data["number cars"] = (
pop_layout["fraction"] * nodal_transport_data["number cars"]
)
# fill missing fuel efficiency with average data
nodal_transport_data.loc[
nodal_transport_data["average fuel efficiency"] == 0.0,
"average fuel efficiency",
Expand All @@ -41,26 +44,20 @@ def build_nodal_transport_data(fn, pop_layout, year):


def build_transport_demand(traffic_fn, airtemp_fn, nodes, nodal_transport_data):
## Get overall demand curve for all vehicles

"""
Returns transport demand per bus in unit km driven [100 km].
"""
# averaged weekly counts from the year 2010-2015
traffic = pd.read_csv(traffic_fn, skiprows=2, usecols=["count"]).squeeze("columns")

# create annual profile take account time zone + summer time
transport_shape = generate_periodic_profiles(
dt_index=snapshots,
nodes=nodes,
weekly_profile=traffic.values,
)
transport_shape = transport_shape / transport_shape.sum()

# electric motors are more efficient, so alter transport demand

plug_to_wheels_eta = options["bev_plug_to_wheel_efficiency"]
battery_to_wheels_eta = plug_to_wheels_eta * options["bev_charge_efficiency"]

efficiency_gain = (
nodal_transport_data["average fuel efficiency"] / battery_to_wheels_eta
)

# get heating demand for correction to demand time series
temperature = xr.open_dataarray(airtemp_fn).to_pandas()

Expand All @@ -73,16 +70,7 @@ def build_transport_demand(traffic_fn, airtemp_fn, nodes, nodal_transport_data):
options["ICE_upper_degree_factor"],
)

dd_EV = transport_degree_factor(
temperature,
options["transport_heating_deadband_lower"],
options["transport_heating_deadband_upper"],
options["EV_lower_degree_factor"],
options["EV_upper_degree_factor"],
)

# divide out the heating/cooling demand from ICE totals
# and multiply back in the heating/cooling demand for EVs
ice_correction = (transport_shape * (1 + dd_ICE)).sum() / transport_shape.sum()

energy_totals_transport = (
Expand All @@ -91,10 +79,11 @@ def build_transport_demand(traffic_fn, airtemp_fn, nodes, nodal_transport_data):
- pop_weighted_energy_totals["electricity rail"]
)

return (
(transport_shape.multiply(energy_totals_transport) * 1e6 * nyears)
.divide(efficiency_gain * ice_correction)
.multiply(1 + dd_EV)
# convert average fuel efficiency from kW/100 km -> MW/100km
eff = nodal_transport_data["average fuel efficiency"] / 1e3

return (transport_shape.multiply(energy_totals_transport) * 1e6 * nyears).divide(
eff * ice_correction
)


Expand Down Expand Up @@ -131,11 +120,14 @@ def bev_availability_profile(fn, snapshots, nodes, options):
"""
Derive plugged-in availability for passenger electric vehicles.
"""
# car count in typical week
traffic = pd.read_csv(fn, skiprows=2, usecols=["count"]).squeeze("columns")

# maximum share plugged-in availability for passenger electric vehicles
avail_max = options["bev_avail_max"]
# average share plugged-in availability for passenger electric vehicles
avail_mean = options["bev_avail_mean"]

# linear scaling, highest when traffic is lowest, decreases if traffic increases
avail = avail_max - (avail_max - avail_mean) * (traffic - traffic.min()) / (
traffic.mean() - traffic.min()
)
Expand All @@ -156,6 +148,8 @@ def bev_availability_profile(fn, snapshots, nodes, options):
def bev_dsm_profile(snapshots, nodes, options):
dsm_week = np.zeros((24 * 7,))

# assuming that at a certain time ("bev_dsm_restriction_time") EVs have to
# be charged to a minimum value (defined in bev_dsm_restriction_value)
dsm_week[(np.arange(0, 7, 1) * 24 + options["bev_dsm_restriction_time"])] = options[
"bev_dsm_restriction_value"
]
Expand All @@ -167,14 +161,15 @@ def bev_dsm_profile(snapshots, nodes, options):
)


# %%
if __name__ == "__main__":
if "snakemake" not in globals():
from _helpers import mock_snakemake

snakemake = mock_snakemake(
"build_transport_demand",
simpl="",
clusters=60,
clusters=37,
)
configure_logging(snakemake)
set_scenario_config(snakemake)
Expand Down
Loading

0 comments on commit 44fb8ca

Please sign in to comment.