Skip to content

Commit

Permalink
Merge pull request #653 from PyPSA/complete-carrier-set
Browse files Browse the repository at this point in the history
Complete carrier set
  • Loading branch information
FabianHofmann authored Jun 29, 2023
2 parents aec5664 + e2270ea commit 89ffa87
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 61 deletions.
68 changes: 66 additions & 2 deletions config/config.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,11 @@ plotting:
solar: "#f9d002"
solar PV: "#f9d002"
solar thermal: '#ffbf2b'
residential rural solar thermal: '#f1c069'
services rural solar thermal: '#eabf61'
residential urban decentral solar thermal: '#e5bc5a'
services urban decentral solar thermal: '#dfb953'
urban central solar thermal: '#d7b24c'
solar rooftop: '#ffea80'
# gas
OCGT: '#e0986c'
Expand All @@ -798,9 +803,15 @@ plotting:
gas boiler: '#db6a25'
gas boilers: '#db6a25'
gas boiler marginal: '#db6a25'
residential rural gas boiler: '#d4722e'
residential urban decentral gas boiler: '#cb7a36'
services rural gas boiler: '#c4813f'
services urban decentral gas boiler: '#ba8947'
urban central gas boiler: '#b0904f'
gas: '#e05b09'
fossil gas: '#e05b09'
natural gas: '#e05b09'
biogas to gas: '#e36311'
CCGT: '#a85522'
CCGT marginal: '#a85522'
allam: '#B98F76'
Expand All @@ -813,6 +824,11 @@ plotting:
# oil
oil: '#c9c9c9'
oil boiler: '#adadad'
residential rural oil boiler: '#a9a9a9'
services rural oil boiler: '#a5a5a5'
residential urban decentral oil boiler: '#a1a1a1'
urban central oil boiler: '#9d9d9d'
services urban decentral oil boiler: '#999999'
agriculture machinery oil: '#949494'
shipping oil: "#808080"
land transport oil: '#afafaf'
Expand All @@ -838,13 +854,20 @@ plotting:
solid biomass for industry CC: '#47411c'
solid biomass for industry co2 from atmosphere: '#736412'
solid biomass for industry co2 to stored: '#47411c'
urban central solid biomass CHP: '#9d9042'
urban central solid biomass CHP CC: '#6c5d28'
biomass boiler: '#8A9A5B'
residential rural biomass boiler: '#a1a066'
residential urban decentral biomass boiler: '#b0b87b'
services rural biomass boiler: '#c6cf98'
services urban decentral biomass boiler: '#dde5b5'
biomass to liquid: '#32CD32'
BioSNG: '#123456'
# power transmission
lines: '#6c9459'
transmission lines: '#6c9459'
electricity distribution grid: '#97ad8c'
low voltage: '#97ad8c'
# electricity demand
Electric load: '#110d63'
electric demand: '#110d63'
Expand All @@ -855,39 +878,75 @@ plotting:
# battery + EVs
battery: '#ace37f'
battery storage: '#ace37f'
battery charger: '#88a75b'
battery discharger: '#5d4e29'
home battery: '#80c944'
home battery storage: '#80c944'
home battery charger: '#5e8032'
home battery discharger: '#3c5221'
BEV charger: '#baf238'
V2G: '#e5ffa8'
land transport EV: '#baf238'
Li ion: '#baf238'
# hot water storage
water tanks: '#e69487'
residential rural water tanks: '#f7b7a3'
services rural water tanks: '#f3afa3'
residential urban decentral water tanks: '#f2b2a3'
services urban decentral water tanks: '#f1b4a4'
urban central water tanks: '#e9977d'
hot water storage: '#e69487'
hot water charging: '#e69487'
hot water discharging: '#e69487'
hot water charging: '#e8998b'
urban central water tanks charger: '#b57a67'
residential rural water tanks charger: '#b4887c'
residential urban decentral water tanks charger: '#b39995'
services rural water tanks charger: '#b3abb0'
services urban decentral water tanks charger: '#b3becc'
hot water discharging: '#e99c8e'
urban central water tanks discharger: '#b9816e'
residential rural water tanks discharger: '#ba9685'
residential urban decentral water tanks discharger: '#baac9e'
services rural water tanks discharger: '#bbc2b8'
services urban decentral water tanks discharger: '#bdd8d3'
# heat demand
Heat load: '#cc1f1f'
heat: '#cc1f1f'
heat demand: '#cc1f1f'
rural heat: '#ff5c5c'
residential rural heat: '#ff7c7c'
services rural heat: '#ff9c9c'
central heat: '#cc1f1f'
urban central heat: '#d15959'
decentral heat: '#750606'
residential urban decentral heat: '#a33c3c'
services urban decentral heat: '#cc1f1f'
low-temperature heat for industry: '#8f2727'
process heat: '#ff0000'
agriculture heat: '#d9a5a5'
# heat supply
heat pumps: '#2fb537'
heat pump: '#2fb537'
air heat pump: '#36eb41'
residential urban decentral air heat pump: '#48f74f'
services urban decentral air heat pump: '#5af95d'
urban central air heat pump: '#6cfb6b'
ground heat pump: '#2fb537'
residential rural ground heat pump: '#48f74f'
services rural ground heat pump: '#5af95d'
Ambient: '#98eb9d'
CHP: '#8a5751'
urban central gas CHP: '#8d5e56'
CHP CC: '#634643'
urban central gas CHP CC: '#6e4e4c'
CHP heat: '#8a5751'
CHP electric: '#8a5751'
district heating: '#e8beac'
resistive heater: '#d8f9b8'
residential rural resistive heater: '#bef5b5'
residential urban decentral resistive heater: '#b2f1a9'
services rural resistive heater: '#a5ed9d'
services urban decentral resistive heater: '#98e991'
urban central resistive heater: '#8cdf85'
retrofitting: '#8487e8'
building retrofitting: '#8487e8'
# hydrogen
Expand All @@ -899,13 +958,16 @@ plotting:
SMR CC: '#4f1745'
H2 liquefaction: '#d647bd'
hydrogen storage: '#bf13a0'
H2 Store: '#bf13a0'
H2 storage: '#bf13a0'
land transport fuel cell: '#6b3161'
H2 pipeline: '#f081dc'
H2 pipeline retrofitted: '#ba99b5'
H2 Fuel Cell: '#c251ae'
H2 fuel cell: '#c251ae'
H2 turbine: '#991f83'
H2 Electrolysis: '#ff29d9'
H2 electrolysis: '#ff29d9'
# ammonia
NH3: '#46caf0'
ammonia: '#46caf0'
Expand Down Expand Up @@ -954,9 +1016,11 @@ plotting:
waste: '#e3d37d'
other: '#000000'
geothermal: '#ba91b1'
AC: "#70af1d"
AC-AC: "#70af1d"
AC line: "#70af1d"
links: "#8a1caf"
HVDC links: "#8a1caf"
DC: "#8a1caf"
DC-DC: "#8a1caf"
DC link: "#8a1caf"
5 changes: 5 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ Upcoming Release
hydrogen fuel cell. Add switches for both re-electrification options under
``sector: hydrogen_turbine:`` and ``sector: hydrogen_fuel_cell:``.

