2022-09-16 13:04:04 +00:00
# -*- coding: utf-8 -*-
2024-02-19 15:21:48 +00:00
# SPDX-FileCopyrightText: : 2017-2024 The PyPSA-Eur Authors
2020-05-29 07:50:55 +00:00
#
2021-09-14 14:37:41 +00:00
# SPDX-License-Identifier: MIT
2019-08-08 13:02:28 +00:00
"""
2019-11-19 18:36:28 +00:00
Adds electrical generators and existing hydro storage units to a base network .
2019-08-11 09:40:47 +00:00
Relevant Settings
- - - - - - - - - - - - - - - - -
2019-08-11 11:17:36 +00:00
. . code : : yaml
costs :
year :
2020-10-16 09:38:26 +00:00
version :
2019-08-11 11:17:36 +00:00
dicountrate :
emission_prices :
electricity :
max_hours :
marginal_cost :
capital_cost :
conventional_carriers :
co2limit :
extendable_carriers :
2022-06-09 18:31:50 +00:00
estimate_renewable_capacities :
2022-06-09 21:40:32 +00:00
2019-08-11 11:17:36 +00:00
load :
scaling_factor :
2020-12-05 16:54:50 +00:00
renewable :
2019-08-11 11:17:36 +00:00
hydro :
carriers :
hydro_max_hours :
hydro_capital_cost :
lines :
length_factor :
2019-10-30 22:09:41 +00:00
. . seealso : :
2023-04-21 08:41:44 +00:00
Documentation of the configuration file ` ` config / config . yaml ` ` at : ref : ` costs_cf ` ,
2019-10-30 22:09:41 +00:00
: ref : ` electricity_cf ` , : ref : ` load_cf ` , : ref : ` renewable_cf ` , : ref : ` lines_cf `
2019-08-13 08:03:46 +00:00
2019-08-11 09:40:47 +00:00
Inputs
- - - - - -
2020-10-16 09:38:26 +00:00
- ` ` resources / costs . csv ` ` : The database of cost assumptions for all included technologies for specific years from various sources ; e . g . discount rate , lifetime , investment ( CAPEX ) , fixed operation and maintenance ( FOM ) , variable operation and maintenance ( VOM ) , fuel costs , efficiency , carbon - dioxide intensity .
2024-04-15 12:48:34 +00:00
- ` ` data / hydro_capacities . csv ` ` : Hydropower plant store / discharge power capacities , energy storage capacity , and average hourly inflow by country .
2019-08-12 17:01:53 +00:00
2023-03-09 12:28:42 +00:00
. . image : : img / hydrocapacities . png
2019-08-12 17:01:53 +00:00
: scale : 34 %
2020-12-05 16:54:50 +00:00
- ` ` data / geth2015_hydro_capacities . csv ` ` : alternative to capacities above ; not currently used !
2024-02-12 09:49:45 +00:00
- ` ` resources / electricity_demand . csv ` ` Hourly per - country electricity demand profiles .
2019-08-11 20:34:18 +00:00
- ` ` resources / regions_onshore . geojson ` ` : confer : ref : ` busregions `
- ` ` resources / nuts3_shapes . geojson ` ` : confer : ref : ` shapes `
- ` ` resources / powerplants . csv ` ` : confer : ref : ` powerplants `
- ` ` resources / profile_ { } . nc ` ` : all technologies in ` ` config [ " renewables " ] . keys ( ) ` ` , confer : ref : ` renewableprofiles ` .
- ` ` networks / base . nc ` ` : confer : ref : ` base `
2019-08-11 09:40:47 +00:00
Outputs
- - - - - - -
2019-08-11 20:34:18 +00:00
- ` ` networks / elec . nc ` ` :
2023-03-09 12:28:42 +00:00
. . image : : img / elec . png
2019-08-11 20:34:18 +00:00
: scale : 33 %
2019-08-11 09:40:47 +00:00
Description
- - - - - - - - - - -
2019-08-13 15:52:33 +00:00
The rule : mod : ` add_electricity ` ties all the different data inputs from the preceding rules together into a detailed PyPSA network that is stored in ` ` networks / elec . nc ` ` . It includes :
2019-08-12 17:01:53 +00:00
2019-08-11 20:34:18 +00:00
- today ' s transmission topology and transfer capacities (optionally including lines which are under construction according to the config settings ``lines: under_construction`` and ``links: under_construction``),
- today ' s thermal and hydro power generation capacities (for the technologies listed in the config setting ``electricity: conventional_carriers``), and
- today ' s load time-series (upsampled in a top-down approach according to population and gross domestic product)
2019-11-19 18:36:28 +00:00
It further adds extendable ` ` generators ` ` with * * zero * * capacity for
2019-08-11 20:34:18 +00:00
2019-08-12 17:01:53 +00:00
- photovoltaic , onshore and AC - as well as DC - connected offshore wind installations with today ' s locational, hourly wind and solar capacity factors (but **no** current capacities),
2019-08-11 20:34:18 +00:00
- additional open - and combined - cycle gas turbines ( if ` ` OCGT ` ` and / or ` ` CCGT ` ` is listed in the config setting ` ` electricity : extendable_carriers ` ` )
2019-08-08 13:02:28 +00:00
"""
2018-01-29 21:28:33 +00:00
2019-11-14 16:50:24 +00:00
import logging
2023-05-10 08:09:43 +00:00
from itertools import product
2024-08-15 09:42:21 +00:00
from pathlib import Path
2023-11-03 13:16:09 +00:00
from typing import Dict , List
2019-11-28 07:22:52 +00:00
2018-01-29 21:28:33 +00:00
import geopandas as gpd
2022-09-16 13:04:04 +00:00
import numpy as np
import pandas as pd
2020-12-03 22:13:41 +00:00
import powerplantmatching as pm
2022-09-16 13:04:04 +00:00
import pypsa
2023-05-10 08:09:43 +00:00
import scipy . sparse as sparse
2022-09-16 13:04:04 +00:00
import xarray as xr
2024-03-14 14:15:56 +00:00
from _helpers import (
configure_logging ,
get_snapshots ,
set_scenario_config ,
update_p_nom_max ,
)
2020-12-03 22:13:41 +00:00
from powerplantmatching . export import map_country_bus
2023-05-10 07:58:25 +00:00
from shapely . prepared import prep
2020-09-11 10:40:53 +00:00
2019-11-14 16:50:24 +00:00
idx = pd . IndexSlice
2020-09-11 10:40:53 +00:00
logger = logging . getLogger ( __name__ )
2018-01-29 21:28:33 +00:00
2022-09-16 13:04:04 +00:00
def normed ( s ) :
return s / s . sum ( )
2018-01-29 21:28:33 +00:00
2020-09-11 10:40:53 +00:00
2022-04-15 16:36:37 +00:00
def calculate_annuity ( n , r ) :
2022-09-16 13:04:04 +00:00
"""
Calculate the annuity factor for an asset with lifetime n years and .
2022-04-15 16:36:37 +00:00
2022-09-16 13:04:04 +00:00
discount rate of r , e . g . annuity ( 20 , 0.05 ) * 20 = 1.6
"""
2022-04-15 16:36:37 +00:00
if isinstance ( r , pd . Series ) :
2022-09-16 13:04:04 +00:00
return pd . Series ( 1 / n , index = r . index ) . where (
r == 0 , r / ( 1.0 - 1.0 / ( 1.0 + r ) * * n )
)
2022-04-15 16:36:37 +00:00
elif r > 0 :
2022-09-16 13:04:04 +00:00
return r / ( 1.0 - 1.0 / ( 1.0 + r ) * * n )
2022-04-15 16:36:37 +00:00
else :
return 1 / n
2023-06-30 11:25:20 +00:00
def add_missing_carriers ( n , carriers ) :
"""
Function to add missing carriers to the network without raising errors .
"""
missing_carriers = set ( carriers ) - set ( n . carriers . index )
if len ( missing_carriers ) > 0 :
n . madd ( " Carrier " , missing_carriers )
2023-04-25 15:26:23 +00:00
2023-06-30 11:28:54 +00:00
2023-05-03 11:24:57 +00:00
def sanitize_carriers ( n , config ) :
"""
Sanitize the carrier information in a PyPSA Network object .
The function ensures that all unique carrier names are present in the network ' s
carriers attribute , and adds nice names and colors for each carrier according
to the provided configuration dictionary .
Parameters
- - - - - - - - - -
n : pypsa . Network
A PyPSA Network object that represents an electrical power system .
config : dict
A dictionary containing configuration information , specifically the
" plotting " key with " nice_names " and " tech_colors " keys for carriers .
Returns
- - - - - - -
None
The function modifies the ' n ' PyPSA Network object in - place , updating the
carriers attribute with nice names and colors .
Warnings
- - - - - - - -
Raises a warning if any carrier ' s " tech_colors " are not defined in the config dictionary.
"""
for c in n . iterate_components ( ) :
if " carrier " in c . df :
2023-07-06 13:01:00 +00:00
add_missing_carriers ( n , c . df . carrier )
2022-09-16 13:04:04 +00:00
2023-04-28 01:43:20 +00:00
carrier_i = n . carriers . index
nice_names = (
pd . Series ( config [ " plotting " ] [ " nice_names " ] )
. reindex ( carrier_i )
2023-08-21 09:19:16 +00:00
. fillna ( carrier_i . to_series ( ) )
2023-04-28 01:43:20 +00:00
)
2023-06-29 14:02:54 +00:00
n . carriers [ " nice_name " ] = n . carriers . nice_name . where (
n . carriers . nice_name != " " , nice_names
2022-09-16 13:04:04 +00:00
)
2023-04-28 01:43:20 +00:00
colors = pd . Series ( config [ " plotting " ] [ " tech_colors " ] ) . reindex ( carrier_i )
if colors . isna ( ) . any ( ) :
missing_i = list ( colors . index [ colors . isna ( ) ] )
logger . warning ( f " tech_colors for carriers { missing_i } not defined in config. " )
2023-06-29 14:02:09 +00:00
n . carriers [ " color " ] = n . carriers . color . where ( n . carriers . color != " " , colors )
2023-04-28 01:43:20 +00:00
2018-01-29 21:28:33 +00:00
2024-02-05 15:16:34 +00:00
def sanitize_locations ( n ) :
2024-03-18 13:37:58 +00:00
if " location " in n . buses . columns :
n . buses [ " x " ] = n . buses . x . where ( n . buses . x != 0 , n . buses . location . map ( n . buses . x ) )
n . buses [ " y " ] = n . buses . y . where ( n . buses . y != 0 , n . buses . location . map ( n . buses . y ) )
n . buses [ " country " ] = n . buses . country . where (
n . buses . country . ne ( " " ) & n . buses . country . notnull ( ) ,
n . buses . location . map ( n . buses . country ) ,
)
2024-02-05 15:16:34 +00:00
2023-05-03 11:24:57 +00:00
def add_co2_emissions ( n , costs , carriers ) :
"""
Add CO2 emissions to the network ' s carriers attribute.
"""
suptechs = n . carriers . loc [ carriers ] . index . str . split ( " - " ) . str [ 0 ]
n . carriers . loc [ carriers , " co2_emissions " ] = costs . co2_emissions [ suptechs ] . values
2020-09-11 10:40:53 +00:00
2018-01-29 21:28:33 +00:00
2023-06-15 17:35:41 +00:00
def load_costs ( tech_costs , config , max_hours , Nyears = 1.0 ) :
2018-01-29 21:28:33 +00:00
# set all asset costs and other parameters
2022-09-16 13:04:04 +00:00
costs = pd . read_csv ( tech_costs , index_col = [ 0 , 1 ] ) . sort_index ( )
2018-01-29 21:28:33 +00:00
2020-08-25 19:41:21 +00:00
# correct units to MW
2022-09-16 13:04:04 +00:00
costs . loc [ costs . unit . str . contains ( " /kW " ) , " value " ] * = 1e3
2020-10-16 09:38:26 +00:00
costs . unit = costs . unit . str . replace ( " /kW " , " /MW " )
2018-01-29 21:28:33 +00:00
2020-10-16 09:38:26 +00:00
fill_values = config [ " fill_values " ]
2020-08-25 19:41:21 +00:00
costs = costs . value . unstack ( ) . fillna ( fill_values )
2018-01-29 21:28:33 +00:00
2022-09-16 13:04:04 +00:00
costs [ " capital_cost " ] = (
(
calculate_annuity ( costs [ " lifetime " ] , costs [ " discount rate " ] )
+ costs [ " FOM " ] / 100.0
)
* costs [ " investment " ]
* Nyears
)
costs . at [ " OCGT " , " fuel " ] = costs . at [ " gas " , " fuel " ]
costs . at [ " CCGT " , " fuel " ] = costs . at [ " gas " , " fuel " ]
2018-07-25 15:15:06 +00:00
2022-09-16 13:04:04 +00:00
costs [ " marginal_cost " ] = costs [ " VOM " ] + costs [ " fuel " ] / costs [ " efficiency " ]
2018-01-29 21:28:33 +00:00
costs = costs . rename ( columns = { " CO2 intensity " : " co2_emissions " } )
2022-09-16 13:04:04 +00:00
costs . at [ " OCGT " , " co2_emissions " ] = costs . at [ " gas " , " co2_emissions " ]
costs . at [ " CCGT " , " co2_emissions " ] = costs . at [ " gas " , " co2_emissions " ]
2018-03-13 09:50:28 +00:00
2024-05-15 16:12:02 +00:00
costs . at [ " solar " , " capital_cost " ] = costs . at [ " solar-utility " , " capital_cost " ]
2018-02-19 09:09:39 +00:00
2024-04-29 11:32:22 +00:00
costs = costs . rename ( { " solar-utility single-axis tracking " : " solar-hsat " } )
2024-04-25 09:32:58 +00:00
2022-09-16 13:04:04 +00:00
def costs_for_storage ( store , link1 , link2 = None , max_hours = 1.0 ) :
capital_cost = link1 [ " capital_cost " ] + max_hours * store [ " capital_cost " ]
2018-01-29 21:28:33 +00:00
if link2 is not None :
2022-09-16 13:04:04 +00:00
capital_cost + = link2 [ " capital_cost " ]
return pd . Series (
dict ( capital_cost = capital_cost , marginal_cost = 0.0 , co2_emissions = 0.0 )
)
costs . loc [ " battery " ] = costs_for_storage (
costs . loc [ " battery storage " ] ,
costs . loc [ " battery inverter " ] ,
max_hours = max_hours [ " battery " ] ,
)
costs . loc [ " H2 " ] = costs_for_storage (
costs . loc [ " hydrogen storage underground " ] ,
costs . loc [ " fuel cell " ] ,
costs . loc [ " electrolysis " ] ,
max_hours = max_hours [ " H2 " ] ,
)
for attr in ( " marginal_cost " , " capital_cost " ) :
2018-05-18 15:09:18 +00:00
overwrites = config . get ( attr )
2018-01-29 21:28:33 +00:00
if overwrites is not None :
overwrites = pd . Series ( overwrites )
costs . loc [ overwrites . index , attr ] = overwrites
return costs
2020-09-11 10:40:53 +00:00
2021-09-14 14:34:02 +00:00
def load_powerplants ( ppl_fn ) :
2022-09-16 13:04:04 +00:00
carrier_dict = {
" ocgt " : " OCGT " ,
" ccgt " : " CCGT " ,
" bioenergy " : " biomass " ,
" ccgt, thermal " : " CCGT " ,
" hard coal " : " coal " ,
}
return (
pd . read_csv ( ppl_fn , index_col = 0 , dtype = { " bus " : " str " } )
. powerplant . to_pypsa_names ( )
. rename ( columns = str . lower )
. replace ( { " carrier " : carrier_dict } )
)
2023-05-11 14:58:35 +00:00
def shapes_to_shapes ( orig , dest ) :
2023-05-10 07:58:25 +00:00
"""
Adopted from vresutils . transfer . Shapes2Shapes ( )
"""
orig_prepped = list ( map ( prep , orig ) )
transfer = sparse . lil_matrix ( ( len ( dest ) , len ( orig ) ) , dtype = float )
2023-05-10 08:09:43 +00:00
for i , j in product ( range ( len ( dest ) ) , range ( len ( orig ) ) ) :
2024-07-08 06:29:16 +00:00
if orig_prepped [ j ] . intersects ( dest . iloc [ i ] ) :
area = orig . iloc [ j ] . intersection ( dest . iloc [ i ] ) . area
transfer [ i , j ] = area / dest . iloc [ i ] . area
2023-05-10 07:58:25 +00:00
return transfer
2022-09-16 13:04:04 +00:00
2024-07-19 17:23:35 +00:00
def attach_load (
n , regions , load , nuts3_shapes , gdp_pop_non_nuts3 , countries , scaling = 1.0
) :
2022-09-16 13:04:04 +00:00
substation_lv_i = n . buses . index [ n . buses [ " substation_lv " ] ]
2024-07-19 17:23:35 +00:00
gdf_regions = gpd . read_file ( regions ) . set_index ( " name " ) . reindex ( substation_lv_i )
2022-09-16 13:04:04 +00:00
opsd_load = pd . read_csv ( load , index_col = 0 , parse_dates = True ) . filter ( items = countries )
2018-08-03 09:53:14 +00:00
2024-01-26 11:10:39 +00:00
logger . info ( f " Load data scaled by factor { scaling } . " )
2020-12-03 11:49:04 +00:00
opsd_load * = scaling
2018-08-03 09:53:14 +00:00
2022-09-16 13:04:04 +00:00
nuts3 = gpd . read_file ( nuts3_shapes ) . set_index ( " index " )
2018-08-03 09:53:14 +00:00
2024-07-19 17:23:35 +00:00
def upsample ( cntry , group , gdp_pop_non_nuts3 ) :
2024-01-19 11:16:07 +00:00
load = opsd_load [ cntry ]
2022-03-04 19:02:27 +00:00
2018-08-03 09:53:14 +00:00
if len ( group ) == 1 :
2024-01-19 11:16:07 +00:00
return pd . DataFrame ( { group . index [ 0 ] : load } )
2023-10-08 09:20:36 +00:00
nuts3_cntry = nuts3 . loc [ nuts3 . country == cntry ]
transfer = shapes_to_shapes ( group , nuts3_cntry . geometry ) . T . tocsr ( )
gdp_n = pd . Series (
transfer . dot ( nuts3_cntry [ " gdp " ] . fillna ( 1.0 ) . values ) , index = group . index
)
pop_n = pd . Series (
transfer . dot ( nuts3_cntry [ " pop " ] . fillna ( 1.0 ) . values ) , index = group . index
)
2018-08-03 09:53:14 +00:00
2023-10-08 09:20:36 +00:00
# relative factors 0.6 and 0.4 have been determined from a linear
# regression on the country to continent load data
factors = normed ( 0.6 * normed ( gdp_n ) + 0.4 * normed ( pop_n ) )
2023-10-09 14:25:49 +00:00
if cntry in [ " UA " , " MD " ] :
# overwrite factor because nuts3 provides no data for UA+MD
2024-07-19 17:23:35 +00:00
gdp_pop_non_nuts3 = gpd . read_file ( gdp_pop_non_nuts3 ) . set_index ( " Bus " )
gdp_pop_non_nuts3 = gdp_pop_non_nuts3 . loc [
( gdp_pop_non_nuts3 . country == cntry )
& ( gdp_pop_non_nuts3 . index . isin ( substation_lv_i ) )
]
factors = normed (
0.6 * normed ( gdp_pop_non_nuts3 [ " gdp " ] )
+ 0.4 * normed ( gdp_pop_non_nuts3 [ " pop " ] )
)
2023-10-08 09:20:36 +00:00
return pd . DataFrame (
2024-01-19 11:16:07 +00:00
factors . values * load . values [ : , np . newaxis ] ,
index = load . index ,
2023-10-08 09:20:36 +00:00
columns = factors . index ,
)
2022-09-16 13:04:04 +00:00
load = pd . concat (
[
2024-07-19 17:23:35 +00:00
upsample ( cntry , group , gdp_pop_non_nuts3 )
for cntry , group in gdf_regions . geometry . groupby ( gdf_regions . country )
2022-09-16 13:04:04 +00:00
] ,
axis = 1 ,
)
2018-08-03 09:53:14 +00:00
2024-01-31 16:10:08 +00:00
n . madd (
" Load " , substation_lv_i , bus = substation_lv_i , p_set = load
) # carrier="electricity"
2018-01-29 21:28:33 +00:00
2022-03-24 13:47:00 +00:00
def update_transmission_costs ( n , costs , length_factor = 1.0 ) :
2022-01-11 08:38:34 +00:00
# TODO: line length factor of lines is applied to lines and links.
# Separate the function to distinguish.
2022-09-16 13:04:04 +00:00
n . lines [ " capital_cost " ] = (
n . lines [ " length " ] * length_factor * costs . at [ " HVAC overhead " , " capital_cost " ]
)
2018-01-29 21:28:33 +00:00
2022-09-16 13:04:04 +00:00
if n . links . empty :
return
2019-02-13 18:03:57 +00:00
2022-09-16 13:04:04 +00:00
dc_b = n . links . carrier == " DC "
2021-03-10 17:16:09 +00:00
# If there are no dc links, then the 'underwater_fraction' column
# may be missing. Therefore we have to return here.
2022-09-16 13:04:04 +00:00
if n . links . loc [ dc_b ] . empty :
return
costs = (
n . links . loc [ dc_b , " length " ]
* length_factor
* (
( 1.0 - n . links . loc [ dc_b , " underwater_fraction " ] )
* costs . at [ " HVDC overhead " , " capital_cost " ]
+ n . links . loc [ dc_b , " underwater_fraction " ]
* costs . at [ " HVDC submarine " , " capital_cost " ]
)
+ costs . at [ " HVDC inverter pair " , " capital_cost " ]
)
n . links . loc [ dc_b , " capital_cost " ] = costs
2019-11-19 18:36:28 +00:00
2018-01-29 21:28:33 +00:00
2022-09-16 13:04:04 +00:00
def attach_wind_and_solar (
2023-05-03 11:24:57 +00:00
n , costs , input_profiles , carriers , extendable_carriers , line_length_factor = 1
2022-09-16 13:04:04 +00:00
) :
2023-06-30 11:25:20 +00:00
add_missing_carriers ( n , carriers )
2023-05-03 11:24:57 +00:00
for car in carriers :
if car == " hydro " :
2022-06-09 18:31:50 +00:00
continue
2018-01-29 21:28:33 +00:00
2023-05-03 11:24:57 +00:00
with xr . open_dataset ( getattr ( input_profiles , " profile_ " + car ) ) as ds :
2022-09-16 13:04:04 +00:00
if ds . indexes [ " bus " ] . empty :
continue
2024-02-05 11:10:35 +00:00
# if-statement for compatibility with old profiles
if " year " in ds . indexes :
ds = ds . sel ( year = ds . year . min ( ) , drop = True )
2023-05-03 11:24:57 +00:00
supcar = car . split ( " - " , 2 ) [ 0 ]
if supcar == " offwind " :
2022-09-16 13:04:04 +00:00
underwater_fraction = ds [ " underwater_fraction " ] . to_pandas ( )
connection_cost = (
line_length_factor
* ds [ " average_distance " ] . to_pandas ( )
* (
underwater_fraction
2023-05-03 11:24:57 +00:00
* costs . at [ car + " -connection-submarine " , " capital_cost " ]
2022-09-16 13:04:04 +00:00
+ ( 1.0 - underwater_fraction )
2023-05-03 11:24:57 +00:00
* costs . at [ car + " -connection-underground " , " capital_cost " ]
2022-09-16 13:04:04 +00:00
)
)
capital_cost = (
costs . at [ " offwind " , " capital_cost " ]
2023-05-03 11:24:57 +00:00
+ costs . at [ car + " -station " , " capital_cost " ]
2022-09-16 13:04:04 +00:00
+ connection_cost
)
logger . info (
" Added connection cost of {:0.0f} - {:0.0f} Eur/MW/a to {} " . format (
2023-05-03 11:24:57 +00:00
connection_cost . min ( ) , connection_cost . max ( ) , car
2022-09-16 13:04:04 +00:00
)
)
2018-12-19 09:30:25 +00:00
else :
2023-05-03 11:24:57 +00:00
capital_cost = costs . at [ car , " capital_cost " ]
2018-01-29 21:28:33 +00:00
2022-09-16 13:04:04 +00:00
n . madd (
" Generator " ,
ds . indexes [ " bus " ] ,
2023-05-03 11:24:57 +00:00
" " + car ,
2022-09-16 13:04:04 +00:00
bus = ds . indexes [ " bus " ] ,
2023-05-03 11:24:57 +00:00
carrier = car ,
p_nom_extendable = car in extendable_carriers [ " Generator " ] ,
2022-09-16 13:04:04 +00:00
p_nom_max = ds [ " p_nom_max " ] . to_pandas ( ) ,
weight = ds [ " weight " ] . to_pandas ( ) ,
2023-05-03 11:24:57 +00:00
marginal_cost = costs . at [ supcar , " marginal_cost " ] ,
2022-09-16 13:04:04 +00:00
capital_cost = capital_cost ,
2023-05-03 11:24:57 +00:00
efficiency = costs . at [ supcar , " efficiency " ] ,
2022-09-16 13:04:04 +00:00
p_max_pu = ds [ " profile " ] . transpose ( " time " , " bus " ) . to_pandas ( ) ,
2023-08-23 11:24:25 +00:00
lifetime = costs . at [ supcar , " lifetime " ] ,
2022-09-16 13:04:04 +00:00
)
def attach_conventional_generators (
n ,
costs ,
ppl ,
conventional_carriers ,
extendable_carriers ,
2023-05-17 17:25:45 +00:00
conventional_params ,
2022-09-16 13:04:04 +00:00
conventional_inputs ,
2023-06-29 12:50:41 +00:00
unit_commitment = None ,
fuel_price = None ,
2022-09-16 13:04:04 +00:00
) :
2023-05-03 11:24:57 +00:00
carriers = list ( set ( conventional_carriers ) | set ( extendable_carriers [ " Generator " ] ) )
2020-12-03 18:50:53 +00:00
2023-07-26 05:24:12 +00:00
# Replace carrier "natural gas" with the respective technology (OCGT or
# CCGT) to align with PyPSA names of "carriers" and avoid filtering "natural
# gas" powerplants in ppl.query("carrier in @carriers")
ppl . loc [ ppl [ " carrier " ] == " natural gas " , " carrier " ] = ppl . loc [
ppl [ " carrier " ] == " natural gas " , " technology "
]
2020-09-11 10:40:53 +00:00
2022-09-16 13:04:04 +00:00
ppl = (
ppl . query ( " carrier in @carriers " )
. join ( costs , on = " carrier " , rsuffix = " _r " )
2023-10-08 09:20:36 +00:00
. rename ( index = lambda s : f " C { str ( s ) } " )
2022-09-16 13:04:04 +00:00
)
2022-06-09 22:54:54 +00:00
ppl [ " efficiency " ] = ppl . efficiency . fillna ( ppl . efficiency_r )
2018-07-10 14:29:11 +00:00
2024-05-25 19:13:47 +00:00
# reduce carriers to those in power plant dataset
carriers = list ( set ( carriers ) & set ( ppl . carrier . unique ( ) ) )
add_missing_carriers ( n , carriers )
add_co2_emissions ( n , costs , carriers )
2023-06-29 12:50:41 +00:00
if unit_commitment is not None :
2023-06-29 12:51:41 +00:00
committable_attrs = ppl . carrier . isin ( unit_commitment ) . to_frame ( " committable " )
2023-06-29 12:50:41 +00:00
for attr in unit_commitment . index :
2023-06-29 12:51:41 +00:00
default = pypsa . components . component_attrs [ " Generator " ] . default [ attr ]
committable_attrs [ attr ] = ppl . carrier . map ( unit_commitment . loc [ attr ] ) . fillna (
default
)
2023-06-29 12:50:41 +00:00
else :
committable_attrs = { }
if fuel_price is not None :
2023-06-29 12:51:41 +00:00
fuel_price = fuel_price . assign (
OCGT = fuel_price [ " gas " ] , CCGT = fuel_price [ " gas " ]
) . drop ( " gas " , axis = 1 )
2023-07-03 13:45:19 +00:00
missing_carriers = list ( set ( carriers ) - set ( fuel_price ) )
2023-06-29 12:50:41 +00:00
fuel_price = fuel_price . assign ( * * costs . fuel [ missing_carriers ] )
fuel_price = fuel_price . reindex ( ppl . carrier , axis = 1 )
fuel_price . columns = ppl . index
marginal_cost = fuel_price . div ( ppl . efficiency ) . add ( ppl . carrier . map ( costs . VOM ) )
else :
2023-06-29 12:51:41 +00:00
marginal_cost = (
ppl . carrier . map ( costs . VOM ) + ppl . carrier . map ( costs . fuel ) / ppl . efficiency
2022-06-09 18:31:50 +00:00
)
2018-07-10 14:29:11 +00:00
2023-06-29 12:50:41 +00:00
# Define generators using modified ppl DataFrame
caps = ppl . groupby ( " carrier " ) . p_nom . sum ( ) . div ( 1e3 ) . round ( 2 )
logger . info ( f " Adding { len ( ppl ) } generators with capacities [GW] \n { caps } " )
2022-09-16 13:04:04 +00:00
n . madd (
" Generator " ,
ppl . index ,
carrier = ppl . carrier ,
bus = ppl . bus ,
p_nom_min = ppl . p_nom . where ( ppl . carrier . isin ( conventional_carriers ) , 0 ) ,
p_nom = ppl . p_nom . where ( ppl . carrier . isin ( conventional_carriers ) , 0 ) ,
p_nom_extendable = ppl . carrier . isin ( extendable_carriers [ " Generator " ] ) ,
efficiency = ppl . efficiency ,
2023-05-16 14:15:51 +00:00
marginal_cost = marginal_cost ,
2022-09-16 13:04:04 +00:00
capital_cost = ppl . capital_cost ,
build_year = ppl . datein . fillna ( 0 ) . astype ( int ) ,
lifetime = ( ppl . dateout - ppl . datein ) . fillna ( np . inf ) ,
2023-06-29 12:51:41 +00:00
* * committable_attrs ,
2022-09-16 13:04:04 +00:00
)
2023-06-30 09:37:53 +00:00
for carrier in set ( conventional_params ) & set ( carriers ) :
2022-04-08 13:41:23 +00:00
# Generators with technology affected
2022-06-09 18:31:50 +00:00
idx = n . generators . query ( " carrier == @carrier " ) . index
2022-06-28 08:14:26 +00:00
2023-05-17 17:25:45 +00:00
for attr in list ( set ( conventional_params [ carrier ] ) & set ( n . generators ) ) :
values = conventional_params [ carrier ] [ attr ]
2022-06-28 08:14:26 +00:00
2022-06-28 14:33:46 +00:00
if f " conventional_ { carrier } _ { attr } " in conventional_inputs :
2022-06-28 08:14:26 +00:00
# Values affecting generators of technology k country-specific
# First map generator buses to countries; then map countries to p_max_pu
2023-05-12 12:51:39 +00:00
values = pd . read_csv (
snakemake . input [ f " conventional_ { carrier } _ { attr } " ] , index_col = 0
) . iloc [ : , 0 ]
2022-06-28 08:14:26 +00:00
bus_values = n . buses . country . map ( values )
2024-01-31 16:10:08 +00:00
n . generators . update (
{ attr : n . generators . loc [ idx ] . bus . map ( bus_values ) . dropna ( ) }
2022-09-16 13:04:04 +00:00
)
2022-06-28 08:14:26 +00:00
else :
# Single value affecting all generators of technology k indiscriminantely of country
2022-06-28 14:33:46 +00:00
n . generators . loc [ idx , attr ] = values
2020-09-11 10:40:53 +00:00
2018-01-29 21:28:33 +00:00
2023-05-17 17:25:45 +00:00
def attach_hydro ( n , costs , ppl , profile_hydro , hydro_capacities , carriers , * * params ) :
2023-06-30 11:25:20 +00:00
add_missing_carriers ( n , carriers )
2023-05-03 11:24:57 +00:00
add_co2_emissions ( n , costs , carriers )
2018-01-29 21:28:33 +00:00
2022-09-16 13:04:04 +00:00
ppl = (
ppl . query ( ' carrier == " hydro " ' )
. reset_index ( drop = True )
2023-10-08 09:20:36 +00:00
. rename ( index = lambda s : f " { str ( s ) } hydro " )
2022-09-16 13:04:04 +00:00
)
2019-10-30 22:09:41 +00:00
ror = ppl . query ( ' technology == " Run-Of-River " ' )
phs = ppl . query ( ' technology == " Pumped Storage " ' )
hydro = ppl . query ( ' technology == " Reservoir " ' )
2018-01-29 21:28:33 +00:00
2022-09-16 13:04:04 +00:00
country = ppl [ " bus " ] . map ( n . buses . country ) . rename ( " country " )
2019-02-22 16:09:52 +00:00
2021-04-27 05:51:46 +00:00
inflow_idx = ror . index . union ( hydro . index )
2019-10-30 22:09:41 +00:00
if not inflow_idx . empty :
2022-09-16 13:04:04 +00:00
dist_key = ppl . loc [ inflow_idx , " p_nom " ] . groupby ( country ) . transform ( normed )
2018-01-29 21:28:33 +00:00
2021-09-14 14:34:02 +00:00
with xr . open_dataarray ( profile_hydro ) as inflow :
2019-10-30 22:09:41 +00:00
inflow_countries = pd . Index ( country [ inflow_idx ] )
2022-09-16 13:04:04 +00:00
missing_c = inflow_countries . unique ( ) . difference (
inflow . indexes [ " countries " ]
)
assert missing_c . empty , (
f " ' { profile_hydro } ' is missing "
f " inflow time-series for at least one country: { ' , ' . join ( missing_c ) } "
)
inflow_t = (
inflow . sel ( countries = inflow_countries )
. rename ( { " countries " : " name " } )
. assign_coords ( name = inflow_idx )
. transpose ( " time " , " name " )
. to_pandas ( )
. multiply ( dist_key , axis = 1 )
)
if " ror " in carriers and not ror . empty :
n . madd (
" Generator " ,
ror . index ,
carrier = " ror " ,
bus = ror [ " bus " ] ,
p_nom = ror [ " p_nom " ] ,
efficiency = costs . at [ " ror " , " efficiency " ] ,
capital_cost = costs . at [ " ror " , " capital_cost " ] ,
weight = ror [ " p_nom " ] ,
p_max_pu = (
inflow_t [ ror . index ]
. divide ( ror [ " p_nom " ] , axis = 1 )
. where ( lambda df : df < = 1.0 , other = 1.0 )
) ,
)
if " PHS " in carriers and not phs . empty :
2023-05-17 17:25:45 +00:00
# fill missing max hours to params value and
2020-09-11 10:40:53 +00:00
# assume no natural inflow due to lack of data
2023-05-17 17:25:45 +00:00
max_hours = params . get ( " PHS_max_hours " , 6 )
2024-03-08 18:29:25 +00:00
phs = phs . replace ( { " max_hours " : { 0 : max_hours , np . nan : max_hours } } )
2022-09-16 13:04:04 +00:00
n . madd (
" StorageUnit " ,
phs . index ,
carrier = " PHS " ,
bus = phs [ " bus " ] ,
p_nom = phs [ " p_nom " ] ,
capital_cost = costs . at [ " PHS " , " capital_cost " ] ,
max_hours = phs [ " max_hours " ] ,
efficiency_store = np . sqrt ( costs . at [ " PHS " , " efficiency " ] ) ,
efficiency_dispatch = np . sqrt ( costs . at [ " PHS " , " efficiency " ] ) ,
cyclic_state_of_charge = True ,
)
if " hydro " in carriers and not hydro . empty :
2023-05-17 17:25:45 +00:00
hydro_max_hours = params . get ( " hydro_max_hours " )
2022-01-11 08:38:34 +00:00
assert hydro_max_hours is not None , " No path for hydro capacities given. "
2022-09-16 13:04:04 +00:00
hydro_stats = pd . read_csv (
hydro_capacities , comment = " # " , na_values = " - " , index_col = 0
)
2019-11-01 12:27:42 +00:00
e_target = hydro_stats [ " E_store[TWh] " ] . clip ( lower = 0.2 ) * 1e6
2022-09-16 13:04:04 +00:00
e_installed = hydro . eval ( " p_nom * max_hours " ) . groupby ( hydro . country ) . sum ( )
2019-11-01 12:27:42 +00:00
e_missing = e_target - e_installed
2023-02-02 13:59:50 +00:00
missing_mh_i = hydro . query ( " max_hours.isnull() " ) . index
2019-11-01 12:27:42 +00:00
2022-09-16 13:04:04 +00:00
if hydro_max_hours == " energy_capacity_totals_by_country " :
2019-11-01 12:27:42 +00:00
# watch out some p_nom values like IE's are totally underrepresented
2022-09-16 13:04:04 +00:00
max_hours_country = (
e_missing / hydro . loc [ missing_mh_i ] . groupby ( " country " ) . p_nom . sum ( )
)
2019-11-01 12:27:42 +00:00
2022-09-16 13:04:04 +00:00
elif hydro_max_hours == " estimate_by_large_installations " :
max_hours_country = (
hydro_stats [ " E_store[TWh] " ] * 1e3 / hydro_stats [ " p_nom_discharge[GW] " ]
)
2019-11-01 12:27:42 +00:00
2023-02-07 12:20:06 +00:00
max_hours_country . clip ( 0 , inplace = True )
2019-11-01 12:27:42 +00:00
2022-09-16 13:04:04 +00:00
missing_countries = pd . Index ( hydro [ " country " ] . unique ( ) ) . difference (
max_hours_country . dropna ( ) . index
)
2019-11-01 12:27:42 +00:00
if not missing_countries . empty :
2022-09-16 13:04:04 +00:00
logger . warning (
2023-10-08 09:20:36 +00:00
f ' Assuming max_hours=6 for hydro reservoirs in the countries: { " , " . join ( missing_countries ) } '
2022-09-16 13:04:04 +00:00
)
hydro_max_hours = hydro . max_hours . where (
hydro . max_hours > 0 , hydro . country . map ( max_hours_country )
) . fillna ( 6 )
2024-01-19 11:16:07 +00:00
if params . get ( " flatten_dispatch " , False ) :
2023-08-02 09:11:41 +00:00
buffer = params . get ( " flatten_dispatch_buffer " , 0.2 )
2023-07-21 11:23:00 +00:00
average_capacity_factor = inflow_t [ hydro . index ] . mean ( ) / hydro [ " p_nom " ]
p_max_pu = ( average_capacity_factor + buffer ) . clip ( upper = 1 )
else :
p_max_pu = 1
2022-09-16 13:04:04 +00:00
n . madd (
" StorageUnit " ,
hydro . index ,
carrier = " hydro " ,
bus = hydro [ " bus " ] ,
p_nom = hydro [ " p_nom " ] ,
max_hours = hydro_max_hours ,
capital_cost = costs . at [ " hydro " , " capital_cost " ] ,
marginal_cost = costs . at [ " hydro " , " marginal_cost " ] ,
2023-07-21 11:23:00 +00:00
p_max_pu = p_max_pu , # dispatch
2022-09-16 13:04:04 +00:00
p_min_pu = 0.0 , # store
efficiency_dispatch = costs . at [ " hydro " , " efficiency " ] ,
efficiency_store = 0.0 ,
cyclic_state_of_charge = True ,
inflow = inflow_t . loc [ : , hydro . index ] ,
)
2018-01-29 21:28:33 +00:00
2023-11-03 11:51:44 +00:00
def attach_OPSD_renewables ( n : pypsa . Network , tech_map : Dict [ str , List [ str ] ] ) - > None :
"""
Attach renewable capacities from the OPSD dataset to the network .
Args :
- n : The PyPSA network to attach the capacities to .
- tech_map : A dictionary mapping fuel types to carrier names .
Returns :
- None
"""
2022-06-09 18:31:50 +00:00
tech_string = " , " . join ( sum ( tech_map . values ( ) , [ ] ) )
2022-09-16 13:04:04 +00:00
logger . info ( f " Using OPSD renewable capacities for carriers { tech_string } . " )
2020-12-03 22:13:41 +00:00
2022-06-07 13:17:49 +00:00
df = pm . data . OPSD_VRE ( ) . powerplant . convert_country_to_alpha2 ( )
2022-09-16 13:04:04 +00:00
technology_b = ~ df . Technology . isin ( [ " Onshore " , " Offshore " ] )
df [ " Fueltype " ] = df . Fueltype . where ( technology_b , df . Technology ) . replace (
{ " Solar " : " PV " }
)
df = df . query ( " Fueltype in @tech_map " ) . powerplant . convert_country_to_alpha2 ( )
2023-06-29 17:22:33 +00:00
df = df . dropna ( subset = [ " lat " , " lon " ] )
2020-12-03 22:13:41 +00:00
2022-06-09 18:31:50 +00:00
for fueltype , carriers in tech_map . items ( ) :
gens = n . generators [ lambda df : df . carrier . isin ( carriers ) ]
2020-12-03 22:13:41 +00:00
buses = n . buses . loc [ gens . bus . unique ( ) ]
2022-09-16 13:04:04 +00:00
gens_per_bus = gens . groupby ( " bus " ) . p_nom . count ( )
2020-12-03 22:13:41 +00:00
2023-06-29 17:22:33 +00:00
caps = map_country_bus ( df . query ( " Fueltype == @fueltype " ) , buses )
2022-09-16 13:04:04 +00:00
caps = caps . groupby ( [ " bus " ] ) . Capacity . sum ( )
2020-12-03 22:13:41 +00:00
caps = caps / gens_per_bus . reindex ( caps . index , fill_value = 1 )
2024-01-31 16:10:08 +00:00
n . generators . update ( { " p_nom " : gens . bus . map ( caps ) . dropna ( ) } )
n . generators . update ( { " p_nom_min " : gens . bus . map ( caps ) . dropna ( ) } )
2020-12-03 22:13:41 +00:00
2023-11-03 11:51:44 +00:00
def estimate_renewable_capacities (
n : pypsa . Network , year : int , tech_map : dict , expansion_limit : bool , countries : list
) - > None :
"""
2023-11-03 13:16:09 +00:00
Estimate a different between renewable capacities in the network and
reported country totals from IRENASTAT dataset . Distribute the difference
with a heuristic .
2023-11-03 11:51:44 +00:00
Heuristic : n . generators_t . p_max_pu . mean ( ) * n . generators . p_nom_max
Args :
- n : The PyPSA network .
- year : The year of optimisation .
- tech_map : A dictionary mapping fuel types to carrier names .
- expansion_limit : Boolean value from config file
- countries : A list of country codes to estimate capacities for .
Returns :
- None
"""
2022-09-16 13:04:04 +00:00
if not len ( countries ) or not len ( tech_map ) :
return
2019-02-05 22:00:35 +00:00
2022-04-04 17:03:09 +00:00
capacities = pm . data . IRENASTAT ( ) . powerplant . convert_country_to_alpha2 ( )
2022-09-16 13:04:04 +00:00
capacities = capacities . query (
" Year == @year and Technology in @tech_map and Country in @countries "
)
2022-04-04 17:03:09 +00:00
capacities = capacities . groupby ( [ " Technology " , " Country " ] ) . Capacity . sum ( )
2020-12-03 22:13:41 +00:00
2022-09-16 13:04:04 +00:00
logger . info (
f " Heuristics applied to distribute renewable capacities [GW]: "
f " \n { capacities . groupby ( ' Technology ' ) . sum ( ) . div ( 1e3 ) . round ( 2 ) } "
)
2020-12-03 22:13:41 +00:00
2022-04-04 17:03:09 +00:00
for ppm_technology , techs in tech_map . items ( ) :
2022-09-16 13:04:04 +00:00
tech_i = n . generators . query ( " carrier in @techs " ) . index
2023-11-03 11:36:40 +00:00
if ppm_technology in capacities . index . get_level_values ( " Technology " ) :
stats = capacities . loc [ ppm_technology ] . reindex ( countries , fill_value = 0.0 )
else :
stats = pd . Series ( 0.0 , index = countries )
2022-06-09 18:31:50 +00:00
country = n . generators . bus [ tech_i ] . map ( n . buses . country )
2022-06-07 13:17:49 +00:00
existent = n . generators . p_nom [ tech_i ] . groupby ( country ) . sum ( )
missing = stats - existent
dist = n . generators_t . p_max_pu . mean ( ) * n . generators . p_nom_max
2022-09-16 13:04:04 +00:00
n . generators . loc [ tech_i , " p_nom " ] + = (
2022-06-07 13:17:49 +00:00
dist [ tech_i ]
. groupby ( country )
. transform ( lambda s : normed ( s ) * missing [ s . name ] )
2022-09-16 13:04:04 +00:00
. where ( lambda s : s > 0.1 , 0.0 ) # only capacities above 100kW
)
n . generators . loc [ tech_i , " p_nom_min " ] = n . generators . loc [ tech_i , " p_nom " ]
2018-01-29 21:28:33 +00:00
2022-04-04 17:03:09 +00:00
if expansion_limit :
assert np . isscalar ( expansion_limit )
2022-09-16 13:04:04 +00:00
logger . info (
f " Reducing capacity expansion limit to { expansion_limit * 100 : .2f } % of installed capacity. "
)
n . generators . loc [ tech_i , " p_nom_max " ] = (
expansion_limit * n . generators . loc [ tech_i , " p_nom_min " ]
)
2022-04-04 17:03:09 +00:00
2020-09-11 10:40:53 +00:00
2023-06-02 15:18:46 +00:00
def attach_line_rating (
n , rating , s_max_pu , correction_factor , max_voltage_difference , max_line_rating
) :
2022-09-06 14:40:00 +00:00
# TODO: Only considers overhead lines
2023-06-02 15:18:46 +00:00
n . lines_t . s_max_pu = ( rating / n . lines . s_nom [ rating . columns ] ) * correction_factor
2022-09-06 14:40:00 +00:00
if max_voltage_difference :
2023-06-02 15:18:46 +00:00
x_pu = (
n . lines . type . map ( n . line_types [ " x_per_length " ] )
* n . lines . length
/ ( n . lines . v_nom * * 2 )
)
# need to clip here as cap values might be below 1
2022-09-06 14:40:00 +00:00
# -> would mean the line cannot be operated at actual given pessimistic ampacity
2023-06-02 15:18:46 +00:00
s_max_pu_cap = (
np . deg2rad ( max_voltage_difference ) / ( x_pu * n . lines . s_nom )
) . clip ( lower = 1 )
n . lines_t . s_max_pu = n . lines_t . s_max_pu . clip (
lower = 1 , upper = s_max_pu_cap , axis = 1
)
2022-09-06 14:40:00 +00:00
if max_line_rating :
n . lines_t . s_max_pu = n . lines_t . s_max_pu . clip ( upper = max_line_rating )
n . lines_t . s_max_pu * = s_max_pu
2023-06-02 15:18:46 +00:00
2022-04-04 17:03:09 +00:00
2024-08-15 09:42:21 +00:00
def add_transmission_projects ( n , transmission_projects ) :
logger . info ( f " Adding transmission projects to network. " )
for path in transmission_projects :
path = Path ( path )
df = pd . read_csv ( path , index_col = 0 , dtype = { " bus0 " : str , " bus1 " : str } )
if df . empty :
continue
if " new_buses " in path . name :
n . madd ( " Bus " , df . index , * * df )
elif " new_lines " in path . name :
n . madd ( " Line " , df . index , * * df )
elif " new_links " in path . name :
n . madd ( " Link " , df . index , * * df )
elif " adjust_lines " :
n . lines . update ( df )
elif " adjust_links " :
n . links . update ( df )
2018-01-29 21:28:33 +00:00
if __name__ == " __main__ " :
2022-09-16 13:04:04 +00:00
if " snakemake " not in globals ( ) :
2019-12-09 20:29:15 +00:00
from _helpers import mock_snakemake
2023-04-29 10:41:37 +00:00
2024-03-04 16:48:56 +00:00
snakemake = mock_snakemake ( " add_electricity " )
2019-11-28 07:22:52 +00:00
configure_logging ( snakemake )
2023-08-15 13:02:41 +00:00
set_scenario_config ( snakemake )
2018-01-29 21:28:33 +00:00
2023-06-15 16:52:25 +00:00
params = snakemake . params
2018-01-29 21:28:33 +00:00
n = pypsa . Network ( snakemake . input . base_network )
2020-10-18 12:25:12 +00:00
2024-08-15 09:42:21 +00:00
if params [ " transmission_projects " ] [ " enable " ] :
add_transmission_projects ( n , snakemake . input . transmission_projects )
2024-03-14 14:15:56 +00:00
time = get_snapshots ( snakemake . params . snapshots , snakemake . params . drop_leap_day )
n . set_snapshots ( time )
2023-04-29 10:41:37 +00:00
Nyears = n . snapshot_weightings . objective . sum ( ) / 8760.0
2018-01-29 21:28:33 +00:00
2022-09-16 13:04:04 +00:00
costs = load_costs (
snakemake . input . tech_costs ,
2023-06-15 16:52:25 +00:00
params . costs ,
params . electricity [ " max_hours " ] ,
2022-09-16 13:04:04 +00:00
Nyears ,
)
2022-01-24 18:48:26 +00:00
ppl = load_powerplants ( snakemake . input . powerplants )
2018-01-29 21:28:33 +00:00
2022-09-16 13:04:04 +00:00
attach_load (
n ,
snakemake . input . regions ,
snakemake . input . load ,
snakemake . input . nuts3_shapes ,
2024-07-19 17:23:35 +00:00
snakemake . input . get ( " gdp_pop_non_nuts3 " ) ,
2023-06-15 16:52:25 +00:00
params . countries ,
params . scaling_factor ,
2022-09-16 13:04:04 +00:00
)
2023-06-15 16:52:25 +00:00
update_transmission_costs ( n , costs , params . length_factor )
2022-09-16 13:04:04 +00:00
2023-06-15 17:12:30 +00:00
renewable_carriers = set ( params . electricity [ " renewable_carriers " ] )
2023-06-15 16:52:25 +00:00
extendable_carriers = params . electricity [ " extendable_carriers " ]
conventional_carriers = params . electricity [ " conventional_carriers " ]
2022-09-16 13:04:04 +00:00
conventional_inputs = {
k : v for k , v in snakemake . input . items ( ) if k . startswith ( " conventional_ " )
}
2023-06-29 06:11:11 +00:00
2023-06-29 12:50:41 +00:00
if params . conventional [ " unit_commitment " ] :
unit_commitment = pd . read_csv ( snakemake . input . unit_commitment , index_col = 0 )
else :
unit_commitment = None
if params . conventional [ " dynamic_fuel_price " ] :
2023-07-06 17:14:01 +00:00
fuel_price = pd . read_csv (
snakemake . input . fuel_price , index_col = 0 , header = 0 , parse_dates = True
2023-06-29 12:51:41 +00:00
)
2024-07-29 09:56:14 +00:00
fuel_price = fuel_price . reindex ( n . snapshots ) . ffill ( )
2023-06-29 12:50:41 +00:00
else :
fuel_price = None
2022-09-16 13:04:04 +00:00
attach_conventional_generators (
n ,
costs ,
ppl ,
conventional_carriers ,
extendable_carriers ,
2023-06-15 16:52:25 +00:00
params . conventional ,
2022-09-16 13:04:04 +00:00
conventional_inputs ,
2023-06-29 12:50:41 +00:00
unit_commitment = unit_commitment ,
fuel_price = fuel_price ,
2022-09-16 13:04:04 +00:00
)
attach_wind_and_solar (
n ,
costs ,
snakemake . input ,
renewable_carriers ,
extendable_carriers ,
2023-06-15 16:52:25 +00:00
params . length_factor ,
2022-09-16 13:04:04 +00:00
)
if " hydro " in renewable_carriers :
2023-07-03 13:45:19 +00:00
p = params . renewable [ " hydro " ]
carriers = p . pop ( " carriers " , [ ] )
2022-09-16 13:04:04 +00:00
attach_hydro (
n ,
costs ,
ppl ,
snakemake . input . profile_hydro ,
snakemake . input . hydro_capacities ,
2023-07-03 13:45:19 +00:00
carriers ,
* * p ,
2022-09-16 13:04:04 +00:00
)
2021-09-14 14:34:02 +00:00
2023-06-15 16:52:25 +00:00
estimate_renewable_caps = params . electricity [ " estimate_renewable_capacities " ]
2022-09-16 13:04:04 +00:00
if estimate_renewable_caps [ " enable " ] :
2024-05-24 07:47:23 +00:00
if params . foresight != " overnight " :
logger . info (
" Skipping renewable capacity estimation because they are added later "
" in rule `add_existing_baseyear` with foresight mode ' myopic ' . "
)
else :
tech_map = estimate_renewable_caps [ " technology_mapping " ]
expansion_limit = estimate_renewable_caps [ " expansion_limit " ]
year = estimate_renewable_caps [ " year " ]
if estimate_renewable_caps [ " from_opsd " ] :
attach_OPSD_renewables ( n , tech_map )
estimate_renewable_capacities (
n , year , tech_map , expansion_limit , params . countries
)
2018-01-29 21:28:33 +00:00
2021-06-30 19:07:38 +00:00
update_p_nom_max ( n )
2020-12-03 22:13:41 +00:00
2022-09-06 14:40:00 +00:00
line_rating_config = snakemake . config [ " lines " ] [ " dynamic_line_rating " ]
if line_rating_config [ " activate " ] :
rating = xr . open_dataarray ( snakemake . input . line_rating ) . to_pandas ( ) . transpose ( )
2023-06-02 15:18:46 +00:00
s_max_pu = snakemake . config [ " lines " ] [ " s_max_pu " ]
correction_factor = line_rating_config [ " correction_factor " ]
max_voltage_difference = line_rating_config [ " max_voltage_difference " ]
max_line_rating = line_rating_config [ " max_line_rating " ]
2022-09-06 14:40:00 +00:00
attach_line_rating (
2023-06-02 15:18:46 +00:00
n ,
rating ,
s_max_pu ,
correction_factor ,
max_voltage_difference ,
max_line_rating ,
2022-09-06 14:40:00 +00:00
)
2020-12-03 22:13:41 +00:00
2023-05-03 11:24:57 +00:00
sanitize_carriers ( n , snakemake . config )
2019-02-05 22:00:35 +00:00
2022-06-30 06:39:03 +00:00
n . meta = snakemake . config
2018-01-29 21:28:33 +00:00
n . export_to_netcdf ( snakemake . output [ 0 ] )