2024-10-04 03:11:24 +02:00
import random
2024-11-15 22:27:25 +01:00
from typing import Any , Optional , Tuple
2024-10-03 11:05:44 +02:00
2024-09-30 08:23:38 +02:00
import numpy as np
2024-10-04 03:11:24 +02:00
from deap import algorithms , base , creator , tools
2024-11-15 22:27:25 +01:00
from pydantic import BaseModel , Field , model_validator
from typing_extensions import Self
from akkudoktoreos . class_akku import EAutoParameters , PVAkku , PVAkkuParameters
from akkudoktoreos . class_ems import (
EnergieManagementSystem ,
EnergieManagementSystemParameters ,
)
from akkudoktoreos . class_haushaltsgeraet import (
Haushaltsgeraet ,
HaushaltsgeraetParameters ,
)
from akkudoktoreos . class_inverter import Wechselrichter , WechselrichterParameters
2024-11-11 21:38:13 +01:00
from akkudoktoreos . config import AppConfig
2024-10-06 12:34:48 +02:00
from akkudoktoreos . visualize import visualisiere_ergebnisse
2024-04-01 14:11:38 +02:00
2024-10-07 19:59:31 +02:00
2024-11-15 22:27:25 +01:00
class OptimizationParameters ( BaseModel ) :
ems : EnergieManagementSystemParameters
pv_akku : PVAkkuParameters
wechselrichter : WechselrichterParameters = WechselrichterParameters ( )
eauto : EAutoParameters
spuelmaschine : Optional [ HaushaltsgeraetParameters ] = None
temperature_forecast : list [ float ] = Field (
" An array of floats representing the temperature forecast in degrees Celsius for different time intervals. "
)
start_solution : Optional [ list [ float ] ] = Field (
None , description = " Can be `null` or contain a previous solution (if available). "
)
@model_validator ( mode = " after " )
def validate_list_length ( self ) - > Self :
arr_length = len ( self . ems . pv_prognose_wh )
if arr_length != len ( self . temperature_forecast ) :
raise ValueError ( " Input lists have different lenghts " )
return self
class EAutoResult ( BaseModel ) :
""" This object contains information related to the electric vehicle and its charging and discharging behavior. """
charge_array : list [ float ] = Field (
description = " Indicates for each hour whether the EV is charging (`0` for no charging, `1` for charging). "
)
discharge_array : list [ int ] = Field (
description = " Indicates for each hour whether the EV is discharging (`0` for no discharging, `1` for discharging). "
)
entlade_effizienz : float = Field ( description = " The discharge efficiency as a float. " )
hours : int = Field ( " Amount of hours the simulation is done for. " )
kapazitaet_wh : int = Field ( " The capacity of the EV’ s battery in watt-hours. " )
lade_effizienz : float = Field ( " The charging efficiency as a float. " )
max_ladeleistung_w : int = Field ( description = " The maximum charging power of the EV in watts. " )
soc_wh : float = Field (
description = " The state of charge of the battery in watt-hours at the start of the simulation. "
)
start_soc_prozent : int = Field (
description = " The state of charge of the battery in percentage at the start of the simulation. "
)
class SimulationResult ( BaseModel ) :
""" This object contains the results of the simulation and provides insights into various parameters over the entire forecast period. """
Last_Wh_pro_Stunde : list [ Optional [ float ] ] = Field ( description = " TBD " )
EAuto_SoC_pro_Stunde : list [ Optional [ float ] ] = Field (
description = " The state of charge of the EV for each hour. "
)
Einnahmen_Euro_pro_Stunde : list [ Optional [ float ] ] = Field (
description = " The revenue from grid feed-in or other sources in euros per hour. "
)
Gesamt_Verluste : float = Field (
description = " The total losses in watt-hours over the entire period. "
)
Gesamtbilanz_Euro : float = Field (
description = " The total balance of revenues minus costs in euros. "
)
Gesamteinnahmen_Euro : float = Field ( description = " The total revenues in euros. " )
Gesamtkosten_Euro : float = Field ( description = " The total costs in euros. " )
Haushaltsgeraet_wh_pro_stunde : list [ Optional [ float ] ] = Field (
description = " The energy consumption of a household appliance in watt-hours per hour. "
)
Kosten_Euro_pro_Stunde : list [ Optional [ float ] ] = Field (
description = " The costs in euros per hour. "
)
Netzbezug_Wh_pro_Stunde : list [ Optional [ float ] ] = Field (
description = " The grid energy drawn in watt-hours per hour. "
)
Netzeinspeisung_Wh_pro_Stunde : list [ Optional [ float ] ] = Field (
description = " The energy fed into the grid in watt-hours per hour. "
)
Verluste_Pro_Stunde : list [ Optional [ float ] ] = Field (
description = " The losses in watt-hours per hour. "
)
akku_soc_pro_stunde : list [ Optional [ float ] ] = Field (
description = " The state of charge of the battery (not the EV) in percentage per hour. "
)
# class SimulationData(BaseModel):
# """An object containing the simulated data."""
#
# Last_Wh_pro_Stunde: list[Optional[float]] = Field(description="TBD")
# EAuto_SoC_pro_Stunde: list[Optional[float]] = Field(
# description="An array of floats representing the simulated state of charge of the electric car per hour.",
# )
# Einnahmen_Euro_pro_Stunde: list[Optional[float]] = Field(
# description="An array of floats representing the simulated income in euros per hour."
# )
# Gesamt_Verluste: float = Field(description="The total simulated losses in watt-hours.")
# Gesamtbilanz_Euro: float = Field(description="The total simulated balance in euros.")
# Gesamteinnahmen_Euro: float = Field(description="The total simulated income in euros.")
# Gesamtkosten_Euro: float = Field(description="The total simulated costs in euros.")
# Haushaltsgeraet_wh_pro_stunde: list[Optional[float]] = Field(
# description="An array of floats representing the simulated energy consumption of a household appliance in watt-hours per hour."
# )
# Kosten_Euro_pro_Stunde: list[Optional[float]] = Field(
# description="An array of floats representing the simulated costs in euros per hour."
# )
# Netzbezug_Wh_pro_Stunde: list[Optional[float]] = Field(
# description="An array of floats representing the simulated grid consumption in watt-hours per hour."
# )
# Netzeinspeisung_Wh_pro_Stunde: list[Optional[float]] = Field(
# description="An array of floats representing the simulated grid feed-in in watt-hours per hour."
# )
# Verluste_Pro_Stunde: list[Optional[float]] = Field(
# description="An array of floats representing the simulated losses per hour."
# )
# akku_soc_pro_stunde: list[Optional[float]] = Field(
# description="An array of floats representing the simulated state of charge of the battery in percentage per hour."
# )
class OptimizeResponse ( BaseModel ) :
""" **Note**: The first value of " Last_Wh_pro_Stunde " , " Netzeinspeisung_Wh_pro_Stunde " and " Netzbezug_Wh_pro_Stunde " , will be set to null in the JSON output and represented as NaN or None in the corresponding classes ' data returns. This approach is adopted to ensure that the current hour ' s processing remains unchanged. """
ac_charge : list [ float ] = Field (
description = " Array with AC charging values as relative power (0-1), other values set to 0. "
)
dc_charge : list [ float ] = Field (
description = " Array with DC charging values as relative power (0-1), other values set to 0. "
)
discharge_allowed : list [ int ] = Field (
description = " Array with discharge values (1 for discharge, 0 otherwise). "
)
result : SimulationResult
eauto_obj : EAutoResult
start_solution : Optional [ list [ float ] ] = Field (
None ,
description = " An array of binary values (0 or 1) representing a possible starting solution for the simulation. " ,
)
spuelstart : Optional [ int ] = Field (
None ,
description = " Can be `null` or contain an object representing the start of washing (if applicable). " ,
)
# simulation_data: Optional[SimulationData] = None
2024-04-01 14:11:38 +02:00
class optimization_problem :
2024-10-04 03:11:24 +02:00
def __init__ (
self ,
2024-11-11 21:38:13 +01:00
config : AppConfig ,
2024-10-04 03:11:24 +02:00
verbose : bool = False ,
fixed_seed : Optional [ int ] = None ,
) :
""" Initialize the optimization problem with the required parameters. """
2024-11-11 21:38:13 +01:00
self . _config = config
self . prediction_hours = config . eos . prediction_hours
self . strafe = config . eos . penalty
2024-04-02 16:46:16 +02:00
self . opti_param = None
2024-11-11 21:38:13 +01:00
self . fixed_eauto_hours = config . eos . prediction_hours - config . eos . optimization_hours
self . possible_charge_values = config . eos . available_charging_rates_in_percentage
2024-10-04 03:11:24 +02:00
self . verbose = verbose
self . fix_seed = fixed_seed
2024-10-14 10:10:12 +02:00
self . optimize_ev = True
2024-10-20 18:18:06 +02:00
self . optimize_dc_charge = False
2024-10-04 03:11:24 +02:00
# Set a fixed seed for random operations if provided
if fixed_seed is not None :
random . seed ( fixed_seed )
2024-09-30 08:23:38 +02:00
2024-10-22 10:29:57 +02:00
def decode_charge_discharge (
self , discharge_hours_bin : np . ndarray
) - > Tuple [ np . ndarray , np . ndarray , np . ndarray ] :
2024-11-10 23:27:52 +01:00
""" Decode the input array `discharge_hours_bin` into three separate arrays for AC charging, DC charging, and discharge.
2024-10-16 15:40:04 +02:00
The function maps AC and DC charging values to relative power levels ( 0 to 1 ) , while the discharge remains binary ( 0 or 1 ) .
2024-10-14 10:10:12 +02:00
Parameters :
2024-10-16 15:40:04 +02:00
- 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 " )
2024-10-22 10:29:57 +02:00
2024-10-14 10:10:12 +02:00
Returns :
2024-10-16 15:40:04 +02:00
- 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 ) .
2024-10-14 10:10:12 +02:00
"""
# Convert the input list to a NumPy array, if it's not already
discharge_hours_bin = np . array ( discharge_hours_bin )
2024-10-16 15:40:04 +02:00
# Create ac_charge array: Only consider values between 2 and 6 (AC charging power levels), set the rest to 0
2024-10-22 10:29:57 +02:00
ac_charge = np . where (
( discharge_hours_bin > = 2 ) & ( discharge_hours_bin < = 6 ) , discharge_hours_bin - 1 , 0
)
2024-10-16 15:40:04 +02:00
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)
2024-10-20 18:18:06 +02:00
# Create dc_charge array: Only if DC charge optimization is enabled
if self . optimize_dc_charge :
dc_charge = np . where ( discharge_hours_bin == 8 , 1 , 0 )
else :
2024-10-22 10:29:57 +02:00
dc_charge = np . ones_like (
discharge_hours_bin
) # Set DC charge to 0 if optimization is disabled
2024-10-16 15:40:04 +02:00
# 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
2024-10-14 10:46:14 +02:00
# Custom mutation function that applies type-specific mutations
2024-10-16 15:40:04 +02:00
def mutate ( self , individual ) :
2024-11-10 23:27:52 +01:00
""" Custom mutation function for the individual.
This function mutates different parts of the individual :
2024-10-16 15:40:04 +02:00
- 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 .
2024-10-14 10:46:14 +02:00
2024-10-16 15:40:04 +02:00
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.
2024-10-22 10:29:57 +02:00
charge_discharge_part = individual [ : self . prediction_hours ]
2024-10-16 15:40:04 +02:00
# Apply the mutation to the charge/discharge part
2024-10-22 10:29:57 +02:00
( charge_discharge_mutated , ) = self . toolbox . mutate_charge_discharge ( charge_discharge_part )
2024-10-16 15:40:04 +02:00
# Ensure that no invalid states are introduced during mutation (valid values: 0-8)
2024-10-20 18:18:06 +02:00
if self . optimize_dc_charge :
charge_discharge_mutated = np . clip ( charge_discharge_mutated , 0 , 8 )
else :
charge_discharge_mutated = np . clip ( charge_discharge_mutated , 0 , 6 )
2024-10-22 10:29:57 +02:00
2024-10-16 15:40:04 +02:00
# Use split_charge_discharge to split the mutated array into AC charge, DC charge, and discharge components
2024-10-22 10:29:57 +02:00
# ac_charge, dc_charge, discharge = self.split_charge_discharge(charge_discharge_mutated)
2024-10-16 15:40:04 +02:00
# 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
2024-10-22 10:29:57 +02:00
individual [ : self . prediction_hours ] = charge_discharge_mutated
2024-10-16 15:40:04 +02:00
# Step 2: Mutate EV charging schedule if enabled
2024-10-14 10:46:14 +02:00
if self . optimize_ev :
2024-10-16 15:40:04 +02:00
# Extract the relevant part for EV charging schedule
2024-10-14 10:46:14 +02:00
ev_charge_part = individual [ self . prediction_hours : self . prediction_hours * 2 ]
2024-10-22 10:29:57 +02:00
2024-10-16 15:40:04 +02:00
# Apply mutation on the EV charging schedule
2024-10-22 10:29:57 +02:00
( ev_charge_part_mutated , ) = self . toolbox . mutate_ev_charge_index ( ev_charge_part )
2024-10-16 15:40:04 +02:00
# Ensure the EV does not charge during fixed hours (set those hours to 0)
2024-10-22 10:29:57 +02:00
ev_charge_part_mutated [ self . prediction_hours - self . fixed_eauto_hours : ] = [
0
] * self . fixed_eauto_hours
2024-10-16 15:40:04 +02:00
# Reassign the mutated EV charging part back to the individual
2024-10-14 10:46:14 +02:00
individual [ self . prediction_hours : self . prediction_hours * 2 ] = ev_charge_part_mutated
2024-10-16 15:40:04 +02:00
# Step 3: Mutate appliance start times if household appliances are part of the optimization
2024-10-14 10:46:14 +02:00
if self . opti_param [ " haushaltsgeraete " ] > 0 :
2024-10-16 15:40:04 +02:00
# Extract the appliance part (typically a single value for the start hour)
2024-10-14 10:46:14 +02:00
appliance_part = [ individual [ - 1 ] ]
2024-10-22 10:29:57 +02:00
2024-10-16 15:40:04 +02:00
# Apply mutation on the appliance start hour
2024-10-22 10:29:57 +02:00
( appliance_part_mutated , ) = self . toolbox . mutate_hour ( appliance_part )
2024-10-16 15:40:04 +02:00
# Reassign the mutated appliance part back to the individual
2024-10-14 10:46:14 +02:00
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
2024-10-22 10:29:57 +02:00
individual_components = [
self . toolbox . attr_discharge_state ( ) for _ in range ( self . prediction_hours )
]
2024-10-14 10:46:14 +02:00
# Add EV charge index values if optimize_ev is True
if self . optimize_ev :
2024-10-22 10:29:57 +02:00
individual_components + = [
self . toolbox . attr_ev_charge_index ( ) for _ in range ( self . prediction_hours )
]
2024-10-14 10:46:14 +02:00
# Add the start time of the household appliance if it's being optimized
if self . opti_param [ " haushaltsgeraete " ] > 0 :
individual_components + = [ self . toolbox . attr_int ( ) ]
return creator . Individual ( individual_components )
2024-10-14 10:10:12 +02:00
2024-10-04 03:11:24 +02:00
def split_individual (
2024-11-15 22:27:25 +01:00
self , individual : list [ float ]
) - > Tuple [ list [ int ] , list [ float ] , Optional [ int ] ] :
2024-11-10 23:27:52 +01:00
""" Split the individual solution into its components.
Components :
1. Discharge hours ( binary ) ,
2. Electric vehicle charge hours ( float ) ,
2024-10-04 03:11:24 +02:00
3. Dishwasher start time ( integer if applicable ) .
2024-09-30 08:23:38 +02:00
"""
2024-10-04 03:11:24 +02:00
discharge_hours_bin = individual [ : self . prediction_hours ]
2024-10-16 15:40:04 +02:00
eautocharge_hours_float = (
individual [ self . prediction_hours : self . prediction_hours * 2 ]
2024-10-22 10:29:57 +02:00
if self . optimize_ev
2024-10-16 15:40:04 +02:00
else None
)
2024-10-04 03:11:24 +02:00
spuelstart_int = (
individual [ - 1 ]
if self . opti_param and self . opti_param . get ( " haushaltsgeraete " , 0 ) > 0
else None
)
2024-08-24 10:22:49 +02:00
return discharge_hours_bin , eautocharge_hours_float , spuelstart_int
2024-11-15 22:27:25 +01:00
def setup_deap_environment ( self , opti_param : dict [ str , Any ] , start_hour : int ) - > None :
2024-11-10 23:27:52 +01:00
""" Set up the DEAP environment with fitness and individual creation rules. """
2024-04-02 16:46:16 +02:00
self . opti_param = opti_param
2024-10-03 11:05:44 +02:00
2024-10-04 03:11:24 +02:00
# Remove existing FitnessMin and Individual classes from creator if present
for attr in [ " FitnessMin " , " Individual " ] :
if attr in creator . __dict__ :
del creator . __dict__ [ attr ]
2024-09-30 08:23:38 +02:00
2024-10-04 03:11:24 +02:00
# Create new FitnessMin and Individual classes
2024-04-02 16:46:16 +02:00
creator . create ( " FitnessMin " , base . Fitness , weights = ( - 1.0 , ) )
creator . create ( " Individual " , list , fitness = creator . FitnessMin )
2024-10-03 11:05:44 +02:00
2024-10-04 03:11:24 +02:00
# Initialize toolbox with attributes and operations
2024-04-02 16:46:16 +02:00
self . toolbox = base . Toolbox ( )
2024-10-20 18:18:06 +02:00
if self . optimize_dc_charge :
2024-10-22 10:29:57 +02:00
self . toolbox . register ( " attr_discharge_state " , random . randint , 0 , 8 )
2024-10-20 18:18:06 +02:00
else :
2024-10-22 10:29:57 +02:00
self . toolbox . register ( " attr_discharge_state " , random . randint , 0 , 6 )
2024-10-20 18:18:06 +02:00
2024-10-14 10:10:12 +02:00
if self . optimize_ev :
2024-10-22 10:29:57 +02:00
self . toolbox . register (
2024-11-11 21:38:13 +01:00
" attr_ev_charge_index " ,
random . randint ,
0 ,
len ( self . _config . eos . available_charging_rates_in_percentage ) - 1 ,
2024-10-22 10:29:57 +02:00
)
2024-05-03 10:56:13 +02:00
self . toolbox . register ( " attr_int " , random . randint , start_hour , 23 )
2024-10-03 11:05:44 +02:00
2024-10-14 10:10:12 +02:00
# Register individual creation function
2024-10-14 10:46:14 +02:00
self . toolbox . register ( " individual " , self . create_individual )
2024-04-02 16:46:16 +02:00
2024-10-04 03:11:24 +02:00
# Register population, mating, mutation, and selection functions
2024-10-10 15:00:32 +02:00
self . toolbox . register ( " population " , tools . initRepeat , list , self . toolbox . individual )
2024-04-01 14:11:38 +02:00
self . toolbox . register ( " mate " , tools . cxTwoPoint )
2024-10-22 10:29:57 +02:00
# self.toolbox.register("mutate", tools.mutFlipBit, indpb=0.1)
2024-10-11 10:47:29 +02:00
# Register separate mutation functions for each type of value:
2024-10-14 10:10:12 +02:00
# - Discharge state mutation (-5, 0, 1)
2024-10-20 18:18:06 +02:00
if self . optimize_dc_charge :
2024-10-22 10:29:57 +02:00
self . toolbox . register (
" mutate_charge_discharge " , tools . mutUniformInt , low = 0 , up = 8 , indpb = 0.2
)
2024-10-20 18:18:06 +02:00
else :
2024-10-22 10:29:57 +02:00
self . toolbox . register (
" mutate_charge_discharge " , tools . mutUniformInt , low = 0 , up = 6 , indpb = 0.2
)
2024-10-11 10:47:29 +02:00
# - Float mutation for EV charging values
2024-10-22 10:29:57 +02:00
self . toolbox . register (
" mutate_ev_charge_index " ,
tools . mutUniformInt ,
low = 0 ,
2024-11-11 21:38:13 +01:00
up = len ( self . _config . eos . available_charging_rates_in_percentage ) - 1 ,
2024-10-22 10:29:57 +02:00
indpb = 0.2 ,
)
2024-10-11 10:47:29 +02:00
# - Start hour mutation for household devices
2024-10-22 08:58:07 +02:00
self . toolbox . register ( " mutate_hour " , tools . mutUniformInt , low = start_hour , up = 23 , indpb = 0.2 )
2024-10-11 10:47:29 +02:00
# Register custom mutation function
2024-10-14 10:46:14 +02:00
self . toolbox . register ( " mutate " , self . mutate )
2024-10-11 10:47:29 +02:00
2024-10-03 11:05:44 +02:00
self . toolbox . register ( " select " , tools . selTournament , tournsize = 3 )
2024-10-04 03:11:24 +02:00
def evaluate_inner (
2024-11-15 22:27:25 +01:00
self , individual : list [ float ] , ems : EnergieManagementSystem , start_hour : int
) - > dict [ str , Any ] :
2024-11-10 23:27:52 +01:00
""" Simulates the energy management system (EMS) using the provided individual solution.
This is an internal function .
2024-10-04 03:11:24 +02:00
"""
2024-10-03 11:05:44 +02:00
ems . reset ( )
2024-10-11 10:47:29 +02:00
discharge_hours_bin , eautocharge_hours_index , spuelstart_int = self . split_individual (
2024-10-10 15:00:32 +02:00
individual
2024-10-03 11:05:44 +02:00
)
2024-10-04 03:11:24 +02:00
if self . opti_param . get ( " haushaltsgeraete " , 0 ) > 0 :
2024-10-03 11:05:44 +02:00
ems . set_haushaltsgeraet_start ( spuelstart_int , global_start_hour = start_hour )
2024-10-22 10:29:57 +02:00
ac , dc , discharge = self . decode_charge_discharge ( discharge_hours_bin )
2024-10-14 10:10:12 +02:00
ems . set_akku_discharge_hours ( discharge )
2024-10-20 18:18:06 +02:00
# Set DC charge hours only if DC optimization is enabled
if self . optimize_dc_charge :
ems . set_akku_dc_charge_hours ( dc )
2024-10-16 15:40:04 +02:00
ems . set_akku_ac_charge_hours ( ac )
2024-10-14 10:46:14 +02:00
2024-10-14 10:10:12 +02:00
if self . optimize_ev :
2024-10-14 10:46:14 +02:00
eautocharge_hours_float = [
2024-11-11 21:38:13 +01:00
self . _config . eos . available_charging_rates_in_percentage [ i ]
for i in eautocharge_hours_index
2024-10-22 10:29:57 +02:00
]
2024-10-17 10:32:24 +02:00
ems . set_ev_charge_hours ( eautocharge_hours_float )
2024-10-20 18:18:06 +02:00
else :
2024-10-22 10:29:57 +02:00
ems . set_ev_charge_hours ( np . full ( self . prediction_hours , 0 ) )
2024-10-04 03:11:24 +02:00
return ems . simuliere ( start_hour )
def evaluate (
self ,
2024-11-15 22:27:25 +01:00
individual : list [ float ] ,
2024-10-04 03:11:24 +02:00
ems : EnergieManagementSystem ,
2024-11-15 22:27:25 +01:00
parameters : OptimizationParameters ,
2024-10-04 03:11:24 +02:00
start_hour : int ,
worst_case : bool ,
) - > Tuple [ float ] :
2024-11-10 23:27:52 +01:00
""" Evaluate the fitness of an individual solution based on the simulation results. """
2024-09-30 08:23:38 +02:00
try :
2024-10-03 11:05:44 +02:00
o = self . evaluate_inner ( individual , ems , start_hour )
2024-10-09 16:52:51 +02:00
except Exception as e :
2024-10-04 03:11:24 +02:00
return ( 100000.0 , ) # Return a high penalty in case of an exception
2024-10-22 10:29:57 +02:00
2024-10-04 03:11:24 +02:00
gesamtbilanz = o [ " Gesamtbilanz_Euro " ] * ( - 1.0 if worst_case else 1.0 )
2024-10-22 10:29:57 +02:00
2024-10-10 15:00:32 +02:00
discharge_hours_bin , eautocharge_hours_float , _ = self . split_individual ( individual )
2024-09-15 11:08:00 +02:00
2024-10-11 10:47:29 +02:00
# Small Penalty for not discharging
2024-10-04 03:11:24 +02:00
gesamtbilanz + = sum (
0.01 for i in range ( self . prediction_hours ) if discharge_hours_bin [ i ] == 0.0
)
2024-10-22 10:29:57 +02:00
2024-10-04 03:11:24 +02:00
# Penalty for not meeting the minimum SOC (State of Charge) requirement
2024-11-15 22:27:25 +01:00
# if parameters.eauto_min_soc_prozent - ems.eauto.ladezustand_in_prozent() <= 0.0 and self.optimize_ev:
2024-10-17 10:32:24 +02:00
# gesamtbilanz += sum(
# self.strafe for ladeleistung in eautocharge_hours_float if ladeleistung != 0.0
# )
2024-10-04 03:11:24 +02:00
2024-10-03 11:05:44 +02:00
individual . extra_data = (
o [ " Gesamtbilanz_Euro " ] ,
o [ " Gesamt_Verluste " ] ,
2024-11-15 22:27:25 +01:00
parameters . eauto . min_soc_prozent - ems . eauto . ladezustand_in_prozent ( ) ,
2024-10-03 11:05:44 +02:00
)
2024-10-04 03:11:24 +02:00
# Adjust total balance with battery value and penalties for unmet SOC
2024-10-22 10:29:57 +02:00
2024-11-15 22:27:25 +01:00
restwert_akku = ems . akku . aktueller_energieinhalt ( ) * parameters . ems . preis_euro_pro_wh_akku
# print(ems.akku.aktueller_energieinhalt()," * ", parameters.ems.preis_euro_pro_wh_akku , " ", restwert_akku, " ", gesamtbilanz)
2024-10-17 10:32:24 +02:00
gesamtbilanz + = - restwert_akku
2024-10-22 10:29:57 +02:00
# print(gesamtbilanz)
2024-10-17 10:32:24 +02:00
if self . optimize_ev :
2024-10-22 10:29:57 +02:00
gesamtbilanz + = max (
0 ,
2024-11-15 22:27:25 +01:00
( parameters . eauto . min_soc_prozent - ems . eauto . ladezustand_in_prozent ( ) )
* self . strafe ,
2024-10-22 10:29:57 +02:00
)
2024-04-01 14:11:38 +02:00
2024-10-03 11:05:44 +02:00
return ( gesamtbilanz , )
2024-09-15 11:08:00 +02:00
2024-10-04 03:11:24 +02:00
def optimize (
2024-11-15 22:27:25 +01:00
self , start_solution : Optional [ list [ float ] ] = None , ngen : int = 400
) - > Tuple [ Any , dict [ str , list [ Any ] ] ] :
2024-10-04 03:11:24 +02:00
""" Run the optimization process using a genetic algorithm. """
2024-09-15 11:08:00 +02:00
population = self . toolbox . population ( n = 300 )
2024-04-01 14:11:38 +02:00
hof = tools . HallOfFame ( 1 )
stats = tools . Statistics ( lambda ind : ind . fitness . values )
stats . register ( " min " , np . min )
2024-10-03 11:05:44 +02:00
2024-10-04 03:11:24 +02:00
if self . verbose :
print ( " Start optimize: " , start_solution )
2024-10-03 11:05:44 +02:00
2024-10-04 03:11:24 +02:00
# Insert the start solution into the population if provided
if start_solution not in [ None , - 1 ] :
for _ in range ( 3 ) :
population . insert ( 0 , creator . Individual ( start_solution ) )
2024-10-03 11:05:44 +02:00
2024-10-22 10:29:57 +02:00
# Run the evolutionary algorithm
2024-10-03 11:05:44 +02:00
algorithms . eaMuPlusLambda (
population ,
self . toolbox ,
mu = 100 ,
2024-10-14 10:10:12 +02:00
lambda_ = 150 ,
2024-10-20 18:18:06 +02:00
cxpb = 0.6 ,
mutpb = 0.4 ,
2024-10-07 19:52:48 +02:00
ngen = ngen ,
2024-10-03 11:05:44 +02:00
stats = stats ,
halloffame = hof ,
2024-10-04 03:11:24 +02:00
verbose = self . verbose ,
2024-10-03 11:05:44 +02:00
)
member = { " bilanz " : [ ] , " verluste " : [ ] , " nebenbedingung " : [ ] }
2024-09-30 08:23:38 +02:00
for ind in population :
2024-10-03 11:05:44 +02:00
if hasattr ( ind , " extra_data " ) :
extra_value1 , extra_value2 , extra_value3 = ind . extra_data
member [ " bilanz " ] . append ( extra_value1 )
member [ " verluste " ] . append ( extra_value2 )
member [ " nebenbedingung " ] . append ( extra_value3 )
2024-09-30 08:23:38 +02:00
2024-10-03 11:05:44 +02:00
return hof [ 0 ] , member
2024-09-30 08:23:38 +02:00
2024-10-03 11:05:44 +02:00
def optimierung_ems (
2024-10-04 03:11:24 +02:00
self ,
2024-11-15 22:27:25 +01:00
parameters : OptimizationParameters ,
2024-10-04 03:11:24 +02:00
start_hour : Optional [ int ] = None ,
worst_case : bool = False ,
startdate : Optional [ Any ] = None , # startdate is not used!
2024-10-07 19:52:48 +02:00
* ,
2024-10-17 10:32:24 +02:00
ngen : int = 600 ,
2024-11-15 22:27:25 +01:00
) - > dict [ str , Any ] :
2024-11-10 23:27:52 +01:00
""" Perform EMS (Energy Management System) optimization and visualize results. """
2024-10-03 11:05:44 +02:00
einspeiseverguetung_euro_pro_wh = np . full (
2024-11-15 22:27:25 +01:00
self . prediction_hours , parameters . ems . einspeiseverguetung_euro_pro_wh
2024-10-04 03:11:24 +02:00
)
# Initialize PV and EV batteries
2024-10-03 11:05:44 +02:00
akku = PVAkku (
2024-11-15 22:27:25 +01:00
parameters . pv_akku ,
2024-10-03 11:05:44 +02:00
hours = self . prediction_hours ,
)
2024-10-04 03:11:24 +02:00
akku . set_charge_per_hour ( np . full ( self . prediction_hours , 1 ) )
2024-10-03 11:05:44 +02:00
2024-10-14 10:10:12 +02:00
self . optimize_ev = True
2024-11-15 22:27:25 +01:00
if parameters . eauto . min_soc_prozent - parameters . eauto . start_soc_prozent < 0 :
2024-10-14 10:10:12 +02:00
self . optimize_ev = False
2024-10-03 11:05:44 +02:00
eauto = PVAkku (
2024-11-15 22:27:25 +01:00
parameters . eauto ,
2024-10-03 11:05:44 +02:00
hours = self . prediction_hours ,
)
2024-10-04 03:11:24 +02:00
eauto . set_charge_per_hour ( np . full ( self . prediction_hours , 1 ) )
# Initialize household appliance if applicable
spuelmaschine = (
Haushaltsgeraet (
2024-11-15 22:27:25 +01:00
parameters = parameters . spuelmaschine ,
2024-10-03 11:05:44 +02:00
hours = self . prediction_hours ,
2024-10-06 14:29:23 +02:00
)
2024-11-15 22:27:25 +01:00
if parameters . spuelmaschine is not None
2024-10-04 03:11:24 +02:00
else None
)
2024-09-30 08:23:38 +02:00
2024-10-04 03:11:24 +02:00
# Initialize the inverter and energy management system
2024-11-15 22:27:25 +01:00
wr = Wechselrichter ( parameters . wechselrichter , akku )
2024-10-03 11:05:44 +02:00
ems = EnergieManagementSystem (
2024-11-15 22:27:25 +01:00
self . _config . eos ,
parameters . ems ,
2024-10-03 11:05:44 +02:00
eauto = eauto ,
haushaltsgeraet = spuelmaschine ,
wechselrichter = wr ,
)
2024-10-04 03:11:24 +02:00
# Setup the DEAP environment and optimization process
2024-10-10 15:00:32 +02:00
self . setup_deap_environment ( { " haushaltsgeraete " : 1 if spuelmaschine else 0 } , start_hour )
2024-10-04 03:11:24 +02:00
self . toolbox . register (
" evaluate " ,
2024-11-15 22:27:25 +01:00
lambda ind : self . evaluate ( ind , ems , parameters , start_hour , worst_case ) ,
2024-10-04 03:11:24 +02:00
)
2024-11-15 22:27:25 +01:00
start_solution , extra_data = self . optimize ( parameters . start_solution , ngen = ngen )
2024-10-04 03:11:24 +02:00
# Perform final evaluation on the best solution
o = self . evaluate_inner ( start_solution , ems , start_hour )
2024-10-10 15:00:32 +02:00
discharge_hours_bin , eautocharge_hours_float , spuelstart_int = self . split_individual (
start_solution
2024-10-03 11:05:44 +02:00
)
2024-10-20 18:18:06 +02:00
if self . optimize_ev :
2024-10-22 10:29:57 +02:00
eautocharge_hours_float = [
2024-11-11 21:38:13 +01:00
self . _config . eos . available_charging_rates_in_percentage [ i ]
for i in eautocharge_hours_float
2024-10-22 10:29:57 +02:00
]
2024-10-16 15:40:04 +02:00
ac_charge , dc_charge , discharge = self . decode_charge_discharge ( discharge_hours_bin )
2024-10-04 03:11:24 +02:00
# Visualize the results
2024-10-03 11:05:44 +02:00
visualisiere_ergebnisse (
2024-11-15 22:27:25 +01:00
parameters . ems . gesamtlast ,
parameters . ems . pv_prognose_wh ,
parameters . ems . strompreis_euro_pro_wh ,
o ,
ac_charge ,
dc_charge ,
discharge ,
parameters . temperature_forecast ,
start_hour ,
einspeiseverguetung_euro_pro_wh ,
2024-11-11 21:38:13 +01:00
config = self . _config ,
2024-10-03 11:05:44 +02:00
extra_data = extra_data ,
)
2024-10-04 03:11:24 +02:00
2024-10-09 16:52:51 +02:00
# List output keys where the first element needs to be changed to None
keys_to_modify = [
" Last_Wh_pro_Stunde " ,
" Netzeinspeisung_Wh_pro_Stunde " ,
" akku_soc_pro_stunde " ,
" Netzbezug_Wh_pro_Stunde " ,
" Kosten_Euro_pro_Stunde " ,
" Einnahmen_Euro_pro_Stunde " ,
2024-11-15 22:27:25 +01:00
" EAuto_SoC_pro_Stunde " ,
2024-10-09 16:52:51 +02:00
" Verluste_Pro_Stunde " ,
" Haushaltsgeraet_wh_pro_stunde " ,
]
# Loop through each key in the list
for key in keys_to_modify :
# Convert the NumPy array to a list
element_list = o [ key ] . tolist ( )
# Change the first value to None
2024-10-22 10:29:57 +02:00
# element_list[0] = None
2024-10-09 16:52:51 +02:00
# Change the NaN to None (JSON)
element_list = [
2024-10-10 15:00:32 +02:00
None if isinstance ( x , ( int , float ) ) and np . isnan ( x ) else x for x in element_list
2024-10-09 16:52:51 +02:00
]
# Assign the modified list back to the dictionary
o [ key ] = element_list
2024-10-04 03:11:24 +02:00
# Return final results as a dictionary
2024-10-03 11:05:44 +02:00
return {
2024-10-16 15:40:04 +02:00
" ac_charge " : ac_charge . tolist ( ) ,
2024-10-22 10:29:57 +02:00
" dc_charge " : dc_charge . tolist ( ) ,
" discharge_allowed " : discharge . tolist ( ) ,
2024-10-03 11:05:44 +02:00
" eautocharge_hours_float " : eautocharge_hours_float ,
" result " : o ,
2024-10-04 03:11:24 +02:00
" eauto_obj " : ems . eauto . to_dict ( ) ,
" start_solution " : start_solution ,
2024-10-03 11:05:44 +02:00
" spuelstart " : spuelstart_int ,
2024-11-15 22:27:25 +01:00
# "simulation_data": o,
2024-10-03 11:05:44 +02:00
}