2019-04-17 12:24:22 +00:00
# coding: utf-8
2021-07-01 18:09:04 +00:00
import pypsa
import re
import os
import pytz
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
import pandas as pd
2019-04-17 12:24:22 +00:00
import numpy as np
import xarray as xr
2021-07-01 18:09:04 +00:00
from itertools import product
from scipy . stats import beta
2019-04-17 12:24:22 +00:00
from vresutils . costdata import annuity
2020-12-30 14:55:08 +00:00
from build_energy_totals import build_eea_co2 , build_eurostat_co2 , build_co2_totals
2021-07-01 18:09:04 +00:00
from helper import override_component_attrs
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
import logging
logger = logging . getLogger ( __name__ )
2019-04-17 12:24:22 +00:00
2021-07-12 10:31:18 +00:00
from types import SimpleNamespace
spatial = SimpleNamespace ( )
def define_spatial ( nodes ) :
"""
Namespace for spatial
2021-09-29 12:37:36 +00:00
2021-07-12 10:31:18 +00:00
Parameters
- - - - - - - - - -
nodes : list - like
"""
global spatial
global options
spatial . nodes = nodes
2021-09-29 12:37:36 +00:00
2021-09-28 09:33:21 +00:00
# biomass
2021-07-12 10:31:18 +00:00
spatial . biomass = SimpleNamespace ( )
if options [ " biomass_transport " ] :
spatial . biomass . nodes = nodes + " solid biomass "
spatial . biomass . locations = nodes
spatial . biomass . industry = nodes + " solid biomass for industry "
spatial . biomass . industry_cc = nodes + " solid biomass for industry CC "
else :
spatial . biomass . nodes = [ " EU solid biomass " ]
2021-08-09 15:51:37 +00:00
spatial . biomass . locations = [ " EU " ]
2021-07-12 10:31:18 +00:00
spatial . biomass . industry = [ " solid biomass for industry " ]
spatial . biomass . industry_cc = [ " solid biomass for industry CC " ]
spatial . biomass . df = pd . DataFrame ( vars ( spatial . biomass ) , index = nodes )
2021-09-29 12:37:36 +00:00
2021-09-28 09:33:21 +00:00
# co2
2021-07-09 10:50:40 +00:00
spatial . co2 = SimpleNamespace ( )
2021-09-29 12:37:36 +00:00
2021-07-09 10:50:40 +00:00
if options [ " co2_network " ] :
spatial . co2 . nodes = nodes + " co2 stored "
spatial . co2 . locations = nodes
spatial . co2 . vents = nodes + " co2 vent "
else :
spatial . co2 . nodes = [ " co2 stored " ]
2021-08-06 10:46:03 +00:00
spatial . co2 . locations = [ " EU " ]
2021-07-09 10:50:40 +00:00
spatial . co2 . vents = [ " co2 vent " ]
spatial . co2 . df = pd . DataFrame ( vars ( spatial . co2 ) , index = nodes )
2021-07-12 10:31:18 +00:00
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
def emission_sectors_from_opts ( opts ) :
2019-04-17 12:24:22 +00:00
2021-09-29 12:37:36 +00:00
sectors = [ " electricity " ]
2021-07-01 18:09:04 +00:00
if " T " in opts :
sectors + = [
" rail non-elec " ,
" road non-elec "
]
if " H " in opts :
sectors + = [
" residential non-elec " ,
" services non-elec "
]
if " I " in opts :
sectors + = [
" industrial non-elec " ,
" industrial processes " ,
" domestic aviation " ,
" international aviation " ,
" domestic navigation " ,
" international navigation "
]
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
return sectors
2020-07-18 09:22:30 +00:00
2020-12-30 14:55:08 +00:00
2021-07-01 18:09:04 +00:00
def get ( item , investment_year = None ) :
""" Check whether item depends on investment year """
if isinstance ( item , dict ) :
return item [ investment_year ]
else :
return item
2020-12-30 14:55:08 +00:00
2021-02-04 08:15:03 +00:00
2021-07-09 08:42:16 +00:00
def create_network_topology ( n , prefix , connector = " -> " ) :
"""
Create a network topology like the power transmission network .
2021-09-29 12:37:36 +00:00
2021-07-09 08:42:16 +00:00
Parameters
- - - - - - - - - -
n : pypsa . Network
prefix : str
connector : str
2021-09-29 12:37:36 +00:00
2021-07-09 08:42:16 +00:00
Returns
- - - - - - -
pd . DataFrame with columns bus0 , bus1 and length
"""
2021-07-09 11:10:43 +00:00
ln_attrs = [ " bus0 " , " bus1 " , " length " ]
lk_attrs = [ " bus0 " , " bus1 " , " length " , " underwater_fraction " ]
2021-07-09 08:42:16 +00:00
candidates = pd . concat ( [
2021-07-09 11:10:43 +00:00
n . lines [ ln_attrs ] ,
n . links . loc [ n . links . carrier == " DC " , lk_attrs ]
] ) . fillna ( 0 )
2021-07-09 08:42:16 +00:00
positive_order = candidates . bus0 < candidates . bus1
candidates_p = candidates [ positive_order ]
swap_buses = { " bus0 " : " bus1 " , " bus1 " : " bus0 " }
candidates_n = candidates [ ~ positive_order ] . rename ( columns = swap_buses )
candidates = pd . concat ( [ candidates_p , candidates_n ] )
topo = candidates . groupby ( [ " bus0 " , " bus1 " ] , as_index = False ) . mean ( )
topo . index = topo . apply ( lambda c : prefix + c . bus0 + connector + c . bus1 , axis = 1 )
return topo
2021-07-01 18:09:04 +00:00
def co2_emissions_year ( countries , opts , year ) :
2020-12-30 14:55:08 +00:00
"""
2021-04-23 09:26:38 +00:00
Calculate CO2 emissions in one specific year ( e . g . 1990 or 2018 ) .
2020-12-30 14:55:08 +00:00
"""
2021-07-01 18:09:04 +00:00
2020-12-30 14:55:08 +00:00
eea_co2 = build_eea_co2 ( year )
2021-01-25 13:17:31 +00:00
2021-07-01 18:09:04 +00:00
# TODO: read Eurostat data from year > 2014
# this only affects the estimation of CO2 emissions for BA, RS, AL, ME, MK
2020-12-30 14:55:08 +00:00
if year > 2014 :
eurostat_co2 = build_eurostat_co2 ( year = 2014 )
else :
eurostat_co2 = build_eurostat_co2 ( year )
2021-01-25 13:17:31 +00:00
2021-04-23 09:26:38 +00:00
co2_totals = build_co2_totals ( eea_co2 , eurostat_co2 )
2020-12-30 14:55:08 +00:00
2021-07-01 18:09:04 +00:00
sectors = emission_sectors_from_opts ( opts )
co2_emissions = co2_totals . loc [ countries , sectors ] . sum ( ) . sum ( )
# convert MtCO2 to GtCO2
2021-07-08 12:41:34 +00:00
co2_emissions * = 0.001
2020-12-30 14:55:08 +00:00
return co2_emissions
2021-07-01 18:09:04 +00:00
# TODO: move to own rule with sector-opts wildcard?
def build_carbon_budget ( o , fn ) :
"""
Distribute carbon budget following beta or exponential transition path .
"""
# opts?
2021-01-25 13:17:31 +00:00
if " be " in o :
2020-12-30 14:55:08 +00:00
#beta decay
carbon_budget = float ( o [ o . find ( " cb " ) + 2 : o . find ( " be " ) ] )
2021-07-01 18:09:04 +00:00
be = float ( o [ o . find ( " be " ) + 2 : ] )
2021-01-25 13:17:31 +00:00
if " ex " in o :
2020-12-30 14:55:08 +00:00
#exponential decay
carbon_budget = float ( o [ o . find ( " cb " ) + 2 : o . find ( " ex " ) ] )
2021-07-01 18:09:04 +00:00
r = float ( o [ o . find ( " ex " ) + 2 : ] )
2021-01-14 09:06:29 +00:00
2021-07-01 18:09:04 +00:00
countries = n . buses . country . dropna ( ) . unique ( )
2021-01-25 13:17:31 +00:00
2021-07-01 18:09:04 +00:00
e_1990 = co2_emissions_year ( countries , opts , year = 1990 )
2021-01-25 13:17:31 +00:00
2020-12-30 14:55:08 +00:00
#emissions at the beginning of the path (last year available 2018)
2021-07-01 18:09:04 +00:00
e_0 = co2_emissions_year ( countries , opts , year = 2018 )
2021-07-08 12:41:34 +00:00
2020-12-30 14:55:08 +00:00
planning_horizons = snakemake . config [ ' scenario ' ] [ ' planning_horizons ' ]
t_0 = planning_horizons [ 0 ]
2021-07-01 18:09:04 +00:00
2021-01-25 13:17:31 +00:00
if " be " in o :
2021-07-01 18:09:04 +00:00
# final year in the path
2021-07-08 12:41:34 +00:00
t_f = t_0 + ( 2 * carbon_budget / e_0 ) . round ( 0 )
2021-07-01 18:09:04 +00:00
def beta_decay ( t ) :
cdf_term = ( t - t_0 ) / ( t_f - t_0 )
return ( e_0 / e_1990 ) * ( 1 - beta . cdf ( cdf_term , be , be ) )
2020-12-30 14:55:08 +00:00
#emissions (relative to 1990)
2021-07-01 18:09:04 +00:00
co2_cap = pd . Series ( { t : beta_decay ( t ) for t in planning_horizons } , name = o )
2021-01-25 13:17:31 +00:00
if " ex " in o :
2021-07-01 18:09:04 +00:00
T = carbon_budget / e_0
m = ( 1 + np . sqrt ( 1 + r * T ) ) / T
2021-02-04 08:15:03 +00:00
2021-07-01 18:09:04 +00:00
def exponential_decay ( t ) :
return ( e_0 / e_1990 ) * ( 1 + ( m + r ) * ( t - t_0 ) ) * np . exp ( - m * ( t - t_0 ) )
2021-01-25 13:17:31 +00:00
2021-07-01 18:09:04 +00:00
co2_cap = pd . Series ( { t : exponential_decay ( t ) for t in planning_horizons } , name = o )
# TODO log in Snakefile
if not os . path . exists ( fn ) :
os . makedirs ( fn )
co2_cap . to_csv ( fn , float_format = ' %.3f ' )
2021-02-04 08:15:03 +00:00
2020-08-19 10:41:17 +00:00
2021-07-01 18:09:04 +00:00
def add_lifetime_wind_solar ( n , costs ) :
""" Add lifetime for solar and wind generators. """
for carrier in [ ' solar ' , ' onwind ' , ' offwind ' ] :
gen_i = n . generators . index . str . contains ( carrier )
n . generators . loc [ gen_i , " lifetime " ] = costs . at [ carrier , ' lifetime ' ]
2021-02-04 08:15:03 +00:00
2020-08-19 10:41:17 +00:00
2021-07-12 10:31:18 +00:00
def create_network_topology ( n , prefix , connector = " -> " , bidirectional = True ) :
2020-10-20 11:46:39 +00:00
"""
2021-07-12 10:31:18 +00:00
Create a network topology like the power transmission network .
2021-09-29 12:37:36 +00:00
2021-07-12 10:31:18 +00:00
Parameters
- - - - - - - - - -
n : pypsa . Network
prefix : str
connector : str
bidirectional : bool , default True
True : one link for each connection
False : one link for each connection and direction ( back and forth )
2021-09-29 12:37:36 +00:00
2021-07-12 10:31:18 +00:00
Returns
- - - - - - -
pd . DataFrame with columns bus0 , bus1 and length
2020-10-20 11:46:39 +00:00
"""
2021-07-12 10:31:18 +00:00
ln_attrs = [ " bus0 " , " bus1 " , " length " ]
lk_attrs = [ " bus0 " , " bus1 " , " length " , " underwater_fraction " ]
2020-10-20 11:46:39 +00:00
2021-07-12 10:31:18 +00:00
candidates = pd . concat ( [
n . lines [ ln_attrs ] ,
n . links . loc [ n . links . carrier == " DC " , lk_attrs ]
] ) . fillna ( 0 )
2020-10-20 11:46:39 +00:00
positive_order = candidates . bus0 < candidates . bus1
candidates_p = candidates [ positive_order ]
2021-07-12 10:31:18 +00:00
swap_buses = { " bus0 " : " bus1 " , " bus1 " : " bus0 " }
candidates_n = candidates [ ~ positive_order ] . rename ( columns = swap_buses )
candidates = pd . concat ( [ candidates_p , candidates_n ] )
2021-09-29 12:37:36 +00:00
2021-07-12 10:31:18 +00:00
def make_index ( c ) :
return prefix + c . bus0 + connector + c . bus1
2020-10-20 11:46:39 +00:00
topo = candidates . groupby ( [ " bus0 " , " bus1 " ] , as_index = False ) . mean ( )
2021-07-12 10:31:18 +00:00
topo . index = topo . apply ( make_index , axis = 1 )
2021-09-29 12:37:36 +00:00
2021-07-12 10:31:18 +00:00
if not bidirectional :
topo_reverse = topo . copy ( )
topo_reverse . rename ( columns = swap_buses , inplace = True )
topo_reverse . index = topo_reverse . apply ( make_index , axis = 1 )
topo = topo . append ( topo_reverse )
2021-09-29 12:37:36 +00:00
2020-10-20 11:46:39 +00:00
return topo
2021-07-01 18:09:04 +00:00
# TODO merge issue with PyPSA-Eur
def update_wind_solar_costs ( n , costs ) :
2020-08-12 09:08:09 +00:00
"""
2020-08-14 07:11:19 +00:00
Update costs for wind and solar generators added with pypsa - eur to those
2020-08-12 09:08:09 +00:00
cost in the planning year
"""
2020-08-14 07:11:19 +00:00
2020-08-19 10:41:17 +00:00
#NB: solar costs are also manipulated for rooftop
#when distribution grid is inserted
2021-07-01 18:09:04 +00:00
n . generators . loc [ n . generators . carrier == ' solar ' , ' capital_cost ' ] = costs . at [ ' solar-utility ' , ' fixed ' ]
2020-08-19 10:41:17 +00:00
2021-07-01 18:09:04 +00:00
n . generators . loc [ n . generators . carrier == ' onwind ' , ' capital_cost ' ] = costs . at [ ' onwind ' , ' fixed ' ]
2020-08-19 10:41:17 +00:00
#for offshore wind, need to calculated connection costs
2020-08-12 09:08:09 +00:00
#assign clustered bus
#map initial network -> simplified network
2020-10-02 10:20:55 +00:00
busmap_s = pd . read_csv ( snakemake . input . busmap_s , index_col = 0 ) . squeeze ( )
2020-10-21 12:31:37 +00:00
busmap_s . index = busmap_s . index . astype ( str )
busmap_s = busmap_s . astype ( str )
2020-08-12 09:08:09 +00:00
#map simplified network -> clustered network
2020-10-02 10:20:55 +00:00
busmap = pd . read_csv ( snakemake . input . busmap , index_col = 0 ) . squeeze ( )
2020-10-21 12:31:37 +00:00
busmap . index = busmap . index . astype ( str )
busmap = busmap . astype ( str )
2020-09-21 15:04:45 +00:00
#map initial network -> clustered network
2020-08-12 09:08:09 +00:00
clustermaps = busmap_s . map ( busmap )
2020-08-19 10:41:17 +00:00
#code adapted from pypsa-eur/scripts/add_electricity.py
2021-07-01 18:09:04 +00:00
for connection in [ ' dc ' , ' ac ' ] :
2020-08-19 10:41:17 +00:00
tech = " offwind- " + connection
profile = snakemake . input [ ' profile_offwind_ ' + connection ]
with xr . open_dataset ( profile ) as ds :
underwater_fraction = ds [ ' underwater_fraction ' ] . to_pandas ( )
connection_cost = ( snakemake . config [ ' costs ' ] [ ' lines ' ] [ ' length_factor ' ] *
ds [ ' average_distance ' ] . to_pandas ( ) *
( underwater_fraction *
costs . at [ tech + ' -connection-submarine ' , ' fixed ' ] +
( 1. - underwater_fraction ) *
costs . at [ tech + ' -connection-underground ' , ' fixed ' ] ) )
#convert to aggregated clusters with weighting
weight = ds [ ' weight ' ] . to_pandas ( )
2020-09-21 15:04:45 +00:00
#e.g. clusters == 37m means that VRE generators are left
#at clustering of simplified network, but that they are
#connected to 37-node network
if snakemake . wildcards . clusters [ - 1 : ] == " m " :
genmap = busmap_s
else :
genmap = clustermaps
connection_cost = ( connection_cost * weight ) . groupby ( genmap ) . sum ( ) / weight . groupby ( genmap ) . sum ( )
2020-08-19 10:41:17 +00:00
capital_cost = ( costs . at [ ' offwind ' , ' fixed ' ] +
costs . at [ tech + ' -station ' , ' fixed ' ] +
connection_cost )
logger . info ( " Added connection cost of {:0.0f} - {:0.0f} Eur/MW/a to {} "
. format ( connection_cost [ 0 ] . min ( ) , connection_cost [ 0 ] . max ( ) , tech ) )
2021-07-01 18:09:04 +00:00
n . generators . loc [ n . generators . carrier == tech , ' capital_cost ' ] = capital_cost . rename ( index = lambda node : node + ' ' + tech )
2020-08-19 10:41:17 +00:00
2020-08-14 07:11:19 +00:00
2020-08-10 18:30:29 +00:00
def add_carrier_buses ( n , carriers ) :
2020-07-18 09:22:30 +00:00
"""
2020-08-10 18:30:29 +00:00
Add buses to connect e . g . coal , nuclear and oil plants
2020-07-18 09:22:30 +00:00
"""
2021-07-01 18:09:04 +00:00
if isinstance ( carriers , str ) :
carriers = [ carriers ]
2019-04-17 12:24:22 +00:00
2020-08-10 18:30:29 +00:00
for carrier in carriers :
2020-07-29 13:50:40 +00:00
2021-07-01 18:09:04 +00:00
n . add ( " Carrier " , carrier )
2020-07-29 13:50:40 +00:00
2021-07-01 18:09:04 +00:00
n . add ( " Bus " ,
" EU " + carrier ,
location = " EU " ,
carrier = carrier
)
2020-07-29 13:50:40 +00:00
2021-07-01 18:09:04 +00:00
#capital cost could be corrected to e.g. 0.2 EUR/kWh * annuity and O&M
n . add ( " Store " ,
" EU " + carrier + " Store " ,
bus = " EU " + carrier ,
e_nom_extendable = True ,
e_cyclic = True ,
carrier = carrier ,
)
2020-07-29 13:50:40 +00:00
n . add ( " Generator " ,
2021-07-01 18:09:04 +00:00
" EU " + carrier ,
bus = " EU " + carrier ,
p_nom_extendable = True ,
carrier = carrier ,
marginal_cost = costs . at [ carrier , ' fuel ' ]
)
2020-07-29 13:50:40 +00:00
2021-07-01 18:09:04 +00:00
# TODO: PyPSA-Eur merge issue
2019-04-18 13:23:37 +00:00
def remove_elec_base_techs ( n ) :
""" remove conventional generators (e.g. OCGT) and storage units (e.g. batteries and H2)
from base electricity - only network , since they ' re added here differently using links
"""
2020-12-09 14:18:01 +00:00
for c in n . iterate_components ( snakemake . config [ " pypsa_eur " ] ) :
to_keep = snakemake . config [ " pypsa_eur " ] [ c . name ]
2021-04-29 15:11:10 +00:00
to_remove = pd . Index ( c . df . carrier . unique ( ) ) . symmetric_difference ( to_keep )
2021-07-01 18:09:04 +00:00
print ( " Removing " , c . list_name , " with carrier " , to_remove )
2020-12-09 14:18:01 +00:00
names = c . df . index [ c . df . carrier . isin ( to_remove ) ]
print ( names )
n . mremove ( c . name , names )
n . carriers . drop ( to_remove , inplace = True , errors = " ignore " )
2019-04-18 13:23:37 +00:00
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
# TODO: PyPSA-Eur merge issue
2020-12-02 12:03:13 +00:00
def remove_non_electric_buses ( n ) :
"""
remove buses from pypsa - eur with carriers which are not AC buses
"""
print ( " drop buses from PyPSA-Eur with carrier: " , n . buses [ ~ n . buses . carrier . isin ( [ " AC " , " DC " ] ) ] . carrier . unique ( ) )
n . buses = n . buses [ n . buses . carrier . isin ( [ " AC " , " DC " ] ) ]
2021-07-01 18:09:04 +00:00
def patch_electricity_network ( n ) :
remove_elec_base_techs ( n )
remove_non_electric_buses ( n )
update_wind_solar_costs ( n , costs )
n . loads [ " carrier " ] = " electricity "
n . buses [ " location " ] = n . buses . index
2021-09-27 09:16:12 +00:00
# remove trailing white space of load index until new PyPSA version after v0.18.
n . loads . rename ( lambda x : x . strip ( ) , inplace = True )
n . loads_t . p_set . rename ( lambda x : x . strip ( ) , axis = 1 , inplace = True )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
def add_co2_tracking ( n , options ) :
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
# minus sign because opposite to how fossil fuels used:
# CH4 burning puts CH4 down, atmosphere up
n . add ( " Carrier " , " co2 " ,
co2_emissions = - 1. )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
# this tracks CO2 in the atmosphere
n . add ( " Bus " ,
" co2 atmosphere " ,
location = " EU " ,
carrier = " co2 "
)
# can also be negative
n . add ( " Store " ,
" co2 atmosphere " ,
e_nom_extendable = True ,
e_min_pu = - 1 ,
carrier = " co2 " ,
bus = " co2 atmosphere "
)
# this tracks CO2 stored, e.g. underground
2021-07-07 15:58:47 +00:00
n . madd ( " Bus " ,
2021-07-09 10:50:40 +00:00
spatial . co2 . nodes ,
location = spatial . co2 . locations ,
2021-07-01 18:09:04 +00:00
carrier = " co2 stored "
)
2021-07-07 15:58:47 +00:00
n . madd ( " Store " ,
2021-07-09 10:50:40 +00:00
spatial . co2 . nodes ,
2021-07-01 18:09:04 +00:00
e_nom_extendable = True ,
2021-09-29 12:37:36 +00:00
e_nom_max = np . inf ,
2021-07-01 18:09:04 +00:00
capital_cost = options [ ' co2_sequestration_cost ' ] ,
carrier = " co2 stored " ,
2021-07-09 10:50:40 +00:00
bus = spatial . co2 . nodes
2021-07-01 18:09:04 +00:00
)
2019-04-17 12:24:22 +00:00
2019-05-11 13:55:06 +00:00
if options [ ' co2_vent ' ] :
2019-04-17 12:24:22 +00:00
2021-07-07 15:58:47 +00:00
n . madd ( " Link " ,
2021-07-09 10:50:40 +00:00
spatial . co2 . vents ,
bus0 = spatial . co2 . nodes ,
2021-07-01 18:09:04 +00:00
bus1 = " co2 atmosphere " ,
carrier = " co2 vent " ,
efficiency = 1. ,
p_nom_extendable = True
)
2019-04-17 12:24:22 +00:00
2021-07-07 15:58:47 +00:00
def add_co2_network ( n , costs ) :
2021-09-29 12:37:36 +00:00
2021-09-24 13:30:43 +00:00
logger . info ( " Adding CO2 network. " )
2021-07-09 08:42:16 +00:00
co2_links = create_network_topology ( n , " CO2 pipeline " )
2021-07-09 11:10:43 +00:00
cost_onshore = ( 1 - co2_links . underwater_fraction ) * costs . at [ ' CO2 pipeline ' , ' fixed ' ] * co2_links . length
2021-07-09 12:36:13 +00:00
cost_submarine = co2_links . underwater_fraction * costs . at [ ' CO2 submarine pipeline ' , ' fixed ' ] * co2_links . length
2021-07-09 11:10:43 +00:00
capital_cost = cost_onshore + cost_submarine
2021-09-29 12:37:36 +00:00
2021-07-07 15:58:47 +00:00
n . madd ( " Link " ,
co2_links . index ,
bus0 = co2_links . bus0 . values + " co2 stored " ,
bus1 = co2_links . bus1 . values + " co2 stored " ,
p_min_pu = - 1 ,
p_nom_extendable = True ,
length = co2_links . length . values ,
2021-07-09 11:10:43 +00:00
capital_cost = capital_cost . values ,
2021-07-07 15:58:47 +00:00
carrier = " CO2 pipeline " ,
lifetime = costs . at [ ' CO2 pipeline ' , ' lifetime ' ]
)
2021-07-01 18:09:04 +00:00
def add_dac ( n , costs ) :
2019-04-17 12:24:22 +00:00
2021-07-09 13:30:21 +00:00
heat_carriers = [ " urban central heat " ]
2021-07-01 18:09:04 +00:00
heat_buses = n . buses . index [ n . buses . carrier . isin ( heat_carriers ) ]
locations = n . buses . location [ heat_buses ]
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
efficiency2 = - ( costs . at [ ' direct air capture ' , ' electricity-input ' ] + costs . at [ ' direct air capture ' , ' compression-electricity-input ' ] )
efficiency3 = - ( costs . at [ ' direct air capture ' , ' heat-input ' ] - costs . at [ ' direct air capture ' , ' compression-heat-output ' ] )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Link " ,
locations ,
suffix = " DAC " ,
bus0 = " co2 atmosphere " ,
2021-07-09 10:50:40 +00:00
bus1 = spatial . co2 . df . loc [ locations , " nodes " ] . values ,
2021-07-01 18:09:04 +00:00
bus2 = locations . values ,
bus3 = heat_buses ,
carrier = " DAC " ,
capital_cost = costs . at [ ' direct air capture ' , ' fixed ' ] ,
efficiency = 1. ,
efficiency2 = efficiency2 ,
efficiency3 = efficiency3 ,
p_nom_extendable = True ,
lifetime = costs . at [ ' direct air capture ' , ' lifetime ' ]
)
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
def add_co2limit ( n , Nyears = 1. , limit = 0. ) :
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
print ( " Adding CO2 budget limit as per unit of 1990 levels of " , limit )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
countries = n . buses . country . dropna ( ) . unique ( )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
sectors = emission_sectors_from_opts ( opts )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
# convert Mt to tCO2
co2_totals = 1e6 * pd . read_csv ( snakemake . input . co2_totals_name , index_col = 0 )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
co2_limit = co2_totals . loc [ countries , sectors ] . sum ( ) . sum ( )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
co2_limit * = limit * Nyears
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
n . add ( " GlobalConstraint " ,
" CO2Limit " ,
carrier_attribute = " co2_emissions " ,
sense = " <= " ,
constant = co2_limit
)
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
# TODO PyPSA-Eur merge issue
2019-04-17 12:24:22 +00:00
def average_every_nhours ( n , offset ) :
2021-07-01 18:09:04 +00:00
logger . info ( f ' Resampling the network to { offset } ' )
2019-04-17 12:24:22 +00:00
m = n . copy ( with_time = False )
2021-07-01 18:09:04 +00:00
# TODO is this still needed?
2019-04-17 15:04:33 +00:00
#fix copying of network attributes
#copied from pypsa/io.py, should be in pypsa/components.py#Network.copy()
2021-07-01 18:09:04 +00:00
allowed_types = ( float , int , bool , str ) + tuple ( np . typeDict . values ( ) )
2019-04-17 15:04:33 +00:00
attrs = dict ( ( attr , getattr ( n , attr ) )
for attr in dir ( n )
if ( not attr . startswith ( " __ " ) and
isinstance ( getattr ( n , attr ) , allowed_types ) ) )
2021-07-01 18:09:04 +00:00
for k , v in attrs . items ( ) :
2019-04-17 15:04:33 +00:00
setattr ( m , k , v )
2019-04-17 12:24:22 +00:00
snapshot_weightings = n . snapshot_weightings . resample ( offset ) . sum ( )
m . set_snapshots ( snapshot_weightings . index )
m . snapshot_weightings = snapshot_weightings
for c in n . iterate_components ( ) :
pnl = getattr ( m , c . list_name + " _t " )
2021-07-01 18:09:04 +00:00
for k , df in c . pnl . items ( ) :
2019-04-17 12:24:22 +00:00
if not df . empty :
if c . list_name == " stores " and k == " e_max_pu " :
pnl [ k ] = df . resample ( offset ) . min ( )
elif c . list_name == " stores " and k == " e_min_pu " :
pnl [ k ] = df . resample ( offset ) . max ( )
else :
pnl [ k ] = df . resample ( offset ) . mean ( )
return m
2021-07-01 18:09:04 +00:00
def generate_periodic_profiles ( dt_index , nodes , weekly_profile , localize = None ) :
"""
Give a 24 * 7 long list of weekly hourly profiles , generate this for each
country for the period dt_index , taking account of time zones and summer time .
2019-04-17 12:24:22 +00:00
"""
2021-07-01 18:09:04 +00:00
weekly_profile = pd . Series ( weekly_profile , range ( 24 * 7 ) )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
week_df = pd . DataFrame ( index = dt_index , columns = nodes )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
for node in nodes :
timezone = pytz . timezone ( pytz . country_timezones [ node [ : 2 ] ] [ 0 ] )
tz_dt_index = dt_index . tz_convert ( timezone )
week_df [ node ] = [ 24 * dt . weekday ( ) + dt . hour for dt in tz_dt_index ]
week_df [ node ] = week_df [ node ] . map ( weekly_profile )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
week_df = week_df . tz_localize ( localize )
2019-04-17 12:24:22 +00:00
return week_df
2021-07-01 18:09:04 +00:00
def cycling_shift ( df , steps = 1 ) :
""" Cyclic shift on index of pd.Series|pd.DataFrame by number of steps """
2019-04-17 12:24:22 +00:00
df = df . copy ( )
2021-07-01 18:09:04 +00:00
new_index = np . roll ( df . index , steps )
df . values [ : ] = df . reindex ( index = new_index ) . values
2019-04-17 12:24:22 +00:00
return df
2021-07-01 18:09:04 +00:00
def transport_degree_factor (
temperature ,
deadband_lower = 15 ,
deadband_upper = 20 ,
lower_degree_factor = 0.5 ,
upper_degree_factor = 1.6 ) :
"""
Work out how much energy demand in vehicles increases due to heating and cooling .
2019-04-17 12:24:22 +00:00
There is a deadband where there is no increase .
Degree factors are % increase in demand compared to no heating / cooling fuel consumption .
Returns per unit increase in demand for each place and time
"""
dd = temperature . copy ( )
dd [ ( temperature > deadband_lower ) & ( temperature < deadband_upper ) ] = 0.
2021-07-01 18:09:04 +00:00
dT_lower = deadband_lower - temperature [ temperature < deadband_lower ]
dd [ temperature < deadband_lower ] = lower_degree_factor / 100 * dT_lower
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
dT_upper = temperature [ temperature > deadband_upper ] - deadband_upper
dd [ temperature > deadband_upper ] = upper_degree_factor / 100 * dT_upper
2019-04-17 12:24:22 +00:00
return dd
2021-07-01 18:09:04 +00:00
# TODO separate sectors and move into own rules
def prepare_data ( n ) :
2019-04-17 12:24:22 +00:00
##############
#Heating
##############
2021-07-01 18:09:04 +00:00
ashp_cop = xr . open_dataarray ( snakemake . input . cop_air_total ) . to_pandas ( ) . reindex ( index = n . snapshots )
gshp_cop = xr . open_dataarray ( snakemake . input . cop_soil_total ) . to_pandas ( ) . reindex ( index = n . snapshots )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
solar_thermal = xr . open_dataarray ( snakemake . input . solar_thermal_total ) . to_pandas ( ) . reindex ( index = n . snapshots )
# 1e3 converts from W/m^2 to MW/(1000m^2) = kW/m^2
solar_thermal = options [ ' solar_cf_correction ' ] * solar_thermal / 1e3
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
energy_totals = pd . read_csv ( snakemake . input . energy_totals_name , index_col = 0 )
2019-04-17 12:24:22 +00:00
nodal_energy_totals = energy_totals . loc [ pop_layout . ct ] . fillna ( 0. )
nodal_energy_totals . index = pop_layout . index
2021-09-29 12:37:36 +00:00
# district heat share not weighted by population
district_heat_share = round ( nodal_energy_totals [ " district heat share " ] ,
ndigits = 2 )
2021-07-01 18:09:04 +00:00
nodal_energy_totals = nodal_energy_totals . multiply ( pop_layout . fraction , axis = 0 )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
# copy forward the daily average heat demand into each hour, so it can be multipled by the intraday profile
daily_space_heat_demand = xr . open_dataarray ( snakemake . input . heat_demand_total ) . to_pandas ( ) . reindex ( index = n . snapshots , method = " ffill " )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
intraday_profiles = pd . read_csv ( snakemake . input . heat_profile , index_col = 0 )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
sectors = [ " residential " , " services " ]
uses = [ " water " , " space " ]
2019-04-17 12:24:22 +00:00
2019-08-01 13:43:04 +00:00
heat_demand = { }
2019-08-01 14:16:05 +00:00
electric_heat_supply = { }
2021-07-01 18:09:04 +00:00
for sector , use in product ( sectors , uses ) :
weekday = list ( intraday_profiles [ f " { sector } { use } weekday " ] )
weekend = list ( intraday_profiles [ f " { sector } { use } weekend " ] )
weekly_profile = weekday * 5 + weekend * 2
intraday_year_profile = generate_periodic_profiles (
daily_space_heat_demand . index . tz_localize ( " UTC " ) ,
nodes = daily_space_heat_demand . columns ,
weekly_profile = weekly_profile
)
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
if use == " space " :
heat_demand_shape = daily_space_heat_demand * intraday_year_profile
else :
heat_demand_shape = intraday_year_profile
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
heat_demand [ f " { sector } { use } " ] = ( heat_demand_shape / heat_demand_shape . sum ( ) ) . multiply ( nodal_energy_totals [ f " total { sector } { use } " ] ) * 1e6
electric_heat_supply [ f " { sector } { use } " ] = ( heat_demand_shape / heat_demand_shape . sum ( ) ) . multiply ( nodal_energy_totals [ f " electricity { sector } { use } " ] ) * 1e6
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
heat_demand = pd . concat ( heat_demand , axis = 1 )
electric_heat_supply = pd . concat ( electric_heat_supply , axis = 1 )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
# subtract from electricity load since heat demand already in heat_demand
2019-08-01 14:16:05 +00:00
electric_nodes = n . loads . index [ n . loads . carrier == " electricity " ]
2021-07-01 18:09:04 +00:00
n . loads_t . p_set [ electric_nodes ] = n . loads_t . p_set [ electric_nodes ] - electric_heat_supply . groupby ( level = 1 , axis = 1 ) . sum ( ) [ electric_nodes ]
2019-04-17 12:24:22 +00:00
##############
#Transport
##############
## Get overall demand curve for all vehicles
2021-07-01 18:09:04 +00:00
traffic = pd . read_csv ( snakemake . input . traffic_data_KFZ , skiprows = 2 , usecols = [ " count " ] , squeeze = True )
2019-04-17 12:24:22 +00:00
#Generate profiles
2021-07-01 18:09:04 +00:00
transport_shape = generate_periodic_profiles (
dt_index = n . snapshots . tz_localize ( " UTC " ) ,
nodes = pop_layout . index ,
weekly_profile = traffic . values
)
transport_shape = transport_shape / transport_shape . sum ( )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
transport_data = pd . read_csv ( snakemake . input . transport_name , index_col = 0 )
2019-04-17 12:24:22 +00:00
nodal_transport_data = transport_data . loc [ pop_layout . ct ] . fillna ( 0. )
nodal_transport_data . index = pop_layout . index
2021-07-01 18:09:04 +00:00
nodal_transport_data [ " number cars " ] = pop_layout [ " fraction " ] * nodal_transport_data [ " number cars " ]
nodal_transport_data . loc [ nodal_transport_data [ " average fuel efficiency " ] == 0. , " average fuel efficiency " ] = transport_data [ " average fuel efficiency " ] . mean ( )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
# electric motors are more efficient, so alter transport demand
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
plug_to_wheels_eta = options . get ( " bev_plug_to_wheel_efficiency " , 0.2 )
battery_to_wheels_eta = plug_to_wheels_eta * options . get ( " bev_charge_efficiency " , 0.9 )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
efficiency_gain = nodal_transport_data [ " average fuel efficiency " ] / battery_to_wheels_eta
2019-04-17 12:24:22 +00:00
#get heating demand for correction to demand time series
2021-07-01 18:09:04 +00:00
temperature = xr . open_dataarray ( snakemake . input . temp_air_total ) . to_pandas ( )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
# correction factors for vehicle heating
dd_ICE = transport_degree_factor (
temperature ,
options [ ' transport_heating_deadband_lower ' ] ,
options [ ' transport_heating_deadband_upper ' ] ,
options [ ' ICE_lower_degree_factor ' ] ,
options [ ' ICE_upper_degree_factor ' ]
)
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
dd_EV = transport_degree_factor (
temperature ,
options [ ' transport_heating_deadband_lower ' ] ,
options [ ' transport_heating_deadband_upper ' ] ,
options [ ' EV_lower_degree_factor ' ] ,
options [ ' EV_upper_degree_factor ' ]
)
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
# divide out the heating/cooling demand from ICE totals
# and multiply back in the heating/cooling demand for EVs
ice_correction = ( transport_shape * ( 1 + dd_ICE ) ) . sum ( ) / transport_shape . sum ( )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
energy_totals_transport = nodal_energy_totals [ " total road " ] + nodal_energy_totals [ " total rail " ] - nodal_energy_totals [ " electricity rail " ]
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
transport = ( transport_shape . multiply ( energy_totals_transport ) * 1e6 * Nyears ) . divide ( efficiency_gain * ice_correction ) . multiply ( 1 + dd_EV )
2019-04-17 12:24:22 +00:00
## derive plugged-in availability for PKW's (cars)
2021-07-01 18:09:04 +00:00
traffic = pd . read_csv ( snakemake . input . traffic_data_Pkw , skiprows = 2 , usecols = [ " count " ] , squeeze = True )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
avail_max = options . get ( " bev_avail_max " , 0.95 )
avail_mean = options . get ( " bev_avail_mean " , 0.8 )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
avail = avail_max - ( avail_max - avail_mean ) * ( traffic - traffic . min ( ) ) / ( traffic . mean ( ) - traffic . min ( ) )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
avail_profile = generate_periodic_profiles (
dt_index = n . snapshots . tz_localize ( " UTC " ) ,
nodes = pop_layout . index ,
weekly_profile = avail . values
)
2019-04-17 12:24:22 +00:00
dsm_week = np . zeros ( ( 24 * 7 , ) )
2021-07-01 18:09:04 +00:00
dsm_week [ ( np . arange ( 0 , 7 , 1 ) * 24 + options [ ' bev_dsm_restriction_time ' ] ) ] = options [ ' bev_dsm_restriction_value ' ]
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
dsm_profile = generate_periodic_profiles (
dt_index = n . snapshots . tz_localize ( " UTC " ) ,
nodes = pop_layout . index ,
weekly_profile = dsm_week
)
2019-04-17 12:24:22 +00:00
2021-09-29 12:37:36 +00:00
return nodal_energy_totals , heat_demand , ashp_cop , gshp_cop , solar_thermal , transport , avail_profile , dsm_profile , nodal_transport_data , district_heat_share
2020-07-07 16:37:42 +00:00
2021-07-01 18:09:04 +00:00
# TODO checkout PyPSA-Eur script
2020-11-30 16:01:14 +00:00
def prepare_costs ( cost_file , USD_to_EUR , discount_rate , Nyears , lifetime ) :
2019-04-17 12:24:22 +00:00
#set all asset costs and other parameters
2021-07-01 18:09:04 +00:00
costs = pd . read_csv ( cost_file , index_col = [ 0 , 1 ] ) . sort_index ( )
2020-07-29 13:50:40 +00:00
2019-04-17 12:24:22 +00:00
#correct units to MW and EUR
2021-07-01 18:09:04 +00:00
costs . loc [ costs . unit . str . contains ( " /kW " ) , " value " ] * = 1e3
costs . loc [ costs . unit . str . contains ( " USD " ) , " value " ] * = USD_to_EUR
2019-04-17 12:24:22 +00:00
2020-07-29 13:50:40 +00:00
#min_count=1 is important to generate NaNs which are then filled by fillna
costs = costs . loc [ : , " value " ] . unstack ( level = 1 ) . groupby ( " technology " ) . sum ( min_count = 1 )
2019-04-17 12:24:22 +00:00
costs = costs . fillna ( { " CO2 intensity " : 0 ,
" FOM " : 0 ,
" VOM " : 0 ,
2020-07-29 13:50:40 +00:00
" discount rate " : discount_rate ,
2019-04-17 12:24:22 +00:00
" efficiency " : 1 ,
" fuel " : 0 ,
" investment " : 0 ,
2020-11-30 16:01:14 +00:00
" lifetime " : lifetime
2019-04-17 12:24:22 +00:00
} )
2021-07-01 18:09:04 +00:00
annuity_factor = lambda v : annuity ( v [ " lifetime " ] , v [ " discount rate " ] ) + v [ " FOM " ] / 100
costs [ " fixed " ] = [ annuity_factor ( v ) * v [ " investment " ] * Nyears for i , v in costs . iterrows ( ) ]
2019-04-17 12:24:22 +00:00
return costs
2021-07-01 18:09:04 +00:00
def add_generation ( n , costs ) :
2019-04-17 12:24:22 +00:00
print ( " adding electricity generation " )
2021-07-01 18:09:04 +00:00
2019-04-17 12:24:22 +00:00
nodes = pop_layout . index
2021-07-01 18:09:04 +00:00
fallback = { " OCGT " : " gas " }
conventionals = options . get ( " conventional_generation " , fallback )
add_carrier_buses ( n , np . unique ( list ( conventionals . values ( ) ) ) )
for generator , carrier in conventionals . items ( ) :
n . madd ( " Link " ,
nodes + " " + generator ,
bus0 = " EU " + carrier ,
bus1 = nodes ,
bus2 = " co2 atmosphere " ,
marginal_cost = costs . at [ generator , ' efficiency ' ] * costs . at [ generator , ' VOM ' ] , #NB: VOM is per MWel
capital_cost = costs . at [ generator , ' efficiency ' ] * costs . at [ generator , ' fixed ' ] , #NB: fixed cost is per MWel
p_nom_extendable = True ,
carrier = generator ,
efficiency = costs . at [ generator , ' efficiency ' ] ,
efficiency2 = costs . at [ carrier , ' CO2 intensity ' ] ,
lifetime = costs . at [ generator , ' lifetime ' ]
)
2020-01-11 08:11:09 +00:00
2021-07-01 18:09:04 +00:00
def add_wave ( n , wave_cost_factor ) :
# TODO: handle in Snakefile
wave_fn = " data/WindWaveWEC_GLTB.xlsx "
2020-01-11 08:11:09 +00:00
#in kW
2021-07-01 18:09:04 +00:00
capacity = pd . Series ( { " Attenuator " : 750 ,
" F2HB " : 1000 ,
" MultiPA " : 600 } )
2020-01-11 08:11:09 +00:00
#in EUR/MW
2021-07-01 18:09:04 +00:00
annuity_factor = annuity ( 25 , 0.07 ) + 0.03
costs = 1e6 * wave_cost_factor * annuity_factor * pd . Series ( { " Attenuator " : 2.5 ,
" F2HB " : 2 ,
" MultiPA " : 1.5 } )
2020-01-11 08:11:09 +00:00
2021-07-01 18:09:04 +00:00
sheets = pd . read_excel ( wave_fn , sheet_name = [ " FirthForth " , " Hebrides " ] ,
usecols = [ " Attenuator " , " F2HB " , " MultiPA " ] ,
index_col = 0 , skiprows = [ 0 ] , parse_dates = True )
2020-01-11 08:11:09 +00:00
2021-07-01 18:09:04 +00:00
wave = pd . concat ( [ sheets [ l ] . divide ( capacity , axis = 1 ) for l in locations ] ,
2020-01-11 08:11:09 +00:00
keys = locations ,
axis = 1 )
for wave_type in costs . index :
n . add ( " Generator " ,
2021-07-01 18:09:04 +00:00
" Hebrides " + wave_type ,
bus = " GB4 0 " , # TODO this location is hardcoded
p_nom_extendable = True ,
carrier = " wave " ,
capital_cost = costs [ wave_type ] ,
p_max_pu = wave [ " Hebrides " , wave_type ]
)
2019-04-17 12:24:22 +00:00
2020-03-25 11:52:25 +00:00
2021-07-01 18:09:04 +00:00
def insert_electricity_distribution_grid ( n , costs ) :
# TODO pop_layout?
# TODO options?
2020-03-25 11:52:25 +00:00
print ( " Inserting electricity distribution grid with investment cost factor of " ,
2021-07-01 18:09:04 +00:00
options [ ' electricity_distribution_grid_cost_factor ' ] )
2020-03-25 11:52:25 +00:00
nodes = pop_layout . index
2021-07-01 18:09:04 +00:00
cost_factor = options [ ' electricity_distribution_grid_cost_factor ' ]
2020-03-25 11:52:25 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Bus " ,
nodes + " low voltage " ,
location = nodes ,
carrier = " low voltage "
)
2020-03-25 11:52:25 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Link " ,
nodes + " electricity distribution grid " ,
bus0 = nodes ,
bus1 = nodes + " low voltage " ,
p_nom_extendable = True ,
p_min_pu = - 1 ,
carrier = " electricity distribution grid " ,
efficiency = 1 ,
lifetime = costs . at [ ' electricity distribution grid ' , ' lifetime ' ] ,
capital_cost = costs . at [ ' electricity distribution grid ' , ' fixed ' ] * cost_factor
)
# this catches regular electricity load and "industry electricity"
loads = n . loads . index [ n . loads . carrier . str . contains ( " electricity " ) ]
n . loads . loc [ loads , " bus " ] + = " low voltage "
bevs = n . links . index [ n . links . carrier == " BEV charger " ]
n . links . loc [ bevs , " bus0 " ] + = " low voltage "
v2gs = n . links . index [ n . links . carrier == " V2G " ]
n . links . loc [ v2gs , " bus1 " ] + = " low voltage "
hps = n . links . index [ n . links . carrier . str . contains ( " heat pump " ) ]
n . links . loc [ hps , " bus0 " ] + = " low voltage "
rh = n . links . index [ n . links . carrier . str . contains ( " resistive heater " ) ]
n . links . loc [ rh , " bus0 " ] + = " low voltage "
mchp = n . links . index [ n . links . carrier . str . contains ( " micro gas " ) ]
n . links . loc [ mchp , " bus1 " ] + = " low voltage "
# set existing solar to cost of utility cost rather the 50-50 rooftop-utility
solar = n . generators . index [ n . generators . carrier == " solar " ]
n . generators . loc [ solar , " capital_cost " ] = costs . at [ ' solar-utility ' , ' fixed ' ]
if snakemake . wildcards . clusters [ - 1 : ] == " m " :
simplified_pop_layout = pd . read_csv ( snakemake . input . simplified_pop_layout , index_col = 0 )
pop_solar = simplified_pop_layout . total . rename ( index = lambda x : x + " solar " )
else :
pop_solar = pop_layout . total . rename ( index = lambda x : x + " solar " )
2020-09-22 18:18:21 +00:00
2021-07-01 18:09:04 +00:00
# add max solar rooftop potential assuming 0.1 kW/m2 and 10 m2/person,
# i.e. 1 kW/person (population data is in thousands of people) so we get MW
potential = 0.1 * 10 * pop_solar
n . madd ( " Generator " ,
solar ,
suffix = " rooftop " ,
bus = n . generators . loc [ solar , " bus " ] + " low voltage " ,
carrier = " solar rooftop " ,
p_nom_extendable = True ,
p_nom_max = potential ,
marginal_cost = n . generators . loc [ solar , ' marginal_cost ' ] ,
capital_cost = costs . at [ ' solar-rooftop ' , ' fixed ' ] ,
efficiency = n . generators . loc [ solar , ' efficiency ' ] ,
2021-08-24 16:58:42 +00:00
p_max_pu = n . generators_t . p_max_pu [ solar ] ,
2021-09-27 09:16:12 +00:00
lifetime = costs . at [ ' solar-rooftop ' , ' lifetime ' ]
2021-07-01 18:09:04 +00:00
)
n . add ( " Carrier " , " home battery " )
2020-03-25 11:52:25 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Bus " ,
nodes + " home battery " ,
location = nodes ,
carrier = " home battery "
)
2020-03-25 11:52:25 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Store " ,
nodes + " home battery " ,
bus = nodes + " home battery " ,
e_cyclic = True ,
e_nom_extendable = True ,
carrier = " home battery " ,
2021-07-01 18:16:12 +00:00
capital_cost = costs . at [ ' home battery storage ' , ' fixed ' ] ,
2021-07-01 18:09:04 +00:00
lifetime = costs . at [ ' battery storage ' , ' lifetime ' ]
)
2020-03-25 11:52:25 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Link " ,
nodes + " home battery charger " ,
bus0 = nodes + " low voltage " ,
bus1 = nodes + " home battery " ,
carrier = " home battery charger " ,
efficiency = costs . at [ ' battery inverter ' , ' efficiency ' ] * * 0.5 ,
2021-07-01 18:16:12 +00:00
capital_cost = costs . at [ ' home battery inverter ' , ' fixed ' ] ,
2021-07-01 18:09:04 +00:00
p_nom_extendable = True ,
lifetime = costs . at [ ' battery inverter ' , ' lifetime ' ]
)
2020-03-26 09:06:59 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Link " ,
nodes + " home battery discharger " ,
bus0 = nodes + " home battery " ,
bus1 = nodes + " low voltage " ,
carrier = " home battery discharger " ,
efficiency = costs . at [ ' battery inverter ' , ' efficiency ' ] * * 0.5 ,
marginal_cost = options [ ' marginal_cost_storage ' ] ,
p_nom_extendable = True ,
lifetime = costs . at [ ' battery inverter ' , ' lifetime ' ]
)
2020-09-15 15:44:27 +00:00
2021-07-01 18:09:04 +00:00
def insert_gas_distribution_costs ( n , costs ) :
# TODO options?
2020-09-25 13:25:41 +00:00
2020-09-15 16:03:33 +00:00
f_costs = options [ ' gas_distribution_grid_cost_factor ' ]
2021-07-08 12:41:34 +00:00
2021-07-01 18:09:04 +00:00
print ( " Inserting gas distribution grid with investment cost factor of " , f_costs )
capital_cost = costs . loc [ ' electricity distribution grid ' ] [ " fixed " ] * f_costs
2020-09-15 16:03:33 +00:00
# gas boilers
2021-07-01 18:09:04 +00:00
gas_b = n . links . index [ n . links . carrier . str . contains ( " gas boiler " ) &
( ~ n . links . carrier . str . contains ( " urban central " ) ) ]
n . links . loc [ gas_b , " capital_cost " ] + = capital_cost
2021-07-08 12:41:34 +00:00
2020-09-15 16:03:33 +00:00
# micro CHPs
2021-07-01 18:09:04 +00:00
mchp = n . links . index [ n . links . carrier . str . contains ( " micro gas " ) ]
n . links . loc [ mchp , " capital_cost " ] + = capital_cost
def add_electricity_grid_connection ( n , costs ) :
carriers = [ " onwind " , " solar " ]
2020-09-15 16:03:33 +00:00
2021-07-01 18:09:04 +00:00
gens = n . generators . index [ n . generators . carrier . isin ( carriers ) ]
2020-03-26 09:06:59 +00:00
2021-07-01 18:09:04 +00:00
n . generators . loc [ gens , " capital_cost " ] + = costs . at [ ' electricity grid connection ' , ' fixed ' ]
2020-05-11 11:37:10 +00:00
2021-07-01 18:09:04 +00:00
def add_storage ( n , costs ) :
# TODO pop_layout
# TODO options?
2020-03-25 11:52:25 +00:00
2019-04-17 12:24:22 +00:00
print ( " adding electricity storage " )
2021-07-01 18:09:04 +00:00
2019-04-17 12:24:22 +00:00
nodes = pop_layout . index
2021-07-01 18:09:04 +00:00
n . add ( " Carrier " , " H2 " )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Bus " ,
nodes + " H2 " ,
location = nodes ,
carrier = " H2 "
)
2020-09-22 07:52:53 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Link " ,
nodes + " H2 Electrolysis " ,
bus1 = nodes + " H2 " ,
bus0 = nodes ,
p_nom_extendable = True ,
carrier = " H2 Electrolysis " ,
efficiency = costs . at [ " electrolysis " , " efficiency " ] ,
capital_cost = costs . at [ " electrolysis " , " fixed " ] ,
lifetime = costs . at [ ' electrolysis ' , ' lifetime ' ]
)
2020-09-22 07:52:53 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Link " ,
nodes + " H2 Fuel Cell " ,
bus0 = nodes + " H2 " ,
bus1 = nodes ,
p_nom_extendable = True ,
carrier = " H2 Fuel Cell " ,
efficiency = costs . at [ " fuel cell " , " efficiency " ] ,
capital_cost = costs . at [ " fuel cell " , " fixed " ] * costs . at [ " fuel cell " , " efficiency " ] , #NB: fixed cost is per MWel
lifetime = costs . at [ ' fuel cell ' , ' lifetime ' ]
)
cavern_nodes = pd . DataFrame ( )
2019-05-13 14:47:54 +00:00
if options [ ' hydrogen_underground_storage ' ] :
2021-07-01 18:09:04 +00:00
h2_salt_cavern_potential = pd . read_csv ( snakemake . input . h2_cavern , index_col = 0 , squeeze = True )
2020-09-25 14:23:45 +00:00
h2_cavern_ct = h2_salt_cavern_potential [ ~ h2_salt_cavern_potential . isna ( ) ]
2020-09-22 07:52:53 +00:00
cavern_nodes = pop_layout [ pop_layout . ct . isin ( h2_cavern_ct . index ) ]
h2_capital_cost = costs . at [ " hydrogen storage underground " , " fixed " ]
2020-09-22 07:54:52 +00:00
# assumptions: weight storage potential in a country by population
2020-09-25 14:23:45 +00:00
# TODO: fix with real geographic potentials
2021-07-01 18:09:04 +00:00
# convert TWh to MWh with 1e6
2020-09-25 14:23:45 +00:00
h2_pot = h2_cavern_ct . loc [ cavern_nodes . ct ]
h2_pot . index = cavern_nodes . index
h2_pot = h2_pot * cavern_nodes . fraction * 1e6
2020-09-22 07:54:52 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Store " ,
cavern_nodes . index + " H2 Store " ,
bus = cavern_nodes . index + " H2 " ,
e_nom_extendable = True ,
e_nom_max = h2_pot . values ,
e_cyclic = True ,
carrier = " H2 Store " ,
capital_cost = h2_capital_cost
)
2020-09-22 07:52:53 +00:00
2021-07-01 18:09:04 +00:00
# hydrogen stored overground (where not already underground)
2021-08-04 16:19:42 +00:00
h2_capital_cost = costs . at [ " hydrogen storage tank incl. compressor " , " fixed " ]
2021-07-01 18:09:04 +00:00
nodes_overground = cavern_nodes . index . symmetric_difference ( nodes )
2019-05-13 14:47:54 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Store " ,
nodes_overground + " H2 Store " ,
bus = nodes_overground + " H2 " ,
e_nom_extendable = True ,
e_cyclic = True ,
carrier = " H2 Store " ,
capital_cost = h2_capital_cost
)
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
attrs = [ " bus0 " , " bus1 " , " length " ]
h2_links = pd . DataFrame ( columns = attrs )
2019-11-26 17:00:45 +00:00
2021-07-01 18:09:04 +00:00
candidates = pd . concat ( { " lines " : n . lines [ attrs ] ,
" links " : n . links . loc [ n . links . carrier == " DC " , attrs ] } )
2019-11-26 17:00:45 +00:00
for candidate in candidates . index :
2021-07-01 18:09:04 +00:00
buses = [ candidates . at [ candidate , " bus0 " ] , candidates . at [ candidate , " bus1 " ] ]
2019-11-26 17:00:45 +00:00
buses . sort ( )
2021-07-01 18:09:04 +00:00
name = f " H2 pipeline { buses [ 0 ] } -> { buses [ 1 ] } "
2019-11-26 17:00:45 +00:00
if name not in h2_links . index :
2021-07-01 18:09:04 +00:00
h2_links . at [ name , " bus0 " ] = buses [ 0 ]
h2_links . at [ name , " bus1 " ] = buses [ 1 ]
h2_links . at [ name , " length " ] = candidates . at [ candidate , " length " ]
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
# TODO Add efficiency losses
n . madd ( " Link " ,
h2_links . index ,
bus0 = h2_links . bus0 . values + " H2 " ,
bus1 = h2_links . bus1 . values + " H2 " ,
p_min_pu = - 1 ,
p_nom_extendable = True ,
length = h2_links . length . values ,
2021-08-04 16:19:42 +00:00
capital_cost = costs . at [ ' H2 (g) pipeline ' , ' fixed ' ] * h2_links . length . values ,
2021-07-01 18:09:04 +00:00
carrier = " H2 pipeline " ,
2021-08-04 16:19:42 +00:00
lifetime = costs . at [ ' H2 (g) pipeline ' , ' lifetime ' ]
2021-07-01 18:09:04 +00:00
)
n . add ( " Carrier " , " battery " )
n . madd ( " Bus " ,
nodes + " battery " ,
location = nodes ,
carrier = " battery "
)
n . madd ( " Store " ,
nodes + " battery " ,
bus = nodes + " battery " ,
e_cyclic = True ,
e_nom_extendable = True ,
carrier = " battery " ,
capital_cost = costs . at [ ' battery storage ' , ' fixed ' ] ,
lifetime = costs . at [ ' battery storage ' , ' lifetime ' ]
)
n . madd ( " Link " ,
nodes + " battery charger " ,
bus0 = nodes ,
bus1 = nodes + " battery " ,
carrier = " battery charger " ,
efficiency = costs . at [ ' battery inverter ' , ' efficiency ' ] * * 0.5 ,
capital_cost = costs . at [ ' battery inverter ' , ' fixed ' ] ,
p_nom_extendable = True ,
lifetime = costs . at [ ' battery inverter ' , ' lifetime ' ]
)
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Link " ,
nodes + " battery discharger " ,
bus0 = nodes + " battery " ,
bus1 = nodes ,
carrier = " battery discharger " ,
efficiency = costs . at [ ' battery inverter ' , ' efficiency ' ] * * 0.5 ,
marginal_cost = options [ ' marginal_cost_storage ' ] ,
p_nom_extendable = True ,
lifetime = costs . at [ ' battery inverter ' , ' lifetime ' ]
)
2019-04-17 12:24:22 +00:00
if options [ ' methanation ' ] :
2021-07-01 18:09:04 +00:00
n . madd ( " Link " ,
2021-07-09 10:50:40 +00:00
spatial . nodes ,
suffix = " Sabatier " ,
2021-07-01 18:09:04 +00:00
bus0 = nodes + " H2 " ,
bus1 = " EU gas " ,
2021-07-09 10:50:40 +00:00
bus2 = spatial . co2 . nodes ,
2021-07-01 18:09:04 +00:00
p_nom_extendable = True ,
carrier = " Sabatier " ,
efficiency = costs . at [ " methanation " , " efficiency " ] ,
efficiency2 = - costs . at [ " methanation " , " efficiency " ] * costs . at [ ' gas ' , ' CO2 intensity ' ] ,
2021-08-04 16:19:42 +00:00
capital_cost = costs . at [ " methanation " , " fixed " ] * costs . at [ " methanation " , " efficiency " ] , # costs given per kW_gas
2021-07-01 18:09:04 +00:00
lifetime = costs . at [ ' methanation ' , ' lifetime ' ]
)
2019-04-17 12:24:22 +00:00
if options [ ' helmeth ' ] :
2021-07-01 18:09:04 +00:00
n . madd ( " Link " ,
2021-07-09 10:50:40 +00:00
spatial . nodes ,
suffix = " helmeth " ,
2021-07-01 18:09:04 +00:00
bus0 = nodes ,
bus1 = " EU gas " ,
2021-07-09 10:50:40 +00:00
bus2 = spatial . co2 . nodes ,
2021-07-01 18:09:04 +00:00
carrier = " helmeth " ,
p_nom_extendable = True ,
efficiency = costs . at [ " helmeth " , " efficiency " ] ,
efficiency2 = - costs . at [ " helmeth " , " efficiency " ] * costs . at [ ' gas ' , ' CO2 intensity ' ] ,
capital_cost = costs . at [ " helmeth " , " fixed " ] ,
lifetime = costs . at [ ' helmeth ' , ' lifetime ' ]
)
2019-04-17 12:24:22 +00:00
2019-05-11 13:55:06 +00:00
if options [ ' SMR ' ] :
2021-07-01 18:09:04 +00:00
n . madd ( " Link " ,
2021-07-09 10:50:40 +00:00
spatial . nodes ,
suffix = " SMR CC " ,
2021-07-01 18:09:04 +00:00
bus0 = " EU gas " ,
bus1 = nodes + " H2 " ,
bus2 = " co2 atmosphere " ,
2021-07-09 10:50:40 +00:00
bus3 = spatial . co2 . nodes ,
2021-07-01 18:09:04 +00:00
p_nom_extendable = True ,
carrier = " SMR CC " ,
efficiency = costs . at [ " SMR CC " , " efficiency " ] ,
efficiency2 = costs . at [ ' gas ' , ' CO2 intensity ' ] * ( 1 - options [ " cc_fraction " ] ) ,
efficiency3 = costs . at [ ' gas ' , ' CO2 intensity ' ] * options [ " cc_fraction " ] ,
capital_cost = costs . at [ " SMR CC " , " fixed " ] ,
lifetime = costs . at [ ' SMR CC ' , ' lifetime ' ]
)
n . madd ( " Link " ,
nodes + " SMR " ,
bus0 = " EU gas " ,
bus1 = nodes + " H2 " ,
bus2 = " co2 atmosphere " ,
p_nom_extendable = True ,
carrier = " SMR " ,
efficiency = costs . at [ " SMR " , " efficiency " ] ,
efficiency2 = costs . at [ ' gas ' , ' CO2 intensity ' ] ,
capital_cost = costs . at [ " SMR " , " fixed " ] ,
lifetime = costs . at [ ' SMR ' , ' lifetime ' ]
)
def add_land_transport ( n , costs ) :
# TODO options?
2020-11-30 15:20:26 +00:00
print ( " adding land transport " )
2021-07-01 18:09:04 +00:00
fuel_cell_share = get ( options [ " land_transport_fuel_cell_share " ] , investment_year )
electric_share = get ( options [ " land_transport_electric_share " ] , investment_year )
2021-01-26 09:55:38 +00:00
ice_share = 1 - fuel_cell_share - electric_share
2020-11-30 15:20:26 +00:00
2021-07-01 18:09:04 +00:00
print ( " FCEV share " , fuel_cell_share )
print ( " EV share " , electric_share )
print ( " ICEV share " , ice_share )
2020-11-30 15:20:26 +00:00
2021-07-01 18:09:04 +00:00
assert ice_share > = 0 , " Error, more FCEV and EV share than 1. "
2020-11-30 15:20:26 +00:00
2019-04-17 12:24:22 +00:00
nodes = pop_layout . index
2020-11-30 15:20:26 +00:00
if electric_share > 0 :
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
n . add ( " Carrier " , " Li ion " )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Bus " ,
nodes ,
location = nodes ,
suffix = " EV battery " ,
carrier = " Li ion "
)
2021-07-08 12:41:34 +00:00
2021-07-01 18:09:04 +00:00
p_set = electric_share * ( transport [ nodes ] + cycling_shift ( transport [ nodes ] , 1 ) + cycling_shift ( transport [ nodes ] , 2 ) ) / 3
n . madd ( " Load " ,
nodes ,
suffix = " land transport EV " ,
bus = nodes + " EV battery " ,
carrier = " land transport EV " ,
p_set = p_set
)
2019-04-17 12:24:22 +00:00
2021-07-08 12:41:34 +00:00
p_nom = nodal_transport_data [ " number cars " ] * options . get ( " bev_charge_rate " , 0.011 ) * electric_share
2021-07-01 18:09:04 +00:00
n . madd ( " Link " ,
nodes ,
suffix = " BEV charger " ,
bus0 = nodes ,
bus1 = nodes + " EV battery " ,
p_nom = p_nom ,
carrier = " BEV charger " ,
p_max_pu = avail_profile [ nodes ] ,
efficiency = options . get ( " bev_charge_efficiency " , 0.9 ) ,
#These were set non-zero to find LU infeasibility when availability = 0.25
#p_nom_extendable=True,
#p_nom_min=p_nom,
#capital_cost=1e6, #i.e. so high it only gets built where necessary
)
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
if electric_share > 0 and options [ " v2g " ] :
n . madd ( " Link " ,
nodes ,
suffix = " V2G " ,
bus1 = nodes ,
bus0 = nodes + " EV battery " ,
p_nom = p_nom ,
carrier = " V2G " ,
p_max_pu = avail_profile [ nodes ] ,
efficiency = options . get ( " bev_charge_efficiency " , 0.9 ) ,
)
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
if electric_share > 0 and options [ " bev_dsm " ] :
2020-11-30 15:20:26 +00:00
2021-07-08 12:41:34 +00:00
e_nom = nodal_transport_data [ " number cars " ] * options . get ( " bev_energy " , 0.05 ) * options [ " bev_availability " ] * electric_share
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Store " ,
nodes ,
suffix = " battery storage " ,
bus = nodes + " EV battery " ,
carrier = " battery storage " ,
e_cyclic = True ,
e_nom = e_nom ,
e_max_pu = 1 ,
e_min_pu = dsm_profile [ nodes ]
)
2019-04-17 12:24:22 +00:00
2020-11-30 15:20:26 +00:00
if fuel_cell_share > 0 :
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Load " ,
nodes ,
suffix = " land transport fuel cell " ,
bus = nodes + " H2 " ,
carrier = " land transport fuel cell " ,
p_set = fuel_cell_share / options [ ' transport_fuel_cell_efficiency ' ] * transport [ nodes ]
)
2020-11-30 15:20:26 +00:00
2021-01-26 09:55:38 +00:00
if ice_share > 0 :
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
if " EU oil " not in n . buses . index :
n . add ( " Bus " ,
" EU oil " ,
location = " EU " ,
carrier = " oil "
)
ice_efficiency = options [ ' transport_internal_combustion_efficiency ' ]
n . madd ( " Load " ,
nodes ,
suffix = " land transport oil " ,
bus = " EU oil " ,
carrier = " land transport oil " ,
p_set = ice_share / ice_efficiency * transport [ nodes ]
)
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
co2 = ice_share / ice_efficiency * transport [ nodes ] . sum ( ) . sum ( ) / 8760 * costs . at [ " oil " , ' CO2 intensity ' ]
2021-03-04 17:20:23 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Load " ,
[ " land transport oil emissions " ] ,
bus = " co2 atmosphere " ,
carrier = " land transport oil emissions " ,
p_set = - co2
)
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
def add_heat ( n , costs ) :
# TODO options?
# TODO pop_layout?
2019-04-17 12:24:22 +00:00
print ( " adding heat " )
2020-04-09 12:58:03 +00:00
sectors = [ " residential " , " services " ]
2019-04-17 12:24:22 +00:00
2021-07-08 12:41:34 +00:00
nodes , dist_fraction , urban_fraction = create_nodes_for_heat_sector ( )
2019-04-17 12:24:22 +00:00
2021-07-08 12:41:34 +00:00
#NB: must add costs of central heating afterwards (EUR 400 / kWpeak, 50a, 1% FOM from Fraunhofer ISE)
2019-04-17 12:24:22 +00:00
2021-04-19 15:20:52 +00:00
# exogenously reduce space heat demand
2021-04-30 15:57:16 +00:00
if options [ " reduce_space_heat_exogenously " ] :
2021-07-01 18:09:04 +00:00
dE = get ( options [ " reduce_space_heat_exogenously_factor " ] , investment_year )
print ( f " assumed space heat reduction of { dE * 100 } % " )
2020-10-21 12:30:26 +00:00
for sector in sectors :
2021-07-01 18:09:04 +00:00
heat_demand [ sector + " space " ] = ( 1 - dE ) * heat_demand [ sector + " space " ]
heat_systems = [
" residential rural " ,
" services rural " ,
" residential urban decentral " ,
" services urban decentral " ,
" urban central "
]
2021-07-08 12:41:34 +00:00
2020-10-21 12:30:26 +00:00
for name in heat_systems :
2019-04-17 12:24:22 +00:00
2019-08-07 09:33:29 +00:00
name_type = " central " if name == " urban central " else " decentral "
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
n . add ( " Carrier " , name + " heat " )
2019-07-16 14:00:21 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Bus " ,
nodes [ name ] + f " { name } heat " ,
location = nodes [ name ] ,
carrier = name + " heat "
)
2019-07-16 14:00:21 +00:00
2019-08-07 09:33:29 +00:00
## Add heat load
2019-04-17 12:24:22 +00:00
2019-08-07 09:33:29 +00:00
for sector in sectors :
2021-07-08 12:41:34 +00:00
# heat demand weighting
2019-08-07 09:33:29 +00:00
if " rural " in name :
2021-07-01 18:09:04 +00:00
factor = 1 - urban_fraction [ nodes [ name ] ]
2021-07-08 12:41:34 +00:00
elif " urban central " in name :
factor = dist_fraction [ nodes [ name ] ]
elif " urban decentral " in name :
factor = urban_fraction [ nodes [ name ] ] - \
dist_fraction [ nodes [ name ] ]
else :
factor = None
2019-08-07 09:33:29 +00:00
if sector in name :
heat_load = heat_demand [ [ sector + " water " , sector + " space " ] ] . groupby ( level = 1 , axis = 1 ) . sum ( ) [ nodes [ name ] ] . multiply ( factor )
2019-04-17 12:24:22 +00:00
2019-08-07 09:33:29 +00:00
if name == " urban central " :
2021-09-29 12:37:36 +00:00
heat_load = heat_demand . groupby ( level = 1 , axis = 1 ) . sum ( ) [ nodes [ name ] ] . multiply ( factor * ( 1 + options [ ' district_heating ' ] [ ' district_heating_loss ' ] ) )
2021-07-01 18:09:04 +00:00
n . madd ( " Load " ,
nodes [ name ] ,
suffix = f " { name } heat " ,
bus = nodes [ name ] + f " { name } heat " ,
carrier = name + " heat " ,
p_set = heat_load
)
2019-04-17 12:24:22 +00:00
2019-08-07 09:33:29 +00:00
## Add heat pumps
2019-04-17 12:24:22 +00:00
2019-08-07 09:33:29 +00:00
heat_pump_type = " air " if " urban " in name else " ground "
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
costs_name = f " { name_type } { heat_pump_type } -sourced heat pump "
2019-08-07 09:33:29 +00:00
cop = { " air " : ashp_cop , " ground " : gshp_cop }
2021-07-01 18:09:04 +00:00
efficiency = cop [ heat_pump_type ] [ nodes [ name ] ] if options [ " time_dep_hp_cop " ] else costs . at [ costs_name , ' efficiency ' ]
n . madd ( " Link " ,
nodes [ name ] ,
suffix = f " { name } { heat_pump_type } heat pump " ,
bus0 = nodes [ name ] ,
bus1 = nodes [ name ] + f " { name } heat " ,
carrier = f " { name } { heat_pump_type } heat pump " ,
efficiency = efficiency ,
capital_cost = costs . at [ costs_name , ' efficiency ' ] * costs . at [ costs_name , ' fixed ' ] ,
p_nom_extendable = True ,
lifetime = costs . at [ costs_name , ' lifetime ' ]
)
2019-08-07 09:33:29 +00:00
if options [ " tes " ] :
2021-07-01 18:09:04 +00:00
n . add ( " Carrier " , name + " water tanks " )
n . madd ( " Bus " ,
nodes [ name ] + f " { name } water tanks " ,
location = nodes [ name ] ,
carrier = name + " water tanks "
)
n . madd ( " Link " ,
nodes [ name ] + f " { name } water tanks charger " ,
bus0 = nodes [ name ] + f " { name } heat " ,
bus1 = nodes [ name ] + f " { name } water tanks " ,
efficiency = costs . at [ ' water tank charger ' , ' efficiency ' ] ,
carrier = name + " water tanks charger " ,
p_nom_extendable = True
)
n . madd ( " Link " ,
nodes [ name ] + f " { name } water tanks discharger " ,
bus0 = nodes [ name ] + f " { name } water tanks " ,
bus1 = nodes [ name ] + f " { name } heat " ,
carrier = name + " water tanks discharger " ,
efficiency = costs . at [ ' water tank discharger ' , ' efficiency ' ] ,
p_nom_extendable = True
)
2021-07-08 12:41:34 +00:00
2021-07-01 18:09:04 +00:00
if isinstance ( options [ " tes_tau " ] , dict ) :
tes_time_constant_days = options [ " tes_tau " ] [ name_type ]
else :
logger . warning ( " Deprecated: a future version will require you to specify ' tes_tau ' " ,
" for ' decentral ' and ' central ' separately. " )
tes_time_constant_days = options [ " tes_tau " ] if name_type == " decentral " else 180.
2021-07-08 12:41:34 +00:00
2021-07-01 18:09:04 +00:00
# conversion from EUR/m^3 to EUR/MWh for 40 K diff and 1.17 kWh/m^3/K
2021-07-08 12:41:34 +00:00
capital_cost = costs . at [ name_type + ' water tank storage ' , ' fixed ' ] / 0.00117 / 40
2021-07-01 18:09:04 +00:00
n . madd ( " Store " ,
nodes [ name ] + f " { name } water tanks " ,
bus = nodes [ name ] + f " { name } water tanks " ,
e_cyclic = True ,
e_nom_extendable = True ,
carrier = name + " water tanks " ,
standing_loss = 1 - np . exp ( - 1 / 24 / tes_time_constant_days ) ,
capital_cost = capital_cost ,
lifetime = costs . at [ name_type + ' water tank storage ' , ' lifetime ' ]
)
2019-08-07 09:33:29 +00:00
if options [ " boilers " ] :
2021-07-01 18:09:04 +00:00
key = f " { name_type } resistive heater "
n . madd ( " Link " ,
nodes [ name ] + f " { name } resistive heater " ,
bus0 = nodes [ name ] ,
bus1 = nodes [ name ] + f " { name } heat " ,
carrier = name + " resistive heater " ,
efficiency = costs . at [ key , ' efficiency ' ] ,
capital_cost = costs . at [ key , ' efficiency ' ] * costs . at [ key , ' fixed ' ] ,
p_nom_extendable = True ,
lifetime = costs . at [ key , ' lifetime ' ]
)
key = f " { name_type } gas boiler "
n . madd ( " Link " ,
nodes [ name ] + f " { name } gas boiler " ,
p_nom_extendable = True ,
bus0 = " EU gas " ,
bus1 = nodes [ name ] + f " { name } heat " ,
bus2 = " co2 atmosphere " ,
carrier = name + " gas boiler " ,
efficiency = costs . at [ key , ' efficiency ' ] ,
efficiency2 = costs . at [ ' gas ' , ' CO2 intensity ' ] ,
capital_cost = costs . at [ key , ' efficiency ' ] * costs . at [ key , ' fixed ' ] ,
lifetime = costs . at [ key , ' lifetime ' ]
)
2019-08-07 09:33:29 +00:00
if options [ " solar_thermal " ] :
2021-07-01 18:09:04 +00:00
n . add ( " Carrier " , name + " solar thermal " )
n . madd ( " Generator " ,
nodes [ name ] ,
suffix = f " { name } solar thermal collector " ,
bus = nodes [ name ] + f " { name } heat " ,
carrier = name + " solar thermal " ,
p_nom_extendable = True ,
capital_cost = costs . at [ name_type + ' solar thermal ' , ' fixed ' ] ,
p_max_pu = solar_thermal [ nodes [ name ] ] ,
lifetime = costs . at [ name_type + ' solar thermal ' , ' lifetime ' ]
)
if options [ " chp " ] and name == " urban central " :
# add gas CHP; biomass CHP is added in biomass section
n . madd ( " Link " ,
nodes [ name ] + " urban central gas CHP " ,
bus0 = " EU gas " ,
bus1 = nodes [ name ] ,
bus2 = nodes [ name ] + " urban central heat " ,
bus3 = " co2 atmosphere " ,
carrier = " urban central gas CHP " ,
p_nom_extendable = True ,
capital_cost = costs . at [ ' central gas CHP ' , ' fixed ' ] * costs . at [ ' central gas CHP ' , ' efficiency ' ] ,
marginal_cost = costs . at [ ' central gas CHP ' , ' VOM ' ] ,
efficiency = costs . at [ ' central gas CHP ' , ' efficiency ' ] ,
efficiency2 = costs . at [ ' central gas CHP ' , ' efficiency ' ] / costs . at [ ' central gas CHP ' , ' c_b ' ] ,
efficiency3 = costs . at [ ' gas ' , ' CO2 intensity ' ] ,
lifetime = costs . at [ ' central gas CHP ' , ' lifetime ' ]
)
n . madd ( " Link " ,
nodes [ name ] + " urban central gas CHP CC " ,
bus0 = " EU gas " ,
bus1 = nodes [ name ] ,
bus2 = nodes [ name ] + " urban central heat " ,
bus3 = " co2 atmosphere " ,
2021-07-09 10:50:40 +00:00
bus4 = spatial . co2 . df . loc [ nodes [ name ] , " nodes " ] . values ,
2021-07-01 18:09:04 +00:00
carrier = " urban central gas CHP CC " ,
p_nom_extendable = True ,
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-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 ' ]
)
if options [ " chp " ] and options [ " micro_chp " ] and name != " urban central " :
n . madd ( " Link " ,
nodes [ name ] + f " { name } micro gas CHP " ,
p_nom_extendable = True ,
bus0 = " EU gas " ,
bus1 = nodes [ name ] ,
bus2 = nodes [ name ] + f " { name } heat " ,
bus3 = " co2 atmosphere " ,
carrier = name + " micro gas CHP " ,
efficiency = costs . at [ ' micro CHP ' , ' efficiency ' ] ,
efficiency2 = costs . at [ ' micro CHP ' , ' efficiency-heat ' ] ,
efficiency3 = costs . at [ ' gas ' , ' CO2 intensity ' ] ,
capital_cost = costs . at [ ' micro CHP ' , ' fixed ' ] ,
lifetime = costs . at [ ' micro CHP ' , ' lifetime ' ]
)
2019-04-17 12:24:22 +00:00
2020-10-21 13:21:26 +00:00
if options [ ' retrofitting ' ] [ ' retro_endogen ' ] :
print ( " adding retrofitting endogenously " )
2020-10-21 17:19:38 +00:00
2020-11-11 17:18:56 +00:00
# resample heat demand temporal 'heat_demand_r' depending on in config
# specified temporal resolution, to not overestimate retrofitting
hours = list ( filter ( re . compile ( r ' ^ \ d+h$ ' , re . IGNORECASE ) . search , opts ) )
if len ( hours ) == 0 :
hours = [ n . snapshots [ 1 ] - n . snapshots [ 0 ] ]
heat_demand_r = heat_demand . resample ( hours [ 0 ] ) . mean ( )
# retrofitting data 'retro_data' with 'costs' [EUR/m^2] and heat
# demand 'dE' [per unit of original heat demand] for each country and
# different retrofitting strengths [additional insulation thickness in m]
retro_data = pd . read_csv ( snakemake . input . retro_cost_energy ,
2020-10-21 13:21:26 +00:00
index_col = [ 0 , 1 ] , skipinitialspace = True ,
header = [ 0 , 1 ] )
2020-11-11 17:18:56 +00:00
# heated floor area [10^6 * m^2] per country
2020-10-21 13:21:26 +00:00
floor_area = pd . read_csv ( snakemake . input . floor_area , index_col = [ 0 , 1 ] )
2021-07-01 18:09:04 +00:00
n . add ( " Carrier " , " retrofitting " )
2020-10-21 13:21:26 +00:00
2020-11-11 17:18:56 +00:00
# share of space heat demand 'w_space' of total heat demand
2020-10-21 13:21:26 +00:00
w_space = { }
for sector in sectors :
w_space [ sector ] = heat_demand_r [ sector + " space " ] / \
( heat_demand_r [ sector + " space " ] + heat_demand_r [ sector + " water " ] )
w_space [ " tot " ] = ( ( heat_demand_r [ " services space " ] +
heat_demand_r [ " residential space " ] ) /
heat_demand_r . groupby ( level = [ 1 ] , axis = 1 ) . sum ( ) )
2021-07-01 18:09:04 +00:00
for name in n . loads [ n . loads . carrier . isin ( [ x + " heat " for x in heat_systems ] ) ] . index :
2020-10-21 17:19:38 +00:00
2021-07-01 18:09:04 +00:00
node = n . buses . loc [ name , " location " ]
2020-11-11 17:18:56 +00:00
ct = pop_layout . loc [ node , " ct " ]
2020-10-21 17:19:38 +00:00
2020-11-11 17:18:56 +00:00
# weighting 'f' depending on the size of the population at the node
f = urban_fraction [ node ] if " urban " in name else ( 1 - urban_fraction [ node ] )
2020-10-21 17:19:38 +00:00
if f == 0 :
continue
2020-11-11 17:18:56 +00:00
# get sector name ("residential"/"services"/or both "tot" for urban central)
2020-10-21 17:19:38 +00:00
sec = [ x if x in name else " tot " for x in sectors ] [ 0 ]
2020-11-11 17:18:56 +00:00
# get floor aread at node and region (urban/rural) in m^2
floor_area_node = ( ( pop_layout . loc [ node ] . fraction
2020-10-21 17:19:38 +00:00
* floor_area . loc [ ct , " value " ] * 10 * * 6 ) . loc [ sec ] * f )
2020-11-11 17:18:56 +00:00
# total heat demand at node [MWh]
2021-07-01 18:09:04 +00:00
demand = ( n . loads_t . p_set [ name ] . resample ( hours [ 0 ] )
2020-10-21 17:19:38 +00:00
. mean ( ) )
2020-11-11 17:18:56 +00:00
# space heat demand at node [MWh]
2020-10-21 17:19:38 +00:00
space_heat_demand = demand * w_space [ sec ] [ node ]
2020-11-11 17:18:56 +00:00
# normed time profile of space heat demand 'space_pu' (values between 0-1),
2020-10-21 17:19:38 +00:00
# p_max_pu/p_min_pu of retrofitting generators
space_pu = ( space_heat_demand / space_heat_demand . max ( ) ) . to_frame ( name = node )
2020-11-11 17:18:56 +00:00
# minimum heat demand 'dE' after retrofitting in units of original heat demand (values between 0-1)
dE = retro_data . loc [ ( ct , sec ) , ( " dE " ) ]
# get addtional energy savings 'dE_diff' between the different retrofitting strengths/generators at one node
2020-10-21 17:19:38 +00:00
dE_diff = abs ( dE . diff ( ) ) . fillna ( 1 - dE . iloc [ 0 ] )
2020-11-11 17:18:56 +00:00
# convert costs Euro/m^2 -> Euro/MWh
capital_cost = retro_data . loc [ ( ct , sec ) , ( " cost " ) ] * floor_area_node / \
2020-10-21 17:19:38 +00:00
( ( 1 - dE ) * space_heat_demand . max ( ) )
2020-11-11 17:18:56 +00:00
# number of possible retrofitting measures 'strengths' (set in list at config.yaml 'l_strength')
# given in additional insulation thickness [m]
# for each measure, a retrofitting generator is added at the node
strengths = retro_data . columns . levels [ 1 ]
2020-10-21 17:19:38 +00:00
2020-11-11 17:18:56 +00:00
# check that ambitious retrofitting has higher costs per MWh than moderate retrofitting
2020-10-21 17:19:38 +00:00
if ( capital_cost . diff ( ) < 0 ) . sum ( ) :
2021-07-01 18:09:04 +00:00
print ( f " Warning: costs are not linear for { ct } { sec } " )
2020-10-21 17:19:38 +00:00
s = capital_cost [ ( capital_cost . diff ( ) < 0 ) ] . index
strengths = strengths . drop ( s )
2020-11-11 17:18:56 +00:00
# reindex normed time profile of space heat demand back to hourly resolution
2021-07-01 18:09:04 +00:00
space_pu = space_pu . reindex ( index = heat_demand . index ) . fillna ( method = " ffill " )
2020-10-21 17:19:38 +00:00
2020-11-11 17:18:56 +00:00
# add for each retrofitting strength a generator with heat generation profile following the profile of the heat demand
2020-10-21 17:19:38 +00:00
for strength in strengths :
2021-07-01 18:09:04 +00:00
n . madd ( ' Generator ' ,
[ node ] ,
suffix = ' retrofitting ' + strength + " " + name [ 6 : : ] ,
bus = name ,
carrier = " retrofitting " ,
p_nom_extendable = True ,
p_nom_max = dE_diff [ strength ] * space_heat_demand . max ( ) , # maximum energy savings for this renovation strength
p_max_pu = space_pu ,
p_min_pu = space_pu ,
country = ct ,
capital_cost = capital_cost [ strength ] * options [ ' retrofitting ' ] [ ' cost_factor ' ]
)
2020-10-21 13:21:26 +00:00
2020-04-09 12:58:03 +00:00
def create_nodes_for_heat_sector ( ) :
2021-07-01 18:09:04 +00:00
# TODO pop_layout
2020-04-09 12:58:03 +00:00
# rural are areas with low heating density and individual heating
# urban are areas with high heating density
# urban can be split into district heating (central) and individual heating (decentral)
2021-07-08 12:41:34 +00:00
ct_urban = pop_layout . urban . groupby ( pop_layout [ " ct " ] ) . sum ( )
2021-09-23 12:10:56 +00:00
# distribution of urban population within a country
2021-07-08 12:41:34 +00:00
pop_layout [ " urban_ct_fraction " ] = pop_layout [ " urban " ] / \
pop_layout [ " ct " ] . map ( ct_urban . get )
2021-07-01 18:09:04 +00:00
sectors = [ " residential " , " services " ]
2021-07-08 12:41:34 +00:00
2021-07-01 18:09:04 +00:00
nodes = { }
2021-07-08 12:41:34 +00:00
urban_fraction = pop_layout [ " urban " ] / \
( pop_layout [ [ " urban " , " rural " ] ] . sum ( axis = 1 ) )
2020-04-09 12:58:03 +00:00
for sector in sectors :
nodes [ sector + " rural " ] = pop_layout . index
2021-07-08 12:41:34 +00:00
nodes [ sector + " urban decentral " ] = pop_layout . index
2021-09-29 12:37:36 +00:00
# maximum potential of urban demand covered by district heating
central_fraction = options [ ' district_heating ' ] [ " potential " ]
# district heating share at each node
dist_fraction_node = district_heat_share * pop_layout [ " urban_ct_fraction " ] / pop_layout [ " fraction " ]
nodes [ " urban central " ] = dist_fraction_node . index
# if district heating share larger than urban fraction -> set urban
# fraction to district heating share
urban_fraction = pd . concat ( [ urban_fraction , dist_fraction_node ] ,
axis = 1 ) . max ( axis = 1 )
# difference of max potential and today's share of district heating
diff = ( urban_fraction * central_fraction ) - dist_fraction_node
progress = get ( options [ " district_heating " ] [ " potential " ] , investment_year )
dist_fraction_node + = diff * progress
print ( " ************************************ " )
print (
" the current DH share compared to the maximum possible is increased \
\n by a progress factor of " ,
progress ,
" resulting DH share: " ,
dist_fraction_node )
print ( " ********************************** " )
return nodes , dist_fraction_node , urban_fraction
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
def add_biomass ( n , costs ) :
2019-04-17 12:24:22 +00:00
print ( " adding biomass " )
2021-07-01 18:09:04 +00:00
# biomass distributed at country level - i.e. transport within country allowed
countries = n . buses . country . dropna ( ) . unique ( )
biomass_potentials = pd . read_csv ( snakemake . input . biomass_potentials , index_col = 0 )
2021-08-09 15:51:37 +00:00
if options [ " biomass_transport " ] :
# potential per node distributed within country by population
biomass_potentials_spatial = ( biomass_potentials . loc [ pop_layout . ct ]
. set_index ( pop_layout . index )
2021-08-09 16:03:20 +00:00
. mul ( pop_layout . fraction , axis = " index " )
. rename ( index = lambda x : x + " solid biomass " ) )
2021-08-09 15:51:37 +00:00
else :
2021-08-09 16:03:20 +00:00
biomass_potentials_spatial = biomass_potentials . sum ( )
2021-07-01 18:09:04 +00:00
2021-07-02 08:12:43 +00:00
n . add ( " Carrier " , " biogas " )
2021-07-01 18:09:04 +00:00
n . add ( " Carrier " , " solid biomass " )
n . add ( " Bus " ,
" EU biogas " ,
location = " EU " ,
carrier = " biogas "
)
2021-07-02 08:12:43 +00:00
n . madd ( " Bus " ,
2021-07-12 10:31:18 +00:00
spatial . biomass . nodes ,
location = spatial . biomass . locations ,
2021-07-01 18:09:04 +00:00
carrier = " solid biomass "
)
n . add ( " Store " ,
" EU biogas " ,
bus = " EU biogas " ,
carrier = " biogas " ,
e_nom = biomass_potentials . loc [ countries , " biogas " ] . sum ( ) ,
marginal_cost = costs . at [ ' biogas ' , ' fuel ' ] ,
e_initial = biomass_potentials . loc [ countries , " biogas " ] . sum ( )
)
2021-07-02 08:12:43 +00:00
n . madd ( " Store " ,
2021-07-12 10:31:18 +00:00
spatial . biomass . nodes ,
bus = spatial . biomass . nodes ,
2021-07-01 18:09:04 +00:00
carrier = " solid biomass " ,
2021-08-09 16:03:20 +00:00
e_nom = biomass_potentials_spatial [ " solid biomass " ] ,
2021-07-01 18:09:04 +00:00
marginal_cost = costs . at [ ' solid biomass ' , ' fuel ' ] ,
2021-08-09 16:03:20 +00:00
e_initial = biomass_potentials_spatial [ " solid biomass " ]
2021-07-01 18:09:04 +00:00
)
n . add ( " Link " ,
" biogas to gas " ,
bus0 = " EU biogas " ,
bus1 = " EU gas " ,
bus2 = " co2 atmosphere " ,
carrier = " biogas to gas " ,
capital_cost = costs . loc [ " biogas upgrading " , " fixed " ] ,
marginal_cost = costs . loc [ " biogas upgrading " , " VOM " ] ,
efficiency2 = - costs . at [ ' gas ' , ' CO2 intensity ' ] ,
p_nom_extendable = True
)
2019-04-17 12:24:22 +00:00
2021-07-12 10:31:18 +00:00
if options [ " biomass_transport " ] :
2021-08-09 15:51:37 +00:00
transport_costs = pd . read_csv (
snakemake . input . biomass_transport_costs ,
index_col = 0 ,
squeeze = True
)
2021-09-29 12:37:36 +00:00
2021-07-12 10:31:18 +00:00
# add biomass transport
biomass_transport = create_network_topology ( n , " biomass transport " , bidirectional = False )
2020-10-20 11:46:39 +00:00
2021-07-12 10:31:18 +00:00
# costs
bus0_costs = biomass_transport . bus0 . apply ( lambda x : transport_costs [ x [ : 2 ] ] )
bus1_costs = biomass_transport . bus1 . apply ( lambda x : transport_costs [ x [ : 2 ] ] )
biomass_transport [ " costs " ] = pd . concat ( [ bus0_costs , bus1_costs ] , axis = 1 ) . mean ( axis = 1 )
2020-10-20 11:46:39 +00:00
2021-07-12 10:31:18 +00:00
n . madd ( " Link " ,
biomass_transport . index ,
bus0 = biomass_transport . bus0 + " solid biomass " ,
bus1 = biomass_transport . bus1 + " solid biomass " ,
p_nom_extendable = True ,
length = biomass_transport . length . values ,
marginal_cost = biomass_transport . costs * biomass_transport . length . values ,
capital_cost = 1 ,
carrier = " solid biomass transport "
)
2019-07-16 14:00:21 +00:00
#AC buses with district heating
2021-07-01 18:09:04 +00:00
urban_central = n . buses . index [ n . buses . carrier == " urban central heat " ]
2019-12-12 14:03:51 +00:00
if not urban_central . empty and options [ " chp " ] :
2019-07-16 14:00:21 +00:00
urban_central = urban_central . str [ : - len ( " urban central heat " ) ]
2021-07-01 18:09:04 +00:00
key = ' central solid biomass CHP '
n . madd ( " Link " ,
urban_central + " urban central solid biomass CHP " ,
2021-07-12 10:31:18 +00:00
bus0 = spatial . biomass . df . loc [ urban_central , " nodes " ] . values ,
2021-07-01 18:09:04 +00:00
bus1 = urban_central ,
bus2 = urban_central + " urban central heat " ,
carrier = " urban central solid biomass CHP " ,
p_nom_extendable = True ,
capital_cost = costs . at [ key , ' fixed ' ] * costs . at [ key , ' efficiency ' ] ,
marginal_cost = costs . at [ key , ' VOM ' ] ,
efficiency = costs . at [ key , ' efficiency ' ] ,
efficiency2 = costs . at [ key , ' efficiency-heat ' ] ,
lifetime = costs . at [ key , ' lifetime ' ]
)
n . madd ( " Link " ,
urban_central + " urban central solid biomass CHP CC " ,
2021-07-12 10:31:18 +00:00
bus0 = spatial . biomass . df . loc [ urban_central , " nodes " ] . values ,
2021-07-01 18:09:04 +00:00
bus1 = urban_central ,
bus2 = urban_central + " urban central heat " ,
bus3 = " co2 atmosphere " ,
2021-07-09 10:50:40 +00:00
bus4 = spatial . co2 . df . loc [ urban_central , " nodes " ] . values ,
2021-07-01 18:09:04 +00:00
carrier = " urban central solid biomass CHP CC " ,
p_nom_extendable = True ,
capital_cost = costs . at [ key , ' fixed ' ] * costs . at [ key , ' efficiency ' ] + costs . at [ ' biomass CHP capture ' , ' fixed ' ] * costs . at [ ' solid biomass ' , ' CO2 intensity ' ] ,
marginal_cost = costs . at [ key , ' VOM ' ] ,
efficiency = costs . at [ key , ' 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 [ key , ' 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 [ key , ' lifetime ' ]
)
def add_industry ( n , costs ) :
2019-04-17 12:24:22 +00:00
print ( " adding industrial demand " )
nodes = pop_layout . index
2021-07-01 18:09:04 +00:00
# 1e6 to convert TWh to MWh
industrial_demand = pd . read_csv ( snakemake . input . industrial_demand , index_col = 0 ) * 1e6
2019-07-19 08:21:12 +00:00
2021-07-02 08:12:43 +00:00
n . madd ( " Bus " ,
2021-08-09 15:51:37 +00:00
spatial . biomass . industry ,
location = spatial . biomass . locations ,
2021-07-01 18:09:04 +00:00
carrier = " solid biomass for industry "
)
2021-08-09 15:51:37 +00:00
if options [ " biomass_transport " ] :
p_set = industrial_demand . loc [ spatial . biomass . locations , " solid biomass " ] . rename ( index = lambda x : x + " solid biomass for industry " ) / 8760
else :
p_set = industrial_demand [ " solid biomass " ] . sum ( ) / 8760
2021-07-02 08:12:43 +00:00
n . madd ( " Load " ,
2021-08-09 15:51:37 +00:00
spatial . biomass . industry ,
bus = spatial . biomass . industry ,
2021-07-01 18:09:04 +00:00
carrier = " solid biomass for industry " ,
2021-07-02 08:12:43 +00:00
p_set = p_set
2021-07-01 18:09:04 +00:00
)
2021-07-02 08:12:43 +00:00
n . madd ( " Link " ,
2021-08-09 15:51:37 +00:00
spatial . biomass . industry ,
bus0 = spatial . biomass . nodes ,
bus1 = spatial . biomass . industry ,
2021-07-01 18:09:04 +00:00
carrier = " solid biomass for industry " ,
p_nom_extendable = True ,
efficiency = 1.
)
2021-07-02 08:12:43 +00:00
n . madd ( " Link " ,
2021-08-09 15:51:37 +00:00
spatial . biomass . industry_cc ,
bus0 = spatial . biomass . nodes ,
bus1 = spatial . biomass . industry ,
2021-07-01 18:09:04 +00:00
bus2 = " co2 atmosphere " ,
2021-07-09 11:22:00 +00:00
bus3 = spatial . co2 . nodes ,
2021-07-01 18:09:04 +00:00
carrier = " solid biomass for industry CC " ,
p_nom_extendable = True ,
capital_cost = costs . at [ " cement capture " , " fixed " ] * costs . at [ ' solid biomass ' , ' CO2 intensity ' ] ,
efficiency = 0.9 , # TODO: make config option
efficiency2 = - costs . at [ ' solid biomass ' , ' CO2 intensity ' ] * costs . at [ " cement capture " , " capture_rate " ] ,
efficiency3 = costs . at [ ' solid biomass ' , ' CO2 intensity ' ] * costs . at [ " cement capture " , " capture_rate " ] ,
lifetime = costs . at [ ' cement capture ' , ' lifetime ' ]
)
n . add ( " Bus " ,
" gas for industry " ,
location = " EU " ,
carrier = " gas for industry " )
n . add ( " Load " ,
" gas for industry " ,
bus = " gas for industry " ,
carrier = " gas for industry " ,
p_set = industrial_demand . loc [ nodes , " methane " ] . sum ( ) / 8760
)
n . add ( " Link " ,
" gas for industry " ,
bus0 = " EU gas " ,
bus1 = " gas for industry " ,
bus2 = " co2 atmosphere " ,
carrier = " gas for industry " ,
p_nom_extendable = True ,
efficiency = 1. ,
efficiency2 = costs . at [ ' gas ' , ' CO2 intensity ' ]
)
2021-07-09 11:22:00 +00:00
n . madd ( " Link " ,
spatial . co2 . locations ,
suffix = " gas for industry CC " ,
2021-07-01 18:09:04 +00:00
bus0 = " EU gas " ,
bus1 = " gas for industry " ,
bus2 = " co2 atmosphere " ,
2021-07-09 11:22:00 +00:00
bus3 = spatial . co2 . nodes ,
2021-07-01 18:09:04 +00:00
carrier = " gas for industry CC " ,
p_nom_extendable = True ,
capital_cost = costs . at [ " cement capture " , " fixed " ] * costs . at [ ' gas ' , ' CO2 intensity ' ] ,
efficiency = 0.9 ,
efficiency2 = costs . at [ ' gas ' , ' CO2 intensity ' ] * ( 1 - costs . at [ " cement capture " , " capture_rate " ] ) ,
efficiency3 = costs . at [ ' gas ' , ' CO2 intensity ' ] * costs . at [ " cement capture " , " capture_rate " ] ,
lifetime = costs . at [ ' cement capture ' , ' lifetime ' ]
)
n . madd ( " Load " ,
nodes ,
suffix = " H2 for industry " ,
bus = nodes + " H2 " ,
carrier = " H2 for industry " ,
p_set = industrial_demand . loc [ nodes , " hydrogen " ] / 8760
)
2021-08-04 16:28:18 +00:00
if options [ " shipping_hydrogen_liquefaction " ] :
n . madd ( " Bus " ,
nodes ,
suffix = " H2 liquid " ,
carrier = " H2 liquid " ,
location = nodes
)
n . madd ( " Link " ,
nodes + " H2 liquefaction " ,
bus0 = nodes + " H2 " ,
bus1 = nodes + " H2 liquid " ,
carrier = " H2 liquefaction " ,
efficiency = costs . at [ " H2 liquefaction " , ' efficiency ' ] ,
capital_cost = costs . at [ " H2 liquefaction " , ' fixed ' ] ,
p_nom_extendable = True ,
lifetime = costs . at [ ' H2 liquefaction ' , ' lifetime ' ]
)
shipping_bus = nodes + " H2 liquid "
else :
shipping_bus = nodes + " H2 "
2021-07-01 18:09:04 +00:00
all_navigation = [ " total international navigation " , " total domestic navigation " ]
efficiency = options [ ' shipping_average_efficiency ' ] / costs . at [ " fuel cell " , " efficiency " ]
2021-07-03 11:03:47 +00:00
shipping_hydrogen_share = get ( options [ ' shipping_hydrogen_share ' ] , investment_year )
2021-07-03 10:46:09 +00:00
p_set = shipping_hydrogen_share * nodal_energy_totals . loc [ nodes , all_navigation ] . sum ( axis = 1 ) * 1e6 * efficiency / 8760
2021-07-01 18:09:04 +00:00
n . madd ( " Load " ,
nodes ,
suffix = " H2 for shipping " ,
2021-08-04 16:28:18 +00:00
bus = shipping_bus ,
2021-07-01 18:09:04 +00:00
carrier = " H2 for shipping " ,
p_set = p_set
)
2019-04-17 12:24:22 +00:00
2021-06-16 15:52:42 +00:00
if shipping_hydrogen_share < 1 :
2021-07-03 10:46:09 +00:00
shipping_oil_share = 1 - shipping_hydrogen_share
2021-07-08 12:41:34 +00:00
2021-07-06 08:54:34 +00:00
p_set = shipping_oil_share * nodal_energy_totals . loc [ nodes , all_navigation ] . sum ( axis = 1 ) * 1e6 / 8760.
2021-09-27 09:16:12 +00:00
2021-07-05 09:29:02 +00:00
n . madd ( " Load " ,
2021-07-06 08:54:34 +00:00
nodes ,
suffix = " shipping oil " ,
bus = " EU oil " ,
carrier = " shipping oil " ,
p_set = p_set
)
2021-07-08 12:41:34 +00:00
2021-07-03 10:46:09 +00:00
co2 = shipping_oil_share * nodal_energy_totals . loc [ nodes , all_navigation ] . sum ( ) . sum ( ) * 1e6 / 8760 * costs . at [ " oil " , " CO2 intensity " ]
n . add ( " Load " ,
" shipping oil emissions " ,
bus = " co2 atmosphere " ,
carrier = " shipping oil emissions " ,
p_set = - co2
)
2021-07-01 18:09:04 +00:00
if " EU oil " not in n . buses . index :
n . add ( " Bus " ,
" EU oil " ,
location = " EU " ,
carrier = " oil "
)
if " EU oil Store " not in n . stores . index :
2021-07-08 12:41:34 +00:00
2021-07-01 18:09:04 +00:00
#could correct to e.g. 0.001 EUR/kWh * annuity and O&M
n . add ( " Store " ,
" EU oil Store " ,
bus = " EU oil " ,
e_nom_extendable = True ,
e_cyclic = True ,
carrier = " oil " ,
)
if " EU oil " not in n . generators . index :
n . add ( " Generator " ,
" EU oil " ,
bus = " EU oil " ,
p_nom_extendable = True ,
carrier = " oil " ,
marginal_cost = costs . at [ " oil " , ' fuel ' ]
)
2019-04-30 10:05:36 +00:00
2020-04-09 12:58:03 +00:00
if options [ " oil_boilers " ] :
2021-07-08 12:41:34 +00:00
nodes_heat = create_nodes_for_heat_sector ( ) [ 0 ]
2020-04-09 12:58:03 +00:00
for name in [ " residential rural " , " services rural " , " residential urban decentral " , " services urban decentral " ] :
2021-07-01 18:09:04 +00:00
n . madd ( " Link " ,
nodes_heat [ name ] + f " { name } oil boiler " ,
p_nom_extendable = True ,
bus0 = " EU oil " ,
bus1 = nodes_heat [ name ] + f " { name } heat " ,
bus2 = " co2 atmosphere " ,
carrier = f " { name } oil boiler " ,
efficiency = costs . at [ ' decentral oil boiler ' , ' efficiency ' ] ,
efficiency2 = costs . at [ ' oil ' , ' CO2 intensity ' ] ,
capital_cost = costs . at [ ' decentral oil boiler ' , ' efficiency ' ] * costs . at [ ' decentral oil boiler ' , ' fixed ' ] ,
lifetime = costs . at [ ' decentral oil boiler ' , ' lifetime ' ]
)
n . madd ( " Link " ,
nodes + " Fischer-Tropsch " ,
bus0 = nodes + " H2 " ,
bus1 = " EU oil " ,
2021-07-09 10:50:40 +00:00
bus2 = spatial . co2 . nodes ,
2021-07-01 18:09:04 +00:00
carrier = " Fischer-Tropsch " ,
efficiency = costs . at [ " Fischer-Tropsch " , ' efficiency ' ] ,
capital_cost = costs . at [ " Fischer-Tropsch " , ' fixed ' ] ,
efficiency2 = - costs . at [ " oil " , ' CO2 intensity ' ] * costs . at [ " Fischer-Tropsch " , ' efficiency ' ] ,
p_nom_extendable = True ,
lifetime = costs . at [ ' Fischer-Tropsch ' , ' lifetime ' ]
)
n . add ( " Load " ,
" naphtha for industry " ,
bus = " EU oil " ,
carrier = " naphtha for industry " ,
p_set = industrial_demand . loc [ nodes , " naphtha " ] . sum ( ) / 8760
)
all_aviation = [ " total international aviation " , " total domestic aviation " ]
p_set = nodal_energy_totals . loc [ nodes , all_aviation ] . sum ( axis = 1 ) . sum ( ) * 1e6 / 8760
n . add ( " Load " ,
" kerosene for aviation " ,
bus = " EU oil " ,
carrier = " kerosene for aviation " ,
p_set = p_set
)
2019-07-19 08:21:12 +00:00
2019-12-18 08:45:14 +00:00
#NB: CO2 gets released again to atmosphere when plastics decay or kerosene is burned
#except for the process emissions when naphtha is used for petrochemicals, which can be captured with other industry process emissions
#tco2 per hour
2021-07-01 18:09:04 +00:00
co2_release = [ " naphtha for industry " , " kerosene for aviation " ]
co2 = n . loads . loc [ co2_release , " p_set " ] . sum ( ) * costs . at [ " oil " , ' CO2 intensity ' ] - industrial_demand . loc [ nodes , " process emission from feedstock " ] . sum ( ) / 8760
n . add ( " Load " ,
" oil emissions " ,
bus = " co2 atmosphere " ,
carrier = " oil emissions " ,
p_set = - co2
)
# TODO simplify bus expression
n . madd ( " Load " ,
nodes ,
suffix = " low-temperature heat for industry " ,
bus = [ node + " urban central heat " if node + " urban central heat " in n . buses . index else node + " services urban decentral heat " for node in nodes ] ,
carrier = " low-temperature heat for industry " ,
p_set = industrial_demand . loc [ nodes , " low-temperature heat " ] / 8760
)
# remove today's industrial electricity demand by scaling down total electricity demand
for ct in n . buses . country . dropna ( ) . unique ( ) :
# TODO map onto n.bus.country
loads_i = n . loads . index [ ( n . loads . index . str [ : 2 ] == ct ) & ( n . loads . carrier == " electricity " ) ]
if n . loads_t . p_set [ loads_i ] . empty : continue
factor = 1 - industrial_demand . loc [ loads_i , " current electricity " ] . sum ( ) / n . loads_t . p_set [ loads_i ] . sum ( ) . sum ( )
n . loads_t . p_set [ loads_i ] * = factor
n . madd ( " Load " ,
nodes ,
suffix = " industry electricity " ,
bus = nodes ,
carrier = " industry electricity " ,
p_set = industrial_demand . loc [ nodes , " electricity " ] / 8760
)
n . add ( " Bus " ,
" process emissions " ,
location = " EU " ,
carrier = " process emissions "
)
# this should be process emissions fossil+feedstock
# then need load on atmosphere for feedstock emissions that are currently going to atmosphere via Link Fischer-Tropsch demand
n . add ( " Load " ,
" process emissions " ,
bus = " process emissions " ,
carrier = " process emissions " ,
p_set = - industrial_demand . loc [ nodes , [ " process emission " , " process emission from feedstock " ] ] . sum ( axis = 1 ) . sum ( ) / 8760
)
n . add ( " Link " ,
" process emissions " ,
bus0 = " process emissions " ,
bus1 = " co2 atmosphere " ,
carrier = " process emissions " ,
p_nom_extendable = True ,
efficiency = 1.
)
2019-12-13 17:06:38 +00:00
2020-12-09 15:38:49 +00:00
#assume enough local waste heat for CC
2021-07-09 11:22:00 +00:00
n . madd ( " Link " ,
spatial . co2 . locations ,
suffix = " process emissions CC " ,
2021-07-01 18:09:04 +00:00
bus0 = " process emissions " ,
bus1 = " co2 atmosphere " ,
2021-07-09 11:22:00 +00:00
bus2 = spatial . co2 . nodes ,
2021-07-01 18:09:04 +00:00
carrier = " process emissions CC " ,
p_nom_extendable = True ,
capital_cost = costs . at [ " cement capture " , " fixed " ] ,
efficiency = 1 - costs . at [ " cement capture " , " capture_rate " ] ,
efficiency2 = costs . at [ " cement capture " , " capture_rate " ] ,
lifetime = costs . at [ ' cement capture ' , ' lifetime ' ]
)
def add_waste_heat ( n ) :
# TODO options?
2019-05-14 09:49:32 +00:00
print ( " adding possibility to use industrial waste heat in district heating " )
#AC buses with district heating
2021-07-01 18:09:04 +00:00
urban_central = n . buses . index [ n . buses . carrier == " urban central heat " ]
2019-07-16 14:00:21 +00:00
if not urban_central . empty :
urban_central = urban_central . str [ : - len ( " urban central heat " ) ]
2019-05-14 09:49:32 +00:00
2021-07-01 18:09:04 +00:00
# TODO what is the 0.95 and should it be a config option?
2019-07-16 14:00:21 +00:00
if options [ ' use_fischer_tropsch_waste_heat ' ] :
2021-07-01 18:09:04 +00:00
n . links . loc [ urban_central + " Fischer-Tropsch " , " bus3 " ] = urban_central + " urban central heat "
n . links . loc [ urban_central + " Fischer-Tropsch " , " efficiency3 " ] = 0.95 - n . links . loc [ urban_central + " Fischer-Tropsch " , " efficiency " ]
2019-05-14 09:49:32 +00:00
2019-07-16 14:00:21 +00:00
if options [ ' use_fuel_cell_waste_heat ' ] :
2021-07-01 18:09:04 +00:00
n . links . loc [ urban_central + " H2 Fuel Cell " , " bus2 " ] = urban_central + " urban central heat "
n . links . loc [ urban_central + " H2 Fuel Cell " , " efficiency2 " ] = 0.95 - n . links . loc [ urban_central + " H2 Fuel Cell " , " efficiency " ]
2019-05-14 09:49:32 +00:00
2019-05-16 14:08:16 +00:00
def decentral ( n ) :
2021-07-08 12:41:34 +00:00
""" Removes the electricity transmission system. """
2021-07-01 18:09:04 +00:00
n . lines . drop ( n . lines . index , inplace = True )
n . links . drop ( n . links . index [ n . links . carrier . isin ( [ " DC " , " B2B " ] ) ] , inplace = True )
2019-05-16 14:08:16 +00:00
2019-07-12 06:51:25 +00:00
def remove_h2_network ( n ) :
2021-07-01 18:09:04 +00:00
n . links . drop ( n . links . index [ n . links . carrier == " H2 pipeline " ] , inplace = True )
2019-07-12 06:51:25 +00:00
2021-07-01 18:09:04 +00:00
if " EU H2 Store " in n . stores . index :
n . stores . drop ( " EU H2 Store " , inplace = True )
2019-07-12 06:51:25 +00:00
2021-07-01 18:09:04 +00:00
def maybe_adjust_costs_and_potentials ( n , opts ) :
2019-07-12 06:51:25 +00:00
2021-07-01 18:09:04 +00:00
for o in opts :
if " + " not in o : continue
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 )
if oo [ 0 ] . startswith ( tuple ( suptechs ) ) :
carrier = oo [ 0 ]
2021-07-06 15:12:39 +00:00
attr_lookup = { " p " : " p_nom_max " , " e " : " e_nom_max " , " c " : " capital_cost " }
2021-07-01 18:09:04 +00:00
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 :
2021-07-06 15:12:39 +00:00
if attr == ' p_nom_max ' :
comps = { " Generator " , " Link " , " StorageUnit " }
2021-08-04 16:13:01 +00:00
elif attr == ' e_nom_max ' :
2021-09-27 09:16:12 +00:00
comps = { " Store " }
2021-07-06 15:12:39 +00:00
else :
comps = { " Generator " , " Link " , " StorageUnit " , " Store " }
2021-07-01 18:09:04 +00:00
for c in n . iterate_components ( comps ) :
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 )
2019-07-12 06:51:25 +00:00
2020-11-30 15:20:26 +00:00
2021-07-01 18:09:04 +00:00
# TODO this should rather be a config no wildcard
def limit_individual_line_extension ( n , maxext ) :
print ( f " limiting new HVAC and HVDC extensions to { maxext } MW " )
n . lines [ ' s_nom_max ' ] = n . lines [ ' s_nom ' ] + maxext
hvdc = n . links . index [ n . links . carrier == ' DC ' ]
n . links . loc [ hvdc , ' p_nom_max ' ] = n . links . loc [ hvdc , ' p_nom ' ] + maxext
2019-07-12 06:51:25 +00:00
2021-07-08 12:41:34 +00:00
#%%
2019-04-17 12:24:22 +00:00
if __name__ == " __main__ " :
if ' snakemake ' not in globals ( ) :
2021-07-01 18:09:04 +00:00
from helper import mock_snakemake
snakemake = mock_snakemake (
' prepare_sector_network ' ,
simpl = ' ' ,
2021-07-08 12:41:34 +00:00
opts = " " ,
clusters = " 37 " ,
2021-07-01 18:09:04 +00:00
lv = 1.0 ,
sector_opts = ' Co2L0-168H-T-H-B-I-solar3-dist1 ' ,
2021-07-09 11:33:26 +00:00
planning_horizons = " 2030 " ,
2020-07-29 13:50:40 +00:00
)
2019-04-17 12:24:22 +00:00
logging . basicConfig ( level = snakemake . config [ ' logging_level ' ] )
options = snakemake . config [ " sector " ]
2019-04-17 15:04:33 +00:00
opts = snakemake . wildcards . sector_opts . split ( ' - ' )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
investment_year = int ( snakemake . wildcards . planning_horizons [ - 4 : ] )
2020-07-14 11:28:10 +00:00
2021-07-01 18:09:04 +00:00
overrides = override_component_attrs ( snakemake . input . overrides )
n = pypsa . Network ( snakemake . input . network , override_component_attrs = overrides )
2020-07-14 11:28:10 +00:00
2021-07-01 18:09:04 +00:00
pop_layout = pd . read_csv ( snakemake . input . clustered_pop_layout , index_col = 0 )
2021-07-08 12:41:34 +00:00
Nyears = n . snapshot_weightings . sum ( ) / 8760
2020-09-25 13:25:41 +00:00
2020-07-29 13:50:40 +00:00
costs = prepare_costs ( snakemake . input . costs ,
2020-07-14 11:28:10 +00:00
snakemake . config [ ' costs ' ] [ ' USD2013_to_EUR2013 ' ] ,
snakemake . config [ ' costs ' ] [ ' discountrate ' ] ,
2020-11-30 16:01:14 +00:00
Nyears ,
snakemake . config [ ' costs ' ] [ ' lifetime ' ] )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
patch_electricity_network ( n )
2019-07-16 14:00:21 +00:00
2021-07-12 10:37:37 +00:00
define_spatial ( pop_layout . index )
2021-07-01 18:09:04 +00:00
if snakemake . config [ " foresight " ] == ' myopic ' :
2021-07-08 12:41:34 +00:00
2021-07-01 18:09:04 +00:00
add_lifetime_wind_solar ( n , costs )
2020-08-19 10:41:17 +00:00
2021-07-01 18:09:04 +00:00
conventional = snakemake . config [ ' existing_capacities ' ] [ ' conventional_carriers ' ]
add_carrier_buses ( n , conventional )
2020-07-29 13:50:40 +00:00
2021-07-01 18:09:04 +00:00
add_co2_tracking ( n , options )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
add_generation ( n , costs )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
add_storage ( n , costs )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
# TODO merge with opts cost adjustment below
2019-07-31 12:22:33 +00:00
for o in opts :
2020-01-14 20:36:35 +00:00
if o [ : 4 ] == " wave " :
2021-07-01 18:09:04 +00:00
wave_cost_factor = float ( o [ 4 : ] . replace ( " p " , " . " ) . replace ( " m " , " - " ) )
2020-01-14 20:36:35 +00:00
print ( " Including wave generators with cost factor of " , wave_cost_factor )
add_wave ( n , wave_cost_factor )
2020-03-25 11:52:25 +00:00
if o [ : 4 ] == " dist " :
2021-07-01 18:09:04 +00:00
options [ ' electricity_distribution_grid ' ] = True
options [ ' electricity_distribution_grid_cost_factor ' ] = float ( o [ 4 : ] . replace ( " p " , " . " ) . replace ( " m " , " - " ) )
2020-10-21 05:21:09 +00:00
if o == " biomasstransport " :
options [ " biomass_transport " ] = True
2019-04-17 12:24:22 +00:00
2021-09-29 12:37:36 +00:00
nodal_energy_totals , heat_demand , ashp_cop , gshp_cop , solar_thermal , transport , avail_profile , dsm_profile , nodal_transport_data , district_heat_share = prepare_data ( n )
2019-04-17 12:24:22 +00:00
2019-07-16 14:00:21 +00:00
if " nodistrict " in opts :
2021-09-29 12:37:36 +00:00
options [ " district_heating " ] [ " progress " ] = 0.0
2019-07-16 14:00:21 +00:00
2019-04-17 12:24:22 +00:00
if " T " in opts :
2021-07-01 18:09:04 +00:00
add_land_transport ( n , costs )
2019-04-17 12:24:22 +00:00
if " H " in opts :
2021-07-01 18:09:04 +00:00
add_heat ( n , costs )
2019-04-17 12:24:22 +00:00
if " B " in opts :
2021-07-01 18:09:04 +00:00
add_biomass ( n , costs )
2019-04-17 12:24:22 +00:00
if " I " in opts :
2021-07-01 18:09:04 +00:00
add_industry ( n , costs )
2019-04-17 12:24:22 +00:00
2019-05-14 09:49:32 +00:00
if " I " in opts and " H " in opts :
add_waste_heat ( n )
2020-12-09 17:19:57 +00:00
if options [ ' dac ' ] :
2021-07-01 18:09:04 +00:00
add_dac ( n , costs )
2020-12-09 17:19:57 +00:00
2019-05-16 14:08:16 +00:00
if " decentral " in opts :
decentral ( n )
2019-07-12 06:51:25 +00:00
if " noH2network " in opts :
remove_h2_network ( n )
2021-07-07 16:07:57 +00:00
if options [ " co2_network " ] :
add_co2_network ( n , costs )
2019-04-17 12:24:22 +00:00
for o in opts :
m = re . match ( r ' ^ \ d+h$ ' , o , re . IGNORECASE )
if m is not None :
n = average_every_nhours ( n , m . group ( 0 ) )
break
2020-07-29 13:50:40 +00:00
2021-07-01 18:09:04 +00:00
limit_type = " config "
limit = get ( snakemake . config [ " co2_budget " ] , investment_year )
2020-12-30 14:55:08 +00:00
for o in opts :
2021-07-01 18:09:04 +00:00
if not " cb " in o : continue
limit_type = " carbon budget "
fn = snakemake . config [ ' results_dir ' ] + snakemake . config [ ' run ' ] + ' /csvs/carbon_budget_distribution.csv '
if not os . path . exists ( fn ) :
build_carbon_budget ( o , fn )
co2_cap = pd . read_csv ( fn , index_col = 0 , squeeze = True )
limit = co2_cap [ investment_year ]
break
2020-11-30 12:21:38 +00:00
for o in opts :
2021-07-01 18:09:04 +00:00
if not " Co2L " in o : continue
limit_type = " wildcard "
limit = o [ o . find ( " Co2L " ) + 4 : ]
limit = float ( limit . replace ( " p " , " . " ) . replace ( " m " , " - " ) )
break
print ( " add CO2 limit from " , limit_type )
2020-11-30 12:21:38 +00:00
add_co2limit ( n , Nyears , limit )
2019-04-17 12:24:22 +00:00
2020-10-28 17:21:28 +00:00
for o in opts :
2021-07-01 18:09:04 +00:00
if not o [ : 10 ] == ' linemaxext ' : continue
maxext = float ( o [ 10 : ] ) * 1e3
limit_individual_line_extension ( n , maxext )
break
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
if options [ ' electricity_distribution_grid ' ] :
insert_electricity_distribution_grid ( n , costs )
2020-10-28 17:21:28 +00:00
2021-07-01 18:09:04 +00:00
maybe_adjust_costs_and_potentials ( n , opts )
2020-10-28 17:21:28 +00:00
2021-07-01 18:09:04 +00:00
if options [ ' gas_distribution_grid ' ] :
insert_gas_distribution_costs ( n , costs )
if options [ ' electricity_grid_connection ' ] :
add_electricity_grid_connection ( n , costs )
2020-07-29 13:50:40 +00:00
2019-04-17 12:24:22 +00:00
n . export_to_netcdf ( snakemake . output [ 0 ] )