Fix prepare_sector_network to reproduce old sector prenetworks
from pypsa-eur sector branch
This commit is contained in:
parent
ad865c18c6
commit
e88b6c850f
29
README.md
Normal file
29
README.md
Normal file
@ -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.
|
18
Snakefile
18
Snakefile
@ -6,7 +6,8 @@ wildcard_constraints:
|
|||||||
simpl="[a-zA-Z0-9]*",
|
simpl="[a-zA-Z0-9]*",
|
||||||
clusters="[0-9]+m?",
|
clusters="[0-9]+m?",
|
||||||
sectors="[+a-zA-Z0-9]+",
|
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:
|
rule prepare_sector_networks:
|
||||||
input:
|
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'])
|
**config['scenario'])
|
||||||
|
|
||||||
|
|
||||||
@ -141,13 +142,16 @@ rule build_industrial_demand:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
rule prepare_network:
|
rule prepare_sector_network:
|
||||||
input:
|
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',
|
energy_totals_name='data/energy_totals.csv',
|
||||||
co2_totals_name='data/co2_totals.csv',
|
co2_totals_name='data/co2_totals.csv',
|
||||||
transport_name='data/transport_data.csv',
|
transport_name='data/transport_data.csv',
|
||||||
biomass_potentials='data/biomass_potentials.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",
|
clustered_pop_layout="resources/pop_layout_{network}_s{simpl}_{clusters}.csv",
|
||||||
industrial_demand="resources/industrial_demand_{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",
|
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_total="resources/solar_thermal_total_{network}_s{simpl}_{clusters}.nc",
|
||||||
solar_thermal_urban="resources/solar_thermal_urban_{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"
|
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
|
threads: 1
|
||||||
resources: mem=1000
|
resources: mem=1000
|
||||||
benchmark: "benchmarks/prepare_network/{network}_s{simpl}_{clusters}_lv{lv}_{opts}"
|
benchmark: "benchmarks/prepare_network/{network}_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}"
|
||||||
script: "scripts/prepare_network.py"
|
script: "scripts/prepare_sector_network.py"
|
||||||
|
69
config.yaml
69
config.yaml
@ -1,3 +1,4 @@
|
|||||||
|
logging_level: INFO
|
||||||
|
|
||||||
results_dir: 'results/'
|
results_dir: 'results/'
|
||||||
summary_dir: results
|
summary_dir: results
|
||||||
@ -6,9 +7,10 @@ run: '190416-modular-test'
|
|||||||
scenario:
|
scenario:
|
||||||
sectors: [E] # ,E+EV,E+BEV,E+BEV+V2G] # [ E+EV, E+BEV, E+BEV+V2G ]
|
sectors: [E] # ,E+EV,E+BEV,E+BEV+V2G] # [ E+EV, E+BEV, E+BEV+V2G ]
|
||||||
simpl: ['']
|
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
|
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
|
# Co2L will give default (5%); Co2L0p25 will give 25% CO2 emissions; Co2Lm0p05 will give 5% negative emissions
|
||||||
|
|
||||||
|
|
||||||
@ -90,3 +92,66 @@ renewable:
|
|||||||
biomass:
|
biomass:
|
||||||
year: 2030
|
year: 2030
|
||||||
scenario: "Med"
|
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.
|
||||||
|
@ -10,17 +10,16 @@ import scipy as sp
|
|||||||
import xarray as xr
|
import xarray as xr
|
||||||
import re, os
|
import re, os
|
||||||
|
|
||||||
from six import iteritems
|
from six import iteritems, string_types
|
||||||
import geopandas as gpd
|
|
||||||
|
|
||||||
import pypsa
|
import pypsa
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from vresutils.costdata import annuity
|
from vresutils.costdata import annuity
|
||||||
|
|
||||||
from add_electricity import load_costs, update_transmission_costs
|
|
||||||
|
|
||||||
|
|
||||||
#First tell PyPSA that links can have multiple outputs by
|
#First tell PyPSA that links can have multiple outputs by
|
||||||
#overriding the component_attrs. This can be done for
|
#overriding the component_attrs. This can be done for
|
||||||
@ -59,7 +58,8 @@ def add_co2_tracking(n):
|
|||||||
bus="co2 atmosphere")
|
bus="co2 atmosphere")
|
||||||
|
|
||||||
#this tracks CO2 stored, e.g. underground
|
#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
|
#NB: can also be negative
|
||||||
#cost of 10 euro/tCO2 for whatever stays
|
#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))
|
logger.info('Resampling the network to {}'.format(offset))
|
||||||
m = n.copy(with_time=False)
|
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()
|
snapshot_weightings = n.snapshot_weightings.resample(offset).sum()
|
||||||
m.set_snapshots(snapshot_weightings.index)
|
m.set_snapshots(snapshot_weightings.index)
|
||||||
m.snapshot_weightings = snapshot_weightings
|
m.snapshot_weightings = snapshot_weightings
|
||||||
@ -188,9 +198,6 @@ def average_every_nhours(n, offset):
|
|||||||
return m
|
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"),
|
def generate_periodic_profiles(dt_index=pd.date_range("2011-01-01 00:00","2011-12-31 23:00",freq="H",tz="UTC"),
|
||||||
nodes=[],
|
nodes=[],
|
||||||
weekly_profile=range(24*7)):
|
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")
|
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"),
|
intraday_year_profiles = generate_periodic_profiles(heat_demand_df.index.tz_localize("UTC"),
|
||||||
nodes=heat_demand_df.columns,
|
nodes=heat_demand_df.columns,
|
||||||
@ -388,7 +395,7 @@ def prepare_data(network):
|
|||||||
def prepare_costs():
|
def prepare_costs():
|
||||||
|
|
||||||
#set all asset costs and other parameters
|
#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
|
#correct units to MW and EUR
|
||||||
costs.loc[costs.unit.str.contains("/kW"),"value"]*=1e3
|
costs.loc[costs.unit.str.contains("/kW"),"value"]*=1e3
|
||||||
@ -1165,15 +1172,20 @@ if __name__ == "__main__":
|
|||||||
from vresutils.snakemake import MockSnakemake
|
from vresutils.snakemake import MockSnakemake
|
||||||
snakemake = MockSnakemake(
|
snakemake = MockSnakemake(
|
||||||
wildcards=dict(network='elec', simpl='', clusters='37', lv='2', opts='Co2L-3H'),
|
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']
|
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'])
|
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"]
|
options = snakemake.config["sector"]
|
||||||
|
|
||||||
opts = snakemake.wildcards.opts.split('-')
|
opts = snakemake.wildcards.sector_opts.split('-')
|
||||||
|
|
||||||
n = pypsa.Network(snakemake.input.network,
|
n = pypsa.Network(snakemake.input.network,
|
||||||
override_component_attrs=override_component_attrs)
|
override_component_attrs=override_component_attrs)
|
||||||
@ -1209,8 +1221,6 @@ if __name__ == "__main__":
|
|||||||
if "I" in opts:
|
if "I" in opts:
|
||||||
add_industry(n)
|
add_industry(n)
|
||||||
|
|
||||||
set_line_s_max_pu(n)
|
|
||||||
|
|
||||||
for o in opts:
|
for o in opts:
|
||||||
m = re.match(r'^\d+h$', o, re.IGNORECASE)
|
m = re.match(r'^\d+h$', o, re.IGNORECASE)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
@ -1239,7 +1249,4 @@ if __name__ == "__main__":
|
|||||||
limit = float(limit.replace("p",".").replace("m","-"))
|
limit = float(limit.replace("p",".").replace("m","-"))
|
||||||
restrict_technology_potential(n,"onwind",limit)
|
restrict_technology_potential(n,"onwind",limit)
|
||||||
|
|
||||||
|
|
||||||
set_line_volume_limit(n, snakemake.wildcards.lv)
|
|
||||||
|
|
||||||
n.export_to_netcdf(snakemake.output[0])
|
n.export_to_netcdf(snakemake.output[0])
|
||||||
|
Loading…
Reference in New Issue
Block a user