* A new function named ``sanitize_carrier`` ensures that all unique carrier names are present in the network's carriers attribute, and adds nice names and colors for each carrier according to the provided configuration dictionary.

* Additional tech_color are added to include previously unlisted carriers.

* Remove ``vresutils`` dependency.

* Add option to include a piecewise linear approximation of transmission losses,
e.g. by setting ``solving: options: transmission_losses: 2`` for an
approximation with two tangents.


PyPSA-Eur 0.8.0 (18th March 2023)
=================================

Expand Down
126 changes: 78 additions & 48 deletions scripts/add_electricity.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,18 +123,61 @@ def calculate_annuity(n, r):
return 1 / n


def _add_missing_carriers_from_costs(n, costs, carriers):
missing_carriers = pd.Index(carriers).difference(n.carriers.index)
if missing_carriers.empty:
return
def sanitize_carriers(n, config):
"""
Sanitize the carrier information in a PyPSA Network object.
The function ensures that all unique carrier names are present in the network's
carriers attribute, and adds nice names and colors for each carrier according
to the provided configuration dictionary.
Parameters
----------
n : pypsa.Network
A PyPSA Network object that represents an electrical power system.
config : dict
A dictionary containing configuration information, specifically the
"plotting" key with "nice_names" and "tech_colors" keys for carriers.
Returns
-------
None
The function modifies the 'n' PyPSA Network object in-place, updating the
carriers attribute with nice names and colors.
Warnings
--------
Raises a warning if any carrier's "tech_colors" are not defined in the config dictionary.
"""

