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
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-11-04 20:48:54 +00:00
import networkx as nx
2019-04-17 12:24:22 +00:00
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
2022-07-20 09:35:12 +00:00
from helper import override_component_attrs , generate_periodic_profiles , update_config_with_sector_opts
2019-04-17 12:24:22 +00:00
2021-11-04 20:48:54 +00:00
from networkx . algorithms . connectivity . edge_augmentation import k_edge_augmentation
from networkx . algorithms import complement
from pypsa . geo import haversine_pts
2021-07-01 18:09:04 +00:00
import logging
logger = logging . getLogger ( __name__ )
2019-04-17 12:24:22 +00:00
2021-08-04 07:48:23 +00:00
from types import SimpleNamespace
spatial = SimpleNamespace ( )
2022-03-18 09:18:24 +00:00
def define_spatial ( nodes , options ) :
2021-08-04 07:48:23 +00:00
"""
Namespace for spatial
2021-09-29 12:37:36 +00:00
2021-08-04 07:48:23 +00:00
Parameters
- - - - - - - - - -
nodes : list - like
"""
global spatial
spatial . nodes = nodes
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 )
2022-03-18 09:18:24 +00:00
2021-11-02 18:03:26 +00:00
# gas
2021-08-04 07:48:23 +00:00
spatial . gas = SimpleNamespace ( )
if options [ " gas_network " ] :
spatial . gas . nodes = nodes + " gas "
spatial . gas . locations = nodes
2021-08-04 12:47:13 +00:00
spatial . gas . biogas = nodes + " biogas "
spatial . gas . industry = nodes + " gas for industry "
spatial . gas . industry_cc = nodes + " gas for industry CC "
2021-12-03 16:22:06 +00:00
spatial . gas . biogas_to_gas = nodes + " biogas to gas "
2021-08-04 07:48:23 +00:00
else :
spatial . gas . nodes = [ " EU gas " ]
2021-12-03 16:22:06 +00:00
spatial . gas . locations = [ " EU " ]
2021-08-04 12:47:13 +00:00
spatial . gas . biogas = [ " EU biogas " ]
spatial . gas . industry = [ " gas for industry " ]
spatial . gas . industry_cc = [ " gas for industry CC " ]
2021-12-03 16:22:06 +00:00
spatial . gas . biogas_to_gas = [ " EU biogas to gas " ]
2021-08-04 07:48:23 +00:00
spatial . gas . df = pd . DataFrame ( vars ( spatial . gas ) , index = nodes )
2022-03-18 09:18:24 +00:00
# oil
spatial . oil = SimpleNamespace ( )
spatial . oil . nodes = [ " EU oil " ]
spatial . oil . locations = [ " EU " ]
# uranium
spatial . uranium = SimpleNamespace ( )
spatial . uranium . nodes = [ " EU uranium " ]
spatial . uranium . locations = [ " EU " ]
# coal
spatial . coal = SimpleNamespace ( )
spatial . coal . nodes = [ " EU coal " ]
spatial . coal . locations = [ " EU " ]
# lignite
spatial . lignite = SimpleNamespace ( )
spatial . lignite . nodes = [ " EU lignite " ]
spatial . lignite . locations = [ " EU " ]
2022-03-18 12:46:40 +00:00
return spatial
2021-08-04 07:48:23 +00:00
2021-11-02 18:03:26 +00:00
from types import SimpleNamespace
spatial = SimpleNamespace ( )
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 ) :
sectors = [ " electricity " ]
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 "
]
2021-07-06 16:32:35 +00:00
if " A " in opts :
sectors + = [
2021-07-06 16:36:04 +00:00
" agriculture "
2021-07-06 16:32:35 +00:00
]
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
return sectors
2019-04-17 12:24:22 +00:00
2020-07-18 09:22:30 +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-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 )
2020-12-30 14:55:08 +00:00
2021-07-01 18:09:04 +00:00
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
2021-06-18 07:45:29 +00:00
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-25 13:17:31 +00:00
2021-07-01 18:09:04 +00:00
countries = n . buses . country . dropna ( ) . unique ( )
2021-01-14 09:06:29 +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 )
2021-02-04 08:15:03 +00:00
2021-07-01 18:09:04 +00:00
# TODO log in Snakefile
if not os . path . exists ( fn ) :
os . makedirs ( fn )
co2_cap . to_csv ( fn , float_format = ' %.3f ' )
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 ' ]
2020-08-19 10:41:17 +00:00
2021-11-04 20:48:54 +00:00
def haversine ( p ) :
coord0 = n . buses . loc [ p . bus0 , [ ' x ' , ' y ' ] ] . values
coord1 = n . buses . loc [ p . bus1 , [ ' x ' , ' y ' ] ] . values
return 1.5 * haversine_pts ( coord0 , coord1 )
def create_network_topology ( n , prefix , carriers = [ " DC " ] , connector = " -> " , bidirectional = True ) :
2020-10-20 11:46:39 +00:00
"""
2021-11-04 20:48:54 +00:00
Create a network topology from transmission lines and link carrier selection .
2021-09-29 12:37:36 +00:00
2021-07-12 10:31:18 +00:00
Parameters
- - - - - - - - - -
n : pypsa . Network
prefix : str
2021-11-04 20:48:54 +00:00
carriers : list - like
2021-07-12 10:31:18 +00:00
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
- - - - - - -
2021-11-04 20:48:54 +00:00
pd . DataFrame with columns bus0 , bus1 , length , underwater_fraction
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 " ]
2022-04-11 15:08:25 +00:00
lk_attrs = n . links . columns . intersection ( lk_attrs )
2020-10-20 11:46:39 +00:00
2021-07-12 10:31:18 +00:00
candidates = pd . concat ( [
n . lines [ ln_attrs ] ,
2021-11-04 20:48:54 +00:00
n . links . loc [ n . links . carrier . isin ( carriers ) , lk_attrs ]
2021-07-12 10:31:18 +00:00
] ) . fillna ( 0 )
2020-10-20 11:46:39 +00:00
2021-11-04 20:48:54 +00:00
# base network topology purely on location not carrier
candidates [ " bus0 " ] = candidates . bus0 . map ( n . buses . location )
candidates [ " bus1 " ] = candidates . bus1 . map ( n . buses . location )
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 )
2022-04-12 12:37:05 +00:00
topo = pd . concat ( [ topo , 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
2021-07-02 13:00:19 +00:00
def add_carrier_buses ( n , carrier , nodes = None ) :
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
"""
2019-04-17 12:24:22 +00:00
2021-07-02 13:00:19 +00:00
if nodes is None :
2022-03-18 12:46:40 +00:00
nodes = vars ( spatial ) [ carrier ] . nodes
location = vars ( spatial ) [ carrier ] . locations
2020-07-29 13:50:40 +00:00
2021-07-02 13:00:19 +00:00
# skip if carrier already exists
if carrier in n . carriers . index :
return
2020-07-29 13:50:40 +00:00
2021-12-03 16:22:06 +00:00
if not isinstance ( nodes , pd . Index ) :
nodes = pd . Index ( nodes )
2021-07-02 13:00:19 +00:00
n . add ( " Carrier " , carrier )
2020-07-29 13:50:40 +00:00
2022-06-30 15:15:41 +00:00
unit = " MWh_LHV " if carrier == " gas " else " MWh_th "
2022-06-29 06:57:08 +00:00
2021-07-02 13:00:19 +00:00
n . madd ( " Bus " ,
nodes ,
2022-03-18 12:46:40 +00:00
location = location ,
2022-06-28 16:31:45 +00:00
carrier = carrier ,
2022-06-29 06:57:08 +00:00
unit = unit
2021-07-02 13:00:19 +00:00
)
2020-07-29 13:50:40 +00:00
2021-07-02 13:00:19 +00:00
#capital cost could be corrected to e.g. 0.2 EUR/kWh * annuity and O&M
n . madd ( " Store " ,
nodes + " Store " ,
bus = nodes ,
e_nom_extendable = True ,
e_cyclic = True ,
carrier = carrier ,
)
n . madd ( " Generator " ,
nodes ,
bus = nodes ,
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 ) ]
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
2022-06-28 16:31:45 +00:00
n . buses [ " unit " ] = " MWh_el "
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 " ,
2022-06-28 16:31:45 +00:00
carrier = " co2 " ,
unit = " t_co2 "
2021-07-01 18:09:04 +00:00
)
# 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 ,
2022-06-28 16:31:45 +00:00
carrier = " co2 stored " ,
unit = " t_co2 "
2021-07-01 18:09:04 +00:00
)
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-01 18:09:04 +00:00
heat_carriers = [ " urban central heat " , " services urban decentral heat " ]
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 " ,
2021-09-29 14:13:23 +00:00
heat_buses . str . replace ( " heat " , " DAC " ) ,
2021-07-01 18:09:04 +00:00
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-12-03 16:22:06 +00:00
logger . info ( f " 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 )
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 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
# 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 ) :
2021-12-03 16:22:06 +00:00
logger . info ( " adding electricity generation " )
2019-04-17 12:24:22 +00:00
2021-06-18 07:45:29 +00:00
nodes = pop_layout . index
2020-01-11 08:11:09 +00:00
2021-07-01 18:09:04 +00:00
fallback = { " OCGT " : " gas " }
conventionals = options . get ( " conventional_generation " , fallback )
for generator , carrier in conventionals . items ( ) :
2022-03-18 12:46:40 +00:00
carrier_nodes = vars ( spatial ) [ carrier ] . nodes
2021-07-02 13:00:19 +00:00
add_carrier_buses ( n , carrier , carrier_nodes )
2021-07-01 18:09:04 +00:00
n . madd ( " Link " ,
nodes + " " + generator ,
2021-07-02 13:00:19 +00:00
bus0 = carrier_nodes ,
2021-07-01 18:09:04 +00:00
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 ,
2022-06-28 16:31:45 +00:00
carrier = " low voltage " ,
unit = " MWh_el "
2021-07-01 18:09:04 +00:00
)
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
)
2021-07-06 16:32:35 +00:00
# this catches regular electricity load and "industry electricity" and
# "agriculture machinery electric" and "agriculture electricity"
loads = n . loads . index [ n . loads . carrier . str . contains ( " electric " ) ]
2021-07-01 18:09:04 +00:00
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 ,
2022-06-28 16:31:45 +00:00
carrier = " home battery " ,
unit = " MWh_el "
2021-07-01 18:09:04 +00:00
)
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
2020-09-15 16:03:33 +00:00
2021-07-01 18:09:04 +00:00
def add_electricity_grid_connection ( n , costs ) :
2020-03-26 09:06:59 +00:00
2021-07-01 18:09:04 +00:00
carriers = [ " onwind " , " solar " ]
2020-05-11 11:37:10 +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
2020-03-25 11:52:25 +00:00
2021-11-03 19:34:43 +00:00
def add_storage_and_grids ( n , costs ) :
2021-12-03 16:22:06 +00:00
logger . info ( " Add hydrogen storage " )
2021-06-18 07:45:29 +00:00
2019-04-17 12:24:22 +00:00
nodes = pop_layout . index
2021-07-02 13:08:06 +00:00
n . add ( " Carrier " , " H2 " )
2021-06-18 07:45:29 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Bus " ,
nodes + " H2 " ,
location = nodes ,
2022-06-28 16:31:45 +00:00
carrier = " H2 " ,
unit = " MWh_LHV "
2021-07-01 18:09:04 +00:00
)
2019-04-17 12:24:22 +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 ' ]
)
2021-11-29 08:12:07 +00:00
cavern_types = snakemake . config [ " sector " ] [ " hydrogen_underground_storage_locations " ]
2022-04-11 15:08:25 +00:00
h2_caverns = pd . read_csv ( snakemake . input . h2_cavern , index_col = 0 )
2022-04-12 08:45:11 +00:00
2022-04-11 15:08:25 +00:00
if not h2_caverns . empty and options [ ' hydrogen_underground_storage ' ] :
2021-11-29 08:12:07 +00:00
2022-04-11 15:08:25 +00:00
h2_caverns = h2_caverns [ cavern_types ] . sum ( axis = 1 )
2021-11-29 08:12:07 +00:00
2022-04-11 15:08:25 +00:00
# only use sites with at least 2 TWh potential
h2_caverns = h2_caverns [ h2_caverns > 2 ]
2021-11-29 08:12:07 +00:00
2022-04-11 15:08:25 +00:00
# convert TWh to MWh
h2_caverns = h2_caverns * 1e6
2021-11-29 08:12:07 +00:00
2022-04-11 15:08:25 +00:00
# clip at 1000 TWh for one location
h2_caverns . clip ( upper = 1e9 , inplace = True )
2021-11-29 08:12:07 +00:00
2021-12-03 16:22:06 +00:00
logger . info ( " Add hydrogen underground storage " )
2021-11-29 08:12:07 +00:00
h2_capital_cost = costs . at [ " hydrogen storage underground " , " fixed " ]
n . madd ( " Store " ,
h2_caverns . index + " H2 Store " ,
bus = h2_caverns . index + " H2 " ,
2021-07-01 18:09:04 +00:00
e_nom_extendable = True ,
2021-11-29 08:12:07 +00:00
e_nom_max = h2_caverns . values ,
2021-07-01 18:09:04 +00:00
e_cyclic = True ,
carrier = " H2 Store " ,
2022-03-18 12:46:40 +00:00
capital_cost = h2_capital_cost ,
lifetime = costs . at [ " hydrogen storage underground " , " lifetime " ]
2021-07-01 18:09:04 +00:00
)
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-11-29 08:12:07 +00:00
nodes_overground = h2_caverns . 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-11-29 11:42:10 +00:00
if options [ " gas_network " ] or options [ " H2_retrofit " ] :
2021-07-02 13:00:19 +00:00
2021-11-03 19:34:43 +00:00
fn = snakemake . input . clustered_gas_network
2021-11-04 20:48:54 +00:00
gas_pipes = pd . read_csv ( fn , index_col = 0 )
2021-06-21 10:36:06 +00:00
2021-11-29 11:42:10 +00:00
if options [ " gas_network " ] :
2021-12-03 16:22:06 +00:00
logger . info ( " Add natural gas infrastructure, incl. LNG terminals, production and entry-points. " )
2021-11-29 11:42:10 +00:00
2021-08-04 09:09:31 +00:00
if options [ " H2_retrofit " ] :
2021-11-04 20:48:54 +00:00
gas_pipes [ " p_nom_max " ] = gas_pipes . p_nom
2021-08-04 09:09:31 +00:00
gas_pipes [ " p_nom_min " ] = 0.
2021-11-29 11:42:10 +00:00
# 0.1 EUR/MWkm/a to prefer decommissioning to address degeneracy
gas_pipes [ " capital_cost " ] = 0.1 * gas_pipes . length
2021-08-04 09:09:31 +00:00
else :
gas_pipes [ " p_nom_max " ] = np . inf
2021-11-04 20:48:54 +00:00
gas_pipes [ " p_nom_min " ] = gas_pipes . p_nom
2021-11-03 19:34:43 +00:00
gas_pipes [ " capital_cost " ] = gas_pipes . length * costs . at [ ' CH4 (g) pipeline ' , ' fixed ' ]
2021-08-04 09:09:31 +00:00
2021-07-02 13:00:19 +00:00
n . madd ( " Link " ,
gas_pipes . index ,
bus0 = gas_pipes . bus0 + " gas " ,
bus1 = gas_pipes . bus1 + " gas " ,
p_min_pu = gas_pipes . p_min_pu ,
2021-11-03 19:34:43 +00:00
p_nom = gas_pipes . p_nom ,
2021-08-04 09:09:31 +00:00
p_nom_extendable = True ,
p_nom_max = gas_pipes . p_nom_max ,
p_nom_min = gas_pipes . p_nom_min ,
2021-11-03 19:34:43 +00:00
length = gas_pipes . length ,
2021-08-04 09:09:31 +00:00
capital_cost = gas_pipes . capital_cost ,
2021-11-13 15:48:08 +00:00
tags = gas_pipes . name ,
2021-11-03 19:34:43 +00:00
carrier = " gas pipeline " ,
2021-11-04 20:48:54 +00:00
lifetime = costs . at [ ' CH4 (g) pipeline ' , ' lifetime ' ]
2021-07-02 13:00:19 +00:00
)
2022-03-18 09:18:24 +00:00
2021-11-03 19:34:43 +00:00
# remove fossil generators where there is neither
# production, LNG terminal, nor entry-point beyond system scope
2021-11-04 20:48:54 +00:00
2021-11-12 11:50:46 +00:00
fn = snakemake . input . gas_input_nodes_simplified
2021-11-10 17:24:13 +00:00
gas_input_nodes = pd . read_csv ( fn , index_col = 0 )
unique = gas_input_nodes . index . unique ( )
gas_i = n . generators . carrier == ' gas '
internal_i = ~ n . generators . bus . map ( n . buses . location ) . isin ( unique )
remove_i = n . generators [ gas_i & internal_i ] . index
2021-07-02 13:00:19 +00:00
n . generators . drop ( remove_i , inplace = True )
2021-06-21 10:36:06 +00:00
2021-11-10 17:24:13 +00:00
p_nom = gas_input_nodes . sum ( axis = 1 ) . rename ( lambda x : x + " gas " )
n . generators . loc [ gas_i , " p_nom_extendable " ] = False
n . generators . loc [ gas_i , " p_nom " ] = p_nom
2021-11-04 20:48:54 +00:00
# add candidates for new gas pipelines to achieve full connectivity
2021-11-03 19:34:43 +00:00
2021-11-04 20:48:54 +00:00
G = nx . Graph ( )
gas_buses = n . buses . loc [ n . buses . carrier == ' gas ' , ' location ' ]
G . add_nodes_from ( np . unique ( gas_buses . values ) )
sel = gas_pipes . p_nom > 1500
attrs = [ " bus0 " , " bus1 " , " length " ]
G . add_weighted_edges_from ( gas_pipes . loc [ sel , attrs ] . values )
# find all complement edges
complement_edges = pd . DataFrame ( complement ( G ) . edges , columns = [ " bus0 " , " bus1 " ] )
complement_edges [ " length " ] = complement_edges . apply ( haversine , axis = 1 )
# apply k_edge_augmentation weighted by length of complement edges
k_edge = options . get ( " gas_network_connectivity_upgrade " , 3 )
2022-04-12 08:45:11 +00:00
augmentation = list ( k_edge_augmentation ( G , k_edge , avail = complement_edges . values ) )
2021-11-04 20:48:54 +00:00
2022-04-12 08:45:11 +00:00
if augmentation :
2021-11-04 20:48:54 +00:00
2022-04-11 15:08:25 +00:00
new_gas_pipes = pd . DataFrame ( augmentation , columns = [ " bus0 " , " bus1 " ] )
new_gas_pipes [ " length " ] = new_gas_pipes . apply ( haversine , axis = 1 )
new_gas_pipes . index = new_gas_pipes . apply (
lambda x : f " gas pipeline new { x . bus0 } <-> { x . bus1 } " , axis = 1 )
n . madd ( " Link " ,
new_gas_pipes . index ,
bus0 = new_gas_pipes . bus0 + " gas " ,
bus1 = new_gas_pipes . bus1 + " gas " ,
p_min_pu = - 1 , # new gas pipes are bidirectional
p_nom_extendable = True ,
length = new_gas_pipes . length ,
capital_cost = new_gas_pipes . length * costs . at [ ' CH4 (g) pipeline ' , ' fixed ' ] ,
carrier = " gas pipeline new " ,
lifetime = costs . at [ ' CH4 (g) pipeline ' , ' lifetime ' ]
)
2021-11-03 19:34:43 +00:00
2021-11-29 11:42:10 +00:00
if options [ " H2_retrofit " ] :
logger . info ( " Add retrofitting options of existing CH4 pipes to H2 pipes. " )
2021-08-04 07:48:23 +00:00
2021-11-29 11:42:10 +00:00
fr = " gas pipeline "
to = " H2 pipeline retrofitted "
h2_pipes = gas_pipes . rename ( index = lambda x : x . replace ( fr , to ) )
2021-08-04 07:48:23 +00:00
n . madd ( " Link " ,
h2_pipes . index ,
bus0 = h2_pipes . bus0 + " H2 " ,
bus1 = h2_pipes . bus1 + " H2 " ,
2021-11-04 20:48:54 +00:00
p_min_pu = - 1. , # allow that all H2 retrofit pipelines can be used in both directions
p_nom_max = h2_pipes . p_nom * options [ " H2_retrofit_capacity_per_CH4 " ] ,
2021-08-04 07:48:23 +00:00
p_nom_extendable = True ,
2021-11-04 20:48:54 +00:00
length = h2_pipes . length ,
capital_cost = costs . at [ ' H2 (g) pipeline repurposed ' , ' fixed ' ] * h2_pipes . length ,
2021-11-13 15:48:08 +00:00
tags = h2_pipes . name ,
2021-08-04 07:48:23 +00:00
carrier = " H2 pipeline retrofitted " ,
2021-11-04 20:48:54 +00:00
lifetime = costs . at [ ' H2 (g) pipeline repurposed ' , ' lifetime ' ]
2021-08-04 07:48:23 +00:00
)
2019-04-17 12:24:22 +00:00
2021-11-04 20:48:54 +00:00
if options . get ( " H2_network " , True ) :
2021-11-03 19:34:43 +00:00
2021-11-29 11:42:10 +00:00
logger . info ( " Add options for new hydrogen pipelines. " )
2021-11-04 20:48:54 +00:00
h2_pipes = create_network_topology ( n , " H2 pipeline " , carriers = [ " DC " , " gas pipeline " ] )
2021-11-03 19:34:43 +00:00
2021-11-04 20:48:54 +00:00
# TODO Add efficiency losses
n . madd ( " Link " ,
h2_pipes . index ,
bus0 = h2_pipes . bus0 . values + " H2 " ,
bus1 = h2_pipes . bus1 . values + " H2 " ,
p_min_pu = - 1 ,
p_nom_extendable = True ,
length = h2_pipes . length . values ,
capital_cost = costs . at [ ' H2 (g) pipeline ' , ' fixed ' ] * h2_pipes . length . values ,
carrier = " H2 pipeline " ,
lifetime = costs . at [ ' H2 (g) pipeline ' , ' lifetime ' ]
)
2021-11-03 19:34:43 +00:00
2021-07-01 18:09:04 +00:00
n . add ( " Carrier " , " battery " )
n . madd ( " Bus " ,
nodes + " battery " ,
location = nodes ,
2022-06-28 16:31:45 +00:00
carrier = " battery " ,
unit = " MWh_el "
2021-07-01 18:09:04 +00:00
)
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 ' ]
)
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 " ,
2021-08-04 12:47:13 +00:00
bus1 = spatial . gas . nodes ,
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 ,
2021-08-04 12:47:13 +00:00
bus1 = spatial . gas . nodes ,
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
2022-06-27 16:06:59 +00:00
if options . get ( ' coal_cc ' ) :
2022-06-28 11:35:44 +00:00
n . madd ( " Link " ,
spatial . nodes ,
suffix = " coal CC " ,
bus0 = spatial . coal . nodes ,
bus1 = spatial . nodes ,
bus2 = " co2 atmosphere " ,
bus3 = " co2 stored " ,
marginal_cost = costs . at [ ' coal ' , ' efficiency ' ] * costs . at [ ' coal ' , ' VOM ' ] , #NB: VOM is per MWel
capital_cost = costs . at [ ' coal ' , ' efficiency ' ] * costs . at [ ' coal ' , ' fixed ' ] + costs . at [ ' biomass CHP capture ' , ' fixed ' ] * costs . at [ ' coal ' , ' CO2 intensity ' ] , #NB: fixed cost is per MWel
p_nom_extendable = True ,
carrier = " coal " ,
efficiency = costs . at [ ' coal ' , ' efficiency ' ] ,
efficiency2 = costs . at [ ' coal ' , ' CO2 intensity ' ] * ( 1 - costs . at [ ' biomass CHP capture ' , ' capture_rate ' ] ) ,
efficiency3 = costs . at [ ' coal ' , ' CO2 intensity ' ] * costs . at [ ' biomass CHP capture ' , ' capture_rate ' ] ,
lifetime = costs . at [ ' coal ' , ' 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-08-04 12:47:13 +00:00
bus0 = spatial . gas . nodes ,
2021-07-01 18:09:04 +00:00
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 " ,
2021-08-04 12:47:13 +00:00
bus0 = spatial . gas . nodes ,
2021-07-01 18:09:04 +00:00
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
2021-12-03 16:22:06 +00:00
logger . info ( " Add land transport " )
2020-11-30 15:20:26 +00:00
2022-04-03 16:49:35 +00:00
transport = pd . read_csv ( snakemake . input . transport_demand , index_col = 0 , parse_dates = True )
number_cars = pd . read_csv ( snakemake . input . transport_data , index_col = 0 ) [ " number cars " ]
avail_profile = pd . read_csv ( snakemake . input . avail_profile , index_col = 0 , parse_dates = True )
dsm_profile = pd . read_csv ( snakemake . input . dsm_profile , index_col = 0 , parse_dates = True )
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 " ,
2022-06-28 16:31:45 +00:00
carrier = " Li ion " ,
unit = " MWh_el "
2021-07-01 18:09:04 +00:00
)
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
2022-04-03 16:49:35 +00:00
p_nom = 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
2022-04-03 16:49:35 +00:00
e_nom = 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
2022-03-18 12:46:40 +00:00
if " oil " not in n . buses . carrier . unique ( ) :
n . madd ( " Bus " ,
2022-03-21 08:14:15 +00:00
spatial . oil . nodes ,
location = spatial . oil . locations ,
2022-06-28 16:31:45 +00:00
carrier = " oil " ,
2022-06-30 15:37:40 +00:00
unit = " MWh_LHV "
2021-07-01 18:09:04 +00:00
)
ice_efficiency = options [ ' transport_internal_combustion_efficiency ' ]
n . madd ( " Load " ,
nodes ,
suffix = " land transport oil " ,
2022-03-21 08:14:15 +00:00
bus = spatial . oil . nodes ,
2021-07-01 18:09:04 +00:00
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-06 16:32:35 +00:00
n . add ( " Load " ,
" land transport oil emissions " ,
2021-07-01 18:09:04 +00:00
bus = " co2 atmosphere " ,
carrier = " land transport oil emissions " ,
p_set = - co2
)
2019-04-17 12:24:22 +00:00
2022-04-03 16:55:53 +00:00
def build_heat_demand ( n ) :
# 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 " )
intraday_profiles = pd . read_csv ( snakemake . input . heat_profile , index_col = 0 )
sectors = [ " residential " , " services " ]
uses = [ " water " , " space " ]
heat_demand = { }
electric_heat_supply = { }
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
)
if use == " space " :
heat_demand_shape = daily_space_heat_demand * intraday_year_profile
else :
heat_demand_shape = intraday_year_profile
heat_demand [ f " { sector } { use } " ] = ( heat_demand_shape / heat_demand_shape . sum ( ) ) . multiply ( pop_weighted_energy_totals [ f " total { sector } { use } " ] ) * 1e6
electric_heat_supply [ f " { sector } { use } " ] = ( heat_demand_shape / heat_demand_shape . sum ( ) ) . multiply ( pop_weighted_energy_totals [ f " electricity { sector } { use } " ] ) * 1e6
heat_demand = pd . concat ( heat_demand , axis = 1 )
electric_heat_supply = pd . concat ( electric_heat_supply , axis = 1 )
# subtract from electricity load since heat demand already in heat_demand
electric_nodes = n . loads . index [ n . loads . carrier == " electricity " ]
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 ]
return heat_demand
2021-07-01 18:09:04 +00:00
def add_heat ( n , costs ) :
2019-04-17 12:24:22 +00:00
2021-12-03 16:22:06 +00:00
logger . info ( " Add heat sector " )
2019-04-17 12:24:22 +00:00
2020-04-09 12:58:03 +00:00
sectors = [ " residential " , " services " ]
2019-04-17 12:24:22 +00:00
2022-04-03 16:55:53 +00:00
heat_demand = build_heat_demand ( n )
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
2022-04-03 16:49:35 +00:00
cop = {
" air " : xr . open_dataarray ( snakemake . input . cop_air_total ) . to_pandas ( ) . reindex ( index = n . snapshots ) ,
" ground " : xr . open_dataarray ( snakemake . input . cop_soil_total ) . to_pandas ( ) . reindex ( index = n . snapshots )
}
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
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 ] ,
2022-06-28 16:31:45 +00:00
carrier = name + " heat " ,
unit = " MWh_th "
2021-07-01 18:09:04 +00:00
)
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 :
2021-09-29 14:13:23 +00:00
raise NotImplementedError ( f " { name } not in " f " heat systems: { heat_systems } " )
2021-07-08 12:41:34 +00:00
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 "
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 ] ,
2022-06-28 16:31:45 +00:00
carrier = name + " water tanks " ,
unit = " MWh_th "
2021-07-01 18:09:04 +00:00
)
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
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 ) ,
2022-07-11 13:46:21 +00:00
capital_cost = costs . at [ name_type + ' water tank storage ' , ' fixed ' ] ,
2021-07-01 18:09:04 +00:00
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 "
2021-06-18 07:45:29 +00:00
2021-07-01 18:09:04 +00:00
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 ' ]
)
2019-08-07 09:33:29 +00:00
2021-07-01 18:09:04 +00:00
key = f " { name_type } gas boiler "
2019-08-07 09:33:29 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Link " ,
nodes [ name ] + f " { name } gas boiler " ,
p_nom_extendable = True ,
2021-08-04 12:47:13 +00:00
bus0 = spatial . gas . df . loc [ nodes [ name ] , " nodes " ] . values ,
2021-07-01 18:09:04 +00:00
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 " )
2019-08-07 09:33:29 +00:00
2021-07-01 18:09:04 +00:00
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 " ,
2021-08-04 12:47:13 +00:00
bus0 = spatial . gas . df . loc [ nodes [ name ] , " nodes " ] . values ,
2021-07-01 18:09:04 +00:00
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 " ,
2021-08-04 12:47:13 +00:00
bus0 = spatial . gas . df . loc [ nodes [ name ] , " nodes " ] . values ,
2021-07-01 18:09:04 +00:00
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 ,
2021-08-04 12:47:13 +00:00
bus0 = spatial . gas . df . loc [ nodes [ name ] , " nodes " ] . values ,
2021-07-01 18:09:04 +00:00
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 ' ] :
2021-12-03 16:22:06 +00:00
logger . info ( " Add 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-12-03 16:22:06 +00:00
logger . warning ( f " 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
2021-09-29 14:13:23 +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-09-29 14:13:23 +00:00
pop_layout [ " urban_ct_fraction " ] = pop_layout . urban / pop_layout . ct . map ( ct_urban . get )
2021-07-08 12:41:34 +00:00
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-09-29 14:13:23 +00:00
urban_fraction = pop_layout . urban / pop_layout [ [ " rural " , " urban " ] ] . sum ( axis = 1 )
2021-07-08 12:41:34 +00:00
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
2022-04-03 16:49:35 +00:00
district_heat_share = pop_weighted_energy_totals [ " district heat share " ]
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
2021-10-04 12:28:59 +00:00
progress = get ( options [ " district_heating " ] [ " progress " ] , investment_year )
2021-09-29 12:37:36 +00:00
dist_fraction_node + = diff * progress
2021-10-02 08:23:27 +00:00
print (
2021-09-30 15:46:26 +00:00
" The current district heating share compared to the maximum " ,
2021-10-02 08:23:27 +00:00
f " possible is increased by a progress factor of \n { progress } " ,
f " resulting in a district heating share of \n { dist_fraction_node } "
2021-09-30 15:46:26 +00:00
)
2020-04-09 12:58:03 +00:00
2021-09-29 12:37:36 +00:00
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
2021-12-03 16:22:06 +00:00
logger . info ( " Add biomass " )
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
biomass_potentials = pd . read_csv ( snakemake . input . biomass_potentials , index_col = 0 )
2019-04-17 12:24:22 +00:00
2021-08-04 12:47:13 +00:00
# need to aggregate potentials if gas not nodally resolved
2021-11-02 18:03:26 +00:00
if options [ " gas_network " ] :
biogas_potentials_spatial = biomass_potentials [ " biogas " ] . rename ( index = lambda x : x + " biogas " )
else :
biogas_potentials_spatial = biomass_potentials [ " biogas " ] . sum ( )
2021-08-09 15:51:37 +00:00
if options [ " biomass_transport " ] :
2021-11-02 18:03:26 +00:00
solid_biomass_potentials_spatial = biomass_potentials [ " solid biomass " ] . rename ( index = lambda x : x + " solid biomass " )
2021-08-09 15:51:37 +00:00
else :
2021-11-02 18:03:26 +00:00
solid_biomass_potentials_spatial = biomass_potentials [ " solid biomass " ] . sum ( )
2021-06-18 07:45:29 +00:00
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 " )
2021-07-02 13:00:19 +00:00
n . madd ( " Bus " ,
2021-08-04 12:47:13 +00:00
spatial . gas . biogas ,
location = spatial . gas . locations ,
2022-06-28 16:31:45 +00:00
carrier = " biogas " ,
2022-06-30 15:37:40 +00:00
unit = " MWh_LHV "
2021-07-01 18:09:04 +00:00
)
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 ,
2022-06-28 16:31:45 +00:00
carrier = " solid biomass " ,
2022-06-30 15:37:40 +00:00
unit = " MWh_LHV "
2021-07-01 18:09:04 +00:00
)
2021-07-02 13:00:19 +00:00
n . madd ( " Store " ,
2021-08-04 12:47:13 +00:00
spatial . gas . biogas ,
bus = spatial . gas . biogas ,
2021-07-01 18:09:04 +00:00
carrier = " biogas " ,
2021-11-02 18:03:26 +00:00
e_nom = biogas_potentials_spatial ,
2021-07-01 18:09:04 +00:00
marginal_cost = costs . at [ ' biogas ' , ' fuel ' ] ,
2021-11-02 18:03:26 +00:00
e_initial = biogas_potentials_spatial
2021-07-01 18:09:04 +00:00
)
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-11-02 18:03:26 +00:00
e_nom = solid_biomass_potentials_spatial ,
2021-07-01 18:09:04 +00:00
marginal_cost = costs . at [ ' solid biomass ' , ' fuel ' ] ,
2021-11-02 18:03:26 +00:00
e_initial = solid_biomass_potentials_spatial
2021-07-01 18:09:04 +00:00
)
2021-07-02 13:00:19 +00:00
n . madd ( " Link " ,
2021-12-03 16:22:06 +00:00
spatial . gas . biogas_to_gas ,
2021-08-04 12:47:13 +00:00
bus0 = spatial . gas . biogas ,
bus1 = spatial . gas . nodes ,
2021-07-01 18:09:04 +00:00
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 ,
2022-04-12 12:37:05 +00:00
) . squeeze ( )
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 ' ]
)
2022-08-01 11:07:59 +00:00
#Solid biomass to liquid fuel
if options [ " biomass " ] [ " biomass_to_liquid " ]
n . madd ( " Link " ,
spatial . biomass . nodes + " biomass to liquid " ,
bus0 = spatial . biomass . nodes ,
bus1 = " EU oil " ,
bus3 = " co2 atmosphere " ,
carrier = " biomass to liquid " ,
lifetime = costs . at [ ' BtL ' , ' lifetime ' ] ,
efficiency = costs . at [ ' BtL ' , ' efficiency ' ] ,
efficiency3 = - costs . at [ ' solid biomass ' , ' CO2 intensity ' ] + costs . at [ ' BtL ' , ' CO2 stored ' ] ,
p_nom_extendable = True ,
capital_cost = costs . at [ ' BtL ' , ' fixed ' ] ,
marginal_cost = costs . at [ ' BtL ' , ' efficiency ' ] * costs . loc [ " BtL " , " VOM " ]
)
#TODO: Update with energy penalty
n . madd ( " Link " ,
spatial . biomass . nodes + " biomass to liquid CC " ,
bus0 = spatial . biomass . nodes ,
bus1 = " EU oil " ,
bus2 = " co2 stored " ,
bus3 = " co2 atmosphere " ,
carrier = " biomass to liquid " ,
lifetime = costs . at [ ' BtL ' , ' lifetime ' ] ,
efficiency = costs . at [ ' BtL ' , ' efficiency ' ] ,
efficiency2 = costs . at [ ' BtL ' , ' CO2 stored ' ] * costs . at [ ' BtL ' , ' capture rate ' ] ,
efficiency3 = - costs . at [ ' solid biomass ' , ' CO2 intensity ' ] + costs . at [ ' BtL ' , ' CO2 stored ' ] * ( 1 - costs . at [ ' BtL ' , ' capture rate ' ] ) ,
p_nom_extendable = True ,
capital_cost = costs . at [ ' BtL ' , ' fixed ' ] + costs . at [ ' biomass CHP capture ' , ' fixed ' ] * costs . at [
" BtL " , " CO2 stored " ] ,
marginal_cost = costs . at [ ' BtL ' , ' efficiency ' ] * costs . loc [ " BtL " , " VOM " ]
)
2021-07-01 18:09:04 +00:00
def add_industry ( n , costs ) :
2019-04-17 12:24:22 +00:00
2021-12-03 16:22:06 +00:00
logger . info ( " Add industrial demand " )
2019-04-17 12:24:22 +00:00
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 ,
2022-06-28 16:31:45 +00:00
carrier = " solid biomass for industry " ,
2022-06-30 15:37:40 +00:00
unit = " MWh_LHV "
2021-07-01 18:09:04 +00:00
)
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 ' ]
)
2021-07-02 13:00:19 +00:00
n . madd ( " Bus " ,
2021-08-04 12:47:13 +00:00
spatial . gas . industry ,
location = spatial . gas . locations ,
2022-06-28 16:31:45 +00:00
carrier = " gas for industry " ,
unit = " MWh_LHV " )
2021-07-01 18:09:04 +00:00
2021-11-03 19:34:43 +00:00
gas_demand = industrial_demand . loc [ nodes , " methane " ] / 8760.
if options [ " gas_network " ] :
spatial_gas_demand = gas_demand . rename ( index = lambda x : x + " gas for industry " )
else :
spatial_gas_demand = gas_demand . sum ( )
2021-07-02 13:00:19 +00:00
n . madd ( " Load " ,
2021-08-04 12:47:13 +00:00
spatial . gas . industry ,
bus = spatial . gas . industry ,
2021-07-01 18:09:04 +00:00
carrier = " gas for industry " ,
2021-11-03 19:34:43 +00:00
p_set = spatial_gas_demand
2021-07-01 18:09:04 +00:00
)
2021-07-02 13:00:19 +00:00
n . madd ( " Link " ,
2021-08-04 12:47:13 +00:00
spatial . gas . industry ,
bus0 = spatial . gas . nodes ,
bus1 = spatial . gas . industry ,
2021-07-01 18:09:04 +00:00
bus2 = " co2 atmosphere " ,
carrier = " gas for industry " ,
p_nom_extendable = True ,
efficiency = 1. ,
efficiency2 = costs . at [ ' gas ' , ' CO2 intensity ' ]
)
2021-07-02 13:00:19 +00:00
n . madd ( " Link " ,
2021-08-04 12:47:13 +00:00
spatial . gas . industry_cc ,
bus0 = spatial . gas . nodes ,
bus1 = spatial . gas . 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 = " 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 " ,
2022-06-28 16:31:45 +00:00
location = nodes ,
unit = " MWh_LHV "
2021-08-04 16:28:18 +00:00
)
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-08-04 16:19:02 +00:00
shipping_hydrogen_share = get ( options [ ' shipping_hydrogen_share ' ] , investment_year )
2022-04-03 16:49:35 +00:00
p_set = shipping_hydrogen_share * pop_weighted_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
)
2021-08-04 16:19:02 +00:00
if shipping_hydrogen_share < 1 :
shipping_oil_share = 1 - shipping_hydrogen_share
2021-07-08 12:41:34 +00:00
2022-04-03 16:49:35 +00:00
p_set = shipping_oil_share * pop_weighted_energy_totals . loc [ nodes , all_navigation ] . sum ( axis = 1 ) * 1e6 / 8760.
2021-09-27 09:16:12 +00:00
2021-08-04 16:19:02 +00:00
n . madd ( " Load " ,
nodes ,
suffix = " shipping oil " ,
2022-03-21 08:14:15 +00:00
bus = spatial . oil . nodes ,
2021-08-04 16:19:02 +00:00
carrier = " shipping oil " ,
p_set = p_set
)
2021-07-08 12:41:34 +00:00
2022-04-03 16:49:35 +00:00
co2 = shipping_oil_share * pop_weighted_energy_totals . loc [ nodes , all_navigation ] . sum ( ) . sum ( ) * 1e6 / 8760 * costs . at [ " oil " , " CO2 intensity " ]
2021-08-04 16:19:02 +00:00
n . add ( " Load " ,
" shipping oil emissions " ,
bus = " co2 atmosphere " ,
carrier = " shipping oil emissions " ,
p_set = - co2
)
2022-03-18 12:46:40 +00:00
if " oil " not in n . buses . carrier . unique ( ) :
n . madd ( " Bus " ,
2022-03-21 08:14:15 +00:00
spatial . oil . nodes ,
location = spatial . oil . locations ,
2022-06-28 16:31:45 +00:00
carrier = " oil " ,
2022-06-30 15:37:40 +00:00
unit = " MWh_LHV "
2021-07-01 18:09:04 +00:00
)
2022-03-18 12:46:40 +00:00
if " oil " not in n . stores . carrier . unique ( ) :
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
2022-03-18 12:46:40 +00:00
n . madd ( " Store " ,
2022-03-21 08:14:15 +00:00
[ oil_bus + " Store " for oil_bus in spatial . oil . nodes ] ,
bus = spatial . oil . nodes ,
2021-07-01 18:09:04 +00:00
e_nom_extendable = True ,
e_cyclic = True ,
carrier = " oil " ,
)
2022-03-18 12:46:40 +00:00
if " oil " not in n . generators . carrier . unique ( ) :
2021-07-01 18:09:04 +00:00
2022-03-18 12:46:40 +00:00
n . madd ( " Generator " ,
2022-03-21 08:14:15 +00:00
spatial . oil . nodes ,
bus = spatial . oil . nodes ,
2021-07-01 18:09:04 +00:00
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 ,
2022-03-21 08:14:15 +00:00
bus0 = spatial . oil . nodes ,
2021-07-01 18:09:04 +00:00
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 " ,
2022-03-21 08:14:15 +00:00
bus1 = spatial . oil . nodes ,
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 ' ]
)
2022-03-18 12:46:40 +00:00
n . madd ( " Load " ,
[ " naphtha for industry " ] ,
2022-03-21 08:14:15 +00:00
bus = spatial . oil . nodes ,
2021-07-01 18:09:04 +00:00
carrier = " naphtha for industry " ,
p_set = industrial_demand . loc [ nodes , " naphtha " ] . sum ( ) / 8760
)
all_aviation = [ " total international aviation " , " total domestic aviation " ]
2022-04-03 16:49:35 +00:00
p_set = pop_weighted_energy_totals . loc [ nodes , all_aviation ] . sum ( axis = 1 ) . sum ( ) * 1e6 / 8760
2021-07-01 18:09:04 +00:00
2022-03-18 12:46:40 +00:00
n . madd ( " Load " ,
[ " kerosene for aviation " ] ,
2022-03-21 08:14:15 +00:00
bus = spatial . oil . nodes ,
2021-07-01 18:09:04 +00:00
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 " ,
2022-06-28 16:31:45 +00:00
carrier = " process emissions " ,
unit = " t_co2 "
2021-07-01 18:09:04 +00:00
)
# 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
2021-12-03 16:22:06 +00:00
logger . info ( " Add possibility to use industrial waste heat in district heating " )
2019-05-14 09:49:32 +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-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
2021-07-06 16:32:35 +00:00
def add_agriculture ( n , costs ) :
2021-08-18 07:42:05 +00:00
logger . info ( ' Add agriculture, forestry and fishing sector. ' )
2021-07-06 16:32:35 +00:00
nodes = pop_layout . index
# electricity
n . madd ( " Load " ,
nodes ,
suffix = " agriculture electricity " ,
bus = nodes ,
carrier = ' agriculture electricity ' ,
2022-04-03 16:49:35 +00:00
p_set = pop_weighted_energy_totals . loc [ nodes , " total agriculture electricity " ] * 1e6 / 8760
2021-07-06 16:32:35 +00:00
)
# heat
n . madd ( " Load " ,
nodes ,
suffix = " agriculture heat " ,
bus = nodes + " services rural heat " ,
carrier = " agriculture heat " ,
2022-04-03 16:49:35 +00:00
p_set = pop_weighted_energy_totals . loc [ nodes , " total agriculture heat " ] * 1e6 / 8760
2021-07-06 16:32:35 +00:00
)
# machinery
electric_share = get ( options [ " agriculture_machinery_electric_share " ] , investment_year )
assert electric_share < = 1.
ice_share = 1 - electric_share
2022-04-03 16:49:35 +00:00
machinery_nodal_energy = pop_weighted_energy_totals . loc [ nodes , " total agriculture machinery " ]
2021-07-06 16:32:35 +00:00
if electric_share > 0 :
efficiency_gain = options [ " agriculture_machinery_fuel_efficiency " ] / options [ " agriculture_machinery_electric_efficiency " ]
2021-08-18 14:17:59 +00:00
n . madd ( " Load " ,
2021-07-06 16:32:35 +00:00
nodes ,
suffix = " agriculture machinery electric " ,
bus = nodes ,
carrier = " agriculture machinery electric " ,
2021-08-16 14:27:25 +00:00
p_set = electric_share / efficiency_gain * machinery_nodal_energy * 1e6 / 8760 ,
2021-07-06 16:32:35 +00:00
)
if ice_share > 0 :
2022-06-03 12:29:23 +00:00
n . madd ( " Load " ,
[ " agriculture machinery oil " ] ,
2022-03-21 08:14:15 +00:00
bus = spatial . oil . nodes ,
2021-07-06 16:32:35 +00:00
carrier = " agriculture machinery oil " ,
2021-08-16 14:27:25 +00:00
p_set = ice_share * machinery_nodal_energy . sum ( ) * 1e6 / 8760
2021-07-06 16:32:35 +00:00
)
2021-08-16 14:27:25 +00:00
co2 = ice_share * machinery_nodal_energy . sum ( ) * 1e6 / 8760 * costs . at [ " oil " , ' CO2 intensity ' ]
2021-07-06 16:32:35 +00:00
n . add ( " Load " ,
" agriculture machinery oil emissions " ,
bus = " co2 atmosphere " ,
carrier = " agriculture machinery oil emissions " ,
p_set = - co2
)
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 ) :
2022-01-25 11:55:57 +00:00
n . links . drop ( n . links . index [ n . links . carrier . str . contains ( " 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
2021-07-01 18:09:04 +00:00
# TODO this should rather be a config no wildcard
def limit_individual_line_extension ( n , maxext ) :
2021-12-03 16:22:06 +00:00
logger . info ( f " limiting new HVAC and HVDC extensions to { maxext } MW " )
2021-07-01 18:09:04 +00:00
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
2020-11-30 15:20:26 +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-06-18 07:45:29 +00:00
from helper import mock_snakemake
2021-07-01 18:09:04 +00:00
snakemake = mock_snakemake (
' prepare_sector_network ' ,
simpl = ' ' ,
2021-07-08 12:41:34 +00:00
opts = " " ,
clusters = " 37 " ,
2022-06-28 16:31:45 +00:00
lv = 1.5 ,
2021-07-01 18:09:04 +00:00
sector_opts = ' Co2L0-168H-T-H-B-I-solar3-dist1 ' ,
2021-09-29 14:13:23 +00:00
planning_horizons = " 2020 " ,
2020-07-29 13:50:40 +00:00
)
2019-04-17 12:24:22 +00:00
logging . basicConfig ( level = snakemake . config [ ' logging_level ' ] )
2022-07-20 09:35:12 +00:00
update_config_with_sector_opts ( snakemake . config , snakemake . wildcards . sector_opts )
2019-04-17 12:24:22 +00:00
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 : ] )
2019-04-17 12:24:22 +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 )
Nyears = n . snapshot_weightings . generators . 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
2022-04-03 16:49:35 +00:00
pop_weighted_energy_totals = pd . read_csv ( snakemake . input . pop_weighted_energy_totals , index_col = 0 )
2021-07-01 18:09:04 +00:00
patch_electricity_network ( n )
2019-04-18 13:23:37 +00:00
2022-03-18 12:46:40 +00:00
spatial = define_spatial ( pop_layout . index , options )
2021-07-12 10:37:37 +00:00
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 )
2019-07-16 14:00:21 +00:00
2021-07-01 18:09:04 +00:00
conventional = snakemake . config [ ' existing_capacities ' ] [ ' conventional_carriers ' ]
2021-07-02 13:00:19 +00:00
for carrier in conventional :
add_carrier_buses ( n , carrier )
2020-12-02 12:03:13 +00:00
2021-07-01 18:09:04 +00:00
add_co2_tracking ( n , options )
2020-07-18 09:22:30 +00:00
2021-07-01 18:09:04 +00:00
add_generation ( n , costs )
2020-08-19 10:41:17 +00:00
2021-11-03 19:34:43 +00:00
add_storage_and_grids ( 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
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 )
2021-07-06 16:32:35 +00:00
if " A " in opts : # requires H and I
add_agriculture ( n , costs )
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 )
2022-04-12 12:37:05 +00:00
co2_cap = pd . read_csv ( fn , index_col = 0 ) . squeeze ( )
2021-07-01 18:09:04 +00:00
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
2021-12-03 16:22:06 +00:00
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 ] )