PyPSA/pypsa-eur

Correctly enforce existing capacities if build year >= base year

Closed this issue · 3 comments

Describe the Bug

The baseyear in pypsa-ariadne is 2020. We want to enforce capacities that have been build in 2020 or afterwards.

At the moment this is done in add_existing_baseyear.py, just as if the build year was before 2020, by setting p_nom.
However, after the optimisation, for some carriers, the optimal capacity is smaller than p_nom. This means that the already existing assets are not used by the model. Why?

  1. A constraint for exisiting capacities is added at
    n.generators.loc[already_build, "p_nom_min"] = capacity.loc[
    already_build.str.replace(name_suffix, "")
    ].values

However, for renewable carriers p_nom_min gets reset to 0 in solve_network.py. I would consider this a bug, since that way existing capacities get disregarded. The responsible lines are

gen_i = n.generators[(n.generators.carrier.isin(carriers)) & (ext_i)].index
n.generators.loc[gen_i, "p_nom_min"] = 0
and
extendable_i = (n.generators.carrier == carrier) & n.generators.p_nom_extendable
n.generators.loc[extendable_i, "p_nom_min"] = 0

Apparently the behaviour was first introduced in #728 to limit the land use of renewables.

  1. When i revert this PR the p_nom_lims are correctly carried forward. However, then p_nom_min is still smaller than the p_nom for most carriers, which i understand to be indicative of the existing capacities. And thus, once again p_nom_opt is lower than it should be.

  2. For offwind-ac the situation is actually the opposite and p_nom is smaller than p_nom_min. This might be related to the issue that existing wind capacities get added without a suffix and are thus overlooked

How to Fix

Basically two approaches could work:

  1. set p_nom_min = p_nom

  2. differentiate between enforced assets and optimized assets. then carrier-year would exist twice if build year >= base year.

(and also assign all already existing offwind capacities to offwind-ac and offwind-dc)

I am a bit reluctant to simply go for 1. since it might break other peoples code (??)

With #967 in place and grouping years [2015,2021] all existing capacities seem to be assigned correctly and do not get overwritten by the optimization. However, for the extendable solar-2020 generator, the p_nom is still very high. I thought it would be 0 if all existing capcities are added as solar-2015 and solar-2021. We might get some double counting in n.statistics.installed_capacity because of that. @p-glaum

possibly related to #1016

However, for the extendable solar-2020 generator, the p_nom is still very high. I thought it would be 0 if all existing capcities are added as solar-2015 and solar-2021.

This is indeed the same issue as in #1016. Renewable capacities can be added in two ways:

  1. In rule add_electricity in case the following configuration is used:
electricity:
  estimate_renewable_capacities:
    enable: true
  1. In rule add_existing_baseyear for any model in myopic mode.

Option 1 should be used only for electricity-only models and overnight sector-coupled models. Option 2 is used in any case for myopic pathway sector-coupled models. This is quite error-prone, and we should change it soon. Will be tracked via #1016.

So, the problem occurs if you do options 1 and 2 simultaneously, in which case all existing capacities are added to p_nom of the first planning horizon. This looks worse than it is, since p_nom only affects the model if the components are not extendable, which they are not in this case.

With option 1 disabled one gets in "results/test-sector-myopic/prenetworks-brownfield/elec_s_5_lvopt___2020.nc":

n.generators.query("carrier == 'solar'").groupby("build_year")[["p_nom_min", "p_nom"]].sum()
build_year p_nom_min p_nom
2005 0 385
2010 0 2629
2015 0 1621.6
2020 1009.8 0

With option 1 enabled, one gets:

build_year p_nom_min p_nom
2005 0 385
2010 0 2629
2015 0 1621.6
2020 1009.8 5646.4

which is wrong / double-counting -- yet without effect on the optimisation