for c in n.iterate_components():
if "carrier" in c.df:
missing_carrier = set(c.df.carrier.unique()) - set(n.carriers.index) - {""}
if len(missing_carrier):
n.madd("Carrier", missing_carrier)

emissions_cols = (
costs.columns.to_series().loc[lambda s: s.str.endswith("_emissions")].values
carrier_i = n.carriers.index
nice_names = (
pd.Series(config["plotting"]["nice_names"])
.reindex(carrier_i)
.fillna(carrier_i.to_series().str.title())
)
n.carriers["nice_name"] = n.carriers.nice_name.where(
n.carriers.nice_name != "", nice_names
)
suptechs = missing_carriers.str.split("-").str[0]
emissions = costs.loc[suptechs, emissions_cols].fillna(0.0)
emissions.index = missing_carriers
n.import_components_from_dataframe(emissions, "Carrier")
colors = pd.Series(config["plotting"]["tech_colors"]).reindex(carrier_i)
if colors.isna().any():
missing_i = list(colors.index[colors.isna()])
logger.warning(f"tech_colors for carriers {missing_i} not defined in config.")
n.carriers["color"] = n.carriers.color.where(n.carriers.color != "", colors)


def add_co2_emissions(n, costs, carriers):
"""
Add CO2 emissions to the network's carriers attribute.
"""
suptechs = n.carriers.loc[carriers].index.str.split("-").str[0]
n.carriers.loc[carriers, "co2_emissions"] = costs.co2_emissions[suptechs].values


def load_costs(tech_costs, config, max_hours, Nyears=1.0):
Expand Down Expand Up @@ -309,57 +352,56 @@ def update_transmission_costs(n, costs, length_factor=1.0):


def attach_wind_and_solar(
n, costs, input_profiles, technologies, extendable_carriers, line_length_factor=1
n, costs, input_profiles, carriers, extendable_carriers, line_length_factor=1
):
# TODO: rename tech -> carrier, technologies -> carriers
_add_missing_carriers_from_costs(n, costs, technologies)
n.madd("Carrier", carriers)

for tech in technologies:
if tech == "hydro":
for car in carriers:
if car == "hydro":
continue

with xr.open_dataset(getattr(input_profiles, "profile_" + tech)) as ds:
with xr.open_dataset(getattr(input_profiles, "profile_" + car)) as ds:
if ds.indexes["bus"].empty:
continue

suptech = tech.split("-", 2)[0]
if suptech == "offwind":
supcar = car.split("-", 2)[0]
if supcar == "offwind":
underwater_fraction = ds["underwater_fraction"].to_pandas()
connection_cost = (
line_length_factor
* ds["average_distance"].to_pandas()
* (
underwater_fraction
* costs.at[tech + "-connection-submarine", "capital_cost"]
* costs.at[car + "-connection-submarine", "capital_cost"]
+ (1.0 - underwater_fraction)
* costs.at[tech + "-connection-underground", "capital_cost"]
* costs.at[car + "-connection-underground", "capital_cost"]
)
)
capital_cost = (
costs.at["offwind", "capital_cost"]
+ costs.at[tech + "-station", "capital_cost"]
+ costs.at[car + "-station", "capital_cost"]
+ connection_cost
)
logger.info(
"Added connection cost of {:0.0f}-{:0.0f} Eur/MW/a to {}".format(
connection_cost.min(), connection_cost.max(), tech
connection_cost.min(), connection_cost.max(), car
)
)
else:
capital_cost = costs.at[tech, "capital_cost"]
capital_cost = costs.at[car, "capital_cost"]

n.madd(
"Generator",
ds.indexes["bus"],
" " + tech,
" " + car,
bus=ds.indexes["bus"],
carrier=tech,
p_nom_extendable=tech in extendable_carriers["Generator"],
carrier=car,
p_nom_extendable=car in extendable_carriers["Generator"],
p_nom_max=ds["p_nom_max"].to_pandas(),
weight=ds["weight"].to_pandas(),
marginal_cost=costs.at[suptech, "marginal_cost"],
marginal_cost=costs.at[supcar, "marginal_cost"],
capital_cost=capital_cost,
efficiency=costs.at[suptech, "efficiency"],
efficiency=costs.at[supcar, "efficiency"],
p_max_pu=ds["profile"].transpose("time", "bus").to_pandas(),
)

Expand All @@ -373,8 +415,9 @@ def attach_conventional_generators(
conventional_params,
conventional_inputs,
):
carriers = set(conventional_carriers) | set(extendable_carriers["Generator"])
_add_missing_carriers_from_costs(n, costs, carriers)
carriers = list(set(conventional_carriers) | set(extendable_carriers["Generator"]))
n.madd("Carrier", carriers)
add_co2_emissions(n, costs, carriers)

ppl = (
ppl.query("carrier in @carriers")
Expand Down Expand Up @@ -430,7 +473,8 @@ def attach_conventional_generators(


def attach_hydro(n, costs, ppl, profile_hydro, hydro_capacities, carriers, **params):
_add_missing_carriers_from_costs(n, costs, carriers)
n.madd("Carrier", carriers)
add_co2_emissions(n, costs, carriers)

ppl = (
ppl.query('carrier == "hydro"')
Expand Down Expand Up @@ -562,7 +606,8 @@ def attach_extendable_generators(n, costs, ppl, carriers):
logger.warning(
"The function `attach_extendable_generators` is deprecated in v0.5.0."
)
_add_missing_carriers_from_costs(n, costs, carriers)
n.madd("Carrier", carriers)
add_co2_emissions(n, costs, carriers)

for tech in carriers:
if tech.startswith("OCGT"):
Expand Down Expand Up @@ -693,21 +738,6 @@ def estimate_renewable_capacities(n, year, tech_map, expansion_limit, countries)
)


def add_nice_carrier_names(n, config):
carrier_i = n.carriers.index
nice_names = (
pd.Series(config["plotting"]["nice_names"])
.reindex(carrier_i)
.fillna(carrier_i.to_series().str.title())
)
n.carriers["nice_name"] = nice_names
colors = pd.Series(config["plotting"]["tech_colors"]).reindex(carrier_i)
if colors.isna().any():
missing_i = list(colors.index[colors.isna()])
logger.warning(f"tech_colors for carriers {missing_i} not defined in config.")
n.carriers["color"] = colors


if __name__ == "__main__":
if "snakemake" not in globals():
from _helpers import mock_snakemake
Expand Down Expand Up @@ -790,7 +820,7 @@ def add_nice_carrier_names(n, config):

update_p_nom_max(n)

add_nice_carrier_names(n, snakemake.config)
sanitize_carriers(n, snakemake.config)

n.meta = snakemake.config
n.export_to_netcdf(snakemake.output[0])
Loading

0 comments on commit 89ffa87

Please sign in to comment.