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
2022-08-01 16:03:11 +00:00
from pypsa . io import import_components_from_dataframe
2021-11-04 20:48:54 +00:00
2021-07-01 18:09:04 +00:00
import logging
logger = logging . getLogger ( __name__ )
2019-04-17 12:24:22 +00:00
2021-08-04 07:48:23 +00:00
from types import SimpleNamespace
spatial = SimpleNamespace ( )
2022-08-02 07:27:37 +00:00
from packaging . version import Version , parse
pd_version = parse ( pd . __version__ )
agg_group_kwargs = dict ( numeric_only = False ) if pd_version > = Version ( " 1.3 " ) else { }
2021-08-04 07:48:23 +00:00
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 ( )
2023-01-24 17:44:39 +00:00
if options . get ( " biomass_spatial " , options [ " biomass_transport " ] ) :
2021-07-12 10:31:18 +00:00
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
2023-02-08 21:57:01 +00:00
if options [ " co2_spatial " ] :
2021-07-09 10:50:40 +00:00
spatial . co2 . nodes = nodes + " co2 stored "
spatial . co2 . locations = nodes
spatial . co2 . vents = nodes + " co2 vent "
2023-02-06 08:46:24 +00:00
spatial . co2 . process_emissions = nodes + " process emissions "
2021-07-09 10:50:40 +00:00
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 " ]
2023-02-06 08:46:24 +00:00
spatial . co2 . process_emissions = [ " process emissions " ]
2021-07-09 10:50:40 +00:00
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 " ]
2021-12-03 16:22:06 +00:00
spatial . gas . biogas_to_gas = [ " EU biogas to gas " ]
2023-01-24 17:44:39 +00:00
if options . get ( " co2_spatial " , options [ " co2network " ] ) :
spatial . gas . industry_cc = nodes + " gas for industry CC "
else :
spatial . gas . industry_cc = [ " gas for industry CC " ]
2021-08-04 07:48:23 +00:00
spatial . gas . df = pd . DataFrame ( vars ( spatial . gas ) , index = nodes )
2022-06-10 14:53:37 +00:00
# ammonia
2022-06-23 13:24:44 +00:00
if options . get ( ' ammonia ' ) :
spatial . ammonia = SimpleNamespace ( )
if options . get ( " ammonia " ) == " regional " :
spatial . ammonia . nodes = nodes + " NH3 "
spatial . ammonia . locations = nodes
else :
spatial . ammonia . nodes = [ " EU NH3 " ]
spatial . ammonia . locations = [ " EU " ]
spatial . ammonia . df = pd . DataFrame ( vars ( spatial . ammonia ) , index = nodes )
2022-06-10 14:53:37 +00:00
2022-11-13 17:25:32 +00:00
# hydrogen
spatial . h2 = SimpleNamespace ( )
spatial . h2 . nodes = nodes + " H2 "
spatial . h2 . locations = nodes
# methanol
spatial . methanol = SimpleNamespace ( )
spatial . methanol . nodes = [ " EU methanol " ]
spatial . methanol . locations = [ " EU " ]
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
2022-08-02 06:52:48 +00:00
def co2_emissions_year ( countries , input_eurostat , opts , emissions_scope , report_year , 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
"""
2022-08-01 13:21:11 +00:00
emissions_scope = snakemake . config [ " energy " ] [ " emissions " ]
eea_co2 = build_eea_co2 ( snakemake . input . co2 , year , emissions_scope )
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
2022-08-01 13:21:11 +00:00
report_year = snakemake . config [ " energy " ] [ " eurostat_report_year " ]
2020-12-30 14:55:08 +00:00
if year > 2014 :
2022-08-01 13:21:11 +00:00
eurostat_co2 = build_eurostat_co2 ( input_eurostat , countries , report_year , year = 2014 )
2020-12-30 14:55:08 +00:00
else :
2022-08-01 13:21:11 +00:00
eurostat_co2 = build_eurostat_co2 ( input_eurostat , countries , report_year , year )
2021-01-25 13:17:31 +00:00
2022-08-01 13:21:11 +00:00
co2_totals = build_co2_totals ( countries , 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?
2022-08-02 06:52:48 +00:00
def build_carbon_budget ( o , input_eurostat , fn , emissions_scope , report_year ) :
2021-07-01 18:09:04 +00:00
"""
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
2022-08-02 06:52:48 +00:00
e_1990 = co2_emissions_year ( countries , input_eurostat , opts , emissions_scope ,
report_year , 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)
2022-08-02 06:52:48 +00:00
e_0 = co2_emissions_year ( countries , input_eurostat , opts , emissions_scope ,
report_year , 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
2022-08-01 13:21:11 +00:00
csvs_folder = fn . rsplit ( " / " , 1 ) [ 0 ]
if not os . path . exists ( csvs_folder ) :
os . makedirs ( csvs_folder )
2021-07-01 18:09:04 +00:00
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 ,
2023-02-08 21:57:01 +00:00
capital_cost = 0.2 * costs . at [ carrier , " discount rate " ] # preliminary value to avoid zeros
2021-07-02 13:00:19 +00:00
)
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 )
2023-02-16 17:42:19 +00:00
if to_remove . empty :
continue
logger . info ( f " Removing { c . list_name } with carrier { list ( 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
"""
2023-02-16 17:42:19 +00:00
to_drop = list ( n . buses . query ( " carrier not in [ ' AC ' , ' DC ' ] " ) . carrier . unique ( ) )
if to_drop :
logger . info ( f " Drop buses from PyPSA-Eur with carrier: { to_drop } " )
n . buses = n . buses [ n . buses . carrier . isin ( [ " AC " , " DC " ] ) ]
2020-12-02 12:03:13 +00:00
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
)
2023-02-16 16:21:58 +00:00
if options [ " regional_co2_sequestration_potential " ] [ " enable " ] :
upper_limit = options [ " regional_co2_sequestration_potential " ] [ " max_size " ] * 1e3 # Mt
annualiser = options [ " regional_co2_sequestration_potential " ] [ " years_of_storage " ]
2023-01-24 17:44:39 +00:00
e_nom_max = pd . read_csv ( snakemake . input . sequestration_potential , index_col = 0 ) . squeeze ( )
e_nom_max = e_nom_max . reindex ( spatial . co2 . locations ) . fillna ( 0. ) . clip ( upper = upper_limit ) . mul ( 1e6 ) / annualiser # t
e_nom_max = e_nom_max . rename ( index = lambda x : x + " co2 stored " )
2021-07-07 15:58:47 +00:00
n . madd ( " Store " ,
2021-07-09 10:50:40 +00:00
spatial . co2 . nodes ,
2021-07-01 18:09:04 +00:00
e_nom_extendable = True ,
2023-01-24 17:44:39 +00:00
e_nom_max = e_nom_max ,
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
2023-02-16 17:42:19 +00:00
n . add ( " Carrier " , " co2 stored " )
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 ' ]
)
2023-02-08 21:57:01 +00:00
def add_allam ( n , costs ) :
2023-02-16 16:21:58 +00:00
logger . info ( " Adding Allam cycle gas power plants. " )
2023-02-08 21:57:01 +00:00
nodes = pop_layout . index
n . madd ( " Link " ,
2023-02-16 16:21:58 +00:00
nodes ,
suffix = " allam " ,
bus0 = spatial . gas . df . loc [ nodes , " nodes " ] . values ,
bus1 = nodes ,
bus2 = spatial . co2 . df . loc [ nodes , " nodes " ] . values ,
carrier = " allam " ,
p_nom_extendable = True ,
# TODO: add costs to technology-data
capital_cost = 0.6 * 1.5e6 * 0.1 , # efficiency * EUR/MW * annuity
marginal_cost = 2 ,
efficiency = 0.6 ,
efficiency2 = costs . at [ ' gas ' , ' CO2 intensity ' ] ,
lifetime = 30. ,
)
2023-02-08 21:57:01 +00:00
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
2022-06-10 12:44:36 +00:00
def add_ammonia ( n , costs ) :
2022-06-10 14:53:37 +00:00
logger . info ( " adding ammonia carrier with synthesis, cracking and storage " )
2022-06-10 12:44:36 +00:00
nodes = pop_layout . index
2022-06-10 14:43:29 +00:00
cf_industry = snakemake . config [ " industry " ]
2022-06-10 12:44:36 +00:00
n . add ( " Carrier " , " NH3 " )
n . madd ( " Bus " ,
2022-06-10 14:53:37 +00:00
spatial . ammonia . nodes ,
location = spatial . ammonia . locations ,
2022-06-10 12:44:36 +00:00
carrier = " NH3 "
)
n . madd ( " Link " ,
nodes ,
suffix = " Haber-Bosch " ,
bus0 = nodes ,
2022-06-10 14:53:37 +00:00
bus1 = spatial . ammonia . nodes ,
2022-06-10 12:44:36 +00:00
bus2 = nodes + " H2 " ,
p_nom_extendable = True ,
carrier = " Haber-Bosch " ,
2022-06-23 13:17:41 +00:00
efficiency = 1 / ( cf_industry [ " MWh_elec_per_tNH3_electrolysis " ] / cf_industry [ " MWh_NH3_per_tNH3 " ] ) , # output: MW_NH3 per MW_elec
efficiency2 = - cf_industry [ " MWh_H2_per_tNH3_electrolysis " ] / cf_industry [ " MWh_elec_per_tNH3_electrolysis " ] , # input: MW_H2 per MW_elec
2022-06-10 12:44:36 +00:00
capital_cost = costs . at [ " Haber-Bosch synthesis " , " fixed " ] ,
lifetime = costs . at [ " Haber-Bosch synthesis " , ' lifetime ' ]
)
n . madd ( " Link " ,
nodes ,
suffix = " ammonia cracker " ,
2022-06-10 14:53:37 +00:00
bus0 = spatial . ammonia . nodes ,
2022-06-10 12:44:36 +00:00
bus1 = nodes + " H2 " ,
p_nom_extendable = True ,
2022-06-10 14:43:29 +00:00
carrier = " ammonia cracker " ,
2022-06-23 13:17:41 +00:00
efficiency = 1 / cf_industry [ " MWh_NH3_per_MWh_H2_cracker " ] ,
2022-06-10 14:43:29 +00:00
capital_cost = costs . at [ " Ammonia cracker " , " fixed " ] / cf_industry [ " MWh_NH3_per_MWh_H2_cracker " ] , # given per MW_H2
2022-06-10 12:44:36 +00:00
lifetime = costs . at [ ' Ammonia cracker ' , ' lifetime ' ]
)
# Ammonia Storage
n . madd ( " Store " ,
2022-06-10 14:53:37 +00:00
spatial . ammonia . nodes ,
2022-06-10 12:44:36 +00:00
suffix = " ammonia store " ,
2022-06-10 14:53:37 +00:00
bus = spatial . ammonia . nodes ,
2022-06-10 12:44:36 +00:00
e_nom_extendable = True ,
e_cyclic = True ,
carrier = " ammonia store " ,
capital_cost = costs . at [ " NH3 (l) storage tank incl. liquefaction " , " fixed " ] ,
lifetime = costs . at [ ' NH3 (l) storage tank incl. liquefaction ' , ' lifetime ' ]
)
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
2023-02-16 17:42:19 +00:00
cost_factor = options [ ' electricity_distribution_grid_cost_factor ' ]
logger . info ( f " Inserting electricity distribution grid with investment cost factor of { cost_factor : .2f } " )
2020-03-25 11:52:25 +00:00
nodes = pop_layout . index
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
2023-02-16 17:42:19 +00:00
logger . info ( f " Inserting gas distribution grid with investment cost factor of { f_costs } " )
2021-07-01 18:09:04 +00:00
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)
2023-02-10 06:49:45 +00:00
h2_capital_cost = costs . at [ " hydrogen storage tank type 1 including 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 " ,
2023-01-24 17:44:39 +00:00
bus3 = spatial . co2 . nodes ,
2022-06-28 11:35:44 +00:00
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 )
2022-12-29 10:46:57 +00:00
ice_share = get ( options [ " land_transport_ice_share " ] , investment_year )
total_share = fuel_cell_share + electric_share + ice_share
if total_share != 1 :
2023-02-16 17:42:19 +00:00
logger . warning ( f " Total land transport shares sum up to { total_share : .2% } , corresponding to increased or decreased demand assumptions. " )
2020-11-30 15:20:26 +00:00
2022-12-29 10:46:57 +00:00
logger . info ( f " FCEV share: { fuel_cell_share * 100 } % " )
logger . info ( f " EV share: { electric_share * 100 } % " )
logger . info ( f " ICEV share: { ice_share * 100 } % " )
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 ) :
2022-11-18 08:08:07 +00:00
# copy forward the daily average heat demand into each hour, so it can be multiplied by the intraday profile
2022-04-03 16:55:53 +00:00
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 )
2023-02-16 17:42:19 +00:00
logger . info ( f " assumed space heat reduction of { dE : .2% } " )
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 )
}
2022-12-28 11:48:51 +00:00
if options [ " solar_thermal " ] :
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
2022-04-03 16:49:35 +00:00
2020-10-21 12:30:26 +00:00
for name in heat_systems :
2019-04-17 12:24:22 +00:00
2019-08-07 09:33:29 +00:00
name_type = " central " if name == " urban central " else " decentral "
2019-04-17 12:24:22 +00:00
2021-07-01 18:09:04 +00:00
n . add ( " Carrier " , name + " heat " )
2019-07-16 14:00:21 +00:00
2021-07-01 18:09:04 +00:00
n . madd ( " Bus " ,
nodes [ name ] + f " { name } heat " ,
location = nodes [ name ] ,
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 " ) ]
2022-11-18 08:08:07 +00:00
# get additional 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
2023-02-16 17:42:19 +00:00
logger . info (
f " Increase district heating share by a progress factor of { progress : .2% } "
f " resulting in new average share of { dist_fraction_node . mean ( ) : .2% } "
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 ( )
2023-01-24 17:44:39 +00:00
if options . get ( " biomass_spatial " , 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 " ,
2023-01-25 07:41:08 +00:00
p_nom_extendable = False ,
p_nom = 5e4 ,
2021-07-12 10:31:18 +00:00
length = biomass_transport . length . values ,
marginal_cost = biomass_transport . costs * biomass_transport . length . values ,
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-05 13:55:41 +00:00
if options [ " biomass_boiler " ] :
2022-08-05 10:19:47 +00:00
#TODO: Add surcharge for pellets
nodes_heat = create_nodes_for_heat_sector ( ) [ 0 ]
for name in [ " residential rural " , " services rural " ,
" residential urban decentral " , " services urban decentral " ] :
n . madd ( " Link " ,
nodes_heat [ name ] + f " { name } biomass boiler " ,
p_nom_extendable = True ,
bus0 = spatial . biomass . df . loc [ nodes_heat [ name ] , " nodes " ] . values ,
bus1 = nodes_heat [ name ] + f " { name } heat " ,
carrier = name + " biomass boiler " ,
efficiency = costs . at [ ' biomass boiler ' , ' efficiency ' ] ,
capital_cost = costs . at [ ' biomass boiler ' , ' efficiency ' ] * costs . at [ ' biomass boiler ' , ' fixed ' ] ,
lifetime = costs . at [ ' biomass boiler ' , ' lifetime ' ]
)
2021-07-01 18:09:04 +00:00
2022-08-01 11:07:59 +00:00
#Solid biomass to liquid fuel
2022-08-01 13:00:27 +00:00
if options [ " biomass_to_liquid " ] :
2022-08-01 11:07:59 +00:00
n . madd ( " Link " ,
2022-08-01 13:00:27 +00:00
spatial . biomass . nodes ,
suffix = " biomass to liquid " ,
2022-08-01 11:07:59 +00:00
bus0 = spatial . biomass . nodes ,
2022-08-01 11:26:51 +00:00
bus1 = spatial . oil . nodes ,
2022-08-04 14:03:16 +00:00
bus2 = " co2 atmosphere " ,
2022-08-01 11:07:59 +00:00
carrier = " biomass to liquid " ,
lifetime = costs . at [ ' BtL ' , ' lifetime ' ] ,
efficiency = costs . at [ ' BtL ' , ' efficiency ' ] ,
2022-08-04 14:03:16 +00:00
efficiency2 = - costs . at [ ' solid biomass ' , ' CO2 intensity ' ] + costs . at [ ' BtL ' , ' CO2 stored ' ] ,
2022-08-01 11:07:59 +00:00
p_nom_extendable = True ,
capital_cost = costs . at [ ' BtL ' , ' fixed ' ] ,
marginal_cost = costs . at [ ' BtL ' , ' efficiency ' ] * costs . loc [ " BtL " , " VOM " ]
2022-08-01 13:00:27 +00:00
)
2022-08-01 11:07:59 +00:00
#TODO: Update with energy penalty
n . madd ( " Link " ,
2022-08-01 13:00:27 +00:00
spatial . biomass . nodes ,
suffix = " biomass to liquid CC " ,
bus0 = spatial . biomass . nodes ,
bus1 = spatial . oil . nodes ,
2022-08-04 14:03:16 +00:00
bus2 = " co2 atmosphere " ,
bus3 = spatial . co2 . nodes ,
2022-08-01 13:00:27 +00:00
carrier = " biomass to liquid " ,
lifetime = costs . at [ ' BtL ' , ' lifetime ' ] ,
efficiency = costs . at [ ' BtL ' , ' efficiency ' ] ,
2022-08-04 14:03:16 +00:00
efficiency2 = - costs . at [ ' solid biomass ' , ' CO2 intensity ' ] + costs . at [ ' BtL ' , ' CO2 stored ' ] * ( 1 - costs . at [ ' BtL ' , ' capture rate ' ] ) ,
efficiency3 = costs . at [ ' BtL ' , ' CO2 stored ' ] * costs . at [ ' BtL ' , ' capture rate ' ] ,
2022-08-01 13:00:27 +00:00
p_nom_extendable = True ,
capital_cost = costs . at [ ' BtL ' , ' fixed ' ] + costs . at [ ' biomass CHP capture ' , ' fixed ' ] * costs . at [
" BtL " , " CO2 stored " ] ,
2022-08-05 13:55:41 +00:00
marginal_cost = costs . at [ ' BtL ' , ' efficiency ' ] * costs . loc [ " BtL " , " VOM " ] )
2022-08-04 14:13:09 +00:00
2022-08-01 11:22:58 +00:00
#BioSNG from solid biomass
if options [ " biosng " ] :
n . madd ( " Link " ,
2022-08-01 13:13:59 +00:00
spatial . biomass . nodes ,
suffix = " solid biomass to gas " ,
bus0 = spatial . biomass . nodes ,
bus1 = spatial . gas . nodes ,
bus3 = " co2 atmosphere " ,
carrier = " BioSNG " ,
lifetime = costs . at [ ' BioSNG ' , ' lifetime ' ] ,
efficiency = costs . at [ ' BioSNG ' , ' efficiency ' ] ,
efficiency3 = - costs . at [ ' solid biomass ' , ' CO2 intensity ' ] + costs . at [ ' BioSNG ' , ' CO2 stored ' ] ,
p_nom_extendable = True ,
capital_cost = costs . at [ ' BioSNG ' , ' fixed ' ] ,
marginal_cost = costs . at [ ' BioSNG ' , ' efficiency ' ] * costs . loc [ " BioSNG " , " VOM " ]
)
2022-08-01 11:22:58 +00:00
#TODO: Update with energy penalty for CC
n . madd ( " Link " ,
2022-08-01 13:13:59 +00:00
spatial . biomass . nodes ,
suffix = " solid biomass to gas CC " ,
bus0 = spatial . biomass . nodes ,
bus1 = spatial . gas . nodes ,
bus2 = spatial . co2 . nodes ,
bus3 = " co2 atmosphere " ,
carrier = " BioSNG " ,
lifetime = costs . at [ ' BioSNG ' , ' lifetime ' ] ,
efficiency = costs . at [ ' BioSNG ' , ' efficiency ' ] ,
efficiency2 = costs . at [ ' BioSNG ' , ' CO2 stored ' ] * costs . at [ ' BioSNG ' , ' capture rate ' ] ,
efficiency3 = - costs . at [ ' solid biomass ' , ' CO2 intensity ' ] + costs . at [ ' BioSNG ' , ' CO2 stored ' ] * ( 1 - costs . at [ ' BioSNG ' , ' capture rate ' ] ) ,
p_nom_extendable = True ,
capital_cost = costs . at [ ' BioSNG ' , ' fixed ' ] + costs . at [ ' biomass CHP capture ' , ' fixed ' ] * costs . at [
" BioSNG " , " CO2 stored " ] ,
marginal_cost = costs . at [ ' BioSNG ' , ' efficiency ' ] * costs . loc [ " BioSNG " , " VOM " ]
2022-08-04 14:13:09 +00:00
2022-08-01 13:00:27 +00:00
)
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
)
2023-01-24 17:44:39 +00:00
if options . get ( " biomass_spatial " , options [ " biomass_transport " ] ) :
2021-08-09 15:51:37 +00:00
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
)
2022-12-03 11:22:05 +00:00
shipping_hydrogen_share = get ( options [ ' shipping_hydrogen_share ' ] , investment_year )
shipping_methanol_share = get ( options [ ' shipping_methanol_share ' ] , investment_year )
shipping_oil_share = get ( options [ ' shipping_oil_share ' ] , investment_year )
2021-08-04 16:28:18 +00:00
2022-12-03 11:22:05 +00:00
total_share = shipping_hydrogen_share + shipping_methanol_share + shipping_oil_share
if total_share != 1 :
2023-02-16 17:42:19 +00:00
logger . warning ( f " Total shipping shares sum up to { total_share : .2% } , corresponding to increased or decreased demand assumptions. " )
2021-08-04 16:28:18 +00:00
2022-12-28 12:45:42 +00:00
domestic_navigation = pop_weighted_energy_totals . loc [ nodes , " total domestic navigation " ] . squeeze ( )
international_navigation = pd . read_csv ( snakemake . input . shipping_demand , index_col = 0 ) . squeeze ( )
2022-11-13 12:43:05 +00:00
all_navigation = domestic_navigation + international_navigation
2022-12-27 10:16:39 +00:00
p_set = all_navigation * 1e6 / 8760
2021-08-04 16:28:18 +00:00
2022-12-03 11:22:05 +00:00
if shipping_hydrogen_share :
2021-08-04 16:28:18 +00:00
2023-01-03 07:38:10 +00:00
oil_efficiency = options . get ( ' shipping_oil_efficiency ' , options . get ( ' shipping_average_efficiency ' , 0.4 ) )
efficiency = oil_efficiency / costs . at [ " fuel cell " , " efficiency " ]
2022-11-13 12:43:05 +00:00
shipping_hydrogen_share = get ( options [ ' shipping_hydrogen_share ' ] , investment_year )
2022-12-27 10:16:39 +00:00
2022-12-03 11:22:05 +00:00
if options [ " shipping_hydrogen_liquefaction " ] :
n . madd ( " Bus " ,
nodes ,
suffix = " H2 liquid " ,
carrier = " H2 liquid " ,
location = nodes ,
unit = " MWh_LHV "
)
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 "
efficiency = options [ ' shipping_oil_efficiency ' ] / costs . at [ " fuel cell " , " efficiency " ]
p_set_hydrogen = shipping_hydrogen_share * p_set * efficiency
n . madd ( " Load " ,
nodes ,
suffix = " H2 for shipping " ,
bus = shipping_bus ,
carrier = " H2 for shipping " ,
p_set = p_set_hydrogen
)
if shipping_methanol_share :
2022-11-13 17:25:32 +00:00
n . madd ( " Bus " ,
spatial . methanol . nodes ,
carrier = " methanol " ,
location = spatial . methanol . locations ,
unit = " MWh_LHV "
)
2023-01-05 12:42:01 +00:00
n . madd ( " Store " ,
spatial . methanol . nodes ,
suffix = " Store " ,
bus = spatial . methanol . nodes ,
e_nom_extendable = True ,
e_cyclic = True ,
carrier = " methanol " ,
)
2022-12-28 11:19:20 +00:00
n . madd ( " Link " ,
2022-12-28 12:45:42 +00:00
spatial . h2 . locations + " methanolisation " ,
2022-11-13 17:25:32 +00:00
bus0 = spatial . h2 . nodes ,
bus1 = spatial . methanol . nodes ,
bus2 = nodes ,
bus3 = spatial . co2 . nodes ,
carrier = " methanolisation " ,
p_nom_extendable = True ,
2023-02-15 12:47:57 +00:00
p_min_pu = options . get ( " min_part_load_methanolisation " , 0 ) ,
2022-11-27 17:28:57 +00:00
capital_cost = costs . at [ " methanolisation " , ' fixed ' ] * options [ " MWh_MeOH_per_MWh_H2 " ] , # EUR/MW_H2/a
2022-11-13 17:25:32 +00:00
lifetime = costs . at [ " methanolisation " , ' lifetime ' ] ,
efficiency = options [ " MWh_MeOH_per_MWh_H2 " ] ,
2022-12-03 11:22:05 +00:00
efficiency2 = - options [ " MWh_MeOH_per_MWh_H2 " ] / options [ " MWh_MeOH_per_MWh_e " ] ,
efficiency3 = - options [ " MWh_MeOH_per_MWh_H2 " ] / options [ " MWh_MeOH_per_tCO2 " ] ,
2022-11-13 17:25:32 +00:00
)
2022-12-03 11:22:05 +00:00
efficiency = options [ " shipping_oil_efficiency " ] / options [ " shipping_methanol_efficiency " ]
p_set_methanol = shipping_methanol_share * p_set . sum ( ) * efficiency
2021-07-01 18:09:04 +00:00
2022-12-03 11:22:05 +00:00
n . madd ( " Load " ,
spatial . methanol . nodes ,
suffix = " shipping methanol " ,
bus = spatial . methanol . nodes ,
carrier = " shipping methanol " ,
p_set = p_set_methanol ,
)
2021-07-01 18:09:04 +00:00
2022-12-03 11:22:05 +00:00
# CO2 intensity methanol based on stoichiometric calculation with 22.7 GJ/t methanol (32 g/mol), CO2 (44 g/mol), 277.78 MWh/TJ = 0.218 t/MWh
co2 = p_set_methanol / options [ " MWh_MeOH_per_tCO2 " ]
2021-08-04 16:19:02 +00:00
2022-12-28 11:19:20 +00:00
n . add ( " Load " ,
2022-12-03 11:22:05 +00:00
" shipping methanol emissions " ,
bus = " co2 atmosphere " ,
carrier = " shipping methanol emissions " ,
p_set = - co2 ,
)
if shipping_oil_share :
2021-07-08 12:41:34 +00:00
2022-12-03 11:22:05 +00:00
p_set_oil = shipping_oil_share * p_set . sum ( )
2021-09-27 09:16:12 +00:00
2021-08-04 16:19:02 +00:00
n . madd ( " Load " ,
2022-12-03 11:22:05 +00:00
spatial . oil . nodes ,
2021-08-04 16:19:02 +00:00
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 " ,
2022-12-03 11:22:05 +00:00
p_set = p_set_oil
2021-08-04 16:19:02 +00:00
)
2021-07-08 12:41:34 +00:00
2022-12-03 11:22:05 +00:00
co2 = p_set_oil * 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-12-28 11:19:20 +00:00
if " oil " not in n . buses . carrier . unique ( ) :
n . madd ( " Bus " ,
spatial . oil . nodes ,
location = spatial . oil . locations ,
carrier = " oil " ,
unit = " MWh_LHV "
)
2021-07-01 18:09:04 +00:00
2022-12-28 11:19:20 +00:00
if " oil " not in n . stores . carrier . unique ( ) :
2021-07-08 12:41:34 +00:00
2022-12-28 11:19:20 +00:00
#could correct to e.g. 0.001 EUR/kWh * annuity and O&M
n . madd ( " Store " ,
[ oil_bus + " Store " for oil_bus in spatial . oil . nodes ] ,
bus = spatial . oil . nodes ,
e_nom_extendable = True ,
e_cyclic = True ,
carrier = " oil " ,
)
2021-07-01 18:09:04 +00:00
2022-12-28 11:19:20 +00:00
if " oil " not in n . generators . carrier . unique ( ) :
2021-07-01 18:09:04 +00:00
2022-12-28 11:19:20 +00:00
n . madd ( " Generator " ,
spatial . oil . nodes ,
bus = spatial . oil . nodes ,
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 ' ] ,
2022-11-13 17:25:32 +00:00
capital_cost = costs . at [ " Fischer-Tropsch " , ' fixed ' ] * costs . at [ " Fischer-Tropsch " , ' efficiency ' ] , # EUR/MW_H2/a
2021-07-01 18:09:04 +00:00
efficiency2 = - costs . at [ " oil " , ' CO2 intensity ' ] * costs . at [ " Fischer-Tropsch " , ' efficiency ' ] ,
p_nom_extendable = True ,
2023-02-15 12:47:57 +00:00
p_min_pu = options . get ( " min_part_load_fischer_tropsch " , 0 ) ,
2021-07-01 18:09:04 +00:00
lifetime = costs . at [ ' Fischer-Tropsch ' , ' lifetime ' ]
)
2022-12-29 10:46:57 +00:00
demand_factor = options . get ( " HVC_demand_factor " , 1 )
p_set = demand_factor * industrial_demand . loc [ nodes , " naphtha " ] . sum ( ) / 8760
if demand_factor != 1 :
logger . warning ( f " Changing HVC demand by { demand_factor * 100 - 100 : +.2f } %. " )
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 " ,
2022-12-29 10:46:57 +00:00
p_set = p_set
2021-07-01 18:09:04 +00:00
)
2022-12-29 10:46:57 +00:00
demand_factor = options . get ( " aviation_demand_factor " , 1 )
2021-07-01 18:09:04 +00:00
all_aviation = [ " total international aviation " , " total domestic aviation " ]
2022-12-29 10:46:57 +00:00
p_set = demand_factor * pop_weighted_energy_totals . loc [ nodes , all_aviation ] . sum ( axis = 1 ) . sum ( ) * 1e6 / 8760
if demand_factor != 1 :
logger . warning ( f " Changing aviation demand by { demand_factor * 100 - 100 : +.2f } %. " )
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
)
2023-02-08 21:57:01 +00:00
n . madd ( " Bus " ,
2023-02-06 08:46:24 +00:00
spatial . co2 . process_emissions ,
location = spatial . co2 . locations ,
2022-06-28 16:31:45 +00:00
carrier = " process emissions " ,
unit = " t_co2 "
2021-07-01 18:09:04 +00:00
)
2023-02-06 08:52:35 +00:00
sel = [ " process emission " , " process emission from feedstock " ]
2023-02-08 21:57:01 +00:00
if options [ " co2_spatial " ] or options [ " co2network " ] :
2023-02-06 08:52:35 +00:00
p_set = - industrial_demand . loc [ nodes , sel ] . sum ( axis = 1 ) . rename ( index = lambda x : x + " process emissions " ) / 8760
else :
p_set = - industrial_demand . loc [ nodes , sel ] . sum ( axis = 1 ) . sum ( ) / 8760
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
2023-02-08 21:57:01 +00:00
n . madd ( " Load " ,
2023-02-06 08:46:24 +00:00
spatial . co2 . process_emissions ,
bus = spatial . co2 . process_emissions ,
2021-07-01 18:09:04 +00:00
carrier = " process emissions " ,
2023-02-06 08:52:35 +00:00
p_set = p_set ,
2021-07-01 18:09:04 +00:00
)
2023-02-08 21:57:01 +00:00
n . madd ( " Link " ,
2023-02-06 08:46:24 +00:00
spatial . co2 . process_emissions ,
bus0 = spatial . co2 . process_emissions ,
2021-07-01 18:09:04 +00:00
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 " ,
2023-02-06 08:46:24 +00:00
bus0 = spatial . co2 . process_emissions ,
2021-07-01 18:09:04 +00:00
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 ' ]
)
2022-06-22 13:50:32 +00:00
if options . get ( " ammonia " ) :
2022-06-10 15:07:48 +00:00
if options [ " ammonia " ] == ' regional ' :
p_set = industrial_demand . loc [ spatial . ammonia . locations , " ammonia " ] . rename ( index = lambda x : x + " NH3 " ) / 8760
else :
p_set = industrial_demand [ " ammonia " ] . sum ( ) / 8760
2022-06-10 12:44:36 +00:00
n . madd ( " Load " ,
2022-06-10 14:53:37 +00:00
spatial . ammonia . nodes ,
bus = spatial . ammonia . nodes ,
2022-06-10 12:44:36 +00:00
carrier = " NH3 " ,
2022-06-10 15:07:48 +00:00
p_set = p_set
2022-06-10 12:44:36 +00:00
)
2021-07-01 18:09:04 +00:00
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
2023-02-15 13:01:00 +00:00
# TODO integrate useable waste heat efficiency into technology-data from DEA
if options . get ( ' use_electrolysis_waste_heat ' , False ) :
n . links . loc [ urban_central + " H2 Electrolysis " , " bus2 " ] = urban_central + " urban central heat "
n . links . loc [ urban_central + " H2 Electrolysis " , " efficiency2 " ] = 0.84 - n . links . loc [ urban_central + " H2 Electrolysis " , " efficiency " ]
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 )
2022-12-29 10:46:57 +00:00
oil_share = get ( options [ " agriculture_machinery_oil_share " ] , investment_year )
total_share = electric_share + oil_share
if total_share != 1 :
2023-02-16 17:42:19 +00:00
logger . warning ( f " Total agriculture machinery shares sum up to { total_share : .2% } , corresponding to increased or decreased demand assumptions. " )
2021-07-06 16:32:35 +00:00
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
)
2022-12-29 10:46:57 +00:00
if oil_share > 0 :
2021-07-06 16:32:35 +00:00
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 " ,
2022-12-29 10:46:57 +00:00
p_set = oil_share * machinery_nodal_energy . sum ( ) * 1e6 / 8760
2021-07-06 16:32:35 +00:00
)
2022-12-29 10:46:57 +00:00
co2 = oil_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
2023-02-16 17:42:19 +00:00
logger . info ( f " 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
2022-08-08 06:53:07 +00:00
2022-08-01 16:03:11 +00:00
aggregate_dict = {
" p_nom " : " sum " ,
" s_nom " : " sum " ,
" v_nom " : " max " ,
" v_mag_pu_max " : " min " ,
" v_mag_pu_min " : " max " ,
" p_nom_max " : " sum " ,
" s_nom_max " : " sum " ,
" p_nom_min " : " sum " ,
" s_nom_min " : " sum " ,
' v_ang_min ' : " max " ,
" v_ang_max " : " min " ,
" terrain_factor " : " mean " ,
" num_parallel " : " sum " ,
" p_set " : " sum " ,
" e_initial " : " sum " ,
" e_nom " : " sum " ,
" e_nom_max " : " sum " ,
" e_nom_min " : " sum " ,
" state_of_charge_initial " : " sum " ,
" state_of_charge_set " : " sum " ,
" inflow " : " sum " ,
" p_max_pu " : " first " ,
" x " : " mean " ,
" y " : " mean "
}
def cluster_heat_buses ( n ) :
""" Cluster residential and service heat buses to one representative bus.
This can be done to save memory and speed up optimisation
"""
def define_clustering ( attributes , aggregate_dict ) :
""" Define how attributes should be clustered.
Input :
attributes : pd . Index ( )
aggregate_dict : dictionary ( key : name of attribute , value
clustering method )
Returns :
agg : clustering dictionary
"""
keys = attributes . intersection ( aggregate_dict . keys ( ) )
agg = dict (
zip (
attributes . difference ( keys ) ,
[ " first " ] * len ( df . columns . difference ( keys ) ) ,
)
)
for key in keys :
agg [ key ] = aggregate_dict [ key ]
return agg
logger . info ( " Cluster residential and service heat buses. " )
components = [ " Bus " , " Carrier " , " Generator " , " Link " , " Load " , " Store " ]
for c in n . iterate_components ( components ) :
df = c . df
cols = df . columns [ df . columns . str . contains ( " bus " ) | ( df . columns == " carrier " ) ]
# rename columns and index
df [ cols ] = ( df [ cols ]
. apply ( lambda x : x . str . replace ( " residential " , " " )
. str . replace ( " services " , " " ) , axis = 1 ) )
df = df . rename ( index = lambda x : x . replace ( " residential " , " " )
. replace ( " services " , " " ) )
# cluster heat nodes
# static dataframe
agg = define_clustering ( df . columns , aggregate_dict )
df = df . groupby ( level = 0 ) . agg ( agg , * * agg_group_kwargs )
# time-varying data
pnl = c . pnl
agg = define_clustering ( pd . Index ( pnl . keys ( ) ) , aggregate_dict )
for k in pnl . keys ( ) :
pnl [ k ] . rename ( columns = lambda x : x . replace ( " residential " , " " )
. replace ( " services " , " " ) , inplace = True )
pnl [ k ] = (
pnl [ k ]
. groupby ( level = 0 , axis = 1 )
. agg ( agg [ k ] , * * agg_group_kwargs )
)
# remove unclustered assets of service/residential
to_drop = c . df . index . difference ( df . index )
n . mremove ( c . name , to_drop )
# add clustered assets
to_add = df . index . difference ( c . df . index )
import_components_from_dataframe ( n , df . loc [ to_add ] , c . name )
2022-08-08 06:53:07 +00:00
def apply_time_segmentation ( n , segments , solver_name = " cbc " ,
2022-09-14 14:16:50 +00:00
overwrite_time_dependent = True ) :
2022-08-08 06:53:07 +00:00
""" Aggregating time series to segments with different lengths
Input :
n : pypsa Network
segments : ( int ) number of segments in which the typical period should be
subdivided
solver_name : ( str ) name of solver
overwrite_time_dependent : ( bool ) overwrite time dependent data of pypsa network
with typical time series created by tsam
"""
try :
import tsam . timeseriesaggregation as tsam
except :
raise ModuleNotFoundError ( " Optional dependency ' tsam ' not found. "
" Install via ' pip install tsam ' " )
# get all time-dependent data
columns = pd . MultiIndex . from_tuples ( [ ] , names = [ ' component ' , ' key ' , ' asset ' ] )
raw = pd . DataFrame ( index = n . snapshots , columns = columns )
2022-09-14 14:16:50 +00:00
for c in n . iterate_components ( ) :
for attr , pnl in c . pnl . items ( ) :
# exclude e_min_pu which is used for SOC of EVs in the morning
if not pnl . empty and attr != ' e_min_pu ' :
df = pnl . copy ( )
df . columns = pd . MultiIndex . from_product ( [ [ c . name ] , [ attr ] , df . columns ] )
2022-08-08 06:53:07 +00:00
raw = pd . concat ( [ raw , df ] , axis = 1 )
# normalise all time-dependent data
annual_max = raw . max ( ) . replace ( 0 , 1 )
raw = raw . div ( annual_max , level = 0 )
# get representative segments
agg = tsam . TimeSeriesAggregation ( raw , hoursPerPeriod = len ( raw ) ,
noTypicalPeriods = 1 , noSegments = int ( segments ) ,
segmentation = True , solver = solver_name )
segmented = agg . createTypicalPeriods ( )
weightings = segmented . index . get_level_values ( " Segment Duration " )
offsets = np . insert ( np . cumsum ( weightings [ : - 1 ] ) , 0 , 0 )
timesteps = [ raw . index [ 0 ] + pd . Timedelta ( f " { offset } h " ) for offset in offsets ]
snapshots = pd . DatetimeIndex ( timesteps )
sn_weightings = pd . Series ( weightings , index = snapshots , name = " weightings " , dtype = " float64 " )
n . set_snapshots ( sn_weightings . index )
n . snapshot_weightings = n . snapshot_weightings . mul ( sn_weightings , axis = 0 )
# overwrite time-dependent data with timeseries created by tsam
if overwrite_time_dependent :
values_t = segmented . mul ( annual_max ) . set_index ( snapshots )
for component , key in values_t . columns . droplevel ( 2 ) . unique ( ) :
n . pnl ( component ) [ key ] = values_t [ component , key ]
return n
def set_temporal_aggregation ( n , opts , solver_name ) :
""" Aggregate network temporally. """
for o in opts :
# temporal averaging
m = re . match ( r " ^ \ d+h$ " , o , re . IGNORECASE )
if m is not None :
n = average_every_nhours ( n , m . group ( 0 ) )
2022-09-14 14:16:50 +00:00
break
2022-11-18 08:08:07 +00:00
# representative snapshots
2022-09-14 14:16:50 +00:00
m = re . match ( r " (^ \ d+)sn$ " , o , re . IGNORECASE )
2022-08-08 06:53:07 +00:00
if m is not None :
2022-09-14 14:16:50 +00:00
sn = int ( m [ 1 ] )
logger . info ( f " use every { sn } snapshot as representative " )
2022-08-08 06:53:07 +00:00
n . set_snapshots ( n . snapshots [ : : sn ] )
n . snapshot_weightings * = sn
2022-09-14 14:16:50 +00:00
break
2022-08-08 06:53:07 +00:00
# segments with package tsam
2022-09-14 14:16:50 +00:00
m = re . match ( r " ^( \ d+)seg$ " , o , re . IGNORECASE )
if m is not None :
segments = int ( m [ 1 ] )
logger . info ( f " use temporal segmentation with { segments } segments " )
2022-08-08 06:53:07 +00:00
n = apply_time_segmentation ( n , segments , solver_name = solver_name )
2022-09-14 14:16:50 +00:00
break
2022-08-08 06:53:07 +00:00
return n
2023-01-30 10:53:00 +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 ,
2022-08-01 13:21:11 +00:00
sector_opts = ' cb40ex0-365H-T-H-B-I-A-solar+p3-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 " , " - " ) )
2023-02-16 17:42:19 +00:00
logger . info ( f " Including wave generators with cost factor of { wave_cost_factor } " )
2020-01-14 20:36:35 +00:00
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
2022-06-10 12:44:36 +00:00
if options [ ' ammonia ' ] :
add_ammonia ( n , costs )
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 )
2023-01-24 17:44:39 +00:00
if options [ " co2network " ] :
2021-07-07 16:07:57 +00:00
add_co2_network ( n , costs )
2023-02-16 16:21:58 +00:00
if options [ " allam_cycle " ] :
2023-02-08 21:57:01 +00:00
add_allam ( n , costs )
2021-07-07 16:07:57 +00:00
2022-08-08 06:53:07 +00:00
solver_name = snakemake . config [ " solving " ] [ " solver " ] [ " name " ]
n = set_temporal_aggregation ( n , opts , solver_name )
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 ) :
2022-08-02 06:52:48 +00:00
emissions_scope = snakemake . config [ " energy " ] [ " emissions " ]
report_year = snakemake . config [ " energy " ] [ " eurostat_report_year " ]
build_carbon_budget ( o , snakemake . input . eurostat , fn , emissions_scope , report_year )
2022-04-12 12:37:05 +00:00
co2_cap = pd . read_csv ( fn , index_col = 0 ) . squeeze ( )
2022-08-01 13:21:11 +00:00
limit = co2_cap . loc [ investment_year ]
2021-07-01 18:09:04 +00:00
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
2023-02-16 17:42:19 +00:00
logger . info ( f " 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
2022-08-01 16:15:35 +00:00
first_year_myopic = ( ( snakemake . config [ " foresight " ] == ' myopic ' ) and
( snakemake . config [ " scenario " ] [ " planning_horizons " ] [ 0 ] == investment_year ) )
2023-01-30 10:54:19 +00:00
if options . get ( " cluster_heat_buses " , False ) and not first_year_myopic :
2022-08-01 16:03:11 +00:00
cluster_heat_buses ( n )
2022-08-01 16:15:35 +00:00
2023-01-30 10:53:00 +00:00
2022-06-30 06:42:18 +00:00
n . meta = dict ( snakemake . config , * * dict ( wildcards = dict ( snakemake . wildcards ) ) )
2023-01-30 10:53:00 +00:00
2019-04-17 12:24:22 +00:00
n . export_to_netcdf ( snakemake . output [ 0 ] )