Merge pull request #3 from PyPSA/master

update from master upstream
This commit is contained in:
martavp 2021-02-04 09:16:21 +01:00 committed by GitHub
commit 1b205b9e4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 267 additions and 235 deletions

View File

@ -26,7 +26,7 @@ the energy system and includes all greenhouse gas emitters except
waste management, agriculture, forestry and land use.
Please see the [documentation](https://pypsa-eur-sec.readthedocs.io/)
for installation instructions and other useful information.
for installation instructions and other useful information about the snakemake workflow.
This diagram gives an overview of the sectors and the links between
them:

View File

@ -15,19 +15,21 @@ scenario:
lv: [1.0,1.5] # allowed transmission line volume expansion, can be any float >= 1.0 (today) or "opt"
clusters: [45,50] # number of nodes in Europe, any integer between 37 (1 node per country-zone) and several hundred
opts: [''] # only relevant for PyPSA-Eur
sector_opts: [Co2L0-3H-T-H-B-I-solar3-dist1] # this is where the main scenario settings are
sector_opts: [Co2L0-3H-T-H-B-I-solar+p3-dist1] # this is where the main scenario settings are
# to really understand the options here, look in scripts/prepare_sector_network.py
# Co2Lx specifies the CO2 target in x% of the 1990 values; default will give default (5%);
# Co2L0p25 will give 25% CO2 emissions; Co2Lm0p05 will give 5% negative emissions
# xH is the temporal resolution; 3H is 3-hourly, i.e. one snapshot every 3 hours
# single letters are sectors: T for land transport, H for building heating,
# B for biomass supply, I for industry, shipping and aviation
# solarx or onwindx changes the available installable potential by factor x
# dist{n} includes distribution grids with investment cost of n times cost in data/costs.csv
# solar+c0.5 reduces the capital cost of solar to 50\% of reference value
# for myopic/perfect foresight cb states the carbon budget in GtCO2 (cumulative
# emissions throughout the transition path in the timeframe determined by the
# solar+p3 multiplies the available installable potential by factor 3
# dist{n} includes distribution grids with investment cost of n times cost in data/costs.csv
# for myopic/perfect foresight cb states the carbon budget in GtCO2 (cumulative
# emissions throughout the transition path in the timeframe determined by the
# planning_horizons), be:beta decay; ex:exponential decay
# cb40ex0 distributes a carbon budget of 40 GtCO2 following an exponential
# decay with initial growth rate 0
planning_horizons : [2030] # investment years for myopic and perfect; or costs year for overnight
# for example, set to [2020, 2030, 2040, 2050] for myopic foresight
@ -69,6 +71,7 @@ pypsa_eur:
"Link": ["DC"]
"Generator": ["onwind", "offwind-ac", "offwind-dc", "solar", "ror"]
"StorageUnit": ["PHS","hydro"]
"Store": []
biomass:
year: 2030
@ -99,7 +102,7 @@ sector:
'bev_dsm' : True #turns on EV battery
'bev_availability' : 0.5 #How many cars do smart charging
'v2g' : True #allows feed-in to grid from EV battery
#what is not EV or FCEV is fossil-fuelled
#what is not EV or FCEV is oil-fuelled ICE
'land_transport_fuel_cell_share': # 1 means all FCEVs
2020: 0
2030: 0.05
@ -331,7 +334,7 @@ plotting:
"Fischer-Tropsch" : "#44DD33"
"kerosene for aviation": "#44BB11"
"naphtha for industry" : "#44FF55"
"land transport fossil" : "#44DD33"
"land transport oil" : "#44DD33"
"water tanks" : "#BBBBBB"
"hot water storage" : "#BBBBBB"
"hot water charging" : "#BBBBBB"
@ -366,6 +369,7 @@ plotting:
"process emissions to stored" : "#444444"
"process emissions to atmosphere" : "#888888"
"process emissions" : "#222222"
"oil emissions" : "#666666"
"land transport fuel cell" : "#AAAAAA"
"biogas" : "#800000"
"solid biomass" : "#DAA520"

View File

@ -2,7 +2,7 @@ description,file/folder,licence,source
JRC IDEES database,jrc-idees-2015/,CC BY 4.0,https://ec.europa.eu/jrc/en/potencia/jrc-idees
urban/rural fraction,urban_percent.csv,unknown,unknown
JRC biomass potentials,biomass/,unknown,https://doi.org/10.2790/39014
EEA emission statistics,eea/,unknown,https://www.eea.europa.eu/data-and-maps/data/national-emissions-reported-to-the-unfccc-and-to-the-eu-greenhouse-gas-monitoring-mechanism-14
EEA emission statistics,eea/UNFCCC_v23.csv,EEA standard re-use policy,https://www.eea.europa.eu/data-and-maps/data/national-emissions-reported-to-the-unfccc-and-to-the-eu-greenhouse-gas-monitoring-mechanism-16
Eurostat Energy Balances,eurostat-energy_balances-*/,Eurostat,https://ec.europa.eu/eurostat/web/energy/data/energy-balances
Swiss energy statistics from Swiss Federal Office of Energy,switzerland-sfoe/,unknown,http://www.bfe.admin.ch/themen/00526/00541/00542/02167/index.html?dossier_id=02169
BASt emobility statistics,emobility/,unknown,http://www.bast.de/DE/Verkehrstechnik/Fachthemen/v2-verkehrszaehlung/Stundenwerte.html?nn=626916

1 description file/folder licence source
2 JRC IDEES database jrc-idees-2015/ CC BY 4.0 https://ec.europa.eu/jrc/en/potencia/jrc-idees
3 urban/rural fraction urban_percent.csv unknown unknown
4 JRC biomass potentials biomass/ unknown https://doi.org/10.2790/39014
5 EEA emission statistics eea/ eea/UNFCCC_v23.csv unknown EEA standard re-use policy https://www.eea.europa.eu/data-and-maps/data/national-emissions-reported-to-the-unfccc-and-to-the-eu-greenhouse-gas-monitoring-mechanism-14 https://www.eea.europa.eu/data-and-maps/data/national-emissions-reported-to-the-unfccc-and-to-the-eu-greenhouse-gas-monitoring-mechanism-16
6 Eurostat Energy Balances eurostat-energy_balances-*/ Eurostat https://ec.europa.eu/eurostat/web/energy/data/energy-balances
7 Swiss energy statistics from Swiss Federal Office of Energy switzerland-sfoe/ unknown http://www.bfe.admin.ch/themen/00526/00541/00542/02167/index.html?dossier_id=02169
8 BASt emobility statistics emobility/ unknown http://www.bast.de/DE/Verkehrstechnik/Fachthemen/v2-verkehrszaehlung/Stundenwerte.html?nn=626916

View File

@ -73,8 +73,8 @@ To download and extract the data bundle on the command line:
.. code:: bash
projects/pypsa-eur-sec/data % wget "https://nworbmot.org/pypsa-eur-sec-data-bundle-201012.tar.gz"
projects/pypsa-eur-sec/data % tar xvzf pypsa-eur-sec-data-bundle-201012.tar.gz
projects/pypsa-eur-sec/data % wget "https://nworbmot.org/pypsa-eur-sec-data-bundle-210125.tar.gz"
projects/pypsa-eur-sec/data % tar xvzf pypsa-eur-sec-data-bundle-210125.tar.gz
The data licences and sources are given in the following table.

View File

@ -4,8 +4,13 @@ Release Notes
Future release
===================
*For the myopic option, a carbon budget and a type of decay (exponential or beta) can be selected in the config file to distribute the budget across the planning_horizons.
*Added an option to alter the capital cost of carriers by a factor via ``carrier+factor`` in the ``{opts}`` wildcard. This can be useful for exploring uncertain cost parameters. Example: ``solar+0.5`` reduces the capital cost of solar to 50\% of original values.
* For the myopic investment option, a carbon budget and a type of decay (exponential or beta) can be selected in the ``config.yaml`` file to distribute the budget across the ``planning_horizons``. For example, ``cb40ex0`` in the ``{sector_opts}`` wildcard will distribute a carbon budget of 40 GtCO2 following an exponential decay with initial growth rate 0.
* Added an option to alter the capital cost or maximum capacity of carriers by a factor via ``carrier+factor`` in the ``{sector_opts}`` wildcard. This can be useful for exploring uncertain cost parameters. Example: ``solar+c0.5`` reduces the ``capital_cost`` of solar to 50\% of original values. Similarly ``solar+p3`` multiplies the ``p_nom_max`` by 3.
* Rename the bus for European liquid hydrocarbons from ``Fischer-Tropsch`` to ``EU oil``, since it can be supplied not just with the Fischer-Tropsch process, but also with fossil oil.
* Bugfix: Fix reading in of ``pypsa-eur/resources/powerplants.csv`` to PyPSA-Eur Version 0.3.0 (use column attribute name ``DateIn`` instead of old ``YearDecommissioned``).
* Bugfix: Make sure that ``Store`` components (battery and H2) are also removed from PyPSA-Eur, so they can be added later by PyPSA-Eur-Sec.
PyPSA-Eur-Sec 0.4.0 (11th December 2020)
=========================================
@ -131,4 +136,4 @@ To make a new release of the data bundle, make an archive of the files in ``data
.. code:: bash
data % tar pczf pypsa-eur-sec-data-bundle-date.tar.gz eea switzerland-sfoe biomass eurostat-energy_balances-* jrc-idees-2015 emobility urban_percent.csv timezone_mappings.csv heat_load_profile_DK_AdamJensen.csv WindWaveWEC_GLTB.xlsx myb1-2017-nitro.xls Industrial_Database.csv
data % tar pczf pypsa-eur-sec-data-bundle-YYMMDD.tar.gz eea/UNFCCC_v23.csv switzerland-sfoe biomass eurostat-energy_balances-* jrc-idees-2015 emobility urban_percent.csv timezone_mappings.csv heat_load_profile_DK_AdamJensen.csv WindWaveWEC_GLTB.xlsx myb1-2017-nitro.xls Industrial_Database.csv

View File

@ -62,6 +62,9 @@ if __name__ == "__main__":
with open('config.yaml', encoding='utf8') as f:
snakemake.config = yaml.safe_load(f)
# This is a hack, to be replaced once snakemake is unicode-conform
if 'Secondary Forestry residues sawdust' in snakemake.config['biomass']['classes']['solid biomass']:
snakemake.config['biomass']['classes']['solid biomass'].remove('Secondary Forestry residues sawdust')
snakemake.config['biomass']['classes']['solid biomass'].append('Secondary Forestry residues sawdust')

View File

@ -1,6 +1,6 @@
import numpy as np
import pandas as pd
#allow plotting without Xwindows
@ -9,7 +9,7 @@ matplotlib.use('Agg')
import matplotlib.pyplot as plt
from prepare_sector_network import co2_emissions_year
#consolidate and rename
def rename_techs(label):
@ -237,7 +237,137 @@ def plot_balances():
fig.savefig(snakemake.output.balances[:-10] + k + ".pdf",transparent=True)
def historical_emissions(cts):
"""
read historical emissions to add them to the carbon budget plot
"""
#https://www.eea.europa.eu/data-and-maps/data/national-emissions-reported-to-the-unfccc-and-to-the-eu-greenhouse-gas-monitoring-mechanism-16
#downloaded 201228 (modified by EEA last on 201221)
fn = "data/eea/UNFCCC_v23.csv"
df = pd.read_csv(fn, encoding="latin-1")
df.loc[df["Year"] == "1985-1987","Year"] = 1986
df["Year"] = df["Year"].astype(int)
df = df.set_index(['Year', 'Sector_name', 'Country_code', 'Pollutant_name']).sort_index()
e = pd.Series()
e["electricity"] = '1.A.1.a - Public Electricity and Heat Production'
e['residential non-elec'] = '1.A.4.b - Residential'
e['services non-elec'] = '1.A.4.a - Commercial/Institutional'
e['rail non-elec'] = "1.A.3.c - Railways"
e["road non-elec"] = '1.A.3.b - Road Transportation'
e["domestic navigation"] = "1.A.3.d - Domestic Navigation"
e['international navigation'] = '1.D.1.b - International Navigation'
e["domestic aviation"] = '1.A.3.a - Domestic Aviation'
e["international aviation"] = '1.D.1.a - International Aviation'
e['total energy'] = '1 - Energy'
e['industrial processes'] = '2 - Industrial Processes and Product Use'
e['agriculture'] = '3 - Agriculture'
e['LULUCF'] = '4 - Land Use, Land-Use Change and Forestry'
e['waste management'] = '5 - Waste management'
e['other'] = '6 - Other Sector'
e['indirect'] = 'ind_CO2 - Indirect CO2'
e["total wL"] = "Total (with LULUCF)"
e["total woL"] = "Total (without LULUCF)"
pol = ["CO2"] # ["All greenhouse gases - (CO2 equivalent)"]
cts
if "GB" in cts:
cts.remove("GB")
cts.append("UK")
year = np.arange(1990,2018).tolist()
idx = pd.IndexSlice
co2_totals = df.loc[idx[year,e.values,cts,pol],"emissions"].unstack("Year").rename(index=pd.Series(e.index,e.values))
co2_totals = (1/1e6)*co2_totals.groupby(level=0, axis=0).sum() #Gton CO2
co2_totals.loc['industrial non-elec'] = co2_totals.loc['total energy'] - co2_totals.loc[['electricity', 'services non-elec','residential non-elec', 'road non-elec',
'rail non-elec', 'domestic aviation', 'international aviation', 'domestic navigation',
'international navigation']].sum()
emissions = co2_totals.loc["electricity"]
if "T" in opts:
emissions += co2_totals.loc[[i+ " non-elec" for i in ["rail","road"]]].sum()
if "H" in opts:
emissions += co2_totals.loc[[i+ " non-elec" for i in ["residential","services"]]].sum()
if "I" in opts:
emissions += co2_totals.loc[["industrial non-elec","industrial processes",
"domestic aviation","international aviation",
"domestic navigation","international navigation"]].sum()
return emissions
def plot_carbon_budget_distribution():
"""
Plot historical carbon emissions in the EU and decarbonization path
"""
import matplotlib.gridspec as gridspec
import seaborn as sns; sns.set()
sns.set_style('ticks')
plt.style.use('seaborn-ticks')
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['xtick.labelsize'] = 20
plt.rcParams['ytick.labelsize'] = 20
plt.figure(figsize=(10, 7))
gs1 = gridspec.GridSpec(1, 1)
ax1 = plt.subplot(gs1[0,0])
ax1.set_ylabel('CO$_2$ emissions (Gt per year)',fontsize=22)
ax1.set_ylim([0,5])
ax1.set_xlim([1990,snakemake.config['scenario']['planning_horizons'][-1]+1])
path_cb = snakemake.config['results_dir'] + snakemake.config['run'] + '/csvs/'
countries=pd.read_csv(path_cb + 'countries.csv', index_col=1)
cts=countries.index.to_list()
e_1990 = co2_emissions_year(cts, opts, year=1990)
CO2_CAP=pd.read_csv(path_cb + 'carbon_budget_distribution.csv',
index_col=0)
ax1.plot(e_1990*CO2_CAP[o],linewidth=3,
color='dodgerblue', label=None)
emissions = historical_emissions(cts)
ax1.plot(emissions, color='black', linewidth=3, label=None)
#plot commited and uder-discussion targets
#(notice that historical emissions include all countries in the
# network, but targets refer to EU)
ax1.plot([2020],[0.8*emissions[1990]],
marker='*', markersize=12, markerfacecolor='black',
markeredgecolor='black')
ax1.plot([2030],[0.45*emissions[1990]],
marker='*', markersize=12, markerfacecolor='white',
markeredgecolor='black')
ax1.plot([2030],[0.6*emissions[1990]],
marker='*', markersize=12, markerfacecolor='black',
markeredgecolor='black')
ax1.plot([2050, 2050],[x*emissions[1990] for x in [0.2, 0.05]],
color='gray', linewidth=2, marker='_', alpha=0.5)
ax1.plot([2050],[0.01*emissions[1990]],
marker='*', markersize=12, markerfacecolor='white',
linewidth=0, markeredgecolor='black',
label='EU under-discussion target', zorder=10,
clip_on=False)
ax1.plot([2050],[0.125*emissions[1990]],'ro',
marker='*', markersize=12, markerfacecolor='black',
markeredgecolor='black', label='EU commited target')
ax1.legend(fancybox=True, fontsize=18, loc=(0.01,0.01),
facecolor='white', frameon=True)
path_cb_plot = snakemake.config['results_dir'] + snakemake.config['run'] + '/graphs/'
plt.savefig(path_cb_plot+'carbon_budget_plot.pdf', dpi=300)
if __name__ == "__main__":
# Detect running outside of snakemake and mock snakemake for testing
@ -249,13 +379,16 @@ if __name__ == "__main__":
snakemake.config = yaml.safe_load(f)
snakemake.input = Dict()
snakemake.output = Dict()
snakemake.wildcards = Dict()
#snakemake.wildcards['sector_opts']='3H-T-H-B-I-solar3-dist1-cb48be3'
for item in ["costs", "energy"]:
snakemake.input[item] = snakemake.config['summary_dir'] + '/{name}/csvs/{item}.csv'.format(name=snakemake.config['run'],item=item)
snakemake.output[item] = snakemake.config['summary_dir'] + '/{name}/graphs/{item}.pdf'.format(name=snakemake.config['run'],item=item)
snakemake.input["balances"] = snakemake.config['summary_dir'] + '/test/csvs/supply_energy.csv'
snakemake.output["balances"] = snakemake.config['summary_dir'] + '/test/graphs/balances-energy.csv'
snakemake.input["balances"] = snakemake.config['summary_dir'] + '/{name}/csvs/supply_energy.csv'.format(name=snakemake.config['run'],item=item)
snakemake.output["balances"] = snakemake.config['summary_dir'] + '/{name}/graphs/balances-energy.csv'.format(name=snakemake.config['run'],item=item)
n_header = 4
plot_costs()
@ -263,3 +396,9 @@ if __name__ == "__main__":
plot_energy()
plot_balances()
for sector_opts in snakemake.config['scenario']['sector_opts']:
opts=sector_opts.split('-')
for o in opts:
if "cb" in o:
plot_carbon_budget_distribution()

View File

@ -48,24 +48,24 @@ override_component_attrs["Store"].loc["build_year"] = ["integer","year",np.nan,"
override_component_attrs["Store"].loc["lifetime"] = ["float","years",np.nan,"lifetime","Input (optional)"]
def co2_emissions_year(year):
def co2_emissions_year(cts, opts, year):
"""
calculate co2 emissions in one specific year (e.g. 1990 or 2018).
"""
eea_co2 = build_eea_co2(year)
#TODO: read Eurostat data from year>2014, this only affects the estimation of
#TODO: read Eurostat data from year>2014, this only affects the estimation of
# CO2 emissions for "BA","RS","AL","ME","MK"
if year > 2014:
eurostat_co2 = build_eurostat_co2(year=2014)
else:
eurostat_co2 = build_eurostat_co2(year)
co2_totals=build_co2_totals(eea_co2, eurostat_co2, year)
pop_layout = pd.read_csv(snakemake.input.clustered_pop_layout, index_col=0)
pop_layout["ct"] = pop_layout.index.str[:2]
cts = pop_layout.ct.value_counts().index
co2_emissions = co2_totals.loc[cts, "electricity"].sum()
@ -80,173 +80,57 @@ def co2_emissions_year(year):
co2_emissions *=0.001 #MtCO2 to GtCO2
return co2_emissions
def historical_emissions():
"""
read historical emissions to add them to the carbon budget plot
"""
#https://www.eea.europa.eu/data-and-maps/data/national-emissions-reported-to-the-unfccc-and-to-the-eu-greenhouse-gas-monitoring-mechanism-16
#downloaded 201228 (modified by EEA last on 201221)
fn = "data/eea/UNFCCC_v23.csv"
df = pd.read_csv(fn, encoding="latin-1")
df.loc[df["Year"] == "1985-1987","Year"] = 1986
df["Year"] = df["Year"].astype(int)
df = df.set_index(['Year', 'Sector_name', 'Country_code', 'Pollutant_name']).sort_index()
e = pd.Series()
e["electricity"] = '1.A.1.a - Public Electricity and Heat Production'
e['residential non-elec'] = '1.A.4.b - Residential'
e['services non-elec'] = '1.A.4.a - Commercial/Institutional'
e['rail non-elec'] = "1.A.3.c - Railways"
e["road non-elec"] = '1.A.3.b - Road Transportation'
e["domestic navigation"] = "1.A.3.d - Domestic Navigation"
e['international navigation'] = '1.D.1.b - International Navigation'
e["domestic aviation"] = '1.A.3.a - Domestic Aviation'
e["international aviation"] = '1.D.1.a - International Aviation'
e['total energy'] = '1 - Energy'
e['industrial processes'] = '2 - Industrial Processes and Product Use'
e['agriculture'] = '3 - Agriculture'
e['LULUCF'] = '4 - Land Use, Land-Use Change and Forestry'
e['waste management'] = '5 - Waste management'
e['other'] = '6 - Other Sector'
e['indirect'] = 'ind_CO2 - Indirect CO2'
e["total wL"] = "Total (with LULUCF)"
e["total woL"] = "Total (without LULUCF)"
pol = ["CO2"] # ["All greenhouse gases - (CO2 equivalent)"]
pop_layout = pd.read_csv(snakemake.input.clustered_pop_layout, index_col=0)
pop_layout["ct"] = pop_layout.index.str[:2]
cts = pop_layout.ct.value_counts().index.to_list()
if "GB" in cts:
cts.remove("GB")
cts.append("UK")
year = np.arange(1990,2018).tolist()
idx = pd.IndexSlice
co2_totals = df.loc[idx[year,e.values,cts,pol],"emissions"].unstack("Year").rename(index=pd.Series(e.index,e.values))
co2_totals = (1/1e6)*co2_totals.groupby(level=0, axis=0).sum() #Gton CO2
co2_totals.loc['industrial non-elec'] = co2_totals.loc['total energy'] - co2_totals.loc[['electricity', 'services non-elec','residential non-elec', 'road non-elec',
'rail non-elec', 'domestic aviation', 'international aviation', 'domestic navigation',
'international navigation']].sum()
emissions = co2_totals.loc["electricity"]
if "T" in opts:
emissions += co2_totals.loc[[i+ " non-elec" for i in ["rail","road"]]].sum()
if "H" in opts:
emissions += co2_totals.loc[[i+ " non-elec" for i in ["residential","services"]]].sum()
if "I" in opts:
emissions += co2_totals.loc[["industrial non-elec","industrial processes",
"domestic aviation","international aviation",
"domestic navigation","international navigation"]].sum()
return emissions
def build_carbon_budget(o):
#distribute carbon budget following beta or exponential transition path
if "be" in o:
if "be" in o:
#beta decay
carbon_budget = float(o[o.find("cb")+2:o.find("be")])
be=float(o[o.find("be")+2:])
if "ex" in o:
be=float(o[o.find("be")+2:])
if "ex" in o:
#exponential decay
carbon_budget = float(o[o.find("cb")+2:o.find("ex")])
r=float(o[o.find("ex")+2:])
e_1990 = co2_emissions_year(year=1990)
pop_layout = pd.read_csv(snakemake.input.clustered_pop_layout, index_col=0)
pop_layout["ct"] = pop_layout.index.str[:2]
cts = pop_layout.ct.value_counts().index
e_1990 = co2_emissions_year(cts, opts, year=1990)
#emissions at the beginning of the path (last year available 2018)
e_0 = co2_emissions_year(year=2018)
e_0 = co2_emissions_year(cts, opts, year=2018)
#emissions in 2019 and 2020 assumed equal to 2018 and substracted
carbon_budget -= 2*e_0
planning_horizons = snakemake.config['scenario']['planning_horizons']
CO2_CAP = pd.DataFrame(index = pd.Series(data=planning_horizons,
CO2_CAP = pd.DataFrame(index = pd.Series(data=planning_horizons,
name='planning_horizon'),
columns=pd.Series(data=[],
name='paths',
name='paths',
dtype='float'))
t_0 = planning_horizons[0]
if "be" in o:
#beta decay
t_f = t_0 + (2*carbon_budget/e_0).round(0) # final year in the path
if "be" in o:
#beta decay
t_f = t_0 + (2*carbon_budget/e_0).round(0) # final year in the path
#emissions (relative to 1990)
CO2_CAP[o] = [(e_0/e_1990)*(1-beta.cdf((t-t_0)/(t_f-t_0), be, be)) for t in planning_horizons]
if "ex" in o:
if "ex" in o:
#exponential decay without delay
T=carbon_budget/e_0
m=(1+np.sqrt(1+r*T))/T
CO2_CAP[o] = [(e_0/e_1990)*(1+(m+r)*(t-t_0))*np.exp(-m*(t-t_0)) for t in planning_horizons]
CO2_CAP.to_csv(path_cb + 'carbon_budget_distribution.csv', sep=',',
line_terminator='\n', float_format='%.3f')
"""
Plot historical carbon emissions in the EU and decarbonization path
"""
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import seaborn as sns; sns.set()
sns.set_style('ticks')
plt.style.use('seaborn-ticks')
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['xtick.labelsize'] = 20
plt.rcParams['ytick.labelsize'] = 20
plt.figure(figsize=(10, 7))
gs1 = gridspec.GridSpec(1, 1)
ax1 = plt.subplot(gs1[0,0])
ax1.set_ylabel('CO$_2$ emissions (Gt per year)',fontsize=22)
ax1.set_ylim([0,5])
ax1.set_xlim([1990,planning_horizons[-1]+1])
ax1.plot(e_1990*CO2_CAP[o],linewidth=3,
color='dodgerblue', label=None)
emissions = historical_emissions()
ax1.plot(emissions, color='black', linewidth=3, label=None)
#plot commited and uder-discussion targets
#(notice that historical emissions include all countries in the
# network, but targets refer to EU)
ax1.plot([2020],[0.8*emissions[1990]],
marker='*', markersize=12, markerfacecolor='black',
markeredgecolor='black')
ax1.plot([2030],[0.45*emissions[1990]],
marker='*', markersize=12, markerfacecolor='white',
markeredgecolor='black')
ax1.plot([2030],[0.6*emissions[1990]],
marker='*', markersize=12, markerfacecolor='black',
markeredgecolor='black')
ax1.plot([2050, 2050],[x*emissions[1990] for x in [0.2, 0.05]],
color='gray', linewidth=2, marker='_', alpha=0.5)
ax1.plot([2050],[0.01*emissions[1990]],
marker='*', markersize=12, markerfacecolor='white',
linewidth=0, markeredgecolor='black',
label='EU under-discussion target', zorder=10,
clip_on=False)
ax1.plot([2050],[0.125*emissions[1990]],'ro',
marker='*', markersize=12, markerfacecolor='black',
markeredgecolor='black', label='EU commited target')
ax1.legend(fancybox=True, fontsize=18, loc=(0.01,0.01),
facecolor='white', frameon=True)
path_cb_plot = snakemake.config['results_dir'] + snakemake.config['run'] + '/graphs/'
if not os.path.exists(path_cb_plot):
os.makedirs(path_cb_plot)
print('carbon budget distribution saved to ' + path_cb_plot + 'carbon_budget_plot.pdf')
plt.savefig(path_cb_plot+'carbon_budget_plot.pdf', dpi=300)
CO2_CAP.to_csv(path_cb + 'carbon_budget_distribution.csv', sep=',',
line_terminator='\n', float_format='%.3f')
countries=pd.Series(data=cts)
countries.to_csv(path_cb + 'countries.csv', sep=',',
line_terminator='\n', float_format='%.3f')
def add_lifetime_wind_solar(n):
"""
Add lifetime for solar and wind generators
@ -799,7 +683,7 @@ def add_generation(network):
capital_cost=0.) #could correct to e.g. 0.2 EUR/kWh * annuity and O&M
network.add("Generator",
"EU fossil " + carrier,
"EU " + carrier,
bus="EU " + carrier,
p_nom_extendable=True,
carrier=carrier,
@ -1179,14 +1063,14 @@ def add_land_transport(network):
fuel_cell_share = get_parameter(options["land_transport_fuel_cell_share"])
electric_share = get_parameter(options["land_transport_electric_share"])
fossil_share = 1 - fuel_cell_share - electric_share
ice_share = 1 - fuel_cell_share - electric_share
print("shares of FCEV, EV and ICEV are",
fuel_cell_share,
electric_share,
fossil_share)
ice_share)
if fossil_share < 0:
if ice_share < 0:
print("Error, more FCEV and EV share than 1.")
sys.exit()
@ -1264,14 +1148,14 @@ def add_land_transport(network):
p_set=fuel_cell_share/options['transport_fuel_cell_efficiency']*transport[nodes])
if fossil_share > 0:
if ice_share > 0:
network.madd("Load",
nodes,
suffix=" land transport fossil",
bus="Fischer-Tropsch",
carrier="land transport fossil",
p_set=fossil_share/options['transport_internal_combustion_efficiency']*transport[nodes])
suffix=" land transport oil",
bus="EU oil",
carrier="land transport oil",
p_set=ice_share/options['transport_internal_combustion_efficiency']*transport[nodes])
@ -1463,7 +1347,7 @@ def add_heat(network):
capital_cost=costs.at['central gas CHP','fixed']*costs.at['central gas CHP','efficiency'] + costs.at['biomass CHP capture','fixed']*costs.at['gas','CO2 intensity'],
marginal_cost=costs.at['central gas CHP','VOM'],
efficiency=costs.at['central gas CHP','efficiency'] - costs.at['gas','CO2 intensity']*(costs.at['biomass CHP capture','electricity-input'] + costs.at['biomass CHP capture','compression-electricity-input']),
efficiency2=costs.at['central gas CHP','efficiency']/costs.at['central gas CHP','c_b'] + costs.at['gas','CO2 intensity']*(costs.at['biomass CHP capture','heat-output'] + costs.at['biomass CHP capture','compression-heat-output'] - costs.at['biomass CHP capture','heat-output']),
efficiency2=costs.at['central gas CHP','efficiency']/costs.at['central gas CHP','c_b'] + costs.at['gas','CO2 intensity']*(costs.at['biomass CHP capture','heat-output'] + costs.at['biomass CHP capture','compression-heat-output'] - costs.at['biomass CHP capture','heat-input']),
efficiency3=costs.at['gas','CO2 intensity']*(1-costs.at['biomass CHP capture','capture_rate']),
efficiency4=costs.at['gas','CO2 intensity']*costs.at['biomass CHP capture','capture_rate'],
lifetime=costs.at['central gas CHP','lifetime'])
@ -1682,7 +1566,7 @@ def add_biomass(network):
capital_cost=costs.at['central solid biomass CHP','fixed']*costs.at['central solid biomass CHP','efficiency'] + costs.at['biomass CHP capture','fixed']*costs.at['solid biomass','CO2 intensity'],
marginal_cost=costs.at['central solid biomass CHP','VOM'],
efficiency=costs.at['central solid biomass CHP','efficiency'] - costs.at['solid biomass','CO2 intensity']*(costs.at['biomass CHP capture','electricity-input'] + costs.at['biomass CHP capture','compression-electricity-input']),
efficiency2=costs.at['central solid biomass CHP','efficiency-heat'] + costs.at['solid biomass','CO2 intensity']*(costs.at['biomass CHP capture','heat-output'] + costs.at['biomass CHP capture','compression-heat-output'] - costs.at['biomass CHP capture','heat-output']),
efficiency2=costs.at['central solid biomass CHP','efficiency-heat'] + costs.at['solid biomass','CO2 intensity']*(costs.at['biomass CHP capture','heat-output'] + costs.at['biomass CHP capture','compression-heat-output'] - costs.at['biomass CHP capture','heat-input']),
efficiency3=-costs.at['solid biomass','CO2 intensity']*costs.at['biomass CHP capture','capture_rate'],
efficiency4=costs.at['solid biomass','CO2 intensity']*costs.at['biomass CHP capture','capture_rate'],
lifetime=costs.at['central solid biomass CHP','lifetime'])
@ -1787,27 +1671,30 @@ def add_industry(network):
carrier="H2 for shipping",
p_set = nodal_energy_totals.loc[nodes,["total international navigation","total domestic navigation"]].sum(axis=1)*1e6*options['shipping_average_efficiency']/costs.at["fuel cell","efficiency"]/8760.)
network.madd("Bus",
["Fischer-Tropsch"],
location="EU",
carrier="Fischer-Tropsch")
if "EU oil" not in network.buses.index:
network.madd("Bus",
["EU oil"],
location="EU",
carrier="oil")
#use madd to get carrier inserted
network.madd("Store",
["Fischer-Tropsch Store"],
bus="Fischer-Tropsch",
e_nom_extendable=True,
e_cyclic=True,
carrier="Fischer-Tropsch",
capital_cost=0.) #could correct to e.g. 0.001 EUR/kWh * annuity and O&M
if "EU oil Store" not in network.stores.index:
network.madd("Store",
["EU oil Store"],
bus="EU oil",
e_nom_extendable=True,
e_cyclic=True,
carrier="oil",
capital_cost=0.) #could correct to e.g. 0.001 EUR/kWh * annuity and O&M
network.add("Generator",
"fossil oil",
bus="Fischer-Tropsch",
p_nom_extendable=True,
carrier="oil",
capital_cost=0.,
marginal_cost=costs.at["oil",'fuel'])
if "EU oil" not in network.generators.index:
network.add("Generator",
"EU oil",
bus="EU oil",
p_nom_extendable=True,
carrier="oil",
capital_cost=0.,
marginal_cost=costs.at["oil",'fuel'])
if options["oil_boilers"]:
@ -1817,7 +1704,7 @@ def add_industry(network):
network.madd("Link",
nodes_heat[name] + " " + name + " oil boiler",
p_nom_extendable=True,
bus0=["Fischer-Tropsch"] * len(nodes_heat[name]),
bus0="EU oil",
bus1=nodes_heat[name] + " " + name + " heat",
bus2="co2 atmosphere",
carrier=name + " oil boiler",
@ -1830,7 +1717,7 @@ def add_industry(network):
network.madd("Link",
nodes + " Fischer-Tropsch",
bus0=nodes + " H2",
bus1="Fischer-Tropsch",
bus1="EU oil",
bus2="co2 stored",
carrier="Fischer-Tropsch",
efficiency=costs.at["Fischer-Tropsch",'efficiency'],
@ -1841,13 +1728,13 @@ def add_industry(network):
network.madd("Load",
["naphtha for industry"],
bus="Fischer-Tropsch",
bus="EU oil",
carrier="naphtha for industry",
p_set = industrial_demand.loc[nodes,"naphtha"].sum()/8760.)
network.madd("Load",
["kerosene for aviation"],
bus="Fischer-Tropsch",
bus="EU oil",
carrier="kerosene for aviation",
p_set = nodal_energy_totals.loc[nodes,["total international aviation","total domestic aviation"]].sum(axis=1).sum()*1e6/8760.)
@ -1857,9 +1744,9 @@ def add_industry(network):
co2 = network.loads.loc[["naphtha for industry","kerosene for aviation"],"p_set"].sum()*costs.at["oil",'CO2 intensity'] - industrial_demand.loc[nodes,"process emission from feedstock"].sum()/8760.
network.madd("Load",
["Fischer-Tropsch emissions"],
["oil emissions"],
bus="co2 atmosphere",
carrier="Fischer-Tropsch emissions",
carrier="oil emissions",
p_set=-co2)
network.madd("Load",
@ -1935,13 +1822,6 @@ def add_waste_heat(network):
network.links.loc[urban_central + " H2 Fuel Cell","bus2"] = urban_central + " urban central heat"
network.links.loc[urban_central + " H2 Fuel Cell","efficiency2"] = 0.95 - network.links.loc[urban_central + " H2 Fuel Cell","efficiency"]
def restrict_technology_potential(n,tech,limit):
print("restricting potentials (p_nom_max) for {} to {} of technical potential".format(tech,limit))
gens = n.generators.index[n.generators.carrier.str.contains(tech)]
#beware if limit is 0 and p_nom_max is np.inf, 0*np.inf is nan
n.generators.loc[gens,"p_nom_max"] *=limit
def decentral(n):
n.lines.drop(n.lines.index,inplace=True)
n.links.drop(n.links.index[n.links.carrier.isin(["DC","B2B"])],inplace=True)
@ -1984,8 +1864,9 @@ if __name__ == "__main__":
from vresutils.snakemake import MockSnakemake
snakemake = MockSnakemake(
wildcards=dict(network='elec', simpl='', clusters='37', lv='1.0',
opts='', planning_horizons='2050',
sector_opts='Co2L0-180H-T-H-B-I-solar3-dist1-solar+c0.5-Sabatier+c0.5'),
opts='', planning_horizons='2020',
sector_opts='120H-T-H-B-I-onwind+p3-dist1-cb48be3'),
input=dict( network='../pypsa-eur/networks/{network}_s{simpl}_{clusters}_ec_lv{lv}_{opts}.nc',
energy_totals_name='resources/energy_totals.csv',
co2_totals_name='resources/co2_totals.csv',
@ -2024,7 +1905,8 @@ if __name__ == "__main__":
retro_cost_energy = "resources/retro_cost_{network}_s{simpl}_{clusters}.csv",
floor_area = "resources/floor_area_{network}_s{simpl}_{clusters}.csv"
),
output=['results/version-0/prenetworks/{network}_s{simpl}_{clusters}_lv{lv}__{sector_opts}_{planning_horizons}.nc']
output=['results/version-cb48be3/prenetworks/{network}_s{simpl}_{clusters}_lv{lv}__{sector_opts}_{planning_horizons}.nc']
)
import yaml
with open('config.yaml', encoding='utf8') as f:
@ -2129,19 +2011,21 @@ if __name__ == "__main__":
print("CO2 limit set to",limit)
for o in opts:
if "cb" in o:
if "cb" in o:
path_cb = snakemake.config['results_dir'] + snakemake.config['run'] + '/csvs/'
if not os.path.exists(path_cb):
os.makedirs(path_cb)
try:
CO2_CAP=pd.read_csv(path_cb + 'carbon_budget_distribution.csv', index_col=0)
except:
except:
build_carbon_budget(o)
CO2_CAP=pd.read_csv(path_cb + 'carbon_budget_distribution.csv', index_col=0)
limit=CO2_CAP.loc[investment_year]
limit=CO2_CAP.loc[investment_year]
print("overriding CO2 limit with scenario limit",limit)
for o in opts:
if "Co2L" in o:
limit = o[o.find("Co2L")+4:]
@ -2150,14 +2034,8 @@ if __name__ == "__main__":
print("adding CO2 budget limit as per unit of 1990 levels of",limit)
add_co2limit(n, Nyears, limit)
for o in opts:
for tech in ["solar","onwind","offwind"]:
if tech in o and "+" not in o:
limit = o[o.find(tech)+len(tech):]
limit = float(limit.replace("p",".").replace("m","-"))
print("changing potential for",tech,"by factor",limit)
restrict_technology_potential(n,tech,limit)
if o[:10] == 'linemaxext':
maxext = float(o[10:])*1e3
@ -2169,28 +2047,31 @@ if __name__ == "__main__":
if snakemake.config["sector"]['electricity_distribution_grid']:
insert_electricity_distribution_grid(n)
for o in opts:
if "+" in o:
oo = o.split("+")
carrier_list=np.hstack((n.generators.carrier.unique(), n.links.carrier.unique(),
n.stores.carrier.unique(), n.storage_units.carrier.unique()))
suptechs = map(lambda c: c.split("-", 2)[0], carrier_list)
carrier_list=np.hstack((n.generators.carrier.unique(), n.links.carrier.unique(),
n.stores.carrier.unique(), n.storage_units.carrier.unique()))
suptechs = map(lambda c: c.split("-", 2)[0], carrier_list)
if oo[0].startswith(tuple(suptechs)):
carrier = oo[0]
# handles only p_nom_max as stores and lines have no potentials
attr_lookup = {"p": "p_nom_max", "c": "capital_cost"}
attr = attr_lookup[oo[1][0]]
factor = float(oo[1][1:])
#beware if factor is 0 and p_nom_max is np.inf, 0*np.inf is nan
if carrier == "AC": # lines do not have carrier
n.lines[attr] *= factor
else:
comps = {"Generator", "Link", "StorageUnit", "Store"}
comps = {"Generator", "Link", "StorageUnit"} if attr=='p_nom_max' else {"Generator", "Link", "StorageUnit", "Store"}
for c in n.iterate_components(comps):
sel = c.df.carrier.str.contains(carrier)
if carrier=='solar':
sel = c.df.carrier.str.contains(carrier) & ~c.df.carrier.str.contains("solar rooftop")
else:
sel = c.df.carrier.str.contains(carrier)
c.df.loc[sel,attr] *= factor
print("changing", attr ,"for",carrier,"by factor",factor)
if snakemake.config["sector"]['gas_distribution_grid']:
insert_gas_distribution_costs(n)
if snakemake.config["sector"]['electricity_grid_connection']: