Improve genetic optimizer convergence

This commit is contained in:
Andreas
2026-07-16 14:32:20 +02:00
parent 6465e22f07
commit 4dfd4b275b
9 changed files with 1084 additions and 665 deletions
+14 -4
View File
@@ -62,10 +62,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
ETS forecasts. A median fallback is used when the available history is too short for ETS. ETS forecasts. A median fallback is used when the available history is too short for ETS.
### Changed ### Changed
- Use a fixed, diverse genetic start population with ten exact warm-start copies, up to fifty - Scale the diverse genetic start population with the configured population size while preserving
locally mutated warm-start neighbours, up to one hundred randomized domain-informed battery, the established 300-member mix: exact warm starts, locally mutated neighbours, randomized
direct-marketing, EV, and flexible-appliance schedules, and a guaranteed random remainder. domain-informed battery/direct-marketing/EV/appliance schedules, and a guaranteed random
Retain 150 parents while generating 150 offspring per generation. remainder. Survivor and offspring counts now follow `optimization.genetic.individuals` instead
of remaining fixed at 150.
- Add coherent battery block mutations and energy-shift mutations that move weak battery exports
into several later expensive self-consumption slots in one step. A bounded, fitness-checked
local search applies the same neighbourhood to the final incumbent, avoiding local minima that
cannot be crossed by an individually disadvantageous single-slot mutation.
- Memoize successful canonical fitness evaluations within one optimization run, including repaired - Memoize successful canonical fitness evaluations within one optimization run, including repaired
EV genomes and auxiliary metrics. Log cache hits, misses, key count, and hit rate after each run; EV genomes and auxiliary metrics. Log cache hits, misses, key count, and hit rate after each run;
failed evaluations and results from previous runs are never reused. failed evaluations and results from previous runs are never reused.
@@ -80,6 +85,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
are deprecated in favour of `appliance_starts` and `result.home_appliance_energy_wh`. are deprecated in favour of `appliance_starts` and `result.home_appliance_energy_wh`.
### Fixed ### Fixed
- Respect `optimization.genetic.individuals` and `optimization.genetic.generations` independently
in automatic and `/optimize` runs. Previously the individual count was accidentally passed as
the generation count, the configured generation count was ignored, and every generation still
generated 150 offspring. The deprecated `?ngen=` query remains a generation-count override;
`?individuals=` can override the population for one API run.
- Allow the direct-marketing optimizer to select a true battery self-consumption state with DC - Allow the direct-marketing optimizer to select a true battery self-consumption state with DC
charging and local-load discharge enabled in the same slot. Existing warm-start state numbers charging and local-load discharge enabled in the same slot. Existing warm-start state numbers
remain compatible, and educated guesses now use the combined state for PV/load overlap instead remain compatible, and educated guesses now use the combined state for PV/load overlap instead
+13 -5
View File
@@ -159,6 +159,7 @@ class EnergyManagement(
mode: EnergyManagementMode, mode: EnergyManagementMode,
genetic_parameters: Optional[GeneticOptimizationParameters] = None, genetic_parameters: Optional[GeneticOptimizationParameters] = None,
genetic_individuals: Optional[int] = None, genetic_individuals: Optional[int] = None,
genetic_generations: Optional[int] = None,
genetic_seed: Optional[int] = None, genetic_seed: Optional[int] = None,
force_enable: Optional[bool] = False, force_enable: Optional[bool] = False,
force_update: Optional[bool] = False, force_update: Optional[bool] = False,
@@ -180,8 +181,9 @@ class EnergyManagement(
parameter set for the genetic algorithm. If not provided, it will parameter set for the genetic algorithm. If not provided, it will
be constructed based on the current configuration and predictions. be constructed based on the current configuration and predictions.
genetic_individuals (int, optional): The number of individuals for the genetic_individuals (int, optional): The number of individuals for the
genetic algorithm. Defaults to the algorithm's internal default (400) initial genetic population. Defaults to the configured value.
if not specified. genetic_generations (int, optional): The number of generations to
evolve. Defaults to the configured value.
genetic_seed (int, optional): The seed for the genetic algorithm. Defaults genetic_seed (int, optional): The seed for the genetic algorithm. Defaults
to the algorithm's internal random seed if not specified. to the algorithm's internal random seed if not specified.
force_enable (bool, optional): If True, bypasses any disabled state force_enable (bool, optional): If True, bypasses any disabled state
@@ -248,6 +250,8 @@ class EnergyManagement(
# Take values from config if not given # Take values from config if not given
if genetic_individuals is None: if genetic_individuals is None:
genetic_individuals = cls.config.optimization.genetic.individuals genetic_individuals = cls.config.optimization.genetic.individuals
if genetic_generations is None:
genetic_generations = cls.config.optimization.genetic.generations
if genetic_seed is None: if genetic_seed is None:
genetic_seed = cls.config.optimization.genetic.seed genetic_seed = cls.config.optimization.genetic.seed
@@ -262,7 +266,8 @@ class EnergyManagement(
solution = optimization.optimierung_ems( solution = optimization.optimierung_ems(
start_hour=cls._start_datetime.hour, start_hour=cls._start_datetime.hour,
parameters=genetic_parameters, parameters=genetic_parameters,
ngen=genetic_individuals, ngen=genetic_generations,
individuals=genetic_individuals,
) )
except: except:
logger.exception("Energy management optimization failed.") logger.exception("Energy management optimization failed.")
@@ -305,6 +310,7 @@ class EnergyManagement(
mode: Optional[EnergyManagementMode] = None, mode: Optional[EnergyManagementMode] = None,
genetic_parameters: Optional[GeneticOptimizationParameters] = None, genetic_parameters: Optional[GeneticOptimizationParameters] = None,
genetic_individuals: Optional[int] = None, genetic_individuals: Optional[int] = None,
genetic_generations: Optional[int] = None,
genetic_seed: Optional[int] = None, genetic_seed: Optional[int] = None,
force_enable: Optional[bool] = False, force_enable: Optional[bool] = False,
force_update: Optional[bool] = False, force_update: Optional[bool] = False,
@@ -328,8 +334,9 @@ class EnergyManagement(
parameter set for the genetic algorithm. If not provided, it will parameter set for the genetic algorithm. If not provided, it will
be constructed based on the current configuration and predictions. be constructed based on the current configuration and predictions.
genetic_individuals (int, optional): The number of individuals for the genetic_individuals (int, optional): The number of individuals for the
genetic algorithm. Defaults to the algorithm's internal default (400) initial genetic population. Defaults to the configured value.
if not specified. genetic_generations (int, optional): The number of generations to
evolve. Defaults to the configured value.
genetic_seed (int, optional): The seed for the genetic algorithm. Defaults genetic_seed (int, optional): The seed for the genetic algorithm. Defaults
to the algorithm's internal random seed if not specified. to the algorithm's internal random seed if not specified.
force_enable (bool, optional): If True, bypasses any disabled state force_enable (bool, optional): If True, bypasses any disabled state
@@ -354,6 +361,7 @@ class EnergyManagement(
mode=mode, mode=mode,
genetic_parameters=genetic_parameters, genetic_parameters=genetic_parameters,
genetic_individuals=genetic_individuals, genetic_individuals=genetic_individuals,
genetic_generations=genetic_generations,
genetic_seed=genetic_seed, genetic_seed=genetic_seed,
force_enable=force_enable, force_enable=force_enable,
force_update=force_update, force_update=force_update,
+311 -60
View File
@@ -180,6 +180,7 @@ class GeneticSimulation(PydanticBaseModel):
ev_discharge_hours: Optional[NDArray[Shape["*"], float]] = Field( ev_discharge_hours: Optional[NDArray[Shape["*"], float]] = Field(
default=None, json_schema_extra={"description": "TBD"} default=None, json_schema_extra={"description": "TBD"}
) )
def prepare( def prepare(
self, self,
parameters: GeneticEnergyManagementParameters, parameters: GeneticEnergyManagementParameters,
@@ -562,8 +563,13 @@ class GeneticOptimization(OptimizationBase):
WARM_START_MUTATIONS = 50 WARM_START_MUTATIONS = 50
EDUCATED_GUESS_TARGET = 100 EDUCATED_GUESS_TARGET = 100
MIN_RANDOM_POPULATION_FRACTION = 0.25 MIN_RANDOM_POPULATION_FRACTION = 0.25
SURVIVOR_COUNT = 150 WARM_START_COPY_FRACTION = 0.10
OFFSPRING_COUNT = 150 WARM_START_MUTATION_FRACTION = 0.20
EDUCATED_GUESS_FRACTION = 0.40
BLOCK_MUTATION_PROBABILITY = 0.20
ENERGY_SHIFT_MUTATION_PROBABILITY = 0.35
LOCAL_SEARCH_MAX_EVALUATIONS = 96
LOCAL_SEARCH_MAX_PASSES = 4
EDUCATED_GUESS_EXPORT_QUANTILES = (0.60, 0.75, 0.90) EDUCATED_GUESS_EXPORT_QUANTILES = (0.60, 0.75, 0.90)
# Slot-math helpers — single source of truth for the optimization grid. # Slot-math helpers — single source of truth for the optimization grid.
@@ -787,9 +793,7 @@ class GeneticOptimization(OptimizationBase):
gene_index += 1 gene_index += 1
return ApplianceGeneLayout(genes) return ApplianceGeneLayout(genes)
def _decode_appliance_starts( def _decode_appliance_starts(self, appliance_gene_values: list[int]) -> dict[int, list[int]]:
self, appliance_gene_values: list[int]
) -> dict[int, list[int]]:
"""Map appliance gene values to absolute start slots per appliance. """Map appliance gene values to absolute start slots per appliance.
Each gene value is an index into its gene's ``allowed_start_slots``; it is Each gene value is an index into its gene's ``allowed_start_slots``; it is
@@ -1053,6 +1057,95 @@ class GeneticOptimization(OptimizationBase):
return ac_charge, dc_charge, discharge, battery_grid_export return ac_charge, dc_charge, discharge, battery_grid_export
def _mutate_battery_block(self, individual: list[int]) -> None:
"""Mutate a short future block to one coherent operating policy."""
start_slot = self._start_day_slot()
if start_slot >= self.total_slots:
return
state_layout = self._battery_state_layout()
len_bat = len(self.bat_possible_charge_values)
policy_states = [0, len_bat]
if state_layout.self_consumption_state is not None:
policy_states.append(state_layout.self_consumption_state)
if state_layout.dc_allowed_state is not None:
policy_states.append(state_layout.dc_allowed_state)
if state_layout.grid_export_state is not None:
policy_states.append(state_layout.grid_export_state)
block_start = random.randint(start_slot, self.total_slots - 1) # noqa: S311
max_length = min(12, self.total_slots - block_start)
block_length = random.randint(2, max(2, max_length)) if max_length > 1 else 1 # noqa: S311
state = random.choice(policy_states) # noqa: S311
individual[block_start : block_start + block_length] = [state] * block_length
def _energy_shift_target_slots(
self,
individual: list[int],
source_slot: int,
) -> list[int]:
"""Return later idle slots where retained battery energy avoids costly import."""
try:
prices = np.asarray(self.simulation.elect_price_hourly, dtype=float)
feed_in = np.asarray(self.simulation.elect_revenue_per_hour_arr, dtype=float)
pv = np.asarray(self.simulation.pv_prediction_wh, dtype=float)
load = np.asarray(self.simulation.load_energy_array, dtype=float)
except Exception:
return []
if any(values.size < self.total_slots for values in (prices, feed_in, pv, load)):
return []
len_bat = len(self.bat_possible_charge_values)
source_tariff = float(feed_in[source_slot])
candidates = [
slot
for slot in range(source_slot + 1, self.total_slots)
if 0 <= int(individual[slot]) < len_bat
and load[slot] > pv[slot]
and prices[slot] > source_tariff
]
return sorted(
candidates,
key=lambda slot: (float(prices[slot]), float(load[slot] - pv[slot])),
reverse=True,
)
def _mutate_energy_shift(self, individual: list[int]) -> bool:
"""Move battery energy from a weak export into later expensive self-consumption."""
state_layout = self._battery_state_layout()
export_state = state_layout.grid_export_state
self_state = state_layout.self_consumption_state
if export_state is None or self_state is None:
return False
start_slot = self._start_day_slot()
viable: list[tuple[int, list[int]]] = []
for source_slot in range(start_slot, self.total_slots):
if int(individual[source_slot]) != export_state:
continue
targets = self._energy_shift_target_slots(individual, source_slot)
if targets:
viable.append((source_slot, targets))
if not viable:
return False
# Prefer later/lower-value exports, but retain random diversity among
# the viable tail instead of always producing one identical neighbour.
try:
feed_in = np.asarray(self.simulation.elect_revenue_per_hour_arr, dtype=float)
viable.sort(key=lambda item: (float(feed_in[item[0]]), -item[0]))
except Exception:
viable.sort(key=lambda item: -item[0])
source_slot, targets = random.choice(viable[: min(6, len(viable))]) # noqa: S311
individual[source_slot] = self_state
target_count = min(len(targets), random.randint(4, 10)) # noqa: S311
len_bat = len(self.bat_possible_charge_values)
pv = np.asarray(self.simulation.pv_prediction_wh, dtype=float)
for target_slot in targets[:target_count]:
individual[target_slot] = self_state if pv[target_slot] > 0.0 else len_bat
return True
def mutate(self, individual: list[int]) -> tuple[list[int]]: def mutate(self, individual: list[int]) -> tuple[list[int]]:
"""Custom mutation function for the individual.""" """Custom mutation function for the individual."""
total_states = self._battery_state_layout().total_states total_states = self._battery_state_layout().total_states
@@ -1065,6 +1158,15 @@ class GeneticOptimization(OptimizationBase):
charge_discharge_mutated = np.clip(charge_discharge_mutated, 0, total_states - 1) charge_discharge_mutated = np.clip(charge_discharge_mutated, 0, total_states - 1)
individual[: self.total_slots] = charge_discharge_mutated individual[: self.total_slots] = charge_discharge_mutated
# Point mutation alone struggles with energy-coupled valleys: removing
# an export is temporarily worse until several later bypass slots also
# consume the retained energy. Add coherent neighbourhood moves that
# can cross that valley in one offspring.
if random.random() < self.BLOCK_MUTATION_PROBABILITY: # noqa: S311
self._mutate_battery_block(individual)
if random.random() < self.ENERGY_SHIFT_MUTATION_PROBABILITY: # noqa: S311
self._mutate_energy_shift(individual)
# 2. Mutating the EV charge part, if active # 2. Mutating the EV charge part, if active
if self.optimize_ev: if self.optimize_ev:
ev_charge_part = individual[self.total_slots : self.total_slots * 2] ev_charge_part = individual[self.total_slots : self.total_slots * 2]
@@ -1214,10 +1316,7 @@ class GeneticOptimization(OptimizationBase):
for offset in range(result_slots): for offset in range(result_slots):
slot = start_slot + offset slot = start_slot + offset
charge_index = int(ev_charge_indices[slot]) charge_index = int(ev_charge_indices[slot])
if ( if ev_soc[offset] >= 100.0 - 1e-9 and ev_possible_charge_values[charge_index] > 0.0:
ev_soc[offset] >= 100.0 - 1e-9
and ev_possible_charge_values[charge_index] > 0.0
):
ev_charge_indices[slot] = zero_charge_index ev_charge_indices[slot] = zero_charge_index
changed = True changed = True
@@ -1245,8 +1344,7 @@ class GeneticOptimization(OptimizationBase):
return schedule return schedule
required_stored_wh = max( required_stored_wh = max(
ev.min_soc_wh ev.min_soc_wh - ev.capacity_wh * ev.initial_soc_percentage / 100.0,
- ev.capacity_wh * ev.initial_soc_percentage / 100.0,
0.0, 0.0,
) )
if required_stored_wh <= 0.0: if required_stored_wh <= 0.0:
@@ -1277,11 +1375,7 @@ class GeneticOptimization(OptimizationBase):
if not positive_rates: if not positive_rates:
return schedule return schedule
max_stored_wh = ( max_stored_wh = ev.max_charge_power_w * self.slot_duration_h * ev.charging_efficiency
ev.max_charge_power_w
* self.slot_duration_h
* ev.charging_efficiency
)
remaining_wh = required_stored_wh remaining_wh = required_stored_wh
for slot in candidates: for slot in candidates:
required_rate = remaining_wh / max(max_stored_wh, 1e-9) required_rate = remaining_wh / max(max_stored_wh, 1e-9)
@@ -1305,6 +1399,7 @@ class GeneticOptimization(OptimizationBase):
load = np.asarray(self.simulation.load_energy_array, dtype=float) load = np.asarray(self.simulation.load_energy_array, dtype=float)
genes: list[int] = [] genes: list[int] = []
for gene in self.appliance_layout.genes: for gene in self.appliance_layout.genes:
def opportunity_cost(position: int) -> float: def opportunity_cost(position: int) -> float:
slot = gene.allowed_start_slots[position] slot = gene.allowed_start_slots[position]
return float(feed_in[slot] if pv[slot] > load[slot] else prices[slot]) return float(feed_in[slot] if pv[slot] > load[slot] else prices[slot])
@@ -1417,15 +1512,23 @@ class GeneticOptimization(OptimizationBase):
# feed-in slots. At low tariffs PV is preferentially stored instead. # feed-in slots. At low tariffs PV is preferentially stored instead.
if self.optimize_battery_grid_export and future_feed_in.size: if self.optimize_battery_grid_export and future_feed_in.size:
for quantile in self.EDUCATED_GUESS_EXPORT_QUANTILES: for quantile in self.EDUCATED_GUESS_EXPORT_QUANTILES:
export_guess = policy_guess(
import_quantile=0.70,
export_quantile=quantile,
pv_surplus_ratio=1.0,
allow_ac_arbitrage=False,
)
add_guess( add_guess(
policy_guess( export_guess,
import_quantile=0.70,
export_quantile=quantile,
pv_surplus_ratio=1.0,
allow_ac_arbitrage=False,
),
ev_pv, ev_pv,
) )
# Seed coordinated alternatives that retain a weak export and
# spend the energy in later expensive import slots.
for shifted in self._grid_export_shift_candidates(
export_guess,
max_sources=2,
)[:6]:
add_guess(shifted, ev_pv)
inverter = self.simulation.inverter inverter = self.simulation.inverter
ac_arbitrage_possible = inverter is not None and ( ac_arbitrage_possible = inverter is not None and (
@@ -1473,6 +1576,9 @@ class GeneticOptimization(OptimizationBase):
elif load[slot] > pv[slot] and prices[slot] >= high_import_price: elif load[slot] > pv[slot] and prices[slot] >= high_import_price:
randomized[slot] = discharge_state randomized[slot] = discharge_state
if random.random() < 0.5: # noqa: S311
self._mutate_energy_shift(randomized)
add_guess( add_guess(
randomized, randomized,
ev_pv if random.random() < 0.5 else ev_price, # noqa: S311 ev_pv if random.random() < 0.5 else ev_price, # noqa: S311
@@ -1510,6 +1616,104 @@ class GeneticOptimization(OptimizationBase):
break break
return neighbors return neighbors
def _grid_export_shift_candidates(
self,
individual: list[int],
*,
max_sources: int = 6,
) -> list[list[int]]:
"""Build deterministic export-to-self-consumption neighbourhood candidates."""
state_layout = self._battery_state_layout()
export_state = state_layout.grid_export_state
self_state = state_layout.self_consumption_state
if export_state is None or self_state is None:
return []
start_slot = self._start_day_slot()
try:
feed_in = np.asarray(self.simulation.elect_revenue_per_hour_arr, dtype=float)
pv = np.asarray(self.simulation.pv_prediction_wh, dtype=float)
except Exception:
return []
if feed_in.size < self.total_slots or pv.size < self.total_slots:
return []
sources = [
slot
for slot in range(start_slot, self.total_slots)
if int(individual[slot]) == export_state
]
# Search weak and late export decisions first. They are the most likely
# to compete with later, more valuable avoided grid imports.
sources.sort(key=lambda slot: (float(feed_in[slot]), -slot))
len_bat = len(self.bat_possible_charge_values)
candidates: list[list[int]] = []
seen: set[tuple[int, ...]] = set()
viable_sources = 0
for source_slot in sources:
targets = self._energy_shift_target_slots(individual, source_slot)
if not targets:
continue
viable_sources += 1
counts = sorted({min(len(targets), count) for count in (2, 4, 6, 8, 10, 12)})
for count in counts:
candidate = list(individual)
candidate[source_slot] = self_state
for target_slot in targets[:count]:
candidate[target_slot] = self_state if pv[target_slot] > 0.0 else len_bat
key = tuple(int(value) for value in candidate)
if key in seen:
continue
seen.add(key)
candidates.append(candidate)
if viable_sources >= max_sources:
break
return candidates
def _locally_improve_grid_export(
self,
individual: list[int],
*,
max_evaluations: int,
) -> tuple[Any, int, int, float, float]:
"""Improve the incumbent through bounded, fitness-checked energy shifts."""
best = creator.Individual(individual)
original_fitness = getattr(individual, "fitness", None)
if original_fitness is not None and original_fitness.valid:
best.fitness.values = original_fitness.values
if hasattr(individual, "extra_data"):
best.extra_data = individual.extra_data
if not hasattr(self.toolbox, "evaluate"):
value = float(best.fitness.values[0]) if best.fitness.valid else float("inf")
return best, 0, 0, value, value
if not best.fitness.valid:
best.fitness.values = self.toolbox.evaluate(best)
initial_value = float(best.fitness.values[0])
evaluations = 0
improvements = 0
for _ in range(self.LOCAL_SEARCH_MAX_PASSES):
pass_best = best
for genome in self._grid_export_shift_candidates(best):
if evaluations >= max_evaluations:
break
candidate = creator.Individual(genome)
candidate.fitness.values = self.toolbox.evaluate(candidate)
evaluations += 1
if candidate.fitness.values[0] < pass_best.fitness.values[0] - 1e-9:
pass_best = candidate
if pass_best is best:
break
best = pass_best
improvements += 1
if evaluations >= max_evaluations:
break
final_value = float(best.fitness.values[0])
return best, evaluations, improvements, initial_value, final_value
def setup_deap_environment(self, opti_param: dict[str, Any], start_hour: int) -> None: def setup_deap_environment(self, opti_param: dict[str, Any], start_hour: int) -> None:
"""Set up the DEAP environment with fitness and individual creation rules.""" """Set up the DEAP environment with fitness and individual creation rules."""
self.opti_param = opti_param self.opti_param = opti_param
@@ -1892,6 +2096,7 @@ class GeneticOptimization(OptimizationBase):
self, self,
start_solution: Optional[list[float]] = None, start_solution: Optional[list[float]] = None,
ngen: int = 200, ngen: int = 200,
individuals: Optional[int] = None,
) -> tuple[Any, dict[str, list[Any]]]: ) -> tuple[Any, dict[str, list[Any]]]:
"""Run the optimization process using a genetic algorithm. """Run the optimization process using a genetic algorithm.
@@ -1904,13 +2109,14 @@ class GeneticOptimization(OptimizationBase):
random.seed(self.fix_seed) random.seed(self.fix_seed)
# Set the number of inviduals in a generation # Set the number of inviduals in a generation
try: if individuals is None:
individuals = self.config.optimization.genetic.individuals try:
if individuals is None: individuals = self.config.optimization.genetic.individuals
raise if individuals is None:
except: raise ValueError("individuals is not configured")
individuals = 300 except Exception:
logger.error("Individuals not configured. Using {}.", individuals) individuals = 300
logger.error("Individuals not configured. Using {}.", individuals)
hof = tools.HallOfFame(1) hof = tools.HallOfFame(1)
stats = tools.Statistics(lambda ind: ind.fitness.values) stats = tools.Statistics(lambda ind: ind.fitness.values)
@@ -1924,9 +2130,7 @@ class GeneticOptimization(OptimizationBase):
valid_start_solution: Optional[list[float]] = None valid_start_solution: Optional[list[float]] = None
if start_solution is not None: if start_solution is not None:
n_appliance_genes = self.appliance_layout.n_genes n_appliance_genes = self.appliance_layout.n_genes
expected_length = ( expected_length = self.total_slots * (2 if self.optimize_ev else 1) + n_appliance_genes
self.total_slots * (2 if self.optimize_ev else 1) + n_appliance_genes
)
start_solution = self._start_solution_for_slot_grid(start_solution) start_solution = self._start_solution_for_slot_grid(start_solution)
if len(start_solution) != expected_length: if len(start_solution) != expected_length:
@@ -1943,42 +2147,57 @@ class GeneticOptimization(OptimizationBase):
else: else:
valid_start_solution = start_solution valid_start_solution = start_solution
# Keep the configured initial population size fixed. With the default # Scale the seed families with small populations without changing the
# 300 individuals this yields 10 exact warm starts, 50 local variants, # established 300-individual defaults. This prevents a 100-member run
# 100 educated guesses and 140 fully random candidates. # from spending 60% of its budget on the warm-start neighbourhood.
exact_warm_target = min(
self.WARM_START_COPIES,
max(1, int(individuals * self.WARM_START_COPY_FRACTION + 0.999999)),
)
warm_mutation_target = min(
self.WARM_START_MUTATIONS,
max(1, int(individuals * self.WARM_START_MUTATION_FRACTION + 0.999999)),
)
educated_guess_target = min(
self.EDUCATED_GUESS_TARGET,
max(1, int(individuals * self.EDUCATED_GUESS_FRACTION + 0.999999)),
)
minimum_random = max( minimum_random = max(
int(individuals * self.MIN_RANDOM_POPULATION_FRACTION + 0.999999), int(individuals * self.MIN_RANDOM_POPULATION_FRACTION + 0.999999),
individuals individuals - (exact_warm_target + warm_mutation_target + educated_guess_target),
- (
self.WARM_START_COPIES
+ self.WARM_START_MUTATIONS
+ self.EDUCATED_GUESS_TARGET
),
) )
seed_budget = max(individuals - minimum_random, 0) seed_budget = max(individuals - minimum_random, 0)
seeded: list[list[float]] = [] seeded: list[list[Any]] = []
exact_warm_count = 0 exact_warm_count = 0
warm_neighbors: list[list[int]] = [] warm_neighbors: list[list[int]] = []
if valid_start_solution is not None and seed_budget > 0: if valid_start_solution is not None and seed_budget > 0:
exact_warm_count = min(self.WARM_START_COPIES, seed_budget) exact_warm_count = min(exact_warm_target, seed_budget)
seeded.extend([valid_start_solution] * exact_warm_count) seeded.extend([valid_start_solution] * exact_warm_count)
remaining_seed_budget = seed_budget - len(seeded) remaining_seed_budget = seed_budget - len(seeded)
warm_neighbors = self._mutated_warm_start_neighbors( warm_neighbors = self._mutated_warm_start_neighbors(
valid_start_solution, valid_start_solution,
min(self.WARM_START_MUTATIONS, remaining_seed_budget), min(warm_mutation_target, remaining_seed_budget),
) )
seeded.extend(warm_neighbors) seeded.extend(warm_neighbors)
remaining_seed_budget = seed_budget - len(seeded) remaining_seed_budget = seed_budget - len(seeded)
educated_guesses = self._educated_guess_individuals( educated_guesses = self._educated_guess_individuals(
min(self.EDUCATED_GUESS_TARGET, remaining_seed_budget) min(educated_guess_target, remaining_seed_budget)
) )
seeded.extend(educated_guesses) seeded.extend(educated_guesses)
random_count = max(individuals - len(seeded), 0) random_count = max(individuals - len(seeded), 0)
population = [creator.Individual(seed) for seed in seeded] population = [creator.Individual(seed) for seed in seeded]
population.extend(self.toolbox.population(n=random_count)) population.extend(self.toolbox.population(n=random_count))
logger.info(
"Genetic settings: {} individuals, {} generations, {} survivors, "
"{} offspring per generation.",
individuals,
ngen,
individuals,
individuals,
)
logger.info( logger.info(
"Initial population {}: {} exact warm starts, {} warm mutations, " "Initial population {}: {} exact warm starts, {} warm mutations, "
"{} educated guesses, {} random candidates.", "{} educated guesses, {} random candidates.",
@@ -1996,12 +2215,16 @@ class GeneticOptimization(OptimizationBase):
self._fitness_cache_hits = 0 self._fitness_cache_hits = 0
self._fitness_cache_misses = 0 self._fitness_cache_misses = 0
self._fitness_cache_enabled = True self._fitness_cache_enabled = True
local_evaluations = 0
local_improvements = 0
local_initial_fitness = float("nan")
local_final_fitness = float("nan")
try: try:
pop, log = algorithms.eaMuPlusLambda( pop, log = algorithms.eaMuPlusLambda(
population, population,
self.toolbox, self.toolbox,
mu=self.SURVIVOR_COUNT, mu=individuals,
lambda_=self.OFFSPRING_COUNT, lambda_=individuals,
cxpb=0.6, cxpb=0.6,
mutpb=0.4, mutpb=0.4,
ngen=ngen, ngen=ngen,
@@ -2009,13 +2232,34 @@ class GeneticOptimization(OptimizationBase):
halloffame=hof, halloffame=hof,
verbose=self.verbose, verbose=self.verbose,
) )
(
best_solution,
local_evaluations,
local_improvements,
local_initial_fitness,
local_final_fitness,
) = self._locally_improve_grid_export(
hof[0],
max_evaluations=min(
self.LOCAL_SEARCH_MAX_EVALUATIONS,
max(individuals, 1),
),
)
finally: finally:
self._fitness_cache_enabled = False self._fitness_cache_enabled = False
if local_improvements:
logger.info(
"Grid-export local search: {} improvements in {} evaluations, "
"fitness {:.6f} -> {:.6f}.",
local_improvements,
local_evaluations,
local_initial_fitness,
local_final_fitness,
)
cache_lookups = self._fitness_cache_hits + self._fitness_cache_misses cache_lookups = self._fitness_cache_hits + self._fitness_cache_misses
cache_hit_rate = ( cache_hit_rate = self._fitness_cache_hits / cache_lookups if cache_lookups > 0 else 0.0
self._fitness_cache_hits / cache_lookups if cache_lookups > 0 else 0.0
)
logger.info( logger.info(
"Fitness cache: {} hits, {} misses, {:.1%} hit rate, {} keys.", "Fitness cache: {} hits, {} misses, {:.1%} hit rate, {} keys.",
self._fitness_cache_hits, self._fitness_cache_hits,
@@ -2036,6 +2280,12 @@ class GeneticOptimization(OptimizationBase):
"hit_rate": cache_hit_rate, "hit_rate": cache_hit_rate,
"keys": len(self._fitness_cache), "keys": len(self._fitness_cache),
}, },
"local_search": {
"evaluations": local_evaluations,
"improvements": local_improvements,
"initial_fitness": local_initial_fitness,
"final_fitness": local_final_fitness,
},
} }
member: dict[str, list[float]] = {"bilanz": [], "verluste": [], "nebenbedingung": []} member: dict[str, list[float]] = {"bilanz": [], "verluste": [], "nebenbedingung": []}
@@ -2046,7 +2296,7 @@ class GeneticOptimization(OptimizationBase):
member["verluste"].append(extra_value2) member["verluste"].append(extra_value2)
member["nebenbedingung"].append(extra_value3) member["nebenbedingung"].append(extra_value3)
return hof[0], member return best_solution, member
def optimierung_ems( def optimierung_ems(
self, self,
@@ -2054,6 +2304,7 @@ class GeneticOptimization(OptimizationBase):
start_hour: Optional[int] = None, start_hour: Optional[int] = None,
worst_case: bool = False, worst_case: bool = False,
ngen: Optional[int] = None, ngen: Optional[int] = None,
individuals: Optional[int] = None,
) -> GeneticSolution: ) -> GeneticSolution:
"""Perform EMS (Energy Management System) optimization and visualize results.""" """Perform EMS (Energy Management System) optimization and visualize results."""
direct_marketing_enabled = self._direct_marketing_enabled() direct_marketing_enabled = self._direct_marketing_enabled()
@@ -2177,9 +2428,7 @@ class GeneticOptimization(OptimizationBase):
) )
for appliance_params in home_appliance_params for appliance_params in home_appliance_params
] ]
self.appliance_layout = self._build_appliance_layout( self.appliance_layout = self._build_appliance_layout(home_appliances, self._slot0_datetime)
home_appliances, self._slot0_datetime
)
# Initialize the inverter and energy management system. slot_duration_h # Initialize the inverter and energy management system. slot_duration_h
# lets the Inverter scale max_power_wh to a per-slot energy cap. # lets the Inverter scale max_power_wh to a per-slot energy cap.
@@ -2205,16 +2454,18 @@ class GeneticOptimization(OptimizationBase):
# Setup the DEAP environment and optimization process. The appliance # Setup the DEAP environment and optimization process. The appliance
# genome layout (built above) drives the appliance gene block; evaluate # genome layout (built above) drives the appliance gene block; evaluate
# gets the slot index (its break-even loop walks the slot arrays from "now"). # gets the slot index (its break-even loop walks the slot arrays from "now").
self.setup_deap_environment( self.setup_deap_environment({"home_appliance": self.appliance_layout.n_genes}, start_hour)
{"home_appliance": self.appliance_layout.n_genes}, start_hour
)
self.toolbox.register( self.toolbox.register(
"evaluate", "evaluate",
lambda ind: self.evaluate(ind, parameters, start_slot, worst_case), lambda ind: self.evaluate(ind, parameters, start_slot, worst_case),
) )
start_time = time.time() start_time = time.time()
start_solution, extra_data = self.optimize(parameters.start_solution, ngen=generations) start_solution, extra_data = self.optimize(
parameters.start_solution,
ngen=generations,
individuals=individuals,
)
elapsed_time = time.time() - start_time elapsed_time = time.time() - start_time
logger.debug(f"Time evaluate inner: {elapsed_time:.4f} sec.") logger.debug(f"Time evaluate inner: {elapsed_time:.4f} sec.")
@@ -2222,8 +2473,8 @@ class GeneticOptimization(OptimizationBase):
simulation_result = self.evaluate_inner(start_solution) simulation_result = self.evaluate_inner(start_solution)
# Prepare results # Prepare results
discharge_hours_bin, eautocharge_hours_index, appliance_gene_values = ( discharge_hours_bin, eautocharge_hours_index, appliance_gene_values = self.split_individual(
self.split_individual(start_solution) start_solution
) )
# Materialize the per-device appliance results only for the final best # Materialize the per-device appliance results only for the final best
+17 -2
View File
@@ -1408,7 +1408,21 @@ async def fastapi_optimize(
Optional[int], Query(description="Defaults to current hour of the day.") Optional[int], Query(description="Defaults to current hour of the day.")
] = None, ] = None,
ngen: Annotated[ ngen: Annotated[
Optional[int], Query(description="Number of indivuals to generate for genetic algorithm.") Optional[int],
Query(
description=(
"Deprecated alias for the number of genetic generations. "
"Defaults to optimization.genetic.generations."
),
ge=1,
),
] = None,
individuals: Annotated[
Optional[int],
Query(
description="Override optimization.genetic.individuals for this run.",
ge=10,
),
] = None, ] = None,
) -> GeneticSolution: ) -> GeneticSolution:
"""Deprecated: Optimize. """Deprecated: Optimize.
@@ -1429,7 +1443,8 @@ async def fastapi_optimize(
start_datetime=start_datetime, start_datetime=start_datetime,
mode=EnergyManagementMode.OPTIMIZATION, mode=EnergyManagementMode.OPTIMIZATION,
genetic_parameters=parameters, genetic_parameters=parameters,
genetic_individuals=ngen, genetic_individuals=individuals,
genetic_generations=ngen,
) )
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=f"Optimize error: {e}.") raise HTTPException(status_code=400, detail=f"Optimize error: {e}.")
+1 -1
View File
@@ -430,7 +430,7 @@ def run_optimization(
start_datetime=start_datetime, start_datetime=start_datetime,
mode=EnergyManagementMode.OPTIMIZATION, mode=EnergyManagementMode.OPTIMIZATION,
genetic_parameters=parameters, genetic_parameters=parameters,
genetic_individuals=ngen, genetic_generations=ngen,
genetic_seed=seed, genetic_seed=seed,
) )
) )
+140 -5
View File
@@ -1,5 +1,5 @@
from types import SimpleNamespace from types import SimpleNamespace
from unittest.mock import patch from unittest.mock import MagicMock, patch
import numpy as np import numpy as np
import pytest import pytest
@@ -7,6 +7,7 @@ from deap import creator
from akkudoktoreos.config.config import ConfigEOS from akkudoktoreos.config.config import ConfigEOS
from akkudoktoreos.core.coreabc import get_ems from akkudoktoreos.core.coreabc import get_ems
from akkudoktoreos.core.emsettings import EnergyManagementMode
from akkudoktoreos.optimization.genetic.genetic import GeneticOptimization from akkudoktoreos.optimization.genetic.genetic import GeneticOptimization
from akkudoktoreos.utils.datetimeutil import to_datetime from akkudoktoreos.utils.datetimeutil import to_datetime
@@ -21,6 +22,36 @@ def _configure_hourly_grid(config_eos: ConfigEOS, *, start_hour: int = 0) -> Non
get_ems(init=True).set_start_datetime(to_datetime().set(hour=start_hour, minute=0)) get_ems(init=True).set_start_datetime(to_datetime().set(hour=start_hour, minute=0))
def test_energy_management_forwards_individuals_and_generations_separately(
config_eos: ConfigEOS,
):
_configure_hourly_grid(config_eos)
config_eos.optimization.genetic.individuals = 100
config_eos.optimization.genetic.generations = 80
ems = get_ems(init=True)
parameters = MagicMock()
solution = MagicMock()
optimizer = MagicMock()
optimizer.optimierung_ems.return_value = solution
with (
patch("akkudoktoreos.adapter.adapterabc.AdapterContainer.update_data"),
patch("akkudoktoreos.core.ems.GeneticOptimization", return_value=optimizer),
):
ems._run(
start_datetime=to_datetime().set(hour=0, minute=0),
mode=EnergyManagementMode.OPTIMIZATION,
genetic_parameters=parameters,
)
optimizer.optimierung_ems.assert_called_once_with(
start_hour=0,
parameters=parameters,
ngen=80,
individuals=100,
)
def test_ev_repair_is_resimulated_before_fitness_assignment(config_eos: ConfigEOS): def test_ev_repair_is_resimulated_before_fitness_assignment(config_eos: ConfigEOS):
_configure_hourly_grid(config_eos) _configure_hourly_grid(config_eos)
opt = GeneticOptimization(fixed_seed=42) opt = GeneticOptimization(fixed_seed=42)
@@ -44,7 +75,9 @@ def test_ev_repair_is_resimulated_before_fitness_assignment(config_eos: ConfigEO
eauto=None, eauto=None,
) )
with patch.object(opt, "evaluate_inner", side_effect=[first_result, repaired_result]) as evaluate: with patch.object(
opt, "evaluate_inner", side_effect=[first_result, repaired_result]
) as evaluate:
fitness = opt.evaluate(individual, parameters, start_hour=0, worst_case=False) # type: ignore[arg-type] fitness = opt.evaluate(individual, parameters, start_hour=0, worst_case=False) # type: ignore[arg-type]
assert evaluate.call_count == 2 assert evaluate.call_count == 2
@@ -124,7 +157,9 @@ def test_mutated_warm_start_neighbors_keep_elapsed_slots(config_eos: ConfigEOS):
assert all(neighbor != start_solution for neighbor in neighbors) assert all(neighbor != start_solution for neighbor in neighbors)
def test_initial_population_uses_fixed_seed_budget_and_150_survivors(config_eos: ConfigEOS): def test_initial_population_uses_fixed_seed_budget_and_configured_population(
config_eos: ConfigEOS,
):
_configure_hourly_grid(config_eos) _configure_hourly_grid(config_eos)
config_eos.optimization.genetic.individuals = 300 config_eos.optimization.genetic.individuals = 300
opt = GeneticOptimization(fixed_seed=42) opt = GeneticOptimization(fixed_seed=42)
@@ -164,8 +199,108 @@ def test_initial_population_uses_fixed_seed_budget_and_150_survivors(config_eos:
assert first_genes.count(6) == 50 assert first_genes.count(6) == 50
assert first_genes.count(7) == 100 assert first_genes.count(7) == 100
assert first_genes.count(9) == 140 assert first_genes.count(9) == 140
assert captured["mu"] == 150 assert captured["mu"] == 300
assert captured["lambda"] == 150 assert captured["lambda"] == 300
def test_small_population_scales_warm_and_educated_seed_families(config_eos: ConfigEOS):
_configure_hourly_grid(config_eos)
config_eos.optimization.genetic.individuals = 100
opt = GeneticOptimization(fixed_seed=42)
opt.optimize_ev = False
opt.setup_deap_environment({"home_appliance": 0}, start_hour=0)
start_solution = [5] * opt.total_slots
captured: dict[str, object] = {}
def warm_neighbors(_solution, count):
captured["warm_count"] = count
return [[6] * opt.total_slots for _ in range(count)]
def educated(count):
captured["educated_count"] = count
return [[7] * opt.total_slots for _ in range(count)]
def fake_ea(population, toolbox, **kwargs):
captured["population"] = list(population)
captured["mu"] = kwargs["mu"]
captured["lambda"] = kwargs["lambda_"]
for individual in population:
individual.fitness.values = (float(sum(individual)),)
individual.extra_data = (0.0, 0.0, 0.0)
kwargs["halloffame"].update(population)
return population, SimpleNamespace(select=lambda _name: [])
with (
patch.object(opt, "_mutated_warm_start_neighbors", side_effect=warm_neighbors),
patch.object(opt, "_educated_guess_individuals", side_effect=educated),
patch.object(
opt.toolbox,
"population",
side_effect=lambda n: [creator.Individual([9] * opt.total_slots) for _ in range(n)],
),
patch("akkudoktoreos.optimization.genetic.genetic.algorithms.eaMuPlusLambda", fake_ea),
):
opt.optimize(start_solution=start_solution, ngen=1)
population = captured["population"]
first_genes = [individual[0] for individual in population] # type: ignore[union-attr]
assert len(population) == 100 # type: ignore[arg-type]
assert first_genes.count(5) == 10
assert first_genes.count(6) == 20
assert first_genes.count(7) == 40
assert first_genes.count(9) == 30
assert captured["warm_count"] == 20
assert captured["educated_count"] == 40
assert captured["mu"] == 100
assert captured["lambda"] == 100
def test_local_search_moves_weak_export_to_later_expensive_import(config_eos: ConfigEOS):
_configure_hourly_grid(config_eos)
opt = GeneticOptimization(fixed_seed=42)
opt.optimize_ev = False
opt.optimize_dc_charge = True
opt.optimize_battery_grid_export = True
opt.bat_possible_charge_values = [1.0]
opt.setup_deap_environment({"home_appliance": 0}, start_hour=0)
slots = opt.total_slots
export_state = 5
self_consumption_state = 6
discharge_state = 1
source = 10
targets = list(range(20, 32))
base = [self_consumption_state] * slots
base[source] = export_state
for slot in targets:
base[slot] = 0
opt.simulation.elect_price_hourly = np.full(slots, 0.10)
opt.simulation.elect_price_hourly[targets] = 0.30
opt.simulation.elect_revenue_per_hour_arr = np.full(slots, 0.05)
opt.simulation.elect_revenue_per_hour_arr[source] = 0.20
opt.simulation.pv_prediction_wh = np.zeros(slots)
opt.simulation.load_energy_array = np.full(slots, 100.0)
def evaluate(individual):
export_value = -0.20 if individual[source] == export_state else 0.0
avoided_import = -0.05 * sum(individual[slot] == discharge_state for slot in targets)
return (export_value + avoided_import,)
opt.toolbox.register("evaluate", evaluate)
incumbent = creator.Individual(base)
incumbent.fitness.values = evaluate(incumbent)
best, evaluations, improvements, initial, final = opt._locally_improve_grid_export(
incumbent,
max_evaluations=96,
)
assert evaluations > 0
assert improvements == 1
assert final < initial
assert best[source] == self_consumption_state
assert sum(best[slot] == discharge_state for slot in targets) >= 6
def test_educated_guesses_encode_high_price_direct_marketing(config_eos: ConfigEOS): def test_educated_guesses_encode_high_price_direct_marketing(config_eos: ConfigEOS):
+68 -68
View File
@@ -110,16 +110,16 @@
0, 0,
0, 0,
0, 0,
1, 0,
1, 0,
0,
0,
0,
0,
0,
0,
0, 0,
1, 1,
0,
0,
0,
0,
0,
0,
1, 1,
1, 1,
0, 0,
@@ -237,12 +237,11 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.022582049506752234,
0.21367321450420126, 0.3039575,
0.19320652266312205, 0.19320652266312205,
0.1358062627100041, 0.1358062627100041,
0.0692592282596561, 0.0692592282596561,
0.023370695807696434,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -262,7 +261,8 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.11745809857246167, 0.0,
0.09514375330616998,
0.15236390731688437, 0.15236390731688437,
0.10316291465819699, 0.10316291465819699,
0.05435777788576338, 0.05435777788576338,
@@ -272,10 +272,10 @@
0.0, 0.0,
0.0 0.0
], ],
"Gesamt_Verluste": 3638.132237848992, "Gesamt_Verluste": 3421.6165248449383,
"Gesamtbilanz_Euro": 0.5580695656866563, "Gesamtbilanz_Euro": 0.5951993628823129,
"Gesamteinnahmen_Euro": 1.0626586223779864, "Gesamteinnahmen_Euro": 1.1298399163065491,
"Gesamtkosten_Euro": 1.6207281880646427, "Gesamtkosten_Euro": 1.725039279188862,
"Home_appliance_wh_per_hour": [ "Home_appliance_wh_per_hour": [
0.0, 0.0,
0.0, 0.0,
@@ -318,16 +318,16 @@
], ],
"home_appliance_energy_wh": {}, "home_appliance_energy_wh": {},
"Kosten_Euro_pro_Stunde": [ "Kosten_Euro_pro_Stunde": [
0.0, 0.10013413727316416,
0.0, 0.09007999454232955,
0.11436917344552285, 0.11436917344552285,
0.0, 0.07557452231671152,
0.0, 0.0,
4.55656845588237e-17, 4.55656845588237e-17,
0.001414013162203277, 0.001414013162203277,
0.005881449073870462, 0.005881449073870462,
0.05258762370598476, 0.05258762370598476,
0.1614775630079859, 0.0,
0.0, 0.0,
0.0, 0.0,
0.26650619799999997, 0.26650619799999997,
@@ -358,16 +358,16 @@
0.16484566 0.16484566
], ],
"Netzbezug_Wh_pro_Stunde": [ "Netzbezug_Wh_pro_Stunde": [
0.0, 439.1848126015972,
0.0, 407.23324838304495,
546.436566868241, 546.436566868241,
0.0, 402.20607938643707,
0.0, 0.0,
2.2737367544323206e-13, 2.2737367544323206e-13,
6.433180901743754, 6.433180901743754,
25.909467285772962, 25.909467285772962,
175.4675465665157, 175.4675465665157,
505.40708296709204, 0.0,
0.0, 0.0,
0.0, 0.0,
912.38, 912.38,
@@ -402,12 +402,11 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 322.60070723931767,
3052.474492917161, 4342.25,
2760.093180901744, 2760.093180901744,
1940.089467285773, 1940.089467285773,
989.4175465665157, 989.4175465665157,
333.86708296709196,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -427,7 +426,8 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
1677.9728367494527, 0.0,
1359.1964758024283,
2176.6272473840627, 2176.6272473840627,
1473.7559236885286, 1473.7559236885286,
776.5396840823341, 776.5396840823341,
@@ -438,16 +438,16 @@
0.0 0.0
], ],
"Verluste_Pro_Stunde": [ "Verluste_Pro_Stunde": [
97.85621559422765, 37.96737751219166,
101.92059640365329, 46.38878980596536,
39.91398802418894, 39.91398802418894,
106.67021307906845, 51.82392952637247,
582.6179999999995, 543.9059151312817,
154.77306084994075,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
108.98319763338179,
106.80230977350088, 106.80230977350088,
133.7321802766326, 133.7321802766326,
0.0, 0.0,
@@ -467,7 +467,7 @@
165.15986945297027, 165.15986945297027,
65.92300791736113, 65.92300791736113,
538.2984000000001, 538.2984000000001,
240.58122702042965, 278.8343903340726,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -479,35 +479,35 @@
], ],
"akku_soc_pro_stunde": [ "akku_soc_pro_stunde": [
80.0, 80.0,
79.16421888032488, 81.05464937533866,
78.69989843940196, 82.3432268699488,
79.80862032896276, 83.45194875950959,
79.51691497639054, 84.8915023574644,
95.70074830972388,
100.0, 100.0,
100.0, 100.0,
100.0, 100.0,
100.0, 100.0,
100.0, 100.0,
96.82533215994886, 98.93741213017658,
92.60407390132525, 95.76274429012544,
92.60407390132525, 91.54148603150185,
92.60407390132525, 91.54148603150185,
90.38140689030597, 91.54148603150185,
86.64496577460349, 89.31881902048255,
83.65624332281286, 85.58237790478007,
81.03576295091202, 82.59365545298944,
78.64117789581559, 79.97317508108861,
76.53679600876325, 77.57859002599218,
74.35484869471367, 75.47420813893983,
70.91195845376538, 73.29226082489025,
67.41428833522274, 69.84937058394196,
68.03406859993031, 66.35170046539932,
69.53927969874645, 66.97148073010689,
71.94548598914552, 68.47669182892304,
76.53326014061692, 70.8828981193221,
78.36445480498806, 75.4706722707935,
93.3171881383214, 77.30186693516465,
92.254600268498,
100.0, 100.0,
100.0, 100.0,
100.0, 100.0,
@@ -717,18 +717,18 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
2.0, 0.0,
2.0, 0.0,
1.0, 0.0,
1.0, 0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0, 0.0,
1.0, 1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0, 1.0,
1.0, 1.0,
0.0, 0.0,
+284 -284
View File
@@ -15,7 +15,7 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 1.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -110,23 +110,23 @@
0, 0,
0, 0,
0, 0,
0, 1,
0, 1,
0, 1,
0, 1,
0, 1,
0,
0, 0,
0, 0,
0, 0,
0, 0,
1, 1,
1, 1,
1,
0, 0,
0, 0,
1, 1,
1, 1,
1, 0,
1, 1,
1, 1,
1, 1,
@@ -145,7 +145,7 @@
0, 0,
1, 1,
1, 1,
1, 0,
0, 0,
0 0
], ],
@@ -161,16 +161,16 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.875,
0.625, 0.625,
0.625, 1.0,
0.375,
0.875,
0.75,
0.75,
0.375,
0.1,
0.0, 0.0,
0.375,
0.625,
1.0,
0.625,
0.875,
0.0,
0.3,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -202,16 +202,16 @@
], ],
"result": { "result": {
"Last_Wh_pro_Stunde": [ "Last_Wh_pro_Stunde": [
10713.07, 7953.07,
7963.91, 12103.91,
8220.56, 1320.56,
7772.03, 5272.03,
13323.67, 8063.67,
9456.82, 17059.173961709643,
9496.22, 8116.22,
5243.78, 10763.78,
2233.12, 1129.12,
1178.71, 4490.71,
1050.98, 1050.98,
988.56, 988.56,
912.38, 912.38,
@@ -231,8 +231,8 @@
827.01, 827.01,
1257.98, 1257.98,
1232.67, 1232.67,
871.26, 3371.26,
860.88, 3360.88,
1158.03, 1158.03,
1222.72, 1222.72,
1221.04, 1221.04,
@@ -243,43 +243,43 @@
], ],
"EAuto_SoC_pro_Stunde": [ "EAuto_SoC_pro_Stunde": [
5.0, 5.0,
20.294999999999998, 15.925,
31.22, 33.405,
42.144999999999996, 33.405,
48.699999999999996, 39.96,
63.995000000000005, 50.885000000000005,
77.105, 68.365,
90.215, 79.29,
96.77, 94.585,
98.518, 94.585,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518, 99.82900000000001,
98.518 99.82900000000001
], ],
"Einnahmen_Euro_pro_Stunde": [ "Einnahmen_Euro_pro_Stunde": [
0.0, 0.0,
@@ -313,19 +313,45 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.053661230306193436, 0.0,
0.05435777788576338, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0 0.0
], ],
"Gesamt_Verluste": 6227.580163897914, "Gesamt_Verluste": 8756.080717524794,
"Gesamtbilanz_Euro": 12.033027623527284, "Gesamtbilanz_Euro": 9.985237573040141,
"Gesamteinnahmen_Euro": 0.10801900819195681, "Gesamteinnahmen_Euro": 0.0,
"Gesamtkosten_Euro": 12.14104663171924, "Gesamtkosten_Euro": 9.985237573040141,
"Home_appliance_wh_per_hour": [ "Home_appliance_wh_per_hour": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -337,36 +363,36 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0 0.0
], ],
"home_appliance_energy_wh": { "home_appliance_energy_wh": {
"dishwasher1": [ "dishwasher1": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -378,112 +404,86 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0 0.0
] ]
}, },
"Kosten_Euro_pro_Stunde": [ "Kosten_Euro_pro_Stunde": [
2.23047612, 0.5980075076051746,
1.5308798733801507, 1.473337992,
1.4889564723943407, 0.0,
1.242149738556099, 0.0,
1.3742255912165289, 0.0,
0.8479283355019763, 2.3442290999999997,
1.2336553365360492, 0.9428514400845971,
0.5407163922683618, 1.762926136273946,
0.20558284318867343, 0.05258762370598476,
0.1614775630079859, 0.0,
0.0, 0.0,
0.0, 0.0,
0.26650619799999997, 0.26650619799999997,
0.19588158, 0.19588158,
0.0, 0.0,
0.0, 0.0,
0.22802125600000003,
0.0, 0.0,
0.0, 0.0,
0.0, 0.08281196422730584,
0.0, 0.16677339,
0.0, 0.264113778992023,
0.0, 0.2536463762855701,
0.0,
0.1306329312971816, 0.1306329312971816,
0.07362195915902499, 0.07362195915902499,
0.060401289430882174, 0.060401289430882174,
0.009619897970888898, 0.009619897970888898,
0.07029121023060134, 0.07029121023060134,
4.179128154646605e-17, 4.179128154646605e-17,
2.3325608707865465e-05, 0.04064749825228731,
0.0021886029750169123, 0.1994538035279033,
0.013012984677295973, 0.013012984677295973,
0.08357424731947552, 0.08357424731947552,
0.0, 0.0,
0.0, 0.0,
0.0, 0.293043269,
0.214398479, 0.214398479,
0.16484566 0.16484566
], ],
"Netzbezug_Wh_pro_Stunde": [ "Netzbezug_Wh_pro_Stunde": [
9782.789999999999, 2622.8399456367306,
6920.795087613701, 6660.66,
7113.9821901306295, 0.0,
6610.695787951565, 0.0,
7476.74423948057, 0.0,
4231.179318872138, 11697.75,
5612.626644840988, 4289.587989465865,
2382.0105386271443, 7766.194432924872,
685.962106068313, 175.4675465665157,
505.40708296709204, 0.0,
0.0, 0.0,
0.0, 0.0,
912.38, 912.38,
704.61, 704.61,
0.0, 0.0,
0.0, 0.0,
694.34,
0.0, 0.0,
0.0, 0.0,
0.0, 248.38621543882974,
0.0, 506.91,
0.0, 799.8600211751151,
0.0, 833.8145177040436,
0.0,
537.58407941227, 537.58407941227,
322.9033296448464, 322.9033296448464,
273.0618871197205, 273.0618871197205,
45.96224544141853, 45.96224544141853,
374.088399311343, 374.088399311343,
2.2737367544323206e-13, 2.2737367544323206e-13,
0.11639525303326081, 202.83182760622412,
9.957247384062384, 907.4331370696236,
57.32592368852852, 57.32592368852852,
278.85968408233407, 278.85968408233407,
0.0, 0.0,
0.0, 0.0,
0.0, 987.01,
733.99, 733.99,
592.97 592.97
], ],
@@ -519,8 +519,8 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
766.589004374192, 0.0,
776.5396840823341, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -528,84 +528,84 @@
0.0 0.0
], ],
"Verluste_Pro_Stunde": [ "Verluste_Pro_Stunde": [
483.0, 945.0059934764078,
345.0162105136441, 1152.0,
345.0194628156755, 114.42806532440363,
207.04269455418785, 768.1700560862765,
503.6273087376684, 752.877211603942,
449.2115182646565, 1152.0,
424.3543973809186, 362.1897587359037,
225.74286463525735, 485.44493195098454,
102.70945272819762, 118.73010558798194,
40.06404995605101, 641.287728412984,
106.80230977350088, 106.80230977350088,
133.7321802766326, 133.7321802766326,
0.0, 0.0,
0.0, 0.0,
70.41409090909087, 70.41409090909087,
118.37045454545455, 118.37045454545455,
94.68272727272722, 0.0,
83.01681818181817, 83.01681818181817,
75.86045454545456, 75.86045454545456,
66.66681818181814, 32.79597062197777,
69.12409090909085, 0.0,
109.07302361034766, 0.0012025410138062752,
116.99491129525349, 3.292931608338464,
22.312089529472388, 22.312089529472388,
54.18759955738153, 54.18759955738153,
86.6234264543665, 86.6234264543665,
165.15986945297027, 165.15986945297027,
65.92300791736113, 65.92300791736113,
538.2984000000001, 538.2984000000001,
441.9379674303641, 166.26381931274682,
261.1952696860876, 68.89237644835487,
84.86003031772043, 176.85071084262336,
0.0, 93.18476208988011,
111.78035844493081, 111.78035844493081,
90.18403329253945, 90.18403329253945,
134.59227272727276, 0.0,
0.0, 0.0,
0.0 0.0
], ],
"akku_soc_pro_stunde": [ "akku_soc_pro_stunde": [
80.0, 80.0,
80.0, 61.060772546061834,
80.00045029204567, 42.12137860666789,
80.00099092581443, 40.878014722863334,
80.00217688565297, 23.18290087405168,
80.57515768392155, 13.89226749676402,
81.55325541349534, 30.55893416343069,
81.8408775629653, 31.036427461650234,
82.36151269172245, 31.104342238066472,
83.68121971195016, 34.40240072662152,
84.79410998850713, 19.405325997784466,
81.619442148456, 16.23065815773333,
77.39818388983241, 12.00939989910972,
77.39818388983241, 12.00939989910972,
77.39818388983241, 12.00939989910972,
75.17551687881311, 9.786732888090437,
71.43907576311062, 6.050291772387957,
68.45035331131999, 6.050291772387957,
65.82987293941916, 3.429811400487131,
63.43528788432273, 1.0352263453907122,
61.33090599727039, 0.0,
59.14895868322081, 0.0,
55.706068442272525, 3.3403917050174315e-05,
52.208398323729895, 0.09144092700684212,
52.82817858843747, 0.7112211917144087,
54.33338968725362, 2.216432290530563,
56.73959597765268, 4.622638580929631,
61.327370129124084, 9.210412732401027,
63.158564793495216, 11.04160739677217,
78.11129812682856, 25.994340730105503,
90.387352777672, 30.612780155459586,
97.64277693561888, 32.526457279024996,
100.0, 37.438977024653425,
100.0, 40.027442638261206,
98.60068046043587, 38.62812309869707,
96.11252124341868, 36.139963881679876,
91.86402778611841, 36.139963881679876,
91.86402778611841 36.139963881679876
], ],
"Electricity_price": [ "Electricity_price": [
0.000228, 0.000228,
@@ -702,16 +702,16 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.875,
0.625, 0.625,
0.625, 1.0,
0.375,
0.875,
0.75,
0.75,
0.375,
0.1,
0.0, 0.0,
0.375,
0.625,
1.0,
0.625,
0.875,
0.0,
0.3,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -795,37 +795,37 @@
"capacity_wh": 60000, "capacity_wh": 60000,
"charging_efficiency": 0.95, "charging_efficiency": 0.95,
"max_charge_power_w": 11040, "max_charge_power_w": 11040,
"soc_wh": 59110.8, "soc_wh": 59897.4,
"initial_soc_percentage": 5 "initial_soc_percentage": 5
}, },
"start_solution": [ "start_solution": [
0.0, 0.0,
0.0, 0.0,
1.0,
2.0,
1.0,
1.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
2.0,
1.0,
1.0,
1.0,
1.0,
1.0,
2.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0, 1.0,
1.0, 1.0,
0.0, 0.0,
0.0, 0.0,
1.0, 1.0,
1.0, 1.0,
1.0, 0.0,
1.0, 1.0,
1.0, 1.0,
1.0, 1.0,
@@ -844,63 +844,63 @@
0.0, 0.0,
1.0, 1.0,
1.0, 1.0,
1.0,
0.0, 0.0,
0.0, 0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
2.0,
3.0,
1.0,
4.0,
3.0,
6.0,
0.0,
1.0,
3.0,
6.0,
3.0,
5.0,
0.0,
2.0,
3.0,
3.0,
5.0,
4.0,
6.0,
3.0,
0.0,
0.0,
1.0,
5.0,
0.0,
3.0, 3.0,
6.0, 6.0,
3.0,
1.0,
3.0,
2.0,
0.0,
2.0,
2.0, 2.0,
4.0, 4.0,
5.0,
6.0,
0.0,
1.0,
2.0,
0.0,
5.0,
3.0,
3.0, 3.0,
1.0, 1.0,
5.0, 5.0,
4.0,
4.0,
1.0,
1.0,
2.0,
2.0,
1.0,
4.0,
6.0,
4.0,
5.0, 5.0,
6.0, 3.0,
0.0, 0.0,
0.0, 2.0,
0.0, 29.0
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
3.0
], ],
"washingstart": 13, "washingstart": 39,
"appliance_starts": { "appliance_starts": {
"dishwasher1": [ "dishwasher1": [
"2025-01-15 13:00:00+01:00" "2025-01-16 15:00:00+01:00"
] ]
} }
} }
+236 -236
View File
@@ -15,7 +15,7 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 1.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -112,6 +112,29 @@
0, 0,
1, 1,
1, 1,
1,
1,
1,
0,
0,
0,
0,
1,
1,
1,
0,
0,
1,
1,
0,
1,
1,
1,
1,
1,
1,
0,
0,
0, 0,
0, 0,
0, 0,
@@ -124,29 +147,6 @@
1, 1,
0, 0,
0, 0,
1,
1,
1,
1,
1,
1,
1,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
1,
1,
0,
0 0
], ],
"battery_grid_export_allowed": [], "battery_grid_export_allowed": [],
@@ -161,16 +161,16 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.625,
1.0, 1.0,
0.5, 0.0,
0.75, 0.375,
0.625,
1.0,
0.625,
0.875, 0.875,
0.5,
0.375,
1.0,
0.375,
0.0,
0.0, 0.0,
0.3,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -202,16 +202,16 @@
], ],
"result": { "result": {
"Last_Wh_pro_Stunde": [ "Last_Wh_pro_Stunde": [
12093.07, 7953.07,
6583.91, 12103.91,
9600.56, 1320.56,
10792.03, 5272.03,
6683.67, 8063.67,
5316.82, 17059.173961709643,
12256.22, 8116.22,
5243.78, 10763.78,
1129.12, 1129.12,
1178.71, 4490.71,
1050.98, 1050.98,
988.56, 988.56,
912.38, 912.38,
@@ -229,10 +229,10 @@
992.46, 992.46,
1155.99, 1155.99,
827.01, 827.01,
3757.98, 1257.98,
3732.67, 1232.67,
871.26, 3371.26,
860.88, 3360.88,
1158.03, 1158.03,
1222.72, 1222.72,
1221.04, 1221.04,
@@ -243,43 +243,43 @@
], ],
"EAuto_SoC_pro_Stunde": [ "EAuto_SoC_pro_Stunde": [
5.0, 5.0,
22.48, 15.925,
31.22, 33.405,
44.330000000000005, 33.405,
59.62499999999999, 39.96,
50.885000000000005,
68.365, 68.365,
74.92, 79.29,
92.4, 94.585,
98.955, 94.585,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001,
98.955, 99.82900000000001
98.955
], ],
"Einnahmen_Euro_pro_Stunde": [ "Einnahmen_Euro_pro_Stunde": [
0.0, 0.0,
@@ -321,10 +321,10 @@
0.0, 0.0,
0.0 0.0
], ],
"Gesamt_Verluste": 7415.050669156861, "Gesamt_Verluste": 8756.080717524794,
"Gesamtbilanz_Euro": 10.086958235191952, "Gesamtbilanz_Euro": 9.985237573040141,
"Gesamteinnahmen_Euro": 0.0, "Gesamteinnahmen_Euro": 0.0,
"Gesamtkosten_Euro": 10.086958235191952, "Gesamtkosten_Euro": 9.985237573040141,
"Home_appliance_wh_per_hour": [ "Home_appliance_wh_per_hour": [
0.0, 0.0,
0.0, 0.0,
@@ -353,10 +353,10 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
2500.0,
2500.0,
0.0, 0.0,
0.0, 0.0,
2500.0,
2500.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -394,10 +394,10 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
2500.0,
2500.0,
0.0, 0.0,
0.0, 0.0,
2500.0,
2500.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -408,82 +408,82 @@
] ]
}, },
"Kosten_Euro_pro_Stunde": [ "Kosten_Euro_pro_Stunde": [
1.5419161199999998, 0.5980075076051746,
0.2524105556335792, 1.473337992,
1.7777665549410084, 0.0,
1.809540886, 0.0,
0.24971825220475874, 0.0,
0.12061259291950001, 2.3442290999999997,
1.83015129135275, 0.9428514400845971,
0.5407163922683618, 1.762926136273946,
0.05258762370598476, 0.05258762370598476,
0.1614775630079859, 0.0,
0.0, 0.0,
0.0, 0.0,
0.26650619799999997, 0.26650619799999997,
0.19588158, 0.19588158,
0.0, 0.0,
0.0, 0.0,
0.22802125600000003,
0.0, 0.0,
0.0, 0.0,
0.0, 0.08281196422730584,
0.0, 0.16677339,
0.0, 0.264113778992023,
0.0, 0.2536463762855701,
0.0,
0.1306329312971816, 0.1306329312971816,
0.07362195915902499, 0.07362195915902499,
0.060401289430882174, 0.060401289430882174,
0.009619897970888898, 0.009619897970888898,
0.4410873661898387, 0.07029121023060134,
0.0, 4.179128154646605e-17,
2.3325608707865465e-05, 0.04064749825228731,
0.0021886029750169123, 0.1994538035279033,
0.013012984677295973, 0.013012984677295973,
0.0, 0.08357424731947552,
0.17784012884918773,
0.0, 0.0,
0.0, 0.0,
0.293043269,
0.214398479, 0.214398479,
0.16484566 0.16484566
], ],
"Netzbezug_Wh_pro_Stunde": [ "Netzbezug_Wh_pro_Stunde": [
6762.789999999999, 2622.8399456367306,
1141.0965444556023, 6660.66,
8493.867916583891, 0.0,
9630.34, 0.0,
1358.64119806724, 0.0,
601.8592461052895, 11697.75,
8326.43899614536, 4289.587989465865,
2382.0105386271443, 7766.194432924872,
175.4675465665157, 175.4675465665157,
505.40708296709204, 0.0,
0.0, 0.0,
0.0, 0.0,
912.38, 912.38,
704.61, 704.61,
0.0, 0.0,
0.0, 0.0,
694.34,
0.0, 0.0,
0.0, 0.0,
0.0, 248.38621543882974,
0.0, 506.91,
0.0, 799.8600211751151,
0.0, 833.8145177040436,
0.0,
537.58407941227, 537.58407941227,
322.9033296448464, 322.9033296448464,
273.0618871197205, 273.0618871197205,
45.96224544141853, 45.96224544141853,
2347.45804252176, 374.088399311343,
0.0, 2.2737367544323206e-13,
0.11639525303326081, 202.83182760622412,
9.957247384062384, 907.4331370696236,
57.32592368852852, 57.32592368852852,
0.0, 278.85968408233407,
556.6201215937018,
0.0, 0.0,
0.0, 0.0,
987.01,
733.99, 733.99,
592.97 592.97
], ],
@@ -528,84 +528,84 @@
0.0 0.0
], ],
"Verluste_Pro_Stunde": [ "Verluste_Pro_Stunde": [
945.0059934764078,
1152.0, 1152.0,
876.0523853346722, 114.42806532440363,
414.00574999006693, 768.1700560862765,
483.0, 752.877211603942,
359.25494376806876, 1152.0,
303.4931095326348, 362.1897587359037,
556.8118795374434, 485.44493195098454,
225.74286463525735,
118.73010558798194, 118.73010558798194,
40.06404995605101, 641.287728412984,
106.80230977350088, 106.80230977350088,
133.7321802766326, 133.7321802766326,
0.0, 0.0,
0.0, 0.0,
70.41409090909087, 70.41409090909087,
118.37045454545455, 118.37045454545455,
94.68272727272722, 0.0,
83.01681818181817, 83.01681818181817,
75.86045454545456, 75.86045454545456,
66.66681818181814, 32.79597062197777,
69.12409090909085, 0.0,
109.07302361034766, 0.0012025410138062752,
116.99491129525349, 3.292931608338464,
22.312089529472388, 22.312089529472388,
54.18759955738153, 54.18759955738153,
86.6234264543665, 86.6234264543665,
165.15986945297027, 165.15986945297027,
2.727365102611202, 65.92300791736113,
238.2983999999999, 538.2984000000001,
441.9379674303641, 166.26381931274682,
261.1952696860876, 68.89237644835487,
176.85071084262336, 176.85071084262336,
131.21108264656203, 93.18476208988011,
35.877614591244196, 111.78035844493081,
90.18403329253945, 90.18403329253945,
134.59227272727276, 0.0,
0.0, 0.0,
0.0 0.0
], ],
"akku_soc_pro_stunde": [ "akku_soc_pro_stunde": [
80.0, 80.0,
61.06060606060606, 61.060772546061834,
42.12266726939745, 42.12137860666789,
42.122826991343764, 40.878014722863334,
42.122826991343764, 23.18290087405168,
44.435464318234565, 13.89226749676402,
47.11582847191886, 30.55893416343069,
47.249491792403404, 31.036427461650234,
47.770126921160546, 31.104342238066472,
51.0681854097156, 34.40240072662152,
52.181075686272585, 19.405325997784466,
49.006407846221435, 16.23065815773333,
44.78514958759783, 12.00939989910972,
44.78514958759783, 12.00939989910972,
44.78514958759783, 12.00939989910972,
42.562482576578546, 9.786732888090437,
38.82604146087607, 6.050291772387957,
35.837319009085434, 6.050291772387957,
33.2168386371846, 3.429811400487131,
30.822253582088187, 1.0352263453907122,
28.717871695035846, 0.0,
26.53592438098626, 0.0,
23.09303414003797, 3.3403917050174315e-05,
19.595364021495346, 0.09144092700684212,
20.215144286202914, 0.7112211917144087,
21.72035538501907, 2.216432290530563,
24.126561675418138, 4.622638580929631,
28.71433582688953, 9.210412732401027,
28.79009596862873, 11.04160739677217,
35.40949596862873, 25.994340730105503,
47.68555061947217, 30.612780155459586,
54.94097477741905, 32.526457279024996,
59.85349452304748, 37.438977024653425,
61.241634361507465, 40.027442638261206,
62.2382347668198, 38.62812309869707,
59.7500755498026, 36.139963881679876,
55.50158209250233, 36.139963881679876,
55.50158209250233 36.139963881679876
], ],
"Electricity_price": [ "Electricity_price": [
0.000228, 0.000228,
@@ -702,16 +702,16 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.625,
1.0, 1.0,
0.5, 0.0,
0.75, 0.375,
0.625,
1.0,
0.625,
0.875, 0.875,
0.5,
0.375,
1.0,
0.375,
0.0,
0.0, 0.0,
0.3,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -795,43 +795,44 @@
"capacity_wh": 60000, "capacity_wh": 60000,
"charging_efficiency": 0.95, "charging_efficiency": 0.95,
"max_charge_power_w": 11040, "max_charge_power_w": 11040,
"soc_wh": 59373.0, "soc_wh": 59897.4,
"initial_soc_percentage": 5 "initial_soc_percentage": 5
}, },
"start_solution": [ "start_solution": [
1.0,
0.0, 0.0,
2.0,
2.0,
0.0, 0.0,
1.0, 1.0,
2.0, 2.0,
2.0,
1.0,
2.0,
1.0, 1.0,
1.0, 1.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 2.0,
0.0, 1.0,
1.0,
1.0,
1.0,
1.0,
2.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
1.0, 1.0,
1.0, 1.0,
1.0,
0.0, 0.0,
0.0, 0.0,
1.0, 1.0,
1.0, 1.0,
0.0,
1.0,
1.0,
1.0, 1.0,
1.0, 1.0,
1.0, 1.0,
1.0, 1.0,
1.0, 0.0,
1.0,
1.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -842,65 +843,64 @@
0.0, 0.0,
0.0, 0.0,
1.0, 1.0,
1.0,
0.0,
0.0,
0.0,
0.0,
0.0, 0.0,
1.0,
1.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
2.0, 2.0,
3.0, 3.0,
6.0, 1.0,
4.0, 4.0,
3.0, 3.0,
1.0,
6.0, 6.0,
0.0,
1.0, 1.0,
3.0,
6.0, 6.0,
2.0, 3.0,
4.0,
5.0, 5.0,
2.0, 0.0,
1.0,
6.0,
1.0,
3.0,
4.0,
3.0,
3.0,
2.0, 2.0,
3.0, 3.0,
0.0,
0.0,
6.0,
4.0,
5.0,
6.0,
1.0,
2.0,
4.0,
3.0, 3.0,
3.0,
0.0,
6.0,
5.0, 5.0,
4.0, 4.0,
1.0,
6.0, 6.0,
3.0,
0.0,
0.0,
1.0, 1.0,
5.0,
0.0,
3.0,
6.0,
3.0,
1.0,
3.0,
2.0,
0.0,
2.0,
2.0, 2.0,
4.0, 4.0,
3.0,
1.0, 1.0,
6.0, 5.0,
1.0, 5.0,
4.0, 3.0,
27.0 0.0,
2.0,
29.0
], ],
"washingstart": 37, "washingstart": 39,
"appliance_starts": { "appliance_starts": {
"dishwasher1": [ "dishwasher1": [
"2025-01-16 13:00:00+01:00" "2025-01-16 15:00:00+01:00"
] ]
} }
} }