# -*- coding: utf-8 -*-
"""
Implementation of the bdew heat 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 os
from math import ceil
import numpy as np
import pandas as pd
from demandlib.tools import add_weekdays2df
[docs]class HeatBuilding:
"""
Parameters
----------
year : int
year for which the profile is created
Attributes
----------
datapath : string
path to the bdew basic data files (csv)
temperature : pandas.Series
Series containing hourly temperature data
annual_heat_demand : float
annual heat demand of building in kWh
building_class: int
class of building according to bdew classification
possible numbers are: 1 - 11
shlp_type : string
type of standardized heat load profile according to bdew
possible types are:
GMF, GPD, GHD, GWA, GGB, EFH, GKO, MFH, GBD, GBA, GMK, GBH, GGA, GHA
wind_class : int
wind classification for building location (0=not windy or 1=windy)
ww_incl : boolean
decider whether warm water load is included in the heat load profile
"""
def __init__(self, df_index, **kwargs):
self.datapath = kwargs.get(
"datapath", os.path.join(os.path.dirname(__file__), "bdew_data")
)
self.df = pd.DataFrame(index=df_index)
self.df = add_weekdays2df(
self.df, holiday_is_sunday=True, holidays=kwargs.get("holidays")
)
self.df["hour"] = self.df.index.hour + 1 # hour of the day
self.temperature = kwargs.get("temperature")
self.annual_heat_demand = kwargs.get("annual_heat_demand")
self.shlp_type = kwargs.get("shlp_type").upper()
self.wind_class = kwargs.get("wind_class")
self.building_class = kwargs.get("building_class", 0)
self.ww_incl = kwargs.get("ww_incl", True)
self.name = kwargs.get("name", self.shlp_type)
[docs] def weighted_temperature(self, how="geometric_series"):
r"""
A new temperature vector is generated containing a multi-day
average temperature as needed in the load profile function.
Parameters
----------
how : string
string which type to return ("geometric_series" or "mean")
Notes
-----
Equation for the mathematical series of the average
temperature [1]_:
.. math::
T=\frac{T_{D}+0.5\cdot T_{D-1}+0.25\cdot T_{D-2}+
0.125\cdot T_{D-3}}{1+0.5+0.25+0.125}
with :math:`T_D` = Average temperature on the present day
:math:`T_{D-i}` = Average temperature on the day - i
References
----------
.. [1] `BDEW <https://www.avacon-netz.de/content/dam/revu-global/avacon-netz/documents/Energie_anschliessen/netzzugang-gas/Leitfaden_20180329_Abwicklung-Standardlastprofile-Gas.pdf>`_, # noqa: E501
BDEW Documentation for heat profiles.
"""
# calculate daily mean temperature
temperature = (
self.df["temperature"]
.resample("D")
.mean()
.reindex(self.df.index)
.fillna(method="ffill")
.fillna(method="bfill")
)
if how == "geometric_series":
temperature_mean = (
temperature
+ 0.5 * np.roll(temperature, 24)
+ 0.25 * np.roll(temperature, 48)
+ 0.125 * np.roll(temperature, 72)
) / 1.875
elif how == "mean":
temperature_mean = temperature
else:
temperature_mean = None
return temperature_mean
[docs] def get_temperature_interval(self):
"""Appoints the corresponding temperature interval to each temperature
in the temperature vector.
"""
intervals = {
-20: 1,
-19: 1,
-18: 1,
-17: 1,
-16: 1,
-15: 1,
-14: 2,
-13: 2,
-12: 2,
-11: 2,
-10: 2,
-9: 3,
-8: 3,
-7: 3,
-6: 3,
-5: 3,
-4: 4,
-3: 4,
-2: 4,
-1: 4,
0: 4,
1: 5,
2: 5,
3: 5,
4: 5,
5: 5,
6: 6,
7: 6,
8: 6,
9: 6,
10: 6,
11: 7,
12: 7,
13: 7,
14: 7,
15: 7,
16: 8,
17: 8,
18: 8,
19: 8,
20: 8,
21: 9,
22: 9,
23: 9,
24: 9,
25: 9,
26: 10,
27: 10,
28: 10,
29: 10,
30: 10,
31: 10,
32: 10,
33: 10,
34: 10,
35: 10,
36: 10,
37: 10,
38: 10,
39: 10,
40: 10,
}
temperature_rounded = [ceil(i) for i in self.df["temperature_geo"]]
temperature_interval = [intervals[i] for i in temperature_rounded]
return np.transpose(np.array(temperature_interval))
[docs] def get_sf_values(self, filename="shlp_hour_factors.csv"):
"""Determine the h-values
Parameters
----------
filename : string
name of file where sigmoid factors are stored
"""
file = os.path.join(self.datapath, filename)
hour_factors = pd.read_csv(file, index_col=0)
hour_factors = hour_factors.query(
'building_class=={0} and shlp_type=="{1}"'.format(
self.building_class, self.shlp_type
)
)
# Join the two DataFrames on the columns 'hour' and 'hour_of_the_day'
# or ['hour' 'weekday'] and ['hour_of_the_day', 'weekday'] if it is
# not a residential slp.
residential = self.building_class > 0
left_cols = ["hour_of_day"] + (["weekday"] if not residential else [])
right_cols = ["hour"] + (["weekday"] if not residential else [])
sf_mat = pd.DataFrame.merge(
hour_factors,
self.df,
left_on=left_cols,
right_on=right_cols,
how="outer",
)
sf_mat.index = pd.to_datetime(sf_mat["date"]) + pd.to_timedelta(
sf_mat["hour_of_day"] - 1, unit="h"
)
sf_mat.sort_index(inplace=True)
# drop unnecessary columns
drop_cols = (
[
"hour_of_day",
"hour",
"building_class",
"shlp_type",
"date",
"temperature",
]
+ (["weekday_x"] if residential else [])
+ (["weekday_y"] if residential else [])
+ (["weekday"] if not residential else [])
)
sf_mat = sf_mat.drop(labels=drop_cols, axis=1)
# Determine the h values
length = len(self.temperature)
sf = np.array(sf_mat)[
np.array(list(range(0, length)))[:],
(self.get_temperature_interval() - 1)[:],
]
return np.array(list(map(float, sf[:])))
[docs] def get_sigmoid_parameters(self, filename="shlp_sigmoid_factors.csv"):
"""Retrieve the sigmoid parameters from csv-files
Parameters
----------
filename : string
name of file where sigmoid factors are stored
"""
file = os.path.join(self.datapath, filename)
sigmoid = pd.read_csv(file, index_col=0)
sigmoid = sigmoid.query(
"building_class=={0} and ".format(self.building_class)
+ 'shlp_type=="{0}" and '.format(self.shlp_type)
+ "wind_impact=={0}".format(self.wind_class)
)
a = float(sigmoid["parameter_a"])
b = float(sigmoid["parameter_b"])
c = float(sigmoid["parameter_c"])
if self.ww_incl:
d = float(sigmoid["parameter_d"])
else:
d = 0
return a, b, c, d
[docs] def get_weekday_parameters(self, filename="shlp_weekday_factors.csv"):
"""Retrieve the weekday parameter from csv-file
Parameters
----------
filename : string
name of file where sigmoid factors are stored
"""
file = os.path.join(self.datapath, filename)
f_df = pd.read_csv(file, index_col=0)
tmp_df = f_df.query('shlp_type=="{0}"'.format(self.shlp_type)).drop(
"shlp_type", axis=1
)
tmp_df["weekdays"] = np.array(list(range(7))) + 1
merged_df = pd.DataFrame.merge(
tmp_df,
self.df,
left_on="weekdays",
right_on="weekday",
how="inner",
)
merged_df.index = pd.to_datetime(merged_df["date"]) + pd.to_timedelta(
merged_df["hour"] - 1, unit="h"
)
merged_df.sort_index(inplace=True)
return np.array(list(map(float, merged_df["wochentagsfaktor"])))
[docs] def get_bdew_profile(self):
"""Calculation of the hourly heat demand using the bdew-equations"""
return self.get_normalized_bdew_profile() * self.annual_heat_demand
[docs] def get_normalized_bdew_profile(self):
"""Calculation of the normalized hourly heat demand"""
self.df["temperature"] = self.temperature.values
self.df["temperature_geo"] = self.weighted_temperature(
how="geometric_series"
)
sf = self.get_sf_values()
[a, b, c, d] = self.get_sigmoid_parameters()
f = self.get_weekday_parameters()
h = a / (1 + (b / (self.df["temperature_geo"] - 40)) ** c) + d
kw = 1.0 / (sum(h * f) / 24)
heat_profile_normalized = kw * h * f * sf
return heat_profile_normalized