Skip to content

Commit

Permalink
Add endogenous transport option
Browse files Browse the repository at this point in the history
- select with endogenous_transport in config file
- it is  possible to select transport shares for part of the fuel types
- creates ICE-fleet for baseyear if myopic is selected and no transport share is defined
- adds constraints for endogenously chosen EVs in solve_network
  • Loading branch information
s8au committed Sep 6, 2023
1 parent 624d240 commit 659f977
Show file tree
Hide file tree
Showing 5 changed files with 345 additions and 89 deletions.
5 changes: 5 additions & 0 deletions config/config.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,11 @@ sector:
bev_avail_max: 0.95
bev_avail_mean: 0.8
v2g: true
endogenous_transport: false
energy_to_cars: 0.01 #Mw@wheel/car assuming 0.2kwh/km and 50km/h
EV_consumption_1car: 0.01 #MWh_elec/hour (assuming 0.2 kWh/km https://github.com/PyPSA/pypsa-eur/blob/1fbe971ab8dab60d972d3a7b905b9cec7171c0ad/config/config.default.yaml#L382 and velocity of 50km/h
ICE_consumption_1car: 0.033 #MWh_oil/hour (assuming 0.66 kWh_oil/km and 50 km/h (with the link efficiency 0.3, they are equivalent to 0.01 MWh_elec/hour)
H2_consumption_1car: 0.02 #MWh_H2/hour (assuming 0.4 kWh_H2/km and 50km/h) (with efficiency 0.5, they are equivalent to 0.01 MWh_elec/hour)
land_transport_fuel_cell_share:
2020: 0
2030: 0.05
Expand Down
1 change: 1 addition & 0 deletions rules/solve_myopic.smk
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ rule add_existing_baseyear:
existing_solar="data/existing_infrastructure/solar_capacity_IRENA.csv",
existing_onwind="data/existing_infrastructure/onwind_capacity_IRENA.csv",
existing_offwind="data/existing_infrastructure/offwind_capacity_IRENA.csv",
existing_transport_demand=RESOURCES + "transport_demand_s{simpl}_{clusters}.csv",
output:
RESULTS
+ "prenetworks-brownfield/elec_s{simpl}_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc",
Expand Down
41 changes: 41 additions & 0 deletions scripts/add_existing_baseyear.py
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,40 @@ def add_heating_capacities_installed_before_baseyear(
],
)

def add_land_transport_installed_before_baseyear(
n,
grouping_year,
):

pop_layout = pd.read_csv(snakemake.input.clustered_pop_layout, index_col=0)
nodes = pop_layout.index

ice_efficiency = options["transport_internal_combustion_efficiency"]
p_set = n.loads_t.p_set[n.loads[n.loads.carrier.str.contains('land transport demand')].index]
p_set.columns = p_set.columns.str.rstrip('land transport')

split_years = costs.at['Liquid fuels ICE (passenger cars)', 'lifetime'] - 1
year = range(int(grouping_year-split_years), int(grouping_year),1)
set_p_nom = p_set.max(axis=0)/len(year)
p_set_year = p_set/(len(year))
for year in year:
n.madd(
"Link",
nodes,
suffix=f" land transport oil-{year}",
bus0=spatial.oil.nodes,
bus1=nodes + " land transport",
bus2="co2 atmosphere",
carrier="land transport oil",
capital_cost = 0,
efficiency = 1.0/ice_efficiency * p_set_year[nodes],
efficiency2 = costs.at['oil', 'CO2 intensity'],
lifetime = costs.at['Liquid fuels ICE (passenger cars)', 'lifetime'],
p_nom = set_p_nom,
p_min_pu = p_set_year/set_p_nom*ice_efficiency/p_set_year[nodes],
p_max_pu = p_set_year/set_p_nom*ice_efficiency/p_set_year[nodes],
build_year = year
)

# %%
if __name__ == "__main__":
Expand Down Expand Up @@ -674,6 +708,13 @@ def add_heating_capacities_installed_before_baseyear(
default_lifetime,
)

endo_transport = ( (options["land_transport_electric_share"][baseyear] is None )
and (options["land_transport_fuel_cell_share"][baseyear] is None)
and (options["land_transport_ice_share"][baseyear] is None))

