pypsa-eur/scripts/build_line_rating.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

180 lines
5.2 KiB
Python
Raw Permalink Normal View History

2021-12-14 09:59:27 +00:00
# -*- coding: utf-8 -*-
# SPDX-FileCopyrightText: : 2017-2020 The PyPSA-Eur Authors
#
# SPDX-License-Identifier: MIT
# coding: utf-8
"""
Calculates dynamic line rating time series from base network.
2021-12-14 09:59:27 +00:00
Relevant Settings
-----------------
.. code:: yaml
lines:
cutout:
dynamic_line_rating:
2021-12-14 09:59:27 +00:00
.. seealso::
Documentation of the configuration file ``config.yaml`
2021-12-14 09:59:27 +00:00
Inputs
------
- ``data/cutouts``:
2021-12-14 09:59:27 +00:00
- ``networks/base.nc``: confer :ref:`base`
Outputs
-------
- ``resources/dlr.nc``
2021-12-14 09:59:27 +00:00
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.
2021-12-14 09:59:27 +00:00
2023-07-03 17:31:34 +00:00
The following heat gains and losses are considered:
2021-12-14 09:59:27 +00:00
- heat gain through resistive losses
2023-07-03 17:31:34 +00:00
- heat gain through solar radiation
2023-10-04 15:31:46 +00:00
- heat loss through radiation of the transmission line
2021-12-14 09:59:27 +00:00
- heat loss through forced convection with wind
- heat loss through natural convection
2021-12-14 09:59:27 +00:00
2023-07-03 17:31:34 +00:00
With a heat balance considering the maximum temperature threshold of the transmission line,
2021-12-14 09:59:27 +00:00
the maximal possible capacity factor "s_max_pu" for each transmission line at each time step is calculated.
"""
import logging
2021-12-14 09:59:27 +00:00
import re
import atlite
import geopandas as gpd
import numpy as np
import pypsa
import xarray as xr
from _helpers import configure_logging, get_snapshots, set_scenario_config
from dask.distributed import Client
from shapely.geometry import LineString as Line
2021-12-14 09:59:27 +00:00
from shapely.geometry import Point
logger = logging.getLogger(__name__)
2022-02-16 13:32:33 +00:00
def calculate_resistance(T, R_ref, T_ref: float | int = 293, alpha: float = 0.00403):
"""
Calculates the resistance at other temperatures than the reference
temperature.
Parameters
----------
T : Temperature at which resistance is calculated in [°C] or [K]
R_ref : Resistance at reference temperature in [Ohm] or [Ohm/Per Length Unit]
T_ref : Reference temperature in [°C] or [K]
alpha: Temperature coefficient in [1/K]
Defaults are:
* T_ref : 20 °C
* alpha : 0.00403 1/K
Returns
-------
Resistance of at given temperature.
"""
2023-10-08 09:20:36 +00:00
return R_ref * (1 + alpha * (T - T_ref))
2021-12-14 09:59:27 +00:00
2022-02-16 13:32:33 +00:00
def calculate_line_rating(
n: pypsa.Network,
cutout: atlite.Cutout,
show_progress: bool = True,
dask_kwargs: dict = None,
) -> xr.DataArray:
"""
Calculates the maximal allowed power flow in each line for each time step
considering the maximal temperature.
Parameters
----------
n : pypsa.Network object containing information on grid
Returns
-------
xarray DataArray object with maximal power.
"""
if dask_kwargs is None:
dask_kwargs = {}
logger.info("Calculating dynamic line rating.")
2024-01-31 16:10:08 +00:00
relevant_lines = n.lines[~n.lines["underground"]].copy()
buses = relevant_lines[["bus0", "bus1"]].values
2021-12-14 09:59:27 +00:00
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)
if relevant_lines.r_pu.eq(0).all():
2022-02-16 13:32:33 +00:00
# Overwrite standard line resistance with line resistance obtained from line type
r_per_length = n.line_types["r_per_length"]
R = (
relevant_lines.join(r_per_length, on=["type"])["r_per_length"] / 1000
) # in meters
# If line type with bundles is given retrieve number of conductors per bundle
relevant_lines["n_bundle"] = (
relevant_lines["type"]
.where(relevant_lines["type"].str.contains("bundle"))
.dropna()
.apply(lambda x: int(re.findall(r"(\d+)-bundle", x)[0]))
)
# Set default number of bundles per line
2024-01-02 15:31:48 +00:00
relevant_lines["n_bundle"] = relevant_lines["n_bundle"].fillna(1)
2022-02-16 13:32:33 +00:00
R *= relevant_lines["n_bundle"]
R = calculate_resistance(T=353, R_ref=R)
Imax = cutout.line_rating(
shapes,
R,
D=0.0218,
Ts=353,
epsilon=0.8,
alpha=0.8,
show_progress=show_progress,
dask_kwargs=dask_kwargs,
)
2022-02-16 13:32:33 +00:00
line_factor = relevant_lines.eval("v_nom * n_bundle * num_parallel") / 1e3 # in mW
2023-10-08 09:20:36 +00:00
return xr.DataArray(
2022-02-16 13:32:33 +00:00
data=np.sqrt(3) * Imax * line_factor.values.reshape(-1, 1),
attrs=dict(
description="Maximal possible power in MW for given line considering line rating"
),
)
2021-12-14 09:59:27 +00:00
2022-02-16 13:32:33 +00:00
2021-12-14 09:59:27 +00:00
if __name__ == "__main__":
2022-02-16 13:32:33 +00:00
if "snakemake" not in globals():
2021-12-14 09:59:27 +00:00
from _helpers import mock_snakemake
2022-02-16 13:32:33 +00:00
snakemake = mock_snakemake("build_line_rating")
2021-12-14 09:59:27 +00:00
configure_logging(snakemake)
2023-08-15 13:02:41 +00:00
set_scenario_config(snakemake)
2021-12-14 09:59:27 +00:00
nprocesses = int(snakemake.threads)
show_progress = not snakemake.config["run"].get("disable_progressbar", True)
show_progress = show_progress and snakemake.config["atlite"]["show_progress"]
if nprocesses > 1:
client = Client(n_workers=nprocesses, threads_per_worker=1)
else:
client = None
dask_kwargs = {"scheduler": client}
2022-02-04 14:58:29 +00:00
n = pypsa.Network(snakemake.input.base_network)
time = get_snapshots(snakemake.params.snapshots, snakemake.params.drop_leap_day)
cutout = atlite.Cutout(snakemake.input.cutout).sel(time=time)
2021-12-14 09:59:27 +00:00
da = calculate_line_rating(n, cutout, show_progress, dask_kwargs)
2022-02-16 13:32:33 +00:00
da.to_netcdf(snakemake.output[0])