move code for national CO2 budgets out of extra_functionality

This can be added by derived workflows like PyPSA-Eur via
additional_functionality.

Changed additional_functionality to pass snakemake rather than
wildcards and config separately. This gives maximal flexibility.
This commit is contained in:
Tom Brown 2024-01-02 16:02:10 +01:00
parent 8a55a55d20
commit 1b569dde1b
2 changed files with 1 additions and 126 deletions

View File

@ -84,22 +84,6 @@ co2_budget:
2045: 0.032
2050: 0.000
co2_budget_national:
2030:
'DE': 0.350
'AT': 0.450
'BE': 0.450
'CH': 0.450
'CZ': 0.450
'DK': 0.450
'FR': 0.450
'GB': 0.450
'LU': 0.450
'NL': 0.450
'NO': 0.450
'PL': 0.450
'SE': 0.450
# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#electricity
electricity:
voltages: [220., 300., 380.]
@ -470,7 +454,6 @@ sector:
hydrogen_turbine: false
SMR: true
SMR_cc: true
co2_budget_national: false
regional_methanol_demand: false #set to true if regional CO2 constraints needed
regional_oil_demand: false #set to true if regional CO2 constraints needed
regional_co2_sequestration_potential:

View File

@ -41,9 +41,6 @@ logger = logging.getLogger(__name__)
pypsa.pf.logger.setLevel(logging.WARNING)
from pypsa.descriptors import get_switchable_as_dense as get_as_dense
from prepare_sector_network import emission_sectors_from_opts
def add_land_use_constraint(n, planning_horizons, config):
if "m" in snakemake.wildcards.clusters:
@ -765,100 +762,6 @@ def add_pipe_retrofit_constraint(n):
n.model.add_constraints(lhs == rhs, name="Link-pipe_retrofit")
def add_co2limit_country(n, limit_countries, nyears=1.0):
"""
Add a set of emissions limit constraints for specified countries.
The countries and emissions limits are specified in the config file entry 'co2_budget_country_{investment_year}'.
Parameters
----------
n : pypsa.Network
config : dict
limit_countries : dict
nyears: float, optional
Used to scale the emissions constraint to the number of snapshots of the base network.
"""
logger.info(f"Adding CO2 budget limit for each country as per unit of 1990 levels")
countries = n.config["countries"]
# TODO: import function from prepare_sector_network? Move to common place?
sectors = emission_sectors_from_opts(opts)
# convert Mt to tCO2
co2_totals = 1e6 * pd.read_csv(snakemake.input.co2_totals_name, index_col=0)
co2_limit_countries = co2_totals.loc[countries, sectors].sum(axis=1)
co2_limit_countries = co2_limit_countries.loc[co2_limit_countries.index.isin(limit_countries.keys())]
co2_limit_countries *= co2_limit_countries.index.map(limit_countries) * nyears
p = n.model["Link-p"] # dimension: (time, component)
# NB: Most country-specific links retain their locational information in bus1 (except for DAC, where it is in bus2, and process emissions, where it is in bus0)
country = n.links.bus1.map(n.buses.location).map(n.buses.country)
country_DAC = (
n.links[n.links.carrier == "DAC"]
.bus2.map(n.buses.location)
.map(n.buses.country)
)
country[country_DAC.index] = country_DAC
country_process_emissions = (
n.links[n.links.carrier.str.contains("process emissions")]
.bus0.map(n.buses.location)
.map(n.buses.country)
)
country[country_process_emissions.index] = country_process_emissions
lhs = []
for port in [col[3:] for col in n.links if col.startswith("bus")]:
if port == str(0):
efficiency = (
n.links["efficiency"].apply(lambda x: -1.0).rename("efficiency0")
)
elif port == str(1):
efficiency = n.links["efficiency"]
else:
efficiency = n.links[f"efficiency{port}"]
mask = n.links[f"bus{port}"].map(n.buses.carrier).eq("co2")
idx = n.links[mask].index
international = n.links.carrier.map(
lambda x: 0.4 if x in ["kerosene for aviation", "shipping oil"] else 1.0
)
grouping = country.loc[idx]
if not grouping.isnull().all():
expr = (
((p.loc[:, idx] * efficiency[idx] * international[idx])
.groupby(grouping, axis=1)
.sum()
*n.snapshot_weightings.generators
)
.sum(dims="snapshot")
)
lhs.append(expr)
lhs = sum(lhs) # dimension: (country)
lhs = lhs.rename({list(lhs.dims.keys())[0]: "snapshot"})
rhs = pd.Series(co2_limit_countries) # dimension: (country)
for ct in lhs.indexes["snapshot"]:
n.model.add_constraints(
lhs.loc[ct] <= rhs[ct],
name=f"GlobalConstraint-co2_limit_per_country{ct}",
)
n.add(
"GlobalConstraint",
f"co2_limit_per_country{ct}",
constant=rhs[ct],
sense="<=",
type="",
)
def extra_functionality(n, snapshots):
"""
Collects supplementary constraints which will be passed to
@ -889,23 +792,12 @@ def extra_functionality(n, snapshots):
add_carbon_budget_constraint(n, snapshots)
add_retrofit_gas_boiler_constraint(n, snapshots)
if n.config["sector"]["co2_budget_national"]:
# prepare co2 constraint
nhours = n.snapshot_weightings.generators.sum()
nyears = nhours / 8760
investment_year = int(snakemake.wildcards.planning_horizons[-4:])
limit_countries = snakemake.config["co2_budget_national"][investment_year]
# add co2 constraint for each country
logger.info(f"Add CO2 limit for each country")
add_co2limit_country(n, limit_countries, nyears)
if "additional_functionality" in snakemake.input.keys():
import importlib, os, sys
sys.path.append(os.path.dirname(snakemake.input.additional_functionality))
additional_functionality = importlib.import_module(os.path.splitext(os.path.basename(snakemake.input.additional_functionality))[0])
additional_functionality.additional_functionality(n, snapshots, snakemake.wildcards, config)
additional_functionality.additional_functionality(n, snapshots, snakemake)
def solve_network(n, config, solving, opts="", **kwargs):