From 1b569dde1bcbcd32175d41b0ba3ed265e76a1aad Mon Sep 17 00:00:00 2001 From: Tom Brown Date: Tue, 2 Jan 2024 16:02:10 +0100 Subject: [PATCH] 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. --- config/config.default.yaml | 17 ------ scripts/solve_network.py | 110 +------------------------------------ 2 files changed, 1 insertion(+), 126 deletions(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index c1e7ed0f..6d2ebd9f 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -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: diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 6f88b904..433b175b 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -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):