Implement line rating

This commit is contained in:
Philipp Glaum 2021-12-21 07:59:02 +01:00
parent 47ab49e86b
commit 8febb73bf1
4 changed files with 375 additions and 3 deletions

View File

@ -215,12 +215,25 @@ if 'hydro' in config['renewable'].keys():
resources: mem=5000 resources: mem=5000
script: 'scripts/build_hydro_profile.py' script: 'scripts/build_hydro_profile.py'
if config['lines'].get('line_rating', False):
rule build_line_rating:
input:
base_network="networks/base.nc",
cutout="cutouts/" + config["lines"]['cutout'] + ".nc"
output:
output="resources/line_rating.nc"
log: "logs/build_line_rating.log"
benchmark: "benchmarks/build_line_rating"
threads: ATLITE_NPROCESSES
resources: mem=ATLITE_NPROCESSES * 1000
script: "scripts/build_line_rating.py"
rule add_electricity: rule add_electricity:
input: input:
base_network='networks/base.nc', base_network = "networks/base.nc",
tech_costs=COSTS, tech_costs=COSTS,
regions="resources/regions_onshore.geojson", regions="resources/regions_onshore.geojson",
line_rating="resources/line_rating.nc" if config['lines'].get('line_rating', False) else None,
powerplants='resources/powerplants.csv', powerplants='resources/powerplants.csv',
hydro_capacities='data/bundle/hydro_capacities.csv', hydro_capacities='data/bundle/hydro_capacities.csv',
geth_hydro_capacities='data/geth2015_hydro_capacities.csv', geth_hydro_capacities='data/geth2015_hydro_capacities.csv',
@ -398,4 +411,3 @@ rule plot_p_nom_max:
output: "results/plots/elec_s{simpl}_cum_p_nom_max_{clusts}_{techs}_{country}.{ext}" output: "results/plots/elec_s{simpl}_cum_p_nom_max_{clusts}_{techs}_{country}.{ext}"
log: "logs/plot_p_nom_max/elec_s{simpl}_{clusts}_{techs}_{country}_{ext}.log" log: "logs/plot_p_nom_max/elec_s{simpl}_{clusts}_{techs}_{country}_{ext}.log"
script: "scripts/plot_p_nom_max.py" script: "scripts/plot_p_nom_max.py"

265
config.yaml Normal file
View File

@ -0,0 +1,265 @@
# SPDX-FileCopyrightText: : 2017-2020 The PyPSA-Eur Authors
#
# SPDX-License-Identifier: CC0-1.0
version: 0.4.0
tutorial: true
logging:
level: INFO
format: '%(levelname)s:%(name)s:%(message)s'
summary_dir: results
scenario:
simpl: ['']
ll: ['copt']
clusters: [5]
opts: [Co2L-24H]
countries: ['DE']
clustering:
simplify:
to_substations: false # network is simplified to nodes with positive or negative power injection (i.e. substations or offwind connections)
snapshots:
start: "2013-03-01"
end: "2013-03-07"
closed: 'left' # end is not inclusive
enable:
prepare_links_p_nom: false
retrieve_databundle: true
build_cutout: true
retrieve_cutout: false
build_natura_raster: false
retrieve_natura_raster: true
custom_busmap: false
electricity:
voltages: [220., 300., 380.]
co2limit: 100.e+6
extendable_carriers:
Generator: [OCGT]
StorageUnit: [] #battery, H2
Store: [battery, H2]
Link: []
max_hours:
battery: 6
H2: 168
powerplants_filter: false # use pandas query strings here, e.g. Country not in ['Germany']
custom_powerplants: false # use pandas query strings here, e.g. Country in ['Germany']
conventional_carriers: [coal, CCGT] # [nuclear, oil, OCGT, CCGT, coal, lignite, geothermal, biomass]
atlite:
nprocesses: 4
cutouts:
europe-2013-era5-tutorial:
module: era5
x: [4., 15.]
y: [46., 56.]
time: ["2013-03", "2013-03"]
renewable:
onwind:
cutout: europe-2013-era5-tutorial
resource:
method: wind
turbine: Vestas_V112_3MW
capacity_per_sqkm: 3 # ScholzPhd Tab 4.3.1: 10MW/km^2
# correction_factor: 0.93
corine:
# Scholz, Y. (2012). Renewable energy based electricity supply at low costs:
# development of the REMix model and application for Europe. ( p.42 / p.28)
grid_codes: [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 31, 32]
distance: 1000
distance_grid_codes: [1, 2, 3, 4, 5, 6]
natura: true
potential: simple # or conservative
clip_p_max_pu: 1.e-2
offwind-ac:
cutout: europe-2013-era5-tutorial
resource:
method: wind
turbine: NREL_ReferenceTurbine_5MW_offshore
capacity_per_sqkm: 3
# correction_factor: 0.93
corine: [44, 255]
natura: true
max_shore_distance: 30000
potential: simple # or conservative
clip_p_max_pu: 1.e-2
offwind-dc:
cutout: europe-2013-era5-tutorial
resource:
method: wind
turbine: NREL_ReferenceTurbine_5MW_offshore
# ScholzPhd Tab 4.3.1: 10MW/km^2
capacity_per_sqkm: 3
# correction_factor: 0.93
corine: [44, 255]
natura: true
min_shore_distance: 30000
potential: simple # or conservative
clip_p_max_pu: 1.e-2
solar:
cutout: europe-2013-era5-tutorial
resource:
method: pv
panel: CSi
orientation:
slope: 35.
azimuth: 180.
capacity_per_sqkm: 1.7 # ScholzPhd Tab 4.3.1: 170 MW/km^2
# Determined by comparing uncorrected area-weighted full-load hours to those
# published in Supplementary Data to
# Pietzcker, Robert Carl, et al. "Using the sun to decarbonize the power
# sector: The economic potential of photovoltaics and concentrating solar
# power." Applied Energy 135 (2014): 704-720.
correction_factor: 0.854337
corine: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
14, 15, 16, 17, 18, 19, 20, 26, 31, 32]
natura: true
potential: simple # or conservative
clip_p_max_pu: 1.e-2
lines:
types:
220.: "Al/St 240/40 2-bundle 220.0"
300.: "Al/St 240/40 3-bundle 300.0"
380.: "Al/St 240/40 4-bundle 380.0"
s_max_pu: 0.7
s_nom_max: .inf
length_factor: 1.25
under_construction: 'zero' # 'zero': set capacity to zero, 'remove': remove, 'keep': with full capacity
cutout: "europe-2013-era5-tutorial"
line_rating: true
links:
p_max_pu: 1.0
p_nom_max: .inf
include_tyndp: true
under_construction: 'zero' # 'zero': set capacity to zero, 'remove': remove, 'keep': with full capacity
transformers:
x: 0.1
s_nom: 2000.
type: ''
load:
power_statistics: True # only for files from <2019; set false in order to get ENTSOE transparency data
interpolate_limit: 3 # data gaps up until this size are interpolated linearly
time_shift_for_large_gaps: 1w # data gaps up until this size are copied by copying from
manual_adjustments: true # false
scaling_factor: 1.0
costs:
year: 2030
discountrate: 0.07 # From a Lion Hirth paper, also reflects average of Noothout et al 2016
USD2013_to_EUR2013: 0.7532 # [EUR/USD] ECB: https://www.ecb.europa.eu/stats/exchange/eurofxref/html/eurofxref-graph-usd.en.html
marginal_cost:
solar: 0.01
onwind: 0.015
offwind: 0.015
H2: 0.
battery: 0.
emission_prices: # in currency per tonne emission, only used with the option Ep
co2: 0.
solving:
options:
formulation: kirchhoff
load_shedding: false
noisy_costs: true
min_iterations: 1
max_iterations: 1
clip_p_max_pu: 0.01
skip_iterations: false
track_iterations: false
solver:
name: cbc
plotting:
map:
figsize: [7, 7]
boundaries: [-10.2, 29, 35, 72]
p_nom:
bus_size_factor: 5.e+4
linewidth_factor: 3.e+3
costs_max: 800
costs_threshold: 1
energy_max: 15000.
energy_min: -10000.
energy_threshold: 50.
vre_techs: ["onwind", "offwind-ac", "offwind-dc", "solar", "ror"]
conv_techs: ["OCGT", "CCGT", "Nuclear", "Coal"]
storage_techs: ["hydro+PHS", "battery", "H2"]
load_carriers: ["AC load"]
AC_carriers: ["AC line", "AC transformer"]
link_carriers: ["DC line", "Converter AC-DC"]
tech_colors:
"onwind" : "#235ebc"
"onshore wind" : "#235ebc"
'offwind' : "#6895dd"
'offwind-ac' : "#6895dd"
'offshore wind' : "#6895dd"
'offshore wind ac' : "#6895dd"
'offwind-dc' : "#74c6f2"
'offshore wind dc' : "#74c6f2"
"hydro" : "#08ad97"
"hydro+PHS" : "#08ad97"
"PHS" : "#08ad97"
"hydro reservoir" : "#08ad97"
'hydroelectricity' : '#08ad97'
"ror" : "#4adbc8"
"run of river" : "#4adbc8"
'solar' : "#f9d002"
'solar PV' : "#f9d002"
'solar thermal' : '#ffef60'
'biomass' : '#0c6013'
'solid biomass' : '#06540d'
'biogas' : '#23932d'
'waste' : '#68896b'
'geothermal' : '#ba91b1'
"OCGT" : "#d35050"
"gas" : "#d35050"
"natural gas" : "#d35050"
"CCGT" : "#b20101"
"nuclear" : "#ff9000"
"coal" : "#707070"
"lignite" : "#9e5a01"
"oil" : "#262626"
"H2" : "#ea048a"
"hydrogen storage" : "#ea048a"
"battery" : "#b8ea04"
"Electric load" : "#f9d002"
"electricity" : "#f9d002"
"lines" : "#70af1d"
"transmission lines" : "#70af1d"
"AC-AC" : "#70af1d"
"AC line" : "#70af1d"
"links" : "#8a1caf"
"HVDC links" : "#8a1caf"
"DC-DC" : "#8a1caf"
"DC link" : "#8a1caf"
nice_names:
OCGT: "Open-Cycle Gas"
CCGT: "Combined-Cycle Gas"
offwind-ac: "Offshore Wind (AC)"
offwind-dc: "Offshore Wind (DC)"
onwind: "Onshore Wind"
solar: "Solar"
PHS: "Pumped Hydro Storage"
hydro: "Reservoir & Dam"
battery: "Battery Storage"
H2: "Hydrogen Storage"
lines: "Transmission Lines"
ror: "Run of River"

View File

@ -539,6 +539,10 @@ def estimate_renewable_capacities(n, tech_map=None):
.where(lambda s: s>0.1, 0.)) # only capacities above 100kW .where(lambda s: s>0.1, 0.)) # only capacities above 100kW
n.generators.loc[tech_i, 'p_nom_min'] = n.generators.loc[tech_i, 'p_nom'] n.generators.loc[tech_i, 'p_nom_min'] = n.generators.loc[tech_i, 'p_nom']
def attach_line_rating(n):
if snakemake.config["lines"]["line_rating"]:
s_max=xr.open_dataarray(snakemake.input.line_rating).to_pandas().transpose()
n.lines_t.s_max_pu=s_max/n.lines.loc[s_max.columns,:]['s_nom'] #only considers overhead lines
def add_nice_carrier_names(n, config=None): def add_nice_carrier_names(n, config=None):
if config is None: config = snakemake.config if config is None: config = snakemake.config
@ -575,9 +579,11 @@ if __name__ == "__main__":
attach_hydro(n, costs, ppl) attach_hydro(n, costs, ppl)
attach_extendable_generators(n, costs, ppl) attach_extendable_generators(n, costs, ppl)
estimate_renewable_capacities(n) estimate_renewable_capacities(n)
attach_OPSD_renewables(n) attach_OPSD_renewables(n)
update_p_nom_max(n) update_p_nom_max(n)
attach_line_rating(n)
add_nice_carrier_names(n) add_nice_carrier_names(n)

View File

@ -0,0 +1,89 @@
# SPDX-FileCopyrightText: : 2017-2020 The PyPSA-Eur Authors
#
# SPDX-License-Identifier: MIT
# coding: utf-8
"""
Adds dynamic line rating timeseries to the base network.
Relevant Settings
-----------------
.. code:: yaml
lines_t:
s_max_pu
.. seealso::
Documentation of the configuration file ``config.yaml`
Inputs
------
- ``data/cutouts``:
- ``networks/base.nc``: confer :ref:`base`
Outputs
-------
- ``networks/base_with_line_rating.nc``
Description
-----------
The rule :mod:`build_line_rating` calculates the line rating for transmission lines.
The line rating provides the maximal capacity of a transmission line considering the heat exchange with the environment.
The folloing heat gains and losses are considered:
- heat gain through resistive losses
- heat gain trough solar radiation
- heat loss through radiation of the trasnmission line
- heat loss through forced convection with wind
- heat loss through natural convection
With a heat balance considering the maximum temperature threshold of the tranmission line,
the maximal possible capacity factor "s_max_pu" for each transmission line at each time step is calculated.
"""
import logging
from _helpers import configure_logging
import pypsa
import pandas as pd
import numpy as np
import geopandas as gpd
from shapely.geometry import Point, LineString as Line
import atlite
import xarray as xr
def calculate_line_rating(n):
relevant_lines=n.lines[(n.lines['underground']==False) & (n.lines['under_construction']==False)]
buses = relevant_lines[["bus0", "bus1"]].values
x = n.buses.x
y = n.buses.y
shapes = [Line([Point(x[b0], y[b0]), Point(x[b1], y[b1])]) for (b0, b1) in buses]
shapes = gpd.GeoSeries(shapes, index=relevant_lines.index)
cutout = atlite.Cutout(snakemake.input.cutout)
if relevant_lines.r_pu.eq(0).all():
#Overwrite standard line resistance with line resistance obtained from line type
relevant_lines["r_pu"]=relevant_lines.join(n.line_types["r_per_length"], on=["type"])['r_per_length']/1000 #in meters
Imax=cutout.line_rating(shapes, relevant_lines.r_pu)
da = xr.DataArray(data=np.sqrt(3) * Imax * relevant_lines["v_nom"].values.reshape(-1,1) * relevant_lines["num_parallel"].values.reshape(-1,1)/1e3, #in mW
attrs=dict(description="Maximal possible power in MW for given line considering line rating"))
return da
if __name__ == "__main__":
if 'snakemake' not in globals():
from _helpers import mock_snakemake
snakemake = mock_snakemake('build_line_rating', network='elec', simpl='',
clusters='6', ll='copt', opts='Co2L-24H')
configure_logging(snakemake)
n = pypsa.Network(snakemake.input.base_network)
da=calculate_line_rating(n)
da.to_netcdf(snakemake.output[0])