add option to vary parameter (#1244)

* add option to vary parameter

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* remove logger.info

* adjust maybe_adjust_costs_and_potentials

* update configtables

* revert removed cost_factor

* reset build_energy_totals to master

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* add release notes

* Revert "revert removed cost_factor"

This reverts commit b7154f046954bd6de34c2910f3f9f52b44d5f233.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Fabian Neumann <fabian.neumann@outlook.de>
This commit is contained in:
lisazeyen 2024-09-12 16:02:10 +02:00 committed by GitHub
parent e8e0833e3a
commit 1b97a16bfa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 96 additions and 69 deletions

View File

@ -662,7 +662,6 @@ sector:
use_electrolysis_waste_heat: 0.25
electricity_transmission_grid: true
electricity_distribution_grid: true
electricity_distribution_grid_cost_factor: 1.0
electricity_grid_connection: true
transmission_efficiency:
DC:
@ -871,7 +870,12 @@ clustering:
# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#adjustments
adjustments:
electricity: false
sector: false
sector:
factor:
Link:
electricity distribution grid:
capital_cost: 1.0
absolute: false
# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#solving
solving:

View File

@ -1,8 +1,14 @@
,Unit,Values,Description
adjustments,,,
-- electricity,bool or dict,,"Parameter adjustments for capital cost, marginal cost, and maximum capacities of carriers. Applied in :mod:`prepare_network.`"
-- -- {attr},,,"Attribute can be ``e_nom_opt``, ``p_nom_opt``, ``marginal_cost`` or ``capital_cost``"
-- -- -- {carrier},float,per-unit,"Any carrier of the network to which parameter adjustment factor should be applied."
-- sector,bool or dict,,"Parameter adjustments for capital cost, marginal cost, and maximum capacities of carriers. Applied in :mod:`prepare_sector_network.`"
-- -- {attr},,,"Attribute can be ``e_nom_opt``, ``p_nom_opt``, ``marginal_cost`` or ``capital_cost``"
-- -- -- {carrier},float,per-unit,"Any carrier of the network to which parameter adjustment factor should be applied."
-- electricity,bool or dict,,Parameter adjustments applied in :mod:`prepare_network.`
-- -- factor,,,Multiply original value with given factor
-- -- absolute,,,Set attribute to absolute value
-- -- -- {component},,,PyPSA component in :mod:`prepare_network.`
-- -- -- -- {carrier},,,Any carrier of the network to which parameter adjustment factor should be applied.
-- -- -- -- -- {attr},float,per-unit,Attribute to which parameter adjustment factor should be applied.
-- sector,bool or dict,,Parameter adjustments applied in :mod:`prepare_sector_network.`
-- -- factor,,,Multiply original value with given factor
-- -- absolute,,,Set attribute to absolute value
-- -- -- {component},,,PyPSA component in :mod:`prepare_network.`
-- -- -- -- {carrier},,,Any carrier of the network to which parameter adjustment factor should be applied.
-- -- -- -- -- {attr},Float or dict,per-unit,Attribute to which parameter adjustment factor should be applied. Can be also a dictionary with planning horizons as keys.

1 Unit Values Description
2 adjustments
3 -- electricity bool or dict Parameter adjustments for capital cost, marginal cost, and maximum capacities of carriers. Applied in :mod:`prepare_network.` Parameter adjustments applied in :mod:`prepare_network.`
4 -- -- {attr} -- -- factor Attribute can be ``e_nom_opt``, ``p_nom_opt``, ``marginal_cost`` or ``capital_cost`` Multiply original value with given factor
5 -- -- -- {carrier} -- -- absolute float per-unit Any carrier of the network to which parameter adjustment factor should be applied. Set attribute to absolute value
6 -- sector -- -- -- {component} bool or dict Parameter adjustments for capital cost, marginal cost, and maximum capacities of carriers. Applied in :mod:`prepare_sector_network.` PyPSA component in :mod:`prepare_network.`
7 -- -- {attr} -- -- -- -- {carrier} Attribute can be ``e_nom_opt``, ``p_nom_opt``, ``marginal_cost`` or ``capital_cost`` Any carrier of the network to which parameter adjustment factor should be applied.
8 -- -- -- {carrier} -- -- -- -- -- {attr} float per-unit Any carrier of the network to which parameter adjustment factor should be applied. Attribute to which parameter adjustment factor should be applied.
9 -- sector bool or dict Parameter adjustments applied in :mod:`prepare_sector_network.`
10 -- -- factor Multiply original value with given factor
11 -- -- absolute Set attribute to absolute value
12 -- -- -- {component} PyPSA component in :mod:`prepare_network.`
13 -- -- -- -- {carrier} Any carrier of the network to which parameter adjustment factor should be applied.
14 -- -- -- -- -- {attr} Float or dict per-unit Attribute to which parameter adjustment factor should be applied. Can be also a dictionary with planning horizons as keys.

View File

@ -10,6 +10,7 @@ Release Notes
.. Upcoming Release
* Add function ``modify_attribute`` which allows to adjust every attribute of every PyPSA component either by a multiplication with a factor or setting an absolute value. These adjustments can also depend on the planning horizons and are set in the config under ``adjustments``. The function ``maybe_adjust_costs_and_potentials`` is removed.
* Add technology options for methanol, like electricity production from methanol, biomass to methanol, methanol to kerosene, ...

View File

@ -282,6 +282,38 @@ def aggregate_p(n):
)
def get(item, investment_year=None):
"""
Check whether item depends on investment year.
"""
if not isinstance(item, dict):
return item
elif investment_year in item.keys():
return item[investment_year]
else:
logger.warning(
f"Investment key {investment_year} not found in dictionary {item}."
)
keys = sorted(item.keys())
if investment_year < keys[0]:
logger.warning(f"Lower than minimum key. Taking minimum key {keys[0]}")
return item[keys[0]]
elif investment_year > keys[-1]:
logger.warning(f"Higher than maximum key. Taking maximum key {keys[0]}")
return item[keys[-1]]
else:
logger.warning(
"Interpolate linearly between the next lower and next higher year."
)
lower_key = max(k for k in keys if k < investment_year)
higher_key = min(k for k in keys if k > investment_year)
lower = item[lower_key]
higher = item[higher_key]
return lower + (higher - lower) * (investment_year - lower_key) / (
higher_key - lower_key
)
def aggregate_e_nom(n):
return pd.concat(
[

View File

@ -64,6 +64,7 @@ import pandas as pd
import pypsa
from _helpers import (
configure_logging,
get,
set_scenario_config,
update_config_from_wildcards,
)
@ -75,26 +76,43 @@ idx = pd.IndexSlice
logger = logging.getLogger(__name__)
def maybe_adjust_costs_and_potentials(n, adjustments):
def modify_attribute(n, adjustments, investment_year, modification="factor"):
if not adjustments[modification]:
return
change_dict = adjustments[modification]
for c in change_dict.keys():
if c not in n.components.keys():
logger.warning(f"{c} needs to be a PyPSA Component")
continue
for carrier in change_dict[c].keys():
ind_i = n.df(c)[n.df(c).carrier == carrier].index
if ind_i.empty:
continue
for parameter in change_dict[c][carrier].keys():
if parameter not in n.df(c).columns:
logger.warning(f"Attribute {parameter} needs to be in {c} columns.")
continue
if investment_year:
factor = get(change_dict[c][carrier][parameter], investment_year)
else:
factor = change_dict[c][carrier][parameter]
if modification == "factor":
logger.info(f"Modify {parameter} of {carrier} by factor {factor} ")
n.df(c).loc[ind_i, parameter] *= factor
elif modification == "absolute":
logger.info(f"Set {parameter} of {carrier} to {factor} ")
n.df(c).loc[ind_i, parameter] = factor
else:
logger.warning(
f"{modification} needs to be either 'absolute' or 'factor'."
)
def maybe_adjust_costs_and_potentials(n, adjustments, investment_year=None):
if not adjustments:
return
for attr, carrier_factor in adjustments.items():
for carrier, factor in carrier_factor.items():
# beware if factor is 0 and p_nom_max is np.inf, 0*np.inf is nan
if carrier == "AC": # lines do not have carrier
n.lines[attr] *= factor
continue
comps = {
"p_nom_max": {"Generator", "Link", "StorageUnit"},
"e_nom_max": {"Store"},
"capital_cost": {"Generator", "Link", "StorageUnit", "Store"},
"marginal_cost": {"Generator", "Link", "StorageUnit", "Store"},
}
for c in n.iterate_components(comps[attr]):
sel = c.df.index[c.df.carrier == carrier]
c.df.loc[sel, attr] *= factor
logger.info(f"changing {attr} for {carrier} by factor {factor}")
for modification in adjustments.keys():
modify_attribute(n, adjustments, investment_year, modification)
def add_co2limit(n, co2limit, Nyears=1.0):
@ -300,6 +318,7 @@ def set_line_nom_max(
n.links["p_nom_max"] = n.links.p_nom_max.clip(upper=p_nom_max_set)
# %%
if __name__ == "__main__":
if "snakemake" not in globals():
from _helpers import mock_snakemake

View File

@ -19,6 +19,7 @@ import pypsa
import xarray as xr
from _helpers import (
configure_logging,
get,
set_scenario_config,
update_config_from_wildcards,
)
@ -241,38 +242,6 @@ def determine_emission_sectors(options):
return sectors
def get(item, investment_year=None):
"""
Check whether item depends on investment year.
"""
if not isinstance(item, dict):
return item
elif investment_year in item.keys():
return item[investment_year]
else:
logger.warning(
f"Investment key {investment_year} not found in dictionary {item}."
)
keys = sorted(item.keys())
if investment_year < keys[0]:
logger.warning(f"Lower than minimum key. Taking minimum key {keys[0]}")
return item[keys[0]]
elif investment_year > keys[-1]:
logger.warning(f"Higher than maximum key. Taking maximum key {keys[0]}")
return item[keys[-1]]
else:
logger.warning(
"Interpolate linearly between the next lower and next higher year."
)
lower_key = max(k for k in keys if k < investment_year)
higher_key = min(k for k in keys if k > investment_year)
lower = item[lower_key]
higher = item[higher_key]
return lower + (higher - lower) * (investment_year - lower_key) / (
higher_key - lower_key
)
def co2_emissions_year(
countries, input_eurostat, options, emissions_scope, input_co2, year
):
@ -1313,12 +1282,6 @@ def insert_electricity_distribution_grid(n, costs):
# TODO pop_layout?
# TODO options?
cost_factor = options["electricity_distribution_grid_cost_factor"]
logger.info(
f"Inserting electricity distribution grid with investment cost factor of {cost_factor:.2f}"
)
nodes = pop_layout.index
n.madd(
@ -1339,7 +1302,7 @@ def insert_electricity_distribution_grid(n, costs):
carrier="electricity distribution grid",
efficiency=1,
lifetime=costs.at["electricity distribution grid", "lifetime"],
capital_cost=costs.at["electricity distribution grid", "fixed"] * cost_factor,
capital_cost=costs.at["electricity distribution grid", "fixed"],
)
# deduct distribution losses from electricity demand as these are included in total load
@ -4794,8 +4757,6 @@ if __name__ == "__main__":
n, snakemake.input["egs_potentials"], snakemake.input["egs_overlap"], costs
)
maybe_adjust_costs_and_potentials(n, snakemake.params["adjustments"])
if options["gas_distribution_grid"]:
insert_gas_distribution_costs(n, costs)
@ -4821,6 +4782,10 @@ if __name__ == "__main__":
if options.get("cluster_heat_buses", False) and not first_year_myopic:
cluster_heat_buses(n)
maybe_adjust_costs_and_potentials(
n, snakemake.params["adjustments"], investment_year
)
n.meta = dict(snakemake.config, **dict(wildcards=dict(snakemake.wildcards)))
sanitize_carriers(n, snakemake.config)