From 1b97a16bfaac6b586e9e6ea06959c883014baa42 Mon Sep 17 00:00:00 2001 From: lisazeyen <35347358+lisazeyen@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:02:10 +0200 Subject: [PATCH] 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 --- config/config.default.yaml | 8 +++-- doc/configtables/adjustments.csv | 22 ++++++++----- doc/release_notes.rst | 1 + scripts/_helpers.py | 32 ++++++++++++++++++ scripts/prepare_network.py | 55 +++++++++++++++++++++---------- scripts/prepare_sector_network.py | 47 ++++---------------------- 6 files changed, 96 insertions(+), 69 deletions(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index 246a15f4..cb4787da 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -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: diff --git a/doc/configtables/adjustments.csv b/doc/configtables/adjustments.csv index 52617352..c1eb2b90 100644 --- a/doc/configtables/adjustments.csv +++ b/doc/configtables/adjustments.csv @@ -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." +,Unit,Values,Description +adjustments,,, +-- 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. diff --git a/doc/release_notes.rst b/doc/release_notes.rst index accbdd23..db139380 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -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, ... diff --git a/scripts/_helpers.py b/scripts/_helpers.py index f79d9258..3f6eac40 100644 --- a/scripts/_helpers.py +++ b/scripts/_helpers.py @@ -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( [ diff --git a/scripts/prepare_network.py b/scripts/prepare_network.py index 382e633d..05f40730 100755 --- a/scripts/prepare_network.py +++ b/scripts/prepare_network.py @@ -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 diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 58df6568..4c7c059a 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -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)