if "T" in opts and options["endogenous_transport"] and endo_transport:
add_land_transport_installed_before_baseyear(n, baseyear)

if options.get("cluster_heat_buses", False):
cluster_heat_buses(n)

Expand Down
256 changes: 167 additions & 89 deletions scripts/prepare_sector_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -1454,114 +1454,193 @@ def add_land_transport(n, costs):
fuel_cell_share = get(options["land_transport_fuel_cell_share"], investment_year)
electric_share = get(options["land_transport_electric_share"], investment_year)
ice_share = get(options["land_transport_ice_share"], investment_year)

if options["endogenous_transport"]==False:
if any([fuel_cell_share == None, electric_share == None, ice_share == None]):
logger.warning(
f"Exogenous transport selected, but not all transport shares are defined."
)
total_share = fuel_cell_share + electric_share + ice_share
if total_share != 1:
logger.warning(
f"Total land transport shares sum up to {total_share:.2%}, corresponding to increased or decreased demand assumptions."
)

total_share = fuel_cell_share + electric_share + ice_share
if total_share != 1:
logger.warning(
f"Total land transport shares sum up to {total_share:.2%}, corresponding to increased or decreased demand assumptions."
)

logger.info(f"FCEV share: {fuel_cell_share*100}%")
logger.info(f"EV share: {electric_share*100}%")
logger.info(f"ICEV share: {ice_share*100}%")
if options["endogenous_transport"]==True:
if any([fuel_cell_share != None, electric_share != None, ice_share != None]):
logger.warning(
f"Endogenous transport activated and transport shares defined."
)

if fuel_cell_share != None:
logger.info(f"FCEV share: {fuel_cell_share*100}%")
if electric_share != None:
logger.info(f"EV share: {electric_share*100}%")
if ice_share != None:
logger.info(f"ICEV share: {ice_share*100}%")

nodes = pop_layout.index

if electric_share > 0:
n.add("Carrier", "Li ion")
# Add load for transport demand
n.add("Carrier", "land transport demand")

n.madd(
"Bus",
n.madd("Bus",
nodes,
location=nodes,
suffix=" EV battery",
carrier="Li ion",
unit="MWh_el",
suffix=" land transport",
carrier="land transport demand")

# p_set formerly used only for the EV share
p_set = (
(
transport[nodes]
+ cycling_shift(transport[nodes], 1)
+ cycling_shift(transport[nodes], 2)
)
/ 3
)
n.madd(
"Load",
nodes,
suffix=" land transport",
bus=nodes + " land transport",
carrier="land transport demand",
p_set=p_set,
)

p_set = (
electric_share
* (
transport[nodes]
+ cycling_shift(transport[nodes], 1)
+ cycling_shift(transport[nodes], 2)
)
/ 3
)
# Add EV links
if electric_share != 0:
n.add("Carrier", "Li ion")

n.madd(
"Load",
"Bus",
nodes,
suffix=" land transport EV",
bus=nodes + " EV battery",
carrier="land transport EV",
p_set=p_set,
location=nodes,
suffix=" EV driving",
carrier="Li ion",
unit="MWh_el",
)

p_nom = number_cars * options.get("bev_charge_rate", 0.011) * electric_share

n.madd(
"Link",
nodes,
suffix=" BEV charger",
bus0=nodes,
bus1=nodes + " EV battery",
p_nom=p_nom,
bus1=nodes + " EV driving",
p_nom_extendable = True,
carrier="BEV charger",
p_max_pu=avail_profile[nodes],
efficiency=options.get("bev_charge_efficiency", 0.9),
efficiency=1, # instead of options.get("bev_charge_efficiency", 0.9) -> efficiency already accounted for in build_transport_demand
# These were set non-zero to find LU infeasibility when availability = 0.25
# p_nom_extendable=True,
# p_nom_min=p_nom,
# capital_cost=1e6, #i.e. so high it only gets built where necessary
)

if electric_share > 0 and options["v2g"]:
n.madd(
"Link",
nodes,
suffix=" V2G",
bus1=nodes,
bus0=nodes + " EV battery",
p_nom=p_nom,
carrier="V2G",
p_max_pu=avail_profile[nodes],
efficiency=options.get("bev_charge_efficiency", 0.9),
)
# Only when v2g option is True, add the v2g link
if options["v2g"]:
n.madd(
"Link",
nodes,
suffix=" V2G",
bus1=nodes,
bus0=nodes + " EV driving",
p_nom_extendable = True,
carrier="V2G",
p_max_pu=avail_profile[nodes],
efficiency=options.get("bev_charge_efficiency", 0.9),
)

if electric_share > 0 and options["bev_dsm"]:
e_nom = (
number_cars
* options.get("bev_energy", 0.05)
* options["bev_availability"]
* electric_share
)

# add bev managment when enabled
n.add("Carrier", "EV battery storage")

if options["bev_dsm"]:
n.madd(
"Store",
nodes,
suffix=" EV battery storage",
bus=nodes + " EV driving",
carrier="EV battery storage",
e_cyclic=True,
e_nom_extendable=True,
e_max_pu=1,
e_min_pu=dsm_profile[nodes],
#lifetime?
)

n.madd(
"Store",
"Link",
nodes,
suffix=" battery storage",
bus=nodes + " EV battery",
carrier="battery storage",
e_cyclic=True,
e_nom=e_nom,
e_max_pu=1,
e_min_pu=dsm_profile[nodes],
suffix=" land transport EV",
bus0=nodes + " EV driving",
bus1 =nodes + " land transport",
carrier="land transport EV",
lifetime = costs.at['Battery electric (passenger cars)', 'lifetime'],
capital_cost = costs.at["Battery electric (passenger cars)", "fixed"]/(options['energy_to_cars']*options.get("bev_charge_efficiency", 0.9)),
efficiency = 1, #costs.at['Battery electric (passenger cars)', 'efficiency'], #efficiency already accounted for in build_transport_demand
p_nom_extendable=True,
)

if fuel_cell_share > 0:
#exogenous pathway if electric share is defined
if float(electric_share or 0) > 0:
# set p_nom to fulfill electric share, capital cost to 0
n.links.loc[n.links.carrier=='land transport EV', "p_nom_extendable"] = False
for place in nodes:
p_nom = p_set #transport
n.links.loc[(n.links.carrier=="land transport EV") & (n.links.bus1.str.contains(place)),'p_nom'] = electric_share*max(p_nom[place])
n.links_t.p_min_pu[n.links[(n.links.carrier=="land transport EV") & (n.links.bus1.str.contains(place))].index.values[0]] = p_nom[place]/max(p_nom[place])/costs.at['Battery electric (passenger cars)', 'efficiency']
n.links_t.p_max_pu[n.links[(n.links.carrier=="land transport EV") & (n.links.bus1.str.contains(place))].index.values[0]] = p_nom[place]/max(p_nom[place])/costs.at['Battery electric (passenger cars)', 'efficiency']

n.links.loc[n.links.carrier=='land transport EV', "capital_cost"] = 0

p_nom = number_cars * options.get("bev_charge_rate", 0.011) * electric_share
n.links.loc[n.links.carrier=="BEV charger", "p_nom_extendable"] = False
for place in nodes:
n.links.loc[(n.links.carrier=="BEV charger") & (n.links.bus1.str.contains(place)), "p_nom"] = number_cars[place] * options.get("bev_charge_rate", 0.011) * electric_share


if options["v2g"]:
n.links.loc[n.links.carrier=="V2G", "p_nom_extendable"] = False
for place in nodes:
n.links.loc[(n.links.carrier=="V2G") & (n.links.bus1.str.contains(place)), "p_nom"] = number_cars[place] * options.get("bev_charge_rate", 0.011) * electric_share


if options["bev_dsm"]:
n.stores.loc[n.stores.carrier=="EV battery storage", "e_nom_extendable"] = False
for place in nodes:
n.stores.loc[(n.stores.carrier=="EV battery storage") & (n.links.bus1.str.contains(place)), "e_nom"] = number_cars[place] * options.get("bev_energy", 0.05) * options["bev_availability"] * electric_share

# Add hydrogen vehicle links
if fuel_cell_share != 0:
n.madd(
"Load",
"Link",
nodes,
suffix=" land transport fuel cell",
bus=nodes + " H2",
bus0=nodes + " H2",
bus1=nodes + " land transport",
carrier="land transport fuel cell",
p_set=fuel_cell_share
/ options["transport_fuel_cell_efficiency"]
* transport[nodes],
lifetime = costs.at['Hydrogen fuel cell (passenger cars)', 'lifetime'],
efficiency= 1.0 / options["transport_fuel_cell_efficiency"] * p_set[nodes],
capital_cost = costs.at["Hydrogen fuel cell (passenger cars)", "fixed"]/(options['energy_to_cars'] * options["transport_fuel_cell_efficiency"]),
p_nom_extendable=True,

)
#exogenous pathway if fuel_cell_share is defined
if float(fuel_cell_share or 0) > 0:
p_nom = p_set
n.links.loc[n.links.carrier=='land transport fuel cell', "p_nom_extendable"] = False
for place in nodes:
n.links.loc[(n.links.carrier=="land transport fuel cell") & (n.links.bus1.str.contains(place)),'p_nom'] = fuel_cell_share * max(p_nom[place])
n.links_t.p_min_pu[n.links[(n.links.carrier=="land transport fuel cell") & (n.links.bus1.str.contains(place))].index.values[0]] = (p_nom[place])/max(p_nom[place])* options["transport_fuel_cell_efficiency"]/p_set[place]
n.links_t.p_max_pu[n.links[(n.links.carrier=="land transport fuel cell") & (n.links.bus1.str.contains(place))].index.values[0]] = (p_nom[place])/max(p_nom[place])* options["transport_fuel_cell_efficiency"]/p_set[place]
n.links.loc[n.links.carrier=='land transport fuel cell', "capital_cost"] = 0

if ice_share > 0:
# add internal combustion engine vehicle links
if ice_share != 0:

if "oil" not in n.buses.carrier.unique():
n.madd(
"Bus",
Expand All @@ -1571,32 +1650,31 @@ def add_land_transport(n, costs):
unit="MWh_LHV",
)

ice_efficiency = options["transport_internal_combustion_efficiency"]

n.madd(
"Load",
"Link",
nodes,
suffix=" land transport oil",
bus=spatial.oil.nodes,
bus0=spatial.oil.nodes,
bus1=nodes + " land transport",
bus2="co2 atmosphere",
carrier="land transport oil",
p_set=ice_share / ice_efficiency * transport[nodes],
)

co2 = (
ice_share
/ ice_efficiency
* transport[nodes].sum().sum()
/ nhours
* costs.at["oil", "CO2 intensity"]
)

n.add(
"Load",
"land transport oil emissions",
bus="co2 atmosphere",
carrier="land transport oil emissions",
p_set=-co2,
)
lifetime = costs.at['Liquid fuels ICE (passenger cars)', 'lifetime'],
capital_cost = costs.at["Liquid fuels ICE (passenger cars)", "fixed"]/(options['energy_to_cars'] * options["transport_internal_combustion_efficiency"]),
efficiency = 1.0/options["transport_internal_combustion_efficiency"] * p_set[nodes],
efficiency2 = costs.at['oil', 'CO2 intensity'],
p_nom_extendable=True,
)

#exogenous pathway if ice_share is defined
if float(ice_share or 0) > 0:
p_nom = p_set
ice_efficiency = options["transport_internal_combustion_efficiency"]
n.links.loc[n.links.carrier=='land transport oil', "p_nom_extendable"] = False
for place in nodes:
n.links.loc[(n.links.carrier=="land transport oil") & (n.links.bus1.str.contains(place)),'p_nom'] = ice_share * max(p_nom[place]),
n.links_t.p_min_pu[n.links[(n.links.carrier=="land transport oil") & (n.links.bus1.str.contains(place))].index.values[0]] = (p_nom[place])/max(p_nom[place])*ice_efficiency/p_set[place]
n.links_t.p_max_pu[n.links[(n.links.carrier=="land transport oil") & (n.links.bus1.str.contains(place))].index.values[0]] = (p_nom[place])/max(p_nom[place])*ice_efficiency/p_set[place]
n.links.loc[n.links.carrier=='land transport oil', "capital_cost"] = 0


def build_heat_demand(n):
Expand Down
Loading

0 comments on commit 659f977

Please sign in to comment.