From e88b6c850ff095664622fe82a294605a2ff1b74e Mon Sep 17 00:00:00 2001 From: Tom Brown Date: Wed, 17 Apr 2019 17:04:33 +0200 Subject: [PATCH] Fix prepare_sector_network to reproduce old sector prenetworks from pypsa-eur sector branch --- README.md | 29 +++++++++++++ Snakefile | 18 ++++---- config.yaml | 69 ++++++++++++++++++++++++++++++- scripts/prepare_sector_network.py | 41 ++++++++++-------- 4 files changed, 131 insertions(+), 26 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..c8165c42 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# PyPSA-Eur-Sec: A Sector-Coupled Open Optimisation Model of the European Energy System + +PyPSA-Eur-Sec builds on the electricity generation and transmission +model [PyPSA-Eur](https://github.com/PyPSA/pypsa-eur) to add demand +and supply for the following sectors: transport, space and water +heating, biomass, industry and industrial feedstocks. This completes +the energy system and includes all greenhouse gas emitters except +waste management, agriculture, forestry and land use. + +PyPSA-Eur-Sec includes PyPSA-Eur as a +[snakemake](https://snakemake.readthedocs.io/en/stable/index.html) +[subworkflow](https://snakemake.readthedocs.io/en/stable/snakefiles/modularization.html#snakefiles-sub-workflows). PyPSA-Eur-Sec +uses PyPSA-Eur to build the clustered transmission model along with +wind, solar PV and hydroelectricity potentials and time series. Then +PyPSA-Eur-Sec adds other conventional generators, storage units and +the additional sectors. + +Currently the scripts to solve and process the resulting PyPSA models +are also included in PyPSA-Eur-Sec, although they could in future be +better integrated with the corresponding scripts in PyPSA-Eur. + +# Installation + +First install [PyPSA-Eur](https://github.com/PyPSA/pypsa-eur) and all +its dependencies. + +## Data requirements + +JRC-IDEES, JRC biomass potentials, .... EEA emissions. \ No newline at end of file diff --git a/Snakefile b/Snakefile index 015b843f..beb87252 100644 --- a/Snakefile +++ b/Snakefile @@ -6,7 +6,8 @@ wildcard_constraints: simpl="[a-zA-Z0-9]*", clusters="[0-9]+m?", sectors="[+a-zA-Z0-9]+", - opts="[-+a-zA-Z0-9]*" + opts="[-+a-zA-Z0-9]*", + sector_opts="[-+a-zA-Z0-9]*" @@ -23,7 +24,7 @@ rule test_script: rule prepare_sector_networks: input: - expand(config['results_dir'] + config['run'] + "/prenetworks/elec_s{simpl}_{clusters}_lv{lv}_{opts}.nc", + expand(config['results_dir'] + config['run'] + "/prenetworks/elec_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}.nc", **config['scenario']) @@ -141,13 +142,16 @@ rule build_industrial_demand: -rule prepare_network: +rule prepare_sector_network: input: - network=config['results_dir'] + config['run'] + '/prenetworks/{network}_s{simpl}_{clusters}.nc', + network=pypsaeur('networks/{network}_s{simpl}_{clusters}_lv{lv}_{opts}.nc'), energy_totals_name='data/energy_totals.csv', co2_totals_name='data/co2_totals.csv', transport_name='data/transport_data.csv', biomass_potentials='data/biomass_potentials.csv', + timezone_mappings='data/timezone_mappings.csv', + heat_profile="data/heat_load_profile_DK_AdamJensen.csv", + costs="data/costs.csv", clustered_pop_layout="resources/pop_layout_{network}_s{simpl}_{clusters}.csv", industrial_demand="resources/industrial_demand_{network}_s{simpl}_{clusters}.csv", heat_demand_urban="resources/heat_demand_urban_{network}_s{simpl}_{clusters}.nc", @@ -168,8 +172,8 @@ rule prepare_network: solar_thermal_total="resources/solar_thermal_total_{network}_s{simpl}_{clusters}.nc", solar_thermal_urban="resources/solar_thermal_urban_{network}_s{simpl}_{clusters}.nc", solar_thermal_rural="resources/solar_thermal_rural_{network}_s{simpl}_{clusters}.nc" - output: config['results_dir'] + config['run'] + '/prenetworks/{network}_s{simpl}_{clusters}_lv{lv}_{opts}.nc' + output: config['results_dir'] + config['run'] + '/prenetworks/{network}_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}.nc' threads: 1 resources: mem=1000 - benchmark: "benchmarks/prepare_network/{network}_s{simpl}_{clusters}_lv{lv}_{opts}" - script: "scripts/prepare_network.py" + benchmark: "benchmarks/prepare_network/{network}_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}" + script: "scripts/prepare_sector_network.py" diff --git a/config.yaml b/config.yaml index 2c058670..6d14af69 100644 --- a/config.yaml +++ b/config.yaml @@ -1,3 +1,4 @@ +logging_level: INFO results_dir: 'results/' summary_dir: results @@ -6,9 +7,10 @@ run: '190416-modular-test' scenario: sectors: [E] # ,E+EV,E+BEV,E+BEV+V2G] # [ E+EV, E+BEV, E+BEV+V2G ] simpl: [''] - lv: [1.0]#[1.0, 1.125, 1.25, 1.5, 2.0, opt]# or opt + lv: [1.0,1.25]#[1.0, 1.125, 1.25, 1.5, 2.0, opt]# or opt clusters: [128] #[90, 128, 181] #[45, 64, 90, 128, 181, 256] #, 362] # (2**np.r_[5.5:9:.5]).astype(int) minimum is 37 - opts: [Co2L0-3H-T-H-B-I,Co2L0-3H-T-H-B-I-onwind0,Co2L0-3H-T-H-B-I-onwind0p1]#,Co2L0p05-3H-T-H-B-I,Co2L0p10-3H-T-H-B-I,Co2L0p20-3H-T-H-B-I,Co2L0p30-3H-T-H-B-I,Co2L0p50-3H-T-H-B-I]#[Co2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0-3H-T-H,Co2L0p20-3H-T-H] #Co2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0p20-3H-T-HCo2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0p30-3H-T-H,Co2L0p50-3H-T-H] #Co2L-3H,Co2L-3H-T,, LC-FL, LC-T, Ep-T, Co2L-T] + opts: [''] #for pypsa-eur + sector_opts: [Co2L0-3H-T-H-B-I,Co2L0-3H-T-H-B-I-onwind0,Co2L0-3H-T-H-B-I-onwind0p1,Co2L0p1-3H-T-H-B-I,Co2L0p1-3H-T-H-B-I-onwind0,Co2L0p1-3H-T-H-B-I-onwind0p1]#,Co2L0p05-3H-T-H-B-I,Co2L0p10-3H-T-H-B-I,Co2L0p20-3H-T-H-B-I,Co2L0p30-3H-T-H-B-I,Co2L0p50-3H-T-H-B-I]#[Co2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0-3H-T-H,Co2L0p20-3H-T-H] #Co2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0p20-3H-T-HCo2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0p30-3H-T-H,Co2L0p50-3H-T-H] #Co2L-3H,Co2L-3H-T,, LC-FL, LC-T, Ep-T, Co2L-T] # Co2L will give default (5%); Co2L0p25 will give 25% CO2 emissions; Co2Lm0p05 will give 5% negative emissions @@ -90,3 +92,66 @@ renewable: biomass: year: 2030 scenario: "Med" + + +sector: + 'central' : True + 'central_fraction' : 0.6 + 'dsm_restriction_value' : 0.75 #Set to 0 for no restriction on BEV DSM + 'dsm_restriction_time' : 7 #Time at which SOC of BEV has to be dsm_restriction_value + 'transport_heating_deadband_upper' : 20. + 'transport_heating_deadband_lower' : 15. + 'ICE_lower_degree_factor' : 0.375 #in per cent increase in fuel consumption per degree above deadband + 'ICE_upper_degree_factor' : 1.6 + 'EV_lower_degree_factor' : 0.98 + 'EV_upper_degree_factor' : 0.63 + 'district_heating_loss' : 0.1 + 'bev' : 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 + 'transport_fuel_cell_share' : 0. #0 means all EVs, 1 means all FCs + 'time_dep_hp_cop' : True + 'retrofitting' : False + 'retroI-fraction' : 0.25 + 'retroI-fraction' : 0.55 + 'retrofitting-cost_factor' : 1.0 + 'tes' : True + 'tes_tau' : 3. + 'boilers' : True + 'chp' : True + 'chp_parameters': + 'eta_elec' : 0.468 #electrical efficiency with no heat output + 'c_v' : 0.15 #loss of fuel for each addition of heat + 'c_m' : 0.75 #backpressure ratio + 'p_nom_ratio' : 1. #ratio of max heat output to max electrical output + 'solar_thermal' : True + 'solar_cf_correction': 0.788457 # = >>> 1/1.2683 + 'marginal_cost_storage' : 0. #1e-4 + 'methanation' : True + 'helmeth' : True + 'dac' : True + 'ccs_fraction' : 0.9 + + + +costs: + year: 2030 + + # From a Lion Hirth paper, also reflects average of Noothout et al 2016 + discountrate: 0.07 + # [EUR/USD] ECB: https://www.ecb.europa.eu/stats/exchange/eurofxref/html/eurofxref-graph-usd.en.html # noqa: E501 + USD2013_to_EUR2013: 0.7532 + + # Marginal and capital costs can be overwritten + # capital_cost: + # Wind: Bla + marginal_cost: # + solar: 0.01 + onwind: 0.015 + offwind: 0.015 + hydro: 0. + H2: 0. + battery: 0. + + emission_prices: # only used with the option Ep (emission prices) + co2: 0. diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index cc575b0d..7f9c476b 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -10,17 +10,16 @@ import scipy as sp import xarray as xr import re, os -from six import iteritems -import geopandas as gpd +from six import iteritems, string_types import pypsa +import yaml + import pytz from vresutils.costdata import annuity -from add_electricity import load_costs, update_transmission_costs - #First tell PyPSA that links can have multiple outputs by #overriding the component_attrs. This can be done for @@ -59,7 +58,8 @@ def add_co2_tracking(n): bus="co2 atmosphere") #this tracks CO2 stored, e.g. underground - n.add("Bus","co2 stored") + n.add("Bus","co2 stored", + carrier="co2 stored") #NB: can also be negative #cost of 10 euro/tCO2 for whatever stays @@ -170,6 +170,16 @@ def average_every_nhours(n, offset): logger.info('Resampling the network to {}'.format(offset)) m = n.copy(with_time=False) + #fix copying of network attributes + #copied from pypsa/io.py, should be in pypsa/components.py#Network.copy() + allowed_types = (float,int,bool) + string_types + tuple(np.typeDict.values()) + attrs = dict((attr, getattr(n, attr)) + for attr in dir(n) + if (not attr.startswith("__") and + isinstance(getattr(n,attr), allowed_types))) + for k,v in iteritems(attrs): + setattr(m,k,v) + snapshot_weightings = n.snapshot_weightings.resample(offset).sum() m.set_snapshots(snapshot_weightings.index) m.snapshot_weightings = snapshot_weightings @@ -188,9 +198,6 @@ def average_every_nhours(n, offset): return m - -timezone_mappings = pd.read_csv("data/timezone_mappings.csv",index_col=0,squeeze=True,header=None) - def generate_periodic_profiles(dt_index=pd.date_range("2011-01-01 00:00","2011-12-31 23:00",freq="H",tz="UTC"), nodes=[], weekly_profile=range(24*7)): @@ -256,7 +263,7 @@ def prepare_data(network): heat_demand_df = xr.open_dataarray(snakemake.input.heat_demand_total).T.to_pandas().reindex(index=network.snapshots, method="ffill") - intraday_profiles = pd.read_csv("data/heating/heat_load_profile_DK_AdamJensen.csv",index_col=0) + intraday_profiles = pd.read_csv(snakemake.input.heat_profile,index_col=0) intraday_year_profiles = generate_periodic_profiles(heat_demand_df.index.tz_localize("UTC"), nodes=heat_demand_df.columns, @@ -388,7 +395,7 @@ def prepare_data(network): def prepare_costs(): #set all asset costs and other parameters - costs = pd.read_csv("data/costs.csv",index_col=list(range(3))).sort_index() + costs = pd.read_csv(snakemake.input.costs,index_col=list(range(3))).sort_index() #correct units to MW and EUR costs.loc[costs.unit.str.contains("/kW"),"value"]*=1e3 @@ -1165,15 +1172,20 @@ if __name__ == "__main__": from vresutils.snakemake import MockSnakemake snakemake = MockSnakemake( wildcards=dict(network='elec', simpl='', clusters='37', lv='2', opts='Co2L-3H'), - input=['networks/{network}_s{simpl}_{clusters}.nc'], + input=dict(network='../pypsa-eur/networks/{network}_s{simpl}_{clusters}.nc', timezone_mappings='data/timezone_mappings.csv'), output=['networks/{network}_s{simpl}_{clusters}_lv{lv}_{opts}.nc'] ) + with open('config.yaml') as f: + snakemake.config = yaml.load(f) + logging.basicConfig(level=snakemake.config['logging_level']) + timezone_mappings = pd.read_csv(snakemake.input.timezone_mappings,index_col=0,squeeze=True,header=None) + options = snakemake.config["sector"] - opts = snakemake.wildcards.opts.split('-') + opts = snakemake.wildcards.sector_opts.split('-') n = pypsa.Network(snakemake.input.network, override_component_attrs=override_component_attrs) @@ -1209,8 +1221,6 @@ if __name__ == "__main__": if "I" in opts: add_industry(n) - set_line_s_max_pu(n) - for o in opts: m = re.match(r'^\d+h$', o, re.IGNORECASE) if m is not None: @@ -1239,7 +1249,4 @@ if __name__ == "__main__": limit = float(limit.replace("p",".").replace("m","-")) restrict_technology_potential(n,"onwind",limit) - - set_line_volume_limit(n, snakemake.wildcards.lv) - n.export_to_netcdf(snakemake.output[0])