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-18 20:54:39 +01:00
from pydantic import BaseModel , Field , field_validator , model_validator
2024-11-15 22:27:25 +01:00
from typing_extensions import Self
2024-11-19 21:47:43 +01:00
from akkudoktoreos . config import AppConfig
2024-11-26 22:28:05 +01:00
from akkudoktoreos . devices . battery import (
EAutoParameters ,
EAutoResult ,
PVAkku ,
PVAkkuParameters ,
)
2024-11-26 00:53:16 +01:00
from akkudoktoreos . devices . generic import HomeAppliance , HomeApplianceParameters
2024-11-19 21:47:43 +01:00
from akkudoktoreos . devices . inverter import Wechselrichter , WechselrichterParameters
from akkudoktoreos . prediction . ems import (
2024-11-15 22:27:25 +01:00
EnergieManagementSystem ,
EnergieManagementSystemParameters ,
2024-11-26 22:28:05 +01:00
SimulationResult ,
2024-11-15 22:27:25 +01:00
)
2024-11-26 22:28:05 +01:00
from akkudoktoreos . utils . utils import NumpyEncoder
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 ( )
2024-11-26 22:28:05 +01:00
eauto : Optional [ EAutoParameters ]
2024-11-26 00:53:16 +01:00
dishwasher : Optional [ HomeApplianceParameters ] = None
2024-11-26 22:28:05 +01:00
temperature_forecast : Optional [ list [ float ] ] = Field (
default = None ,
description = " An array of floats representing the temperature forecast in degrees Celsius for different time intervals. " ,
2024-11-15 22:27:25 +01:00
)
start_solution : Optional [ list [ float ] ] = Field (
2024-11-26 22:28:05 +01:00
default = None , description = " Can be `null` or contain a previous solution (if available). "
2024-11-15 22:27:25 +01:00
)
@model_validator ( mode = " after " )
def validate_list_length ( self ) - > Self :
arr_length = len ( self . ems . pv_prognose_wh )
2024-11-26 22:28:05 +01:00
if self . temperature_forecast is not None and arr_length != len ( self . temperature_forecast ) :
2024-11-15 22:27:25 +01:00
raise ValueError ( " Input lists have different lenghts " )
return self
2024-11-18 20:54:39 +01:00
@field_validator ( " start_solution " )
def validate_start_solution (
cls , start_solution : Optional [ list [ float ] ]
) - > Optional [ list [ float ] ] :
if start_solution is not None and len ( start_solution ) < 2 :
raise ValueError ( " Requires at least two values. " )
return start_solution
2024-11-15 22:27:25 +01:00
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). "
)
2024-11-26 22:28:05 +01:00
eautocharge_hours_float : Optional [ list [ float ] ] = Field ( description = " TBD " )
2024-11-15 22:27:25 +01:00
result : SimulationResult
2024-11-26 22:28:05 +01:00
eauto_obj : Optional [ EAutoResult ]
2024-11-15 22:27:25 +01:00
start_solution : Optional [ list [ float ] ] = Field (
2024-11-26 22:28:05 +01:00
default = None ,
2024-11-15 22:27:25 +01:00
description = " An array of binary values (0 or 1) representing a possible starting solution for the simulation. " ,
)
2024-11-26 00:53:16 +01:00
washingstart : Optional [ int ] = Field (
2024-11-26 22:28:05 +01:00
default = None ,
2024-11-15 22:27:25 +01:00
description = " Can be `null` or contain an object representing the start of washing (if applicable). " ,
)
2024-11-26 22:28:05 +01:00
@field_validator (
" ac_charge " ,
" dc_charge " ,
" discharge_allowed " ,
mode = " before " ,
)
def convert_numpy ( cls , field : Any ) - > Any :
return NumpyEncoder . convert_numpy ( field ) [ 0 ]
@field_validator (
" eauto_obj " ,
mode = " before " ,
)
def convert_eauto ( cls , field : Any ) - > Any :
if isinstance ( field , PVAkku ) :
return EAutoResult ( * * field . to_dict ( ) )
return field
2024-11-15 22:27:25 +01:00
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-11-26 22:28:05 +01:00
self . opti_param : dict [ str , Any ] = { }
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 (
2024-11-26 22:28:05 +01:00
self , discharge_hours_bin : list [ int ]
2024-10-22 10:29:57 +02:00
) - > 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
2024-11-26 22:28:05 +01:00
discharge_hours_bin_np = np . array ( discharge_hours_bin )
2024-10-14 10:10:12 +02:00
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 (
2024-11-26 22:28:05 +01:00
( discharge_hours_bin_np > = 2 ) & ( discharge_hours_bin_np < = 6 ) ,
discharge_hours_bin_np - 1 ,
0 ,
2024-10-22 10:29:57 +02:00
)
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 :
2024-11-26 22:28:05 +01:00
dc_charge = np . where ( discharge_hours_bin_np == 8 , 1 , 0 )
2024-10-20 18:18:06 +02:00
else :
2024-10-22 10:29:57 +02:00
dc_charge = np . ones_like (
2024-11-26 22:28:05 +01:00
discharge_hours_bin_np
2024-10-22 10:29:57 +02:00
) # 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)
2024-11-26 22:28:05 +01:00
discharge = np . where ( discharge_hours_bin_np == 1 , 1 , 0 )
2024-10-16 15:40:04 +02:00
return ac_charge , dc_charge , discharge
2024-10-14 10:46:14 +02:00
# Custom mutation function that applies type-specific mutations
2024-11-26 22:28:05 +01:00
def mutate ( self , individual : list [ int ] ) - > tuple [ list [ int ] ] :
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-11-26 00:53:16 +01:00
if self . opti_param [ " home_appliance " ] > 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
2024-11-26 22:28:05 +01:00
def create_individual ( self ) - > list [ int ] :
2024-10-14 10:46:14 +02:00
# 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
2024-11-26 00:53:16 +01:00
if self . opti_param [ " home_appliance " ] > 0 :
2024-10-14 10:46:14 +02:00
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-26 22:28:05 +01:00
self , individual : list [ int ]
) - > tuple [ list [ int ] , Optional [ list [ int ] ] , 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-11-26 22:28:05 +01:00
eautocharge_hours_index = (
2024-10-16 15:40:04 +02:00
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-11-26 00:53:16 +01:00
washingstart_int = (
2024-11-26 22:28:05 +01:00
int ( individual [ - 1 ] )
2024-11-26 00:53:16 +01:00
if self . opti_param and self . opti_param . get ( " home_appliance " , 0 ) > 0
2024-10-04 03:11:24 +02:00
else None
)
2024-11-26 22:28:05 +01:00
return discharge_hours_bin , eautocharge_hours_index , washingstart_int
2024-08-24 10:22:49 +02:00
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-26 22:28:05 +01:00
self , individual : list [ int ] , ems : EnergieManagementSystem , start_hour : int
2024-11-15 22:27:25 +01:00
) - > 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-11-26 00:53:16 +01:00
discharge_hours_bin , eautocharge_hours_index , washingstart_int = self . split_individual (
2024-10-10 15:00:32 +02:00
individual
2024-10-03 11:05:44 +02:00
)
2024-11-26 22:28:05 +01:00
if washingstart_int is not None :
2024-11-26 00:53:16 +01:00
ems . set_home_appliance_start ( washingstart_int , global_start_hour = start_hour )
2024-10-03 11:05:44 +02:00
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-11-26 22:28:05 +01:00
if eautocharge_hours_index is not None :
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-11-26 22:28:05 +01:00
ems . set_ev_charge_hours ( np . array ( 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-26 22:28:05 +01:00
individual : list [ int ] ,
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-11-26 22:28:05 +01:00
discharge_hours_bin , eautocharge_hours_index , _ = 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(
2024-11-26 22:28:05 +01:00
# self.strafe for ladeleistung in eautocharge_hours_index if ladeleistung != 0.0
2024-10-17 10:32:24 +02:00
# )
2024-10-04 03:11:24 +02:00
2024-11-26 22:28:05 +01:00
individual . extra_data = ( # type: ignore[attr-defined]
2024-10-03 11:05:44 +02:00
o [ " Gesamtbilanz_Euro " ] ,
o [ " Gesamt_Verluste " ] ,
2024-11-26 22:28:05 +01:00
parameters . eauto . min_soc_prozent - ems . eauto . ladezustand_in_prozent ( )
if parameters . eauto and ems . eauto
else 0 ,
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-26 22:28:05 +01:00
(
parameters . eauto . min_soc_prozent - ems . eauto . ladezustand_in_prozent ( )
if parameters . eauto and ems . eauto
else 0
)
2024-11-15 22:27:25 +01:00
* 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
2024-11-26 22:28:05 +01:00
if start_solution is not None :
2024-10-04 03:11:24 +02:00
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
)
2024-11-26 22:28:05 +01:00
member : dict [ str , list [ float ] ] = { " 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-11-26 22:28:05 +01:00
start_hour : int ,
2024-10-04 03:11:24 +02:00
worst_case : bool = False ,
2024-10-17 10:32:24 +02:00
ngen : int = 600 ,
2024-11-26 22:28:05 +01:00
) - > OptimizeResponse :
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-11-26 22:28:05 +01:00
eauto : Optional [ PVAkku ] = None
if parameters . eauto :
eauto = PVAkku (
parameters . eauto ,
hours = self . prediction_hours ,
)
eauto . set_charge_per_hour ( np . full ( self . prediction_hours , 1 ) )
self . optimize_ev = (
parameters . eauto . min_soc_prozent - parameters . eauto . start_soc_prozent > = 0
)
else :
2024-10-14 10:10:12 +02:00
self . optimize_ev = False
2024-10-04 03:11:24 +02:00
# Initialize household appliance if applicable
2024-11-26 00:53:16 +01:00
dishwasher = (
HomeAppliance (
parameters = parameters . dishwasher ,
2024-10-03 11:05:44 +02:00
hours = self . prediction_hours ,
2024-10-06 14:29:23 +02:00
)
2024-11-26 00:53:16 +01:00
if parameters . dishwasher 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-11-26 22:28:05 +01:00
wechselrichter = wr ,
2024-10-03 11:05:44 +02:00
eauto = eauto ,
2024-11-26 00:53:16 +01:00
home_appliance = dishwasher ,
2024-10-03 11:05:44 +02:00
)
2024-10-04 03:11:24 +02:00
# Setup the DEAP environment and optimization process
2024-11-26 00:53:16 +01:00
self . setup_deap_environment ( { " home_appliance " : 1 if dishwasher 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-11-26 22:28:05 +01:00
discharge_hours_bin , eautocharge_hours_index , washingstart_int = self . split_individual (
2024-10-10 15:00:32 +02:00
start_solution
2024-10-03 11:05:44 +02:00
)
2024-11-26 22:28:05 +01:00
eautocharge_hours_float = (
[
2024-11-11 21:38:13 +01:00
self . _config . eos . available_charging_rates_in_percentage [ i ]
2024-11-26 22:28:05 +01:00
for i in eautocharge_hours_index
2024-10-22 10:29:57 +02:00
]
2024-11-26 22:28:05 +01:00
if eautocharge_hours_index is not None
else None
)
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-11-26 22:28:05 +01:00
return OptimizeResponse (
* * {
" ac_charge " : ac_charge ,
" dc_charge " : dc_charge ,
" discharge_allowed " : discharge ,
" eautocharge_hours_float " : eautocharge_hours_float ,
" result " : SimulationResult ( * * o ) ,
" eauto_obj " : ems . eauto ,
" start_solution " : start_solution ,
" washingstart " : washingstart_int ,
}
)