mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2025-08-25 06:52:23 +00:00
class_ems: AC / DC Charging
class_optimize: Timing Bugs fixed class_numpy_encoder: JSON Encoder with Numpy support visualize: AC / DC / Discharge test_class_ems_2: New Test for AC / DC charging decision
This commit is contained in:
@@ -20,7 +20,7 @@ class PVAkku:
|
||||
self.soc_wh = (start_soc_prozent / 100) * kapazitaet_wh
|
||||
self.hours = hours if hours is not None else 24 # Default to 24 hours if not specified
|
||||
self.discharge_array = np.full(self.hours, 1)
|
||||
self.charge_array = np.full(self.hours, 0)
|
||||
self.charge_array = np.full(self.hours, 1)
|
||||
# Charge and discharge efficiency
|
||||
self.lade_effizienz = lade_effizienz
|
||||
self.entlade_effizienz = entlade_effizienz
|
||||
@@ -70,26 +70,19 @@ class PVAkku:
|
||||
self.soc_wh = min(max(self.soc_wh, self.min_soc_wh), self.max_soc_wh)
|
||||
|
||||
self.discharge_array = np.full(self.hours, 1)
|
||||
self.charge_array = np.full(self.hours, 0)
|
||||
self.charge_array = np.full(self.hours, 1)
|
||||
|
||||
def set_discharge_per_hour(self, discharge_array):
|
||||
assert len(discharge_array) == self.hours
|
||||
self.discharge_array = np.array(discharge_array)
|
||||
|
||||
# Ensure no simultaneous charging and discharging in the same hour using NumPy mask
|
||||
conflict_mask = (self.charge_array > 0) & (self.discharge_array > 0)
|
||||
# Prioritize discharge by setting charge to 0 where both are > 0
|
||||
self.charge_array[conflict_mask] = 0
|
||||
|
||||
def set_charge_per_hour(self, charge_array):
|
||||
assert len(charge_array) == self.hours
|
||||
self.charge_array = np.array(charge_array)
|
||||
|
||||
# Ensure no simultaneous charging and discharging in the same hour using NumPy mask
|
||||
conflict_mask = (self.charge_array > 0) & (self.discharge_array > 0)
|
||||
# Prioritize discharge by setting charge to 0 where both are > 0
|
||||
self.discharge_array[conflict_mask] = 0
|
||||
|
||||
def set_charge_allowed_for_hour(self, charge, hour):
|
||||
assert hour < self.hours
|
||||
self.charge_array[hour] = charge
|
||||
|
||||
def ladezustand_in_prozent(self):
|
||||
return (self.soc_wh / self.kapazitaet_wh) * 100
|
||||
@@ -125,17 +118,14 @@ class PVAkku:
|
||||
# Return the actually discharged energy and the losses
|
||||
return tatsaechlich_abgegeben_wh, verluste_wh
|
||||
|
||||
def energie_laden(self, wh, hour):
|
||||
def energie_laden(self, wh, hour, relative_power=0.0):
|
||||
if hour is not None and self.charge_array[hour] == 0:
|
||||
return 0, 0 # Charging not allowed in this hour
|
||||
|
||||
if relative_power > 0.0:
|
||||
wh=self.max_ladeleistung_w*relative_power
|
||||
# If no value for wh is given, use the maximum charging power
|
||||
wh = wh if wh is not None else self.max_ladeleistung_w
|
||||
|
||||
# Relative to the maximum charging power (between 0 and 1)
|
||||
relative_ladeleistung = self.charge_array[hour]
|
||||
effektive_ladeleistung = relative_ladeleistung * self.max_ladeleistung_w
|
||||
|
||||
# Calculate the maximum energy that can be charged considering max_soc and efficiency
|
||||
if self.lade_effizienz > 0:
|
||||
max_possible_charge_wh = (self.max_soc_wh - self.soc_wh) / self.lade_effizienz
|
||||
@@ -144,8 +134,8 @@ class PVAkku:
|
||||
max_possible_charge_wh = max(max_possible_charge_wh, 0.0) # Ensure non-negative
|
||||
|
||||
# The actually charged energy cannot exceed requested energy, charging power, or maximum possible charge
|
||||
effektive_lademenge = min(wh, effektive_ladeleistung, max_possible_charge_wh)
|
||||
|
||||
effektive_lademenge = min(wh, max_possible_charge_wh)
|
||||
|
||||
# Energy actually stored in the battery
|
||||
geladene_menge = effektive_lademenge * self.lade_effizienz
|
||||
|
||||
@@ -153,10 +143,9 @@ class PVAkku:
|
||||
self.soc_wh += geladene_menge
|
||||
# Ensure soc_wh does not exceed max_soc_wh
|
||||
self.soc_wh = min(self.soc_wh, self.max_soc_wh)
|
||||
|
||||
|
||||
# Calculate losses
|
||||
verluste_wh = effektive_lademenge - geladene_menge
|
||||
|
||||
return geladene_menge, verluste_wh
|
||||
|
||||
def aktueller_energieinhalt(self):
|
||||
|
@@ -1,6 +1,6 @@
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
from akkudoktoreos.config import *
|
||||
import numpy as np
|
||||
|
||||
|
||||
@@ -23,12 +23,17 @@ class EnergieManagementSystem:
|
||||
self.eauto = eauto
|
||||
self.haushaltsgeraet = haushaltsgeraet
|
||||
self.wechselrichter = wechselrichter
|
||||
self.ac_charge_hours = np.full(prediction_hours,0)
|
||||
self.dc_charge_hours = np.full(prediction_hours,1)
|
||||
|
||||
def set_akku_discharge_hours(self, ds: List[int]) -> None:
|
||||
self.akku.set_discharge_per_hour(ds)
|
||||
|
||||
def set_akku_charge_hours(self, ds: List[int]) -> None:
|
||||
self.akku.set_charge_per_hour(ds)
|
||||
def set_akku_ac_charge_hours(self, ds: np.ndarray) -> None:
|
||||
self.ac_charge_hours = ds
|
||||
|
||||
def set_akku_dc_charge_hours(self, ds: np.ndarray) -> None:
|
||||
self.dc_charge_hours = ds
|
||||
|
||||
def set_eauto_charge_hours(self, ds: List[int]) -> None:
|
||||
self.eauto.set_charge_per_hour(ds)
|
||||
@@ -46,7 +51,12 @@ class EnergieManagementSystem:
|
||||
return self.simuliere(start_stunde)
|
||||
|
||||
def simuliere(self, start_stunde: int) -> dict:
|
||||
# Ensure arrays have the same length
|
||||
'''
|
||||
hour:
|
||||
akku_soc_pro_stunde begin of the hour, initial hour state!
|
||||
last_wh_pro_stunde integral of last hour (end state)
|
||||
'''
|
||||
|
||||
lastkurve_wh = self.gesamtlast
|
||||
assert (
|
||||
len(lastkurve_wh) == len(self.pv_prognose_wh) == len(self.strompreis_euro_pro_wh)
|
||||
@@ -91,16 +101,21 @@ class EnergieManagementSystem:
|
||||
eauto_soc_pro_stunde[stunde_since_now] = self.eauto.ladezustand_in_prozent()
|
||||
|
||||
# AC PV Battery Charge
|
||||
if self.akku.charge_array[stunde] > 0.0:
|
||||
geladene_menge, verluste_wh = self.akku.energie_laden(None,stunde)
|
||||
if self.ac_charge_hours[stunde] > 0.0:
|
||||
self.akku.set_charge_allowed_for_hour(self.ac_charge_hours[stunde],stunde)
|
||||
geladene_menge, verluste_wh = self.akku.energie_laden(None,stunde,relative_power=self.ac_charge_hours[stunde])
|
||||
verbrauch += geladene_menge
|
||||
verluste_wh_pro_stunde[stunde_since_now] += verluste_wh
|
||||
|
||||
verluste_wh_pro_stunde[stunde_since_now] += verluste_wh
|
||||
|
||||
# Process inverter logic
|
||||
erzeugung = self.pv_prognose_wh[stunde]
|
||||
self.akku.set_charge_allowed_for_hour(self.dc_charge_hours[stunde],stunde)
|
||||
netzeinspeisung, netzbezug, verluste, eigenverbrauch = (
|
||||
self.wechselrichter.energie_verarbeiten(erzeugung, verbrauch, stunde)
|
||||
)
|
||||
|
||||
|
||||
|
||||
netzeinspeisung_wh_pro_stunde[stunde_since_now] = netzeinspeisung
|
||||
netzbezug_wh_pro_stunde[stunde_since_now] = netzbezug
|
||||
verluste_wh_pro_stunde[stunde_since_now] += verluste
|
||||
@@ -136,4 +151,5 @@ class EnergieManagementSystem:
|
||||
"Gesamt_Verluste": np.nansum(verluste_wh_pro_stunde),
|
||||
"Haushaltsgeraet_wh_pro_stunde": haushaltsgeraet_wh_pro_stunde,
|
||||
}
|
||||
|
||||
return out
|
||||
|
24
src/akkudoktoreos/class_numpy_encoder.py
Normal file
24
src/akkudoktoreos/class_numpy_encoder.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import json
|
||||
import numpy as np
|
||||
|
||||
class NumpyEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, np.ndarray):
|
||||
return obj.tolist() # Convert NumPy arrays to lists
|
||||
if isinstance(obj, np.generic):
|
||||
return obj.item() # Convert NumPy scalars to native Python types
|
||||
return super(NumpyEncoder, self).default(obj)
|
||||
|
||||
@staticmethod
|
||||
def dumps(data):
|
||||
"""
|
||||
Static method to serialize a Python object into a JSON string using NumpyEncoder.
|
||||
|
||||
Args:
|
||||
data: The Python object to serialize.
|
||||
|
||||
Returns:
|
||||
str: A JSON string representation of the object.
|
||||
"""
|
||||
return json.dumps(data, cls=NumpyEncoder)
|
||||
|
@@ -12,6 +12,7 @@ from akkudoktoreos.config import possible_ev_charge_currents
|
||||
from akkudoktoreos.visualize import visualisiere_ergebnisse
|
||||
|
||||
|
||||
|
||||
class optimization_problem:
|
||||
def __init__(
|
||||
self,
|
||||
@@ -35,53 +36,104 @@ class optimization_problem:
|
||||
if fixed_seed is not None:
|
||||
random.seed(fixed_seed)
|
||||
|
||||
def split_charge_discharge(self, discharge_hours_bin: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
||||
|
||||
def decode_charge_discharge(self, discharge_hours_bin: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
||||
"""
|
||||
Split the input array `discharge_hours_bin` into two separate arrays:
|
||||
- `charge`: Contains only the negative values from `discharge_hours_bin` (charging values).
|
||||
- `discharge`: Contains only the positive values from `discharge_hours_bin` (discharging values).
|
||||
|
||||
Decode the input array `discharge_hours_bin` into three separate arrays for AC charging, DC charging, and discharge.
|
||||
The function maps AC and DC charging values to relative power levels (0 to 1), while the discharge remains binary (0 or 1).
|
||||
|
||||
Parameters:
|
||||
- discharge_hours_bin (np.ndarray): Input array with both positive and negative values.
|
||||
- discharge_hours_bin (np.ndarray): Input array with integer values representing the different states.
|
||||
The states are:
|
||||
0: No action ("idle")
|
||||
1: Discharge ("discharge")
|
||||
2-6: AC charging with different power levels ("ac_charge")
|
||||
7-8: DC charging Dissallowed/allowed ("dc_charge")
|
||||
|
||||
Returns:
|
||||
- charge (np.ndarray): Array with negative values from `discharge_hours_bin`, other values set to 0.
|
||||
- discharge (np.ndarray): Array with positive values from `discharge_hours_bin`, other values set to 0.
|
||||
- ac_charge (np.ndarray): Array with AC charging values as relative power (0-1), other values set to 0.
|
||||
- dc_charge (np.ndarray): Array with DC charging values as relative power (0-1), other values set to 0.
|
||||
- discharge (np.ndarray): Array with discharge values (1 for discharge, 0 otherwise).
|
||||
"""
|
||||
# Convert the input list to a NumPy array, if it's not already
|
||||
discharge_hours_bin = np.array(discharge_hours_bin)
|
||||
|
||||
# Create charge array: Keep only negative values, set the rest to 0
|
||||
charge = -np.where(discharge_hours_bin < 0, discharge_hours_bin, 0)
|
||||
charge = charge / np.max(charge)
|
||||
|
||||
# Create discharge array: Keep only positive values, set the rest to 0
|
||||
discharge = np.where(discharge_hours_bin > 0, discharge_hours_bin, 0)
|
||||
# Create ac_charge array: Only consider values between 2 and 6 (AC charging power levels), set the rest to 0
|
||||
ac_charge = np.where((discharge_hours_bin >= 2) & (discharge_hours_bin <= 6), discharge_hours_bin - 1, 0)
|
||||
ac_charge = ac_charge / 5.0 # Normalize AC charge to range between 0 and 1
|
||||
|
||||
# Create dc_charge array: 7 = Not allowed (mapped to 0), 8 = Allowed (mapped to 1)
|
||||
dc_charge = np.where(discharge_hours_bin == 8, 1, 0)
|
||||
|
||||
# Create discharge array: Only consider value 1 (Discharge), set the rest to 0 (binary output)
|
||||
discharge = np.where(discharge_hours_bin == 1, 1, 0)
|
||||
|
||||
return ac_charge, dc_charge, discharge
|
||||
|
||||
|
||||
return charge, discharge
|
||||
|
||||
# Custom mutation function that applies type-specific mutations
|
||||
def mutate(self,individual):
|
||||
# Mutate the discharge state genes (-1, 0, 1)
|
||||
individual[:self.prediction_hours], = self.toolbox.mutate_discharge(
|
||||
individual[:self.prediction_hours]
|
||||
)
|
||||
def mutate(self, individual):
|
||||
"""
|
||||
Custom mutation function for the individual. This function mutates different parts of the individual:
|
||||
- Mutates the discharge and charge states (AC, DC, idle) using the split_charge_discharge method.
|
||||
- Mutates the EV charging schedule if EV optimization is enabled.
|
||||
- Mutates appliance start times if household appliances are part of the optimization.
|
||||
|
||||
Parameters:
|
||||
- individual (list): The individual being mutated, which includes different optimization parameters.
|
||||
|
||||
Returns:
|
||||
- (tuple): The mutated individual as a tuple (required by DEAP).
|
||||
"""
|
||||
|
||||
# Step 1: Mutate the charge/discharge states (idle, discharge, AC charge, DC charge)
|
||||
# Extract the relevant part of the individual for prediction hours, which represents the charge/discharge behavior.
|
||||
charge_discharge_part = individual[:self.prediction_hours]
|
||||
|
||||
# Apply the mutation to the charge/discharge part
|
||||
charge_discharge_mutated, = self.toolbox.mutate_charge_discharge(charge_discharge_part)
|
||||
|
||||
# Ensure that no invalid states are introduced during mutation (valid values: 0-8)
|
||||
charge_discharge_mutated = np.clip(charge_discharge_mutated, 0, 8)
|
||||
|
||||
# Use split_charge_discharge to split the mutated array into AC charge, DC charge, and discharge components
|
||||
#ac_charge, dc_charge, discharge = self.split_charge_discharge(charge_discharge_mutated)
|
||||
|
||||
# Optionally: You can process the split arrays further if needed, for example,
|
||||
# applying additional constraints or penalties, or keeping track of charging limits.
|
||||
|
||||
# Reassign the mutated values back to the individual
|
||||
individual[:self.prediction_hours] = charge_discharge_mutated
|
||||
|
||||
# Step 2: Mutate EV charging schedule if enabled
|
||||
if self.optimize_ev:
|
||||
# Mutate the EV charging indices
|
||||
# Extract the relevant part for EV charging schedule
|
||||
ev_charge_part = individual[self.prediction_hours : self.prediction_hours * 2]
|
||||
|
||||
# Apply mutation on the EV charging schedule
|
||||
ev_charge_part_mutated, = self.toolbox.mutate_ev_charge_index(ev_charge_part)
|
||||
|
||||
# Ensure the EV does not charge during fixed hours (set those hours to 0)
|
||||
ev_charge_part_mutated[self.prediction_hours - self.fixed_eauto_hours :] = [0] * self.fixed_eauto_hours
|
||||
|
||||
# Reassign the mutated EV charging part back to the individual
|
||||
individual[self.prediction_hours : self.prediction_hours * 2] = ev_charge_part_mutated
|
||||
|
||||
# Mutate the appliance start hour if present
|
||||
# Step 3: Mutate appliance start times if household appliances are part of the optimization
|
||||
if self.opti_param["haushaltsgeraete"] > 0:
|
||||
# Extract the appliance part (typically a single value for the start hour)
|
||||
appliance_part = [individual[-1]]
|
||||
|
||||
# Apply mutation on the appliance start hour
|
||||
appliance_part_mutated, = self.toolbox.mutate_hour(appliance_part)
|
||||
|
||||
# Reassign the mutated appliance part back to the individual
|
||||
individual[-1] = appliance_part_mutated[0]
|
||||
|
||||
return (individual,)
|
||||
|
||||
|
||||
# Method to create an individual based on the conditions
|
||||
def create_individual(self):
|
||||
# Start with discharge states for the individual
|
||||
@@ -106,8 +158,14 @@ class optimization_problem:
|
||||
2. Electric vehicle charge hours (possible_charge_values),
|
||||
3. Dishwasher start time (integer if applicable).
|
||||
"""
|
||||
|
||||
discharge_hours_bin = individual[: self.prediction_hours]
|
||||
eautocharge_hours_float = individual[self.prediction_hours : self.prediction_hours * 2]
|
||||
eautocharge_hours_float = (
|
||||
individual[self.prediction_hours : self.prediction_hours * 2]
|
||||
if self.optimize_ev
|
||||
else None
|
||||
)
|
||||
|
||||
spuelstart_int = (
|
||||
individual[-1]
|
||||
if self.opti_param and self.opti_param.get("haushaltsgeraete", 0) > 0
|
||||
@@ -132,13 +190,12 @@ class optimization_problem:
|
||||
|
||||
# Initialize toolbox with attributes and operations
|
||||
self.toolbox = base.Toolbox()
|
||||
self.toolbox.register("attr_discharge_state", random.randint, -5, 1)
|
||||
self.toolbox.register("attr_discharge_state", random.randint, 0,11)
|
||||
if self.optimize_ev:
|
||||
self.toolbox.register("attr_ev_charge_index", random.randint, 0, len(possible_ev_charge_currents) - 1)
|
||||
self.toolbox.register("attr_int", random.randint, start_hour, 23)
|
||||
|
||||
|
||||
|
||||
# Register individual creation function
|
||||
self.toolbox.register("individual", self.create_individual)
|
||||
|
||||
@@ -148,7 +205,7 @@ class optimization_problem:
|
||||
#self.toolbox.register("mutate", tools.mutFlipBit, indpb=0.1)
|
||||
# Register separate mutation functions for each type of value:
|
||||
# - Discharge state mutation (-5, 0, 1)
|
||||
self.toolbox.register("mutate_discharge", tools.mutUniformInt, low=-5, up=1, indpb=0.1)
|
||||
self.toolbox.register("mutate_charge_discharge", tools.mutUniformInt, low=0, up=8, indpb=0.1)
|
||||
# - Float mutation for EV charging values
|
||||
self.toolbox.register("mutate_ev_charge_index", tools.mutUniformInt, low=0, up=len(possible_ev_charge_currents) - 1, indpb=0.1)
|
||||
# - Start hour mutation for household devices
|
||||
@@ -173,11 +230,12 @@ class optimization_problem:
|
||||
if self.opti_param.get("haushaltsgeraete", 0) > 0:
|
||||
ems.set_haushaltsgeraet_start(spuelstart_int, global_start_hour=start_hour)
|
||||
|
||||
charge, discharge = self.split_charge_discharge(discharge_hours_bin)
|
||||
ac,dc,discharge = self.decode_charge_discharge(discharge_hours_bin)
|
||||
|
||||
|
||||
ems.set_akku_discharge_hours(discharge)
|
||||
ems.set_akku_charge_hours(charge)
|
||||
ems.set_akku_dc_charge_hours(dc)
|
||||
ems.set_akku_ac_charge_hours(ac)
|
||||
|
||||
if self.optimize_ev:
|
||||
eautocharge_hours_float = [
|
||||
@@ -212,13 +270,6 @@ class optimization_problem:
|
||||
0.01 for i in range(self.prediction_hours) if discharge_hours_bin[i] == 0.0
|
||||
)
|
||||
|
||||
# Penalty for charging the electric vehicle during restricted hours
|
||||
# gesamtbilanz += sum(
|
||||
# self.strafe
|
||||
# for i in range(self.prediction_hours - self.fixed_eauto_hours, self.prediction_hours)
|
||||
# if eautocharge_hours_float[i] != 0.0
|
||||
# )
|
||||
|
||||
# Penalty for not meeting the minimum SOC (State of Charge) requirement
|
||||
if parameter["eauto_min_soc"] - ems.eauto.ladezustand_in_prozent() <= 0.0:
|
||||
gesamtbilanz += sum(
|
||||
@@ -266,8 +317,8 @@ class optimization_problem:
|
||||
self.toolbox,
|
||||
mu=100,
|
||||
lambda_=150,
|
||||
cxpb=0.5,
|
||||
mutpb=0.5,
|
||||
cxpb=0.7,
|
||||
mutpb=0.3,
|
||||
ngen=ngen,
|
||||
stats=stats,
|
||||
halloffame=hof,
|
||||
@@ -361,14 +412,18 @@ class optimization_problem:
|
||||
start_solution
|
||||
)
|
||||
|
||||
|
||||
|
||||
ac_charge, dc_charge, discharge = self.decode_charge_discharge(discharge_hours_bin)
|
||||
# Visualize the results
|
||||
visualisiere_ergebnisse(
|
||||
parameter["gesamtlast"],
|
||||
parameter["pv_forecast"],
|
||||
parameter["strompreis_euro_pro_wh"],
|
||||
o,
|
||||
discharge_hours_bin,
|
||||
eautocharge_hours_float,
|
||||
ac_charge,
|
||||
dc_charge,
|
||||
discharge,
|
||||
parameter["temperature_forecast"],
|
||||
start_hour,
|
||||
self.prediction_hours,
|
||||
@@ -395,7 +450,7 @@ class optimization_problem:
|
||||
element_list = o[key].tolist()
|
||||
|
||||
# Change the first value to None
|
||||
element_list[0] = None
|
||||
#element_list[0] = None
|
||||
# Change the NaN to None (JSON)
|
||||
element_list = [
|
||||
None if isinstance(x, (int, float)) and np.isnan(x) else x for x in element_list
|
||||
@@ -406,7 +461,9 @@ class optimization_problem:
|
||||
|
||||
# Return final results as a dictionary
|
||||
return {
|
||||
"discharge_hours_bin": discharge_hours_bin,
|
||||
"ac_charge": ac_charge.tolist(),
|
||||
"dc_charge":dc_charge.tolist(),
|
||||
"discharge_allowed":discharge.tolist(),
|
||||
"eautocharge_hours_float": eautocharge_hours_float,
|
||||
"result": o,
|
||||
"eauto_obj": ems.eauto.to_dict(),
|
||||
|
@@ -18,8 +18,9 @@ def visualisiere_ergebnisse(
|
||||
pv_forecast,
|
||||
strompreise,
|
||||
ergebnisse,
|
||||
discharge_hours,
|
||||
laden_moeglich,
|
||||
ac, # AC charging allowed
|
||||
dc, # DC charging allowed
|
||||
discharge, # Discharge allowed
|
||||
temperature,
|
||||
start_hour,
|
||||
prediction_hours,
|
||||
@@ -58,21 +59,7 @@ def visualisiere_ergebnisse(
|
||||
plt.grid(True)
|
||||
plt.legend()
|
||||
|
||||
# Electricity prices
|
||||
hours_p = np.arange(0, len(strompreise))
|
||||
plt.subplot(3, 2, 2)
|
||||
plt.plot(
|
||||
hours_p,
|
||||
strompreise,
|
||||
label="Electricity Price (€/Wh)",
|
||||
color="purple",
|
||||
marker="s",
|
||||
)
|
||||
plt.title("Electricity Prices")
|
||||
plt.xlabel("Hour of the Day")
|
||||
plt.ylabel("Price (€/Wh)")
|
||||
plt.legend()
|
||||
plt.grid(True)
|
||||
|
||||
|
||||
# PV forecast
|
||||
plt.subplot(3, 2, 3)
|
||||
@@ -122,30 +109,48 @@ def visualisiere_ergebnisse(
|
||||
|
||||
# Energy flow, grid feed-in, and grid consumption
|
||||
plt.subplot(3, 2, 1)
|
||||
plt.plot(hours, ergebnisse["Last_Wh_pro_Stunde"], label="Load (Wh)", marker="o")
|
||||
# Plot with transparency (alpha) and different linestyles
|
||||
plt.plot(
|
||||
hours,
|
||||
ergebnisse["Haushaltsgeraet_wh_pro_stunde"],
|
||||
label="Household Device (Wh)",
|
||||
marker="o",
|
||||
hours, ergebnisse["Last_Wh_pro_Stunde"], label="Load (Wh)", marker="o", linestyle="-", alpha=0.8
|
||||
)
|
||||
plt.plot(
|
||||
hours,
|
||||
ergebnisse["Netzeinspeisung_Wh_pro_Stunde"],
|
||||
label="Grid Feed-in (Wh)",
|
||||
marker="x",
|
||||
hours, ergebnisse["Haushaltsgeraet_wh_pro_stunde"], label="Household Device (Wh)", marker="o", linestyle="--", alpha=0.8
|
||||
)
|
||||
plt.plot(
|
||||
hours,
|
||||
ergebnisse["Netzbezug_Wh_pro_Stunde"],
|
||||
label="Grid Consumption (Wh)",
|
||||
marker="^",
|
||||
hours, ergebnisse["Netzeinspeisung_Wh_pro_Stunde"], label="Grid Feed-in (Wh)", marker="x", linestyle=":", alpha=0.8
|
||||
)
|
||||
plt.plot(hours, ergebnisse["Verluste_Pro_Stunde"], label="Losses (Wh)", marker="^")
|
||||
plt.plot(
|
||||
hours, ergebnisse["Netzbezug_Wh_pro_Stunde"], label="Grid Consumption (Wh)", marker="^", linestyle="-.", alpha=0.8
|
||||
)
|
||||
plt.plot(
|
||||
hours, ergebnisse["Verluste_Pro_Stunde"], label="Losses (Wh)", marker="^", linestyle="-", alpha=0.8
|
||||
)
|
||||
|
||||
# Title and labels
|
||||
plt.title("Energy Flow per Hour")
|
||||
plt.xlabel("Hour")
|
||||
plt.ylabel("Energy (Wh)")
|
||||
|
||||
# Show legend with a higher number of columns to avoid overlap
|
||||
plt.legend(ncol=2)
|
||||
|
||||
|
||||
|
||||
# Electricity prices
|
||||
hours_p = np.arange(0, len(strompreise))
|
||||
plt.subplot(3, 2, 3)
|
||||
plt.plot(
|
||||
hours_p,
|
||||
strompreise,
|
||||
label="Electricity Price (€/Wh)",
|
||||
color="purple",
|
||||
marker="s",
|
||||
)
|
||||
plt.title("Electricity Prices")
|
||||
plt.xlabel("Hour of the Day")
|
||||
plt.ylabel("Price (€/Wh)")
|
||||
plt.legend()
|
||||
plt.grid(True)
|
||||
|
||||
# State of charge for batteries
|
||||
plt.subplot(3, 2, 2)
|
||||
@@ -159,42 +164,30 @@ def visualisiere_ergebnisse(
|
||||
plt.legend(loc="upper left", bbox_to_anchor=(1, 1)) # Place legend outside the plot
|
||||
plt.grid(True, which="both", axis="x") # Grid for every hour
|
||||
|
||||
ax1 = plt.subplot(3, 2, 3)
|
||||
# Plot charge and discharge values
|
||||
for hour, value in enumerate(discharge_hours):
|
||||
# Determine color and label based on the value
|
||||
if value > 0: # Positive values (discharge)
|
||||
color = "red"
|
||||
label = "Discharge" if hour == 0 else ""
|
||||
elif value < 0: # Negative values (charge)
|
||||
color = "blue"
|
||||
label = "Charge" if hour == 0 else ""
|
||||
# Plot for AC, DC charging, and Discharge status using bar charts
|
||||
ax1 = plt.subplot(3, 2, 5)
|
||||
|
||||
else:
|
||||
continue # Skip zero values
|
||||
# Plot AC charging as bars (relative values between 0 and 1)
|
||||
plt.bar(hours, ac, width=0.4, label="AC Charging (relative)", color="blue", alpha=0.6)
|
||||
|
||||
# Create colored areas with `axvspan`
|
||||
ax1.axvspan(
|
||||
hour, # Start of the hour
|
||||
hour + 1, # End of the hour
|
||||
ymin=0, # Lower bound
|
||||
ymax=abs(value) / 5 if value < 0 else value, # Adjust height based on the value
|
||||
color=color,
|
||||
alpha=0.3,
|
||||
label=label
|
||||
)
|
||||
# Plot DC charging as bars (relative values between 0 and 1)
|
||||
plt.bar(hours + 0.4, dc, width=0.4, label="DC Charging (relative)", color="green", alpha=0.6)
|
||||
|
||||
# Plot Discharge as bars (0 or 1, binary values)
|
||||
plt.bar(hours, discharge, width=0.4, label="Discharge Allowed", color="red", alpha=0.6, bottom=np.maximum(ac, dc))
|
||||
|
||||
# Configure the plot
|
||||
ax1.legend(loc="upper left")
|
||||
ax1.set_xlim(0, prediction_hours)
|
||||
ax1.set_xlabel("Hour")
|
||||
ax1.set_ylabel("Charge/Discharge Level")
|
||||
ax1.set_title("Charge and Discharge Hours Overview")
|
||||
ax1.set_ylabel("Relative Power (0-1) / Discharge (0 or 1)")
|
||||
ax1.set_title("AC/DC Charging and Discharge Overview")
|
||||
ax1.grid(True)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
pdf.savefig() # Save the current figure state to the PDF
|
||||
plt.close() # Close the current figure to free up memory
|
||||
|
||||
@@ -219,7 +212,6 @@ def visualisiere_ergebnisse(
|
||||
)
|
||||
# Annotate costs
|
||||
for hour, value in enumerate(costs):
|
||||
print(hour, " ", value)
|
||||
if value == None or np.isnan(value):
|
||||
value=0
|
||||
axs[0].annotate(f'{value:.2f}', (hour, value), textcoords="offset points", xytext=(0,5), ha='center', fontsize=8, color='red')
|
||||
|
Reference in New Issue
Block a user