"""
Generate simple energy load curves for analysis of weather data
Either as step function or as sine, differences for weekdays and weekends
included.
B. Atakan, Uni Duisburg-Essen, Germany
SPP2403
2025-01-20
"""
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
[docs]
def generate_seasonal_modulation(time_index, amplitude=0.05):
"""
Generate a seasonal modulation function using a cosine wave.
Parameters
----------
time_index : pandas DateTimeIndex
The period for which values are generated.
amplitude : float, optional
The amplitude of the seasonal variation. Default is 0.05.
Returns
-------
pandas Series
Seasonal modulation factor for each timestamp.
"""
# Umwandlung der Zeitstempel in den Tagesindex innerhalb eines Jahres (0 bis 364 oder 365)
day_of_year = time_index.day_of_year
# Kosinusfunktion für die jahreszeitliche Modulation
seasonal_factor = 1 + amplitude * np.cos(2 * np.pi * day_of_year / 365)
return pd.Series(seasonal_factor, index=time_index)
[docs]
def generate_load_curve(time_index, step=True, load_parameters=None):
"""
Generate simple load curves either step function or sine,
with an additional seasonal modulation.
Parameters
----------
time_index : pandas DateTimeIndex
The period for which values are generated.
step : Boolean, optional
If True, it is a step function, else a sine. Default is True.
load_parameters : dictionary, optional
Values for min/max and weekend/workdays and intermediate values.
Returns
-------
pandas dataframe
With the load values.
"""
if load_parameters is None:
load_parameters = {
'min_weekday': 0.650,
'max_weekday': 1.0,
'min_weekend': 0.650,
'max_weekend': 0.85,
'intermediate_fraction': 0.80,
'multiplier': 1.0,
'offset': 0.0,
'normalize': True,
'amplitude_season':0.05, # seasonal
}
load_curve = pd.DataFrame(index=time_index)
load_curve['load'] = 0.0
def generate_sinusoidal_load(t, wert_min, wert_max):
amplitude = (wert_max - wert_min) / 2
offset = wert_min + amplitude
phase_shift = 6
return offset + amplitude * np.sin((2 * np.pi / 24) * (t - phase_shift))
for timestamp in load_curve.index:
hour = timestamp.hour
is_weekend = timestamp.weekday() >= 5
if is_weekend:
if step:
if hour < 6:
load_curve.at[timestamp, 'load'] = load_parameters['min_weekend']
elif hour < 7:
load_curve.at[timestamp, 'load'] = load_parameters['intermediate_fraction']
elif hour < 22:
load_curve.at[timestamp, 'load'] = load_parameters['max_weekend']
elif hour < 23:
load_curve.at[timestamp, 'load'] = load_parameters['intermediate_fraction']
else:
load_curve.at[timestamp, 'load'] = load_parameters['min_weekend']
else:
load_curve.at[timestamp, 'load'] = generate_sinusoidal_load(
hour, load_parameters['min_weekend'], load_parameters['max_weekend']
)
else:
if step:
if hour < 6:
load_curve.at[timestamp, 'load'] = load_parameters['min_weekday']
elif hour < 7:
load_curve.at[timestamp, 'load'] = load_parameters['intermediate_fraction']
elif hour < 19:
load_curve.at[timestamp, 'load'] = load_parameters['max_weekday']
elif hour < 22:
load_curve.at[timestamp, 'load'] = load_parameters['intermediate_fraction']
else:
load_curve.at[timestamp, 'load'] = load_parameters['min_weekday']
else:
load_curve.at[timestamp, 'load'] = generate_sinusoidal_load(
hour, load_parameters['min_weekday'], load_parameters['max_weekday']
)
# Offset und Skalierung
load_curve['load'] -= load_parameters['offset']
load_curve['load'] *= load_parameters['multiplier']
# Normierung (falls gewünscht)
l_mean = load_curve['load'].mean()
if load_parameters['normalize']:
load_curve["load not normalized"] = load_curve['load']
load_curve['load'] /= l_mean
if load_parameters['multiplier']!=1.0:
print('WARNING, load-multiplier neglected, due to normalization!')
# Saisonale Modulation
load_curve['seasonal_factor'] = generate_seasonal_modulation(time_index, amplitude=load_parameters["amplitude_season"])
load_curve['load'] *= load_curve['seasonal_factor']
load_curve['load'] = load_curve['load'] /load_curve['load'].mean()
return load_curve
def _generate_sinusoidal_load(t, wert_min, wert_max):
"""
Generiert einen sinusförmigen Verlauf mit einer Periode von 24 Stunden,
einer Phasenverschiebung von 6 Stunden, sowie einem bestimmten
minimalen und maximalen Wert.
:param t: Array von Zeitpunkten (in Stunden), von 0 bis 24.
:param wert_min: Minimalwert der Lastkurve.
:param wert_max: Maximalwert der Lastkurve.
:return: Sinusförmige Lastkurve.
"""
# Definiere die Amplitude und den Offset
amplitude = (wert_max - wert_min) / 2
offset = wert_min + amplitude
# Phasenverschiebung von 6 Stunden für die Sinusfunktion
phase_shift = 6
# Generiere die sinusförmige Lastkurve
load_curve = offset + amplitude * \
np.sin((2 * np.pi / 24) * (t - phase_shift))
return load_curve
if __name__ == '__main__':
time_ind = pd.date_range(start='2021-01-01', end='2022-12-31', freq="h")
# Beispielaufruf der Funktion
load_params = {
'min_weekday': 0.650,
'max_weekday': 1.0,
'min_weekend': 0.650,
'max_weekend': 0.85,
'intermediate_fraction': 0.80,
'multiplier': 50.0,
'offset':0.0,
'normalize': True,
'amplitude_season':0.05, # seasonal
}
# For step function
load_curve_step = generate_load_curve(time_ind,
step=True, load_parameters=load_params)
# For sinusoidal function
load_curve_sine = generate_load_curve(time_ind,
step=False, load_parameters=load_params)
# Plot der ersten zwei Wochen
N_H = 24*14
plt.figure(figsize=(12, 6))
plt.plot(load_curve_step.index[:N_H], load_curve_step['load'][:N_H],
label='Stufenfunktion', color='blue') # N_H Stunden = 14 Tage
plt.plot(load_curve_sine.index[:N_H], load_curve_sine['load']
[:N_H], label='Sinusfunktion', color='orange')
plt.title('Lastkurve: Stufenfunktion vs. Sinusfunktion (Erste zwei Wochen)')
plt.xlabel('Datum')
plt.ylabel('Last (Skaliert)')
plt.grid()
plt.legend()
plt.show()
int_step = load_curve_step["load"].sum()
int_sine = load_curve_sine["load"].sum()
print(f"Sum/Integral, sine: {int_sine}, setep: {int_step}")
print("step:\n", load_curve_step.describe())
print("sine:\n", load_curve_sine.describe())