diff --git a/config/config.default.yaml b/config/config.default.yaml index 331f7382d..b9e8dc917 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -59,6 +59,9 @@ snapshots: start: "2013-01-01" end: "2014-01-01" inclusive: 'left' + resolution: false + segmentation: false + #representative: false # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#enable enable: @@ -74,6 +77,7 @@ enable: retrieve_natura_raster: true custom_busmap: false + # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#co2-budget co2_budget: 2020: 0.701 @@ -87,7 +91,9 @@ co2_budget: # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#electricity electricity: voltages: [220., 300., 380., 500., 750.] + gaslimit_enable: false gaslimit: false + co2limit_enable: false co2limit: 7.75e+7 co2base: 1.487e+9 agg_p_nom_limits: data/agg_p_nom_minmax.csv @@ -124,6 +130,10 @@ electricity: Onshore: [onwind] PV: [solar] + autarky: + enable: false + by_country: false + # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#atlite atlite: default_cutout: europe-2013-era5 @@ -618,7 +628,9 @@ costs: battery: 0. battery inverter: 0. emission_prices: + enable: false co2: 0. + co2_monthly_prices: false # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#clustering clustering: @@ -660,6 +672,12 @@ solving: linearized_unit_commitment: true horizon: 365 + constraints: + CCL: false + EQ: false + BAU: false + SAFE: false + solver: name: gurobi options: gurobi-default diff --git a/doc/configtables/costs.csv b/doc/configtables/costs.csv index 9797d77e4..e307985df 100644 --- a/doc/configtables/costs.csv +++ b/doc/configtables/costs.csv @@ -1,9 +1,11 @@ -,Unit,Values,Description -year,--,"YYYY; e.g. '2030'","Year for which to retrieve cost assumptions of ``resources/costs.csv``." -version,--,"vX.X.X; e.g. 'v0.5.0'","Version of ``technology-data`` repository to use." -rooftop_share,--,float,"Share of rooftop PV when calculating capital cost of solar (joint rooftop and utility-scale PV)." -fill_values,--,float,"Default values if not specified for a technology in ``resources/costs.csv``." -capital_cost,EUR/MW,"Keys should be in the 'technology' column of ``resources/costs.csv``. Values can be any float.","For the given technologies, assumptions about their capital investment costs are set to the corresponding value. Optional; overwrites cost assumptions from ``resources/costs.csv``." -marginal_cost,EUR/MWh,"Keys should be in the 'technology' column of ``resources/costs.csv``. Values can be any float.","For the given technologies, assumptions about their marginal operating costs are set to the corresponding value. Optional; overwrites cost assumptions from ``resources/costs.csv``." -emission_prices,,,"Specify exogenous prices for emission types listed in ``network.carriers`` to marginal costs." --- co2,EUR/t,float,"Exogenous price of carbon-dioxide added to the marginal costs of fossil-fuelled generators according to their carbon intensity. Added through the keyword ``Ep`` in the ``{opts}`` wildcard only in the rule :mod:`prepare_network``." +,Unit,Values,Description +year,--,YYYY; e.g. '2030',Year for which to retrieve cost assumptions of ``resources/costs.csv``. +version,--,vX.X.X; e.g. 'v0.5.0',Version of ``technology-data`` repository to use. +rooftop_share,--,float,Share of rooftop PV when calculating capital cost of solar (joint rooftop and utility-scale PV). +fill_values,--,float,Default values if not specified for a technology in ``resources/costs.csv``. +capital_cost,EUR/MW,Keys should be in the 'technology' column of ``resources/costs.csv``. Values can be any float.,"For the given technologies, assumptions about their capital investment costs are set to the corresponding value. Optional; overwrites cost assumptions from ``resources/costs.csv``." +marginal_cost,EUR/MWh,Keys should be in the 'technology' column of ``resources/costs.csv``. Values can be any float.,"For the given technologies, assumptions about their marginal operating costs are set to the corresponding value. Optional; overwrites cost assumptions from ``resources/costs.csv``." +emission_prices,,,Specify exogenous prices for emission types listed in ``network.carriers`` to marginal costs. +-- enable,bool,true or false,Add cost for a carbon-dioxide price configured in ``costs: emission_prices: co2`` to ``marginal_cost`` of generators (other emission types listed in ``network.carriers`` possible as well) +-- co2,EUR/t,float,Exogenous price of carbon-dioxide added to the marginal costs of fossil-fuelled generators according to their carbon intensity. Added through the keyword ``Ep`` in the ``{opts}`` wildcard only in the rule :mod:`prepare_network``. +-- co2_monthly_price,bool,true or false,Add monthly cost for a carbon-dioxide price based on historical values built by the rule ``build_monthly_prices`` diff --git a/doc/configtables/electricity.csv b/doc/configtables/electricity.csv index 4c04fee66..00eec0c43 100644 --- a/doc/configtables/electricity.csv +++ b/doc/configtables/electricity.csv @@ -1,6 +1,8 @@ ,Unit,Values,Description voltages,kV,"Any subset of {220., 300., 380.}",Voltage levels to consider +gaslimit_enable,bool,true or false,Add an overall absolute gas limit configured in ``electricity: gaslimit``. gaslimit,MWhth,float or false,Global gas usage limit +co2limit_enable,bool,true or false,Add an overall absolute carbon-dioxide emissions limit configured in ``electricity: co2limit``. co2limit,:math:`t_{CO_2-eq}/a`,float,Cap on total annual system carbon dioxide emissions co2base,:math:`t_{CO_2-eq}/a`,float,Reference value of total annual system carbon dioxide emissions if relative emission reduction target is specified in ``{opts}`` wildcard. agg_p_nom_limits,file,path,Reference to ``.csv`` file specifying per carrier generator nominal capacity constraints for individual countries if ``'CCL'`` is in ``{opts}`` wildcard. Defaults to ``data/agg_p_nom_minmax.csv``. @@ -34,3 +36,6 @@ estimate_renewable_capacities,,, -- -- Offshore,--,"Any subset of {offwind-ac, offwind-dc}","List of PyPSA-Eur carriers that is considered as (IRENA, OPSD) onshore technology." -- -- Offshore,--,{onwind},"List of PyPSA-Eur carriers that is considered as (IRENA, OPSD) offshore technology." -- -- PV,--,{solar},"List of PyPSA-Eur carriers that is considered as (IRENA, OPSD) PV technology." +autarky,,, +-- enable,bool,true or false,Require each node to be autarkic by removing all lines and links. +-- by_country,bool,true or false,Require each country to be autarkic by removing all cross-border lines and links. ``electricity: autarky`` must be enabled. diff --git a/doc/configtables/opts.csv b/doc/configtables/opts.csv index 8c8a706fb..b133c718d 100644 --- a/doc/configtables/opts.csv +++ b/doc/configtables/opts.csv @@ -1,13 +1,13 @@ -Trigger, Description, Definition, Status -``nH``; i.e. ``2H``-``6H``, Resample the time-resolution by averaging over every ``n`` snapshots, ``prepare_network``: `average_every_nhours() `_ and its `caller `__), In active use -``nSEG``; e.g. ``4380SEG``, "Apply time series segmentation with `tsam `_ package to ``n`` adjacent snapshots of varying lengths based on capacity factors of varying renewables, hydro inflow and load.", ``prepare_network``: apply_time_segmentation(), In active use -``Co2L``, Add an overall absolute carbon-dioxide emissions limit configured in ``electricity: co2limit``. If a float is appended an overall emission limit relative to the emission level given in ``electricity: co2base`` is added (e.g. ``Co2L0.05`` limits emissisions to 5% of what is given in ``electricity: co2base``), ``prepare_network``: `add_co2limit() `_ and its `caller `__, In active use -``Ep``, Add cost for a carbon-dioxide price configured in ``costs: emission_prices: co2`` to ``marginal_cost`` of generators (other emission types listed in ``network.carriers`` possible as well), ``prepare_network``: `add_emission_prices() `_ and its `caller `__, In active use -``Ept``, Add monthly cost for a carbon-dioxide price based on historical values built by the rule ``build_monthly_prices``, In active use -``CCL``, Add minimum and maximum levels of generator nominal capacity per carrier for individual countries. These can be specified in the file linked at ``electricity: agg_p_nom_limits`` in the configuration. File defaults to ``data/agg_p_nom_minmax.csv``., ``solve_network``, In active use -``EQ``, "Require each country or node to on average produce a minimal share of its total consumption itself. Example: ``EQ0.5c`` demands each country to produce on average at least 50% of its consumption; ``EQ0.5`` demands each node to produce on average at least 50% of its consumption.", ``solve_network``, In active use -``ATK``, "Require each node to be autarkic. Example: ``ATK`` removes all lines and links. ``ATKc`` removes all cross-border lines and links.", ``prepare_network``, In active use -``BAU``, Add a per-``carrier`` minimal overall capacity; i.e. at least ``40GW`` of ``OCGT`` in Europe; configured in ``electricity: BAU_mincapacities``, ``solve_network``: `add_opts_constraints() `__, Untested -``SAFE``, Add a capacity reserve margin of a certain fraction above the peak demand to which renewable generators and storage do *not* contribute. Ignores network., ``solve_network`` `add_opts_constraints() `__, Untested -``carrier+{c|p|m}factor``,"Alter the capital cost (``c``), installable potential (``p``) or marginal costs (``m``) of a carrier by a factor. Example: ``solar+c0.5`` reduces the capital cost of solar to 50\% of original values.", ``prepare_network``, In active use -``CH4L``,"Add an overall absolute gas limit. If configured in ``electricity: gaslimit`` it is given in MWh thermal, if a float is appended, the overall gaslimit is assumed to be given in TWh thermal (e.g. ``CH4L200`` limits gas dispatch to 200 TWh termal)", ``prepare_network``: ``add_gaslimit()``, In active use +Trigger, Description, Definition, Status +``nH``; i.e. ``2H``-``6H``, Resample the time-resolution by averaging over every ``n`` snapshots, ``prepare_network``: `average_every_nhours() `_ and its `caller `__), In active use +``nSEG``; e.g. ``4380SEG``,"Apply time series segmentation with `tsam `_ package to ``n`` adjacent snapshots of varying lengths based on capacity factors of varying renewables, hydro inflow and load.", ``prepare_network``: apply_time_segmentation(), In active use +``Co2L``,Add an overall absolute carbon-dioxide emissions limit configured in ``electricity: co2limit``. If a float is appended an overall emission limit relative to the emission level given in ``electricity: co2base`` is added (e.g. ``Co2L0.05`` limits emissisions to 5% of what is given in ``electricity: co2base``), ``prepare_network``: `add_co2limit() `_ and its `caller `__, In active use +``Ep``,Add cost for a carbon-dioxide price configured in ``costs: emission_prices: co2`` to ``marginal_cost`` of generators (other emission types listed in ``network.carriers`` possible as well), ``prepare_network``: `add_emission_prices() `_ and its `caller `__, In active use +``Ept``,Add monthly cost for a carbon-dioxide price based on historical values built by the rule ``build_monthly_prices``, In active use, +``CCL``,Add minimum and maximum levels of generator nominal capacity per carrier for individual countries. These can be specified in the file linked at ``electricity: agg_p_nom_limits`` in the configuration. File defaults to ``data/agg_p_nom_minmax.csv``., ``solve_network``, In active use +``EQ``,Require each country or node to on average produce a minimal share of its total consumption itself. Example: ``EQ0.5c`` demands each country to produce on average at least 50% of its consumption; ``EQ0.5`` demands each node to produce on average at least 50% of its consumption., ``solve_network``, In active use +``ATK``,Require each node to be autarkic. Example: ``ATK`` removes all lines and links. ``ATKc`` removes all cross-border lines and links., ``prepare_network``, In active use +``BAU``,Add a per-``carrier`` minimal overall capacity; i.e. at least ``40GW`` of ``OCGT`` in Europe; configured in ``electricity: BAU_mincapacities``, ``solve_network``: `add_opts_constraints() `__, Untested +``SAFE``,Add a capacity reserve margin of a certain fraction above the peak demand to which renewable generators and storage do *not* contribute. Ignores network., ``solve_network`` `add_opts_constraints() `__, Untested +``carrier+{c|p|m}factor``,"Alter the capital cost (``c``), installable potential (``p``) or marginal costs (``m``) of a carrier by a factor. Example: ``solar+c0.5`` reduces the capital cost of solar to 50\% of original values.", ``prepare_network``, In active use +``CH4L``,"Add an overall absolute gas limit. If configured in ``electricity: gaslimit`` it is given in MWh thermal, if a float is appended, the overall gaslimit is assumed to be given in TWh thermal (e.g. ``CH4L200`` limits gas dispatch to 200 TWh termal)", ``prepare_network``: ``add_gaslimit()``, In active use diff --git a/doc/configtables/snapshots.csv b/doc/configtables/snapshots.csv index d60c78dc0..4a3e12128 100644 --- a/doc/configtables/snapshots.csv +++ b/doc/configtables/snapshots.csv @@ -1,4 +1,6 @@ -,Unit,Values,Description -start,--,"str or datetime-like; e.g. YYYY-MM-DD","Left bound of date range" -end,--,"str or datetime-like; e.g. YYYY-MM-DD","Right bound of date range" -inclusive,--,"One of {'neither', 'both', ‘left’, ‘right’}","Make the time interval closed to the ``left``, ``right``, or both sides ``both`` or neither side ``None``." +,Unit,Values,Description +start,--,str or datetime-like; e.g. YYYY-MM-DD,Left bound of date range +end,--,str or datetime-like; e.g. YYYY-MM-DD,Right bound of date range +inclusive,--,"One of {'neither', 'both', ‘left’, ‘right’}","Make the time interval closed to the ``left``, ``right``, or both sides ``both`` or neither side ``None``." +resolution ,--,"{false,``nH``; i.e. ``2H``-``6H``}",Resample the time-resolution by averaging over every ``n`` snapshots +segmentation,--,"{false,``n``; e.g. ``4380``}","Apply time series segmentation with `tsam `_ package to ``n`` adjacent snapshots of varying lengths based on capacity factors of varying renewables, hydro inflow and load." diff --git a/doc/configtables/solving.csv b/doc/configtables/solving.csv index dcff54e42..6eff10ae6 100644 --- a/doc/configtables/solving.csv +++ b/doc/configtables/solving.csv @@ -13,6 +13,11 @@ options,,, -- transmission_losses,int,[0-9],"Add piecewise linear approximation of transmission losses based on n tangents. Defaults to 0, which means losses are ignored." -- linearized_unit_commitment,bool,"{'true','false'}",Whether to optimise using the linearized unit commitment formulation. -- horizon,--,int,Number of snapshots to consider in each iteration. Defaults to 100. +constraints ,,, +-- CCL,bool,"{'true','false'}",Add minimum and maximum levels of generator nominal capacity per carrier for individual countries. These can be specified in the file linked at ``electricity: agg_p_nom_limits`` in the configuration. File defaults to ``data/agg_p_nom_minmax.csv``. +-- EQ,bool/string,"{'false',`n(c| )``; i.e. ``0.5``-``0.7c``}",Require each country or node to on average produce a minimal share of its total consumption itself. Example: ``EQ0.5c`` demands each country to produce on average at least 50% of its consumption; ``EQ0.5`` demands each node to produce on average at least 50% of its consumption. +-- BAU,bool,"{'true','false'}",Add a per-``carrier`` minimal overall capacity; i.e. at least ``40GW`` of ``OCGT`` in Europe; configured in ``electricity: BAU_mincapacities`` +-- SAFE,bool,"{'true','false'}",Add a capacity reserve margin of a certain fraction above the peak demand to which renewable generators and storage do *not* contribute. Ignores network. solver,,, -- name,--,"One of {'gurobi', 'cplex', 'cbc', 'glpk', 'ipopt'}; potentially more possible",Solver to use for optimisation problems in the workflow; e.g. clustering and linear optimal power flow. -- options,--,Key listed under ``solver_options``.,Link to specific parameter settings. diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 88f05854f..7a045866d 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -10,6 +10,11 @@ Release Notes Upcoming Release ================ +* More wildcard options now have a corresponding config entry. If the wildcard + is given, then its value is used. If the wildcard is not given but the options + in config are enabled, then the value from config is used. If neither is + given, the options are skipped. + * Distinguish between stored and sequestered CO2. Stored CO2 is stored overground in tanks and can be used for CCU (e.g. methanolisation). Sequestered CO2 is stored underground and can no longer be used for CCU. This diff --git a/rules/build_electricity.smk b/rules/build_electricity.smk index 055cffca5..8cba33bc9 100644 --- a/rules/build_electricity.smk +++ b/rules/build_electricity.smk @@ -20,7 +20,7 @@ if config["enable"].get("prepare_links_p_nom", False): rule build_electricity_demand: params: - snapshots=config["snapshots"], + snapshots={k: config["snapshots"][k] for k in ["start", "end", "inclusive"]}, countries=config["countries"], load=config["load"], input: @@ -61,7 +61,7 @@ rule build_powerplants: rule base_network: params: countries=config["countries"], - snapshots=config["snapshots"], + snapshots={k: config["snapshots"][k] for k in ["start", "end", "inclusive"]}, lines=config["lines"], links=config["links"], transformers=config["transformers"], @@ -144,7 +144,7 @@ if config["enable"].get("build_cutout", False): rule build_cutout: params: - snapshots=config["snapshots"], + snapshots={k: config["snapshots"][k] for k in ["start", "end", "inclusive"]}, cutouts=config["atlite"]["cutouts"], input: regions_onshore=RESOURCES + "regions_onshore.geojson", @@ -258,6 +258,7 @@ else: rule build_renewable_profiles: params: + snapshots={k: config["snapshots"][k] for k in ["start", "end", "inclusive"]}, renewable=config["renewable"], input: **opt, @@ -354,6 +355,8 @@ rule build_hydro_profile: if config["lines"]["dynamic_line_rating"]["activate"]: rule build_line_rating: + params: + snapshots={k: config["snapshots"][k] for k in ["start", "end", "inclusive"]}, input: base_network=RESOURCES + "networks/base.nc", cutout="cutouts/" @@ -531,13 +534,20 @@ rule add_extra_components: rule prepare_network: params: + snapshots={ + "resolution": config["snapshots"].get("resolution", False), + "segmentation": config["snapshots"].get("segmentation", False), + }, links=config["links"], lines=config["lines"], co2base=config["electricity"]["co2base"], + co2limit_enable=config["electricity"].get("co2limit_enable", False), co2limit=config["electricity"]["co2limit"], + gaslimit_enable=config["electricity"].get("gaslimit_enable", False), gaslimit=config["electricity"].get("gaslimit"), max_hours=config["electricity"]["max_hours"], costs=config["costs"], + autarky=config["electricity"].get("autarky", {}), input: RESOURCES + "networks/elec_s{simpl}_{clusters}_ec.nc", tech_costs=COSTS, diff --git a/rules/build_sector.smk b/rules/build_sector.smk index ef2fc6c8b..4744aa250 100644 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -125,7 +125,7 @@ rule cluster_gas_network: rule build_heat_demands: params: - snapshots=config["snapshots"], + snapshots={k: config["snapshots"][k] for k in ["start", "end", "inclusive"]}, input: pop_layout=RESOURCES + "pop_layout_{scope}.nc", regions_onshore=RESOURCES + "regions_onshore_elec_s{simpl}_{clusters}.geojson", @@ -147,7 +147,7 @@ rule build_heat_demands: rule build_temperature_profiles: params: - snapshots=config["snapshots"], + snapshots={k: config["snapshots"][k] for k in ["start", "end", "inclusive"]}, input: pop_layout=RESOURCES + "pop_layout_{scope}.nc", regions_onshore=RESOURCES + "regions_onshore_elec_s{simpl}_{clusters}.geojson", @@ -199,7 +199,7 @@ rule build_cop_profiles: rule build_solar_thermal_profiles: params: - snapshots=config["snapshots"], + snapshots={k: config["snapshots"][k] for k in ["start", "end", "inclusive"]}, solar_thermal=config["solar_thermal"], input: pop_layout=RESOURCES + "pop_layout_{scope}.nc", @@ -662,7 +662,7 @@ rule build_shipping_demand: rule build_transport_demand: params: - snapshots=config["snapshots"], + snapshots={k: config["snapshots"][k] for k in ["start", "end", "inclusive"]}, sector=config["sector"], input: clustered_pop_layout=RESOURCES + "pop_layout_elec_s{simpl}_{clusters}.csv", diff --git a/rules/postprocess.smk b/rules/postprocess.smk index 1ac4fec97..9f4ac78e2 100644 --- a/rules/postprocess.smk +++ b/rules/postprocess.smk @@ -85,7 +85,7 @@ rule make_summary: params: foresight=config["foresight"], costs=config["costs"], - snapshots=config["snapshots"], + snapshots={k: config["snapshots"][k] for k in ["start", "end", "inclusive"]}, scenario=config["scenario"], RDIR=RDIR, input: diff --git a/rules/validate.smk b/rules/validate.smk index cfb8c959a..0fa1f607c 100644 --- a/rules/validate.smk +++ b/rules/validate.smk @@ -17,7 +17,7 @@ rule build_electricity_production: The data is used for validation of the optimization results. """ params: - snapshots=config["snapshots"], + snapshots={k: config["snapshots"][k] for k in ["start", "end", "inclusive"]}, countries=config["countries"], output: RESOURCES + "historical_electricity_production.csv", @@ -35,7 +35,7 @@ rule build_cross_border_flows: The data is used for validation of the optimization results. """ params: - snapshots=config["snapshots"], + snapshots={k: config["snapshots"][k] for k in ["start", "end", "inclusive"]}, countries=config["countries"], input: network=RESOURCES + "networks/base.nc", @@ -55,7 +55,7 @@ rule build_electricity_prices: The data is used for validation of the optimization results. """ params: - snapshots=config["snapshots"], + snapshots={k: config["snapshots"][k] for k in ["start", "end", "inclusive"]}, countries=config["countries"], output: RESOURCES + "historical_electricity_prices.csv", diff --git a/scripts/_helpers.py b/scripts/_helpers.py index 9945f70f0..03bde8407 100644 --- a/scripts/_helpers.py +++ b/scripts/_helpers.py @@ -7,6 +7,7 @@ import hashlib import logging import os +import re import urllib from pathlib import Path @@ -23,6 +24,35 @@ REGION_COLS = ["geometry", "name", "x", "y", "country"] +def get_opt(opts, expr, flags=None): + """ + Return the first option matching the regular expression. + + The regular expression is case-insensitive by default. + """ + if flags is None: + flags = re.IGNORECASE + for o in opts: + match = re.match(expr, o, flags=flags) + if match: + return match.group(0) + return None + + +def find_opt(opts, expr): + """ + Return if available the float after the expression. + """ + for o in opts: + if expr in o: + m = re.findall("[0-9]*\.?[0-9]+$", o) + if len(m) > 0: + return True, float(m[0]) + else: + return True, None + return False, None + + # Define a context manager to temporarily mute print statements @contextlib.contextmanager def mute_print(): diff --git a/scripts/base_network.py b/scripts/base_network.py index eda29451f..eeb87bf5d 100644 --- a/scripts/base_network.py +++ b/scripts/base_network.py @@ -725,11 +725,12 @@ def base_network( transformers = _set_electrical_parameters_transformers(transformers, config) links = _set_electrical_parameters_links(links, config, links_p_nom) converters = _set_electrical_parameters_converters(converters, config) + snapshots = snakemake.params.snapshots n = pypsa.Network() n.name = "PyPSA-Eur" - n.set_snapshots(pd.date_range(freq="h", **config["snapshots"])) + n.set_snapshots(pd.date_range(freq="h", **snapshots)) n.madd("Carrier", ["AC", "DC"]) n.import_components_from_dataframe(buses, "Bus") diff --git a/scripts/build_line_rating.py b/scripts/build_line_rating.py index c53d2899b..589f3656f 100755 --- a/scripts/build_line_rating.py +++ b/scripts/build_line_rating.py @@ -146,8 +146,10 @@ def calculate_line_rating(n, cutout): ) configure_logging(snakemake) + snapshots = snakemake.params.snapshots + n = pypsa.Network(snakemake.input.base_network) - time = pd.date_range(freq="h", **snakemake.config["snapshots"]) + time = pd.date_range(freq="h", **snapshots) cutout = atlite.Cutout(snakemake.input.cutout).sel(time=time) da = calculate_line_rating(n, cutout) diff --git a/scripts/build_renewable_profiles.py b/scripts/build_renewable_profiles.py index b736f68a9..f9db92711 100644 --- a/scripts/build_renewable_profiles.py +++ b/scripts/build_renewable_profiles.py @@ -210,6 +210,7 @@ resource = params["resource"] # pv panel params / wind turbine params correction_factor = params.get("correction_factor", 1.0) capacity_per_sqkm = params["capacity_per_sqkm"] + snapshots = snakemake.params.snapshots if correction_factor != 1.0: logger.info(f"correction_factor is set as {correction_factor}") @@ -219,7 +220,7 @@ else: client = None - sns = pd.date_range(freq="h", **snakemake.config["snapshots"]) + sns = pd.date_range(freq="h", **snapshots) cutout = atlite.Cutout(snakemake.input.cutout).sel(time=sns) regions = gpd.read_file(snakemake.input.regions) assert not regions.empty, ( diff --git a/scripts/prepare_network.py b/scripts/prepare_network.py index 90d6ed2e2..632e6078c 100755 --- a/scripts/prepare_network.py +++ b/scripts/prepare_network.py @@ -63,7 +63,7 @@ import numpy as np import pandas as pd import pypsa -from _helpers import configure_logging +from _helpers import configure_logging, find_opt, get_opt from add_electricity import load_costs, update_transmission_costs from pypsa.descriptors import expand_series @@ -296,42 +296,42 @@ def set_line_nom_max( set_line_s_max_pu(n, snakemake.params.lines["s_max_pu"]) - for o in opts: - m = re.match(r"^\d+h$", o, re.IGNORECASE) - if m is not None: - n = average_every_nhours(n, m.group(0)) - break - - for o in opts: - m = re.match(r"^\d+seg$", o, re.IGNORECASE) - if m is not None: - solver_name = snakemake.config["solving"]["solver"]["name"] - n = apply_time_segmentation(n, m.group(0)[:-3], solver_name) - break - - for o in opts: - if "Co2L" in o: - m = re.findall("[0-9]*\.?[0-9]+$", o) - if len(m) > 0: - co2limit = float(m[0]) * snakemake.params.co2base - add_co2limit(n, co2limit, Nyears) - logger.info("Setting CO2 limit according to wildcard value.") - else: - add_co2limit(n, snakemake.params.co2limit, Nyears) - logger.info("Setting CO2 limit according to config value.") - break - - for o in opts: - if "CH4L" in o: - m = re.findall("[0-9]*\.?[0-9]+$", o) - if len(m) > 0: - limit = float(m[0]) * 1e6 - add_gaslimit(n, limit, Nyears) - logger.info("Setting gas usage limit according to wildcard value.") - else: - add_gaslimit(n, snakemake.params.gaslimit, Nyears) - logger.info("Setting gas usage limit according to config value.") - break + # temporal averaging + nhours_config = snakemake.params.snapshots.get("resolution", False) + nhours_wildcard = get_opt(opts, r"^\d+h$") + nhours = nhours_wildcard or nhours_config + if nhours: + n = average_every_nhours(n, nhours) + + # segments with package tsam + time_seg_config = snakemake.params.snapshots.get("segmentation", False) + time_seg_wildcard = get_opt(opts, r"^\d+seg$") + time_seg = time_seg_wildcard or time_seg_config + if time_seg: + solver_name = snakemake.config["solving"]["solver"]["name"] + n = apply_time_segmentation(n, time_seg.replace("seg", ""), solver_name) + + Co2L_config = snakemake.params.co2limit_enable + Co2L_wildcard, co2limit_wildcard = find_opt(opts, "Co2L") + if Co2L_wildcard or Co2L_config: + if co2limit_wildcard is not None: + co2limit = co2limit_wildcard * snakemake.params.co2base + add_co2limit(n, co2limit, Nyears) + logger.info("Setting CO2 limit according to wildcard value.") + else: + add_co2limit(n, snakemake.params.co2limit, Nyears) + logger.info("Setting CO2 limit according to config value.") + + CH4L_config = snakemake.params.gaslimit_enable + CH4L_wildcard, gaslimit_wildcard = find_opt(opts, "CH4L") + if CH4L_wildcard or CH4L_config: + if gaslimit_wildcard is not None: + gaslimit = gaslimit_wildcard * 1e6 + add_gaslimit(n, gaslimit, Nyears) + logger.info("Setting gas usage limit according to wildcard value.") + else: + add_gaslimit(n, snakemake.params.gaslimit, Nyears) + logger.info("Setting gas usage limit according to config value.") for o in opts: if "+" not in o: @@ -352,21 +352,26 @@ def set_line_nom_max( sel = c.df.carrier.str.contains(carrier) c.df.loc[sel, attr] *= factor - for o in opts: - if "Ept" in o: - logger.info( - "Setting time dependent emission prices according spot market price" + emission_prices = snakemake.params.costs["emission_prices"] + Ept_config = emission_prices.get("co2_monthly_prices", False) + Ept_wildcard = "Ept" in opts + Ep_config = emission_prices.get("enable", False) + Ep_wildcard, co2_wildcard = find_opt(opts, "Ep") + + if Ept_wildcard or Ept_config: + logger.info( + "Setting time dependent emission prices according spot market price" + ) + add_dynamic_emission_prices(n) + elif Ep_wildcard or Ep_config: + if co2_wildcard is not None: + logger.info("Setting CO2 prices according to wildcard value.") + add_emission_prices(n, dict(co2=co2_wildcard)) + else: + logger.info("Setting CO2 prices according to config value.") + add_emission_prices( + n, dict(co2=snakemake.params.costs["emission_prices"]["co2"]) ) - add_dynamic_emission_prices(n) - elif "Ep" in o: - m = re.findall("[0-9]*\.?[0-9]+$", o) - if len(m) > 0: - logger.info("Setting emission prices according to wildcard value.") - add_emission_prices(n, dict(co2=float(m[0]))) - else: - logger.info("Setting emission prices according to config value.") - add_emission_prices(n, snakemake.params.costs["emission_prices"]) - break ll_type, factor = snakemake.wildcards.ll[0], snakemake.wildcards.ll[1:] set_transmission_limit(n, ll_type, factor, costs, Nyears) @@ -379,10 +384,12 @@ def set_line_nom_max( p_nom_max_ext=snakemake.params.links.get("max_extension", np.inf), ) - if "ATK" in opts: - enforce_autarky(n) - elif "ATKc" in opts: - enforce_autarky(n, only_crossborder=True) + autarky_config = snakemake.params.autarky + if "ATK" in opts or autarky_config.get("enable", False): + only_crossborder = False + if "ATKc" in opts or autarky_config.get("by_country", False): + only_crossborder = True + enforce_autarky(n, only_crossborder=only_crossborder) n.meta = dict(snakemake.config, **dict(wildcards=dict(snakemake.wildcards))) n.export_to_netcdf(snakemake.output[0]) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 36b530868..1c0f6505c 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -37,7 +37,7 @@ import pypsa import xarray as xr from _benchmark import memory_logger -from _helpers import configure_logging, update_config_with_sector_opts +from _helpers import configure_logging, get_opt, update_config_with_sector_opts from pypsa.descriptors import get_activity_mask logger = logging.getLogger(__name__) @@ -806,18 +806,30 @@ def extra_functionality(n, snapshots): """ opts = n.opts config = n.config - if "BAU" in opts and n.generators.p_nom_extendable.any(): + constraints = config["solving"].get("constraints", {}) + if ( + "BAU" in opts or constraints.get("BAU", False) + ) and n.generators.p_nom_extendable.any(): add_BAU_constraints(n, config) - if "SAFE" in opts and n.generators.p_nom_extendable.any(): + if ( + "SAFE" in opts or constraints.get("SAFE", False) + ) and n.generators.p_nom_extendable.any(): add_SAFE_constraints(n, config) - if "CCL" in opts and n.generators.p_nom_extendable.any(): + if ( + "CCL" in opts or constraints.get("CCL", False) + ) and n.generators.p_nom_extendable.any(): add_CCL_constraints(n, config) + reserve = config["electricity"].get("operational_reserve", {}) if reserve.get("activate"): add_operational_reserve_margin(n, snapshots, config) - for o in opts: - if "EQ" in o: - add_EQ_constraints(n, o) + + EQ_config = constraints.get("EQ", False) + EQ_wildcard = get_opt(opts, r"^EQ+[0-9]*\.?[0-9]+(c|)") + EQ_o = EQ_wildcard or EQ_config + if EQ_o: + add_EQ_constraints(n, EQ_o.replace("EQ", "")) + add_battery_constraints(n) add_lossy_bidirectional_link_constraints(n) add_pipe_retrofit_constraint(n)