Skip to content

Commit

Permalink
Merge pull request #793 from martacki/main
Browse files Browse the repository at this point in the history
add res_share constraint
  • Loading branch information
davide-f authored Jul 29, 2023
2 parents 071718c + ac48bf0 commit 8eb0d6d
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 12 deletions.
25 changes: 13 additions & 12 deletions doc/configtables/opts.csv
Original file line number Diff line number Diff line change
@@ -1,12 +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() <https://github.com/PyPSA/pypsa-eur/blob/6b964540ed39d44079cdabddee8333f486d0cd63/scripts/prepare_network.py#L110>`_ and its `caller <https://github.com/PyPSA/pypsa-eur/blob/6b964540ed39d44079cdabddee8333f486d0cd63/scripts/prepare_network.py#L146>`__), In active use
``nSEG``; e.g. ``4380SEG``, "Apply time series segmentation with `tsam <https://tsam.readthedocs.io/en/latest/index.html>`_ 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() <https://github.com/PyPSA/pypsa-eur/blob/6b964540ed39d44079cdabddee8333f486d0cd63/scripts/prepare_network.py#L19>`_ and its `caller <https://github.com/PyPSA/pypsa-eur/blob/6b964540ed39d44079cdabddee8333f486d0cd63/scripts/prepare_network.py#L154>`__, 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() <https://github.com/PyPSA/pypsa-eur/blob/6b964540ed39d44079cdabddee8333f486d0cd63/scripts/prepare_network.py#L24>`_ and its `caller <https://github.com/PyPSA/pypsa-eur/blob/6b964540ed39d44079cdabddee8333f486d0cd63/scripts/prepare_network.py#L158>`__, 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() <https://github.com/PyPSA/pypsa-eur/blob/6b964540ed39d44079cdabddee8333f486d0cd63/scripts/solve_network.py#L66>`__, 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() <https://github.com/PyPSA/pypsa-eur/blob/6b964540ed39d44079cdabddee8333f486d0cd63/scripts/solve_network.py#L73>`__, 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() <https://github.com/PyPSA/pypsa-eur/blob/6b964540ed39d44079cdabddee8333f486d0cd63/scripts/prepare_network.py#L110>`_ and its `caller <https://github.com/PyPSA/pypsa-eur/blob/6b964540ed39d44079cdabddee8333f486d0cd63/scripts/prepare_network.py#L146>`__), In active use,
``nSEG``, e.g. ``4380SEG``,"Apply time series segmentation with `tsam <https://tsam.readthedocs.io/en/latest/index.html>`_ 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() <https://github.com/PyPSA/pypsa-eur/blob/6b964540ed39d44079cdabddee8333f486d0cd63/scripts/prepare_network.py#L19>`_ and its `caller <https://github.com/PyPSA/pypsa-eur/blob/6b964540ed39d44079cdabddee8333f486d0cd63/scripts/prepare_network.py#L154>`__, 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() <https://github.com/PyPSA/pypsa-eur/blob/6b964540ed39d44079cdabddee8333f486d0cd63/scripts/prepare_network.py#L24>`_ and its `caller <https://github.com/PyPSA/pypsa-eur/blob/6b964540ed39d44079cdabddee8333f486d0cd63/scripts/prepare_network.py#L158>`__, 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() <https://github.com/PyPSA/pypsa-eur/blob/6b964540ed39d44079cdabddee8333f486d0cd63/scripts/solve_network.py#L66>`__, 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() <https://github.com/PyPSA/pypsa-eur/blob/6b964540ed39d44079cdabddee8333f486d0cd63/scripts/solve_network.py#L73>`__, 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,,
``RES``, Add an overall relative share of renewable generation to total load (e.g. ``RES0.5`` enforces renewables to fulfil a 50% share of total load ).,,,,
2 changes: 2 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ E.g. if a new rule becomes available describe how to use it `snakemake -j1 run_t

* Add a check to ensure match between a cutout and a modelled area `PR #805 <https://github.com/pypsa-meets-earth/pypsa-earth/pull/805>`__

* Support renewables or renewable expansion to meet a desired share of total load. `PR #793 <https://github.com/pypsa-meets-earth/pypsa-earth/pull/793>`__

PyPSA-Earth 0.2.2
=================

Expand Down
123 changes: 123 additions & 0 deletions scripts/solve_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,125 @@ def add_battery_constraints(n):
define_constraints(n, lhs, "=", 0, "Link", "charger_ratio")


def add_RES_constraints(n, res_share):
lgrouper = n.loads.bus.map(n.buses.country)
ggrouper = n.generators.bus.map(n.buses.country)
sgrouper = n.storage_units.bus.map(n.buses.country)
cgrouper = n.links.bus0.map(n.buses.country)

logger.warning(
"The add_RES_constraints functionality is still work in progress. "
"Unexpected results might be incurred, particularly if "
"temporal clustering is applied or if an unexpected change of technologies "
"is subject to the obtimisation."
)

load = (
n.snapshot_weightings.generators
@ n.loads_t.p_set.groupby(lgrouper, axis=1).sum()
)

rhs = res_share * load

res_techs = [
"solar",
"onwind",
"offwind-dc",
"offwind-ac",
"battery",
"hydro",
"ror",
]
charger = ["H2 electrolysis", "battery charger"]
discharger = ["H2 fuel cell", "battery discharger"]

gens_i = n.generators.query("carrier in @res_techs").index
stores_i = n.storage_units.query("carrier in @res_techs").index
charger_i = n.links.query("carrier in @charger").index
discharger_i = n.links.query("carrier in @discharger").index

# Generators
lhs_gen = (
linexpr(
(n.snapshot_weightings.generators, get_var(n, "Generator", "p")[gens_i].T)
)
.T.groupby(ggrouper, axis=1)
.apply(join_exprs)
)

# StorageUnits
lhs_dispatch = (
(
linexpr(
(
n.snapshot_weightings.stores,
get_var(n, "StorageUnit", "p_dispatch")[stores_i].T,
)
)
.T.groupby(sgrouper, axis=1)
.apply(join_exprs)
)
.reindex(lhs_gen.index)
.fillna("")
)

lhs_store = (
(
linexpr(
(
-n.snapshot_weightings.stores,
get_var(n, "StorageUnit", "p_store")[stores_i].T,
)
)
.T.groupby(sgrouper, axis=1)
.apply(join_exprs)
)
.reindex(lhs_gen.index)
.fillna("")
)

# Stores (or their resp. Link components)
# Note that the variables "p0" and "p1" currently do not exist.
# Thus, p0 and p1 must be derived from "p" (which exists), taking into account the link efficiency.
lhs_charge = (
(
linexpr(
(
-n.snapshot_weightings.stores,
get_var(n, "Link", "p")[charger_i].T,
)
)
.T.groupby(cgrouper, axis=1)
.apply(join_exprs)
)
.reindex(lhs_gen.index)
.fillna("")
)

lhs_discharge = (
(
linexpr(
(
n.snapshot_weightings.stores.apply(
lambda r: r * n.links.loc[discharger_i].efficiency
),
get_var(n, "Link", "p")[discharger_i],
)
)
.groupby(cgrouper, axis=1)
.apply(join_exprs)
)
.reindex(lhs_gen.index)
.fillna("")
)

# signs of resp. terms are coded in the linexpr.
# todo: for links (lhs_charge and lhs_discharge), account for snapshot weightings
lhs = lhs_gen + lhs_dispatch + lhs_store + lhs_charge + lhs_discharge

define_constraints(n, lhs, "=", rhs, "RES share")


def extra_functionality(n, snapshots):
"""
Collects supplementary constraints which will be passed to
Expand All @@ -365,6 +484,10 @@ def extra_functionality(n, snapshots):
reserve = config["electricity"].get("operational_reserve", {})
if reserve.get("activate"):
add_operational_reserve_margin(n, snapshots, config)
for o in opts:
if "RES" in o:
res_share = float(re.findall("[0-9]*\.?[0-9]+$", o)[0])
add_RES_constraints(n, res_share)
for o in opts:
if "EQ" in o:
add_EQ_constraints(n, o)
Expand Down

0 comments on commit 8eb0d6d

Please sign in to comment.