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:
parent
8a55a55d20
commit
1b569dde1b
@ -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:
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user