# -*- coding: utf-8 -*-
"""
Implementation of the standard load profiles
SPDX-FileCopyrightText: Birgit Schachler
SPDX-FileCopyrightText: Uwe Krien <krien@uni-bremen.de>
SPDX-FileCopyrightText: jnnr
SPDX-FileCopyrightText: Stephen Bosch
SPDX-FileCopyrightText: Patrik Schönfeldt
SPDX-License-Identifier: MIT
"""
import calendar
import datetime
import os
import pandas as pd
from demandlib.tools import add_weekdays2df
[docs]class ElecSlp:
"""Generate electrical standardized load profiles based on the BDEW method.
Attributes
----------
datapath : string
Path to the csv files containing the load profile data.
date_time_index : pandas.DateTimeIndex
Time range for and frequency for the profile.
Parameters
----------
year : integer
Year of the demand series.
Optional Parameters
-------------------
seasons : dictionary
Describing the time ranges for summer, winter and transition periods.
holidays : dictionary or list
The keys of the dictionary or the items of the list should be datetime
objects of the days that are holidays.
"""
def __init__(self, year, seasons=None, holidays=None):
if calendar.isleap(year):
hoy = 8784
else:
hoy = 8760
self.datapath = os.path.join(os.path.dirname(__file__), "bdew_data")
self.date_time_index = pd.date_range(
datetime.datetime(year, 1, 1, 0), periods=hoy * 4, freq="15Min"
)
if seasons is None:
self.seasons = {
"summer1": [5, 15, 9, 14], # summer: 15.05. to 14.09
"transition1": [3, 21, 5, 14], # transition1 :21.03. to 14.05
"transition2": [9, 15, 10, 31], # transition2 :15.09. to 31.10
"winter1": [1, 1, 3, 20], # winter1: 01.01. to 20.03
"winter2": [11, 1, 12, 31], # winter2: 01.11. to 31.12
}
else:
self.seasons = seasons
self.year = year
# Create the default profiles
self.slp_frame = self.all_load_profiles(
self.date_time_index, holidays=holidays
)
# Add the dynamic H0 profile
self.create_dynamic_h0_profile()
[docs] def all_load_profiles(self, time_df, holidays=None):
slp_types = [
"h0",
"g0",
"g1",
"g2",
"g3",
"g4",
"g5",
"g6",
"l0",
"l1",
"l2",
]
new_df = self.create_bdew_load_profiles(
time_df, slp_types, holidays=holidays
)
return new_df
[docs] def create_bdew_load_profiles(self, dt_index, slp_types, holidays=None):
"""
Calculates the hourly electricity load profile in MWh/h of a region.
"""
# define file path of slp csv data
file_path = os.path.join(self.datapath, "selp_series.csv")
# Read standard load profile series from csv file
selp_series = pd.read_csv(file_path)
tmp_df = selp_series
# Create an index to merge. The year and month will be ignored only the
# time index is necessary.
index = pd.date_range(
datetime.datetime(2007, 1, 1, 0), periods=2016, freq="15Min"
)
tmp_df.set_index(index, inplace=True)
# Create empty DataFrame to take the results.
new_df = pd.DataFrame(index=dt_index, columns=slp_types).fillna(0)
new_df = add_weekdays2df(
new_df, holidays=holidays, holiday_is_sunday=True
)
new_df["hour"] = dt_index.hour
new_df["minute"] = dt_index.minute
time_df = new_df[["date", "hour", "minute", "weekday"]].copy()
tmp_df[slp_types] = tmp_df[slp_types].astype(float)
# Inner join the slps on the time_df to the slp's for a whole year
tmp_df["hour_of_day"] = tmp_df.index.hour
tmp_df["minute_of_hour"] = tmp_df.index.minute
left_cols = ["hour_of_day", "minute_of_hour", "weekday"]
right_cols = ["hour", "minute", "weekday"]
tmp_df = tmp_df.reset_index(drop=True)
for p in self.seasons.keys():
a = datetime.datetime(
self.year, self.seasons[p][0], self.seasons[p][1], 0, 0
)
b = datetime.datetime(
self.year, self.seasons[p][2], self.seasons[p][3], 23, 59
)
merged_df = pd.DataFrame.merge(
tmp_df[tmp_df["period"] == p[:-1]],
time_df[a:b],
left_on=left_cols,
right_on=right_cols,
how="inner",
).drop(labels=["hour_of_day"], axis=1)
merged_df.index = (
pd.to_datetime(merged_df["date"])
+ pd.to_timedelta(merged_df["hour"], unit="h")
+ pd.to_timedelta(merged_df["minute"], unit="m")
)
merged_df.sort_index(inplace=True)
new_df.update(merged_df)
new_df.drop(
["date", "minute", "hour", "weekday"], axis=1, inplace=True
)
return new_df.div(new_df.sum(axis=0), axis=1)
[docs] def create_dynamic_h0_profile(self):
"""
Use the dynamisation function of the BDEW to smoothen the seasonal
edges. Functions resolution is daily.
.. math::
f(x) = -3.916649251 * 10^-10 * x^4 + 3,2 * 10^-7 * x³ - 7,02
* 10^-5 * x²+0,0021 * x +1,24
Adjustment of accuracy: from -3,92 to -3.916649251
"""
# Create a Series with the day of the year as decimal number
decimal_day = pd.Series(
[((q + 1) / (24 * 4)) for q in range(len(self.slp_frame))],
index=self.slp_frame.index,
)
# Calculate the smoothing factor of the BDEW dynamic H0 profile
smoothing_factor = (
-3.916649251 * 10**-10 * decimal_day**4
+ 3.2 * 10**-7 * decimal_day**3
- 7.02 * 10**-5 * decimal_day**2
+ 0.0021 * decimal_day
+ 1.24
)
# Multiply the smoothing factor with the default H0 profile
self.slp_frame["h0_dyn"] = self.slp_frame["h0"].mul(smoothing_factor)
return self.slp_frame["h0_dyn"]
[docs] def get_profile(self, ann_el_demand_per_sector):
"""Get the profiles for the given annual demand
Parameters
----------
ann_el_demand_per_sector : dictionary
Key: sector, value: annual value
Returns
-------
pandas.DataFrame : Table with all profiles
"""
return (
self.slp_frame.multiply(
pd.Series(ann_el_demand_per_sector), axis=1
).dropna(how="all", axis=1)
* 4
)