Add adaptive genetic evolution

This commit is contained in:
Andreas
2026-07-16 15:14:13 +02:00
parent 4dfd4b275b
commit f7e2ac3619
8 changed files with 1294 additions and 844 deletions
+11
View File
@@ -62,6 +62,14 @@ 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
- Replace the fixed DEAP variation loop with adaptive genetic evolution. Crossover offspring may
now also mutate; population diversity and stagnation are tracked per generation; diversity
boosts inject fresh educated/random candidates; and incumbent-preserving soft restarts recover
from collapsed populations without stopping the run early.
- Apply small point mutations only to future, fitness-relevant controls and choose point, coherent
block, energy-shift, or flexible-device mutations as alternatives instead of stacking random
changes on top of every specialized move. Tournament selection retains useful duplicates while
enforcing a 30% minimum diversity floor.
- Scale the diverse genetic start population with the configured population size while preserving - Scale the diverse genetic start population with the configured population size while preserving
the established 300-member mix: exact warm starts, locally mutated neighbours, randomized the established 300-member mix: exact warm starts, locally mutated neighbours, randomized
domain-informed battery/direct-marketing/EV/appliance schedules, and a guaranteed random domain-informed battery/direct-marketing/EV/appliance schedules, and a guaranteed random
@@ -85,6 +93,9 @@ 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
- Exclude elapsed control slots from fitness-cache keys and clear cached genome tuples after run
metrics are captured, avoiding false misses and delayed memory retention in long-lived API
processes. Random EV individuals now also keep the fixed horizon tail switched off.
- Respect `optimization.genetic.individuals` and `optimization.genetic.generations` independently - Respect `optimization.genetic.individuals` and `optimization.genetic.generations` independently
in automatic and `/optimize` runs. Previously the individual count was accidentally passed as 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 the generation count, the configured generation count was ignored, and every generation still
+413 -53
View File
@@ -7,7 +7,7 @@ from dataclasses import dataclass, field
from typing import Any, Optional from typing import Any, Optional
import numpy as np import numpy as np
from deap import algorithms, base, creator, tools from deap import base, creator, tools
from loguru import logger from loguru import logger
from numpydantic import NDArray, Shape from numpydantic import NDArray, Shape
from pydantic import ConfigDict, Field from pydantic import ConfigDict, Field
@@ -566,11 +566,20 @@ class GeneticOptimization(OptimizationBase):
WARM_START_COPY_FRACTION = 0.10 WARM_START_COPY_FRACTION = 0.10
WARM_START_MUTATION_FRACTION = 0.20 WARM_START_MUTATION_FRACTION = 0.20
EDUCATED_GUESS_FRACTION = 0.40 EDUCATED_GUESS_FRACTION = 0.40
BLOCK_MUTATION_PROBABILITY = 0.20
ENERGY_SHIFT_MUTATION_PROBABILITY = 0.35
LOCAL_SEARCH_MAX_EVALUATIONS = 96 LOCAL_SEARCH_MAX_EVALUATIONS = 96
LOCAL_SEARCH_MAX_PASSES = 4 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)
CROSSOVER_PROBABILITY = 0.50
MUTATION_PROBABILITY = 0.55
STAGNATION_MUTATION_PROBABILITY = 0.80
STAGNATION_GENERATIONS = 8
SOFT_RESTART_GENERATIONS = 20
DIVERSITY_BOOST_THRESHOLD = 0.35
SELECTION_DIVERSITY_FLOOR = 0.30
SOFT_RESTART_DIVERSITY_THRESHOLD = 0.10
IMMIGRANT_FRACTION = 0.12
SOFT_RESTART_SURVIVOR_FRACTION = 0.20
POINT_MUTATION_EXPECTED_GENES = 3.0
# Slot-math helpers — single source of truth for the optimization grid. # Slot-math helpers — single source of truth for the optimization grid.
# At the default optimization interval of 3600 s, slot_duration_h is 1.0 and # At the default optimization interval of 3600 s, slot_duration_h is 1.0 and
@@ -1146,46 +1155,104 @@ class GeneticOptimization(OptimizationBase):
individual[target_slot] = self_state if pv[target_slot] > 0.0 else len_bat individual[target_slot] = self_state if pv[target_slot] > 0.0 else len_bat
return True return True
def mutate(self, individual: list[int]) -> tuple[list[int]]: @staticmethod
"""Custom mutation function for the individual.""" def _force_segment_change(values: list[int], low: int, up: int) -> bool:
"""Change one value when probabilistic mutation produced no effective change."""
if not values or up <= low:
return False
position = random.randrange(len(values)) # noqa: S311
old_value = int(values[position])
replacement = random.randint(low, up - 1) # noqa: S311
if replacement >= old_value:
replacement += 1
values[position] = replacement
return True
def _mutate_point_controls(self, individual: list[int]) -> bool:
"""Apply a small point mutation only to controls that can still affect fitness."""
changed = False
start_slot = self._start_day_slot()
total_states = self._battery_state_layout().total_states total_states = self._battery_state_layout().total_states
battery_part = list(individual[start_slot : self.total_slots])
battery_before = list(battery_part)
(battery_part,) = self.toolbox.mutate_charge_discharge(battery_part)
if battery_part == battery_before:
self._force_segment_change(battery_part, 0, total_states - 1)
if battery_part != battery_before:
individual[start_slot : self.total_slots] = battery_part
changed = True
# 1. Mutating the charge_discharge part if self.optimize_ev and random.random() < 0.40: # noqa: S311
charge_discharge_part = individual[: self.total_slots] ev_start = self.total_slots + start_slot
(charge_discharge_mutated,) = self.toolbox.mutate_charge_discharge(charge_discharge_part) ev_end = self.total_slots * 2 - self.fixed_eauto_hours
ev_part = list(individual[ev_start:ev_end])
ev_before = list(ev_part)
(ev_part,) = self.toolbox.mutate_ev_charge_index(ev_part)
if ev_part == ev_before:
self._force_segment_change(ev_part, 0, len(self.ev_possible_charge_values) - 1)
if ev_part != ev_before:
individual[ev_start:ev_end] = ev_part
changed = True
# Instead of a fixed clamping to 0..8 or 0..6 dynamically: return changed
charge_discharge_mutated = np.clip(charge_discharge_mutated, 0, total_states - 1)
individual[: self.total_slots] = charge_discharge_mutated
# Point mutation alone struggles with energy-coupled valleys: removing def _mutate_flexible_controls(self, individual: list[int]) -> bool:
# an export is temporarily worse until several later bypass slots also """Mutate EV or appliance controls without disturbing a good battery schedule."""
# consume the retained energy. Add coherent neighbourhood moves that changed = False
# 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
if self.optimize_ev: if self.optimize_ev:
ev_charge_part = individual[self.total_slots : self.total_slots * 2] ev_start = self.total_slots + self._start_day_slot()
(ev_charge_part_mutated,) = self.toolbox.mutate_ev_charge_index(ev_charge_part) ev_end = self.total_slots * 2 - self.fixed_eauto_hours
ev_charge_part_mutated[self.total_slots - self.fixed_eauto_hours :] = [ ev_part = list(individual[ev_start:ev_end])
0 ev_before = list(ev_part)
] * self.fixed_eauto_hours (ev_part,) = self.toolbox.mutate_ev_charge_index(ev_part)
individual[self.total_slots : self.total_slots * 2] = ev_charge_part_mutated if ev_part == ev_before:
self._force_segment_change(ev_part, 0, len(self.ev_possible_charge_values) - 1)
if ev_part != ev_before:
individual[ev_start:ev_end] = ev_part
changed = True
# 3. Mutating the appliance start genes. Each gene is an index into its
# own allowed_start_slots list, so the redraw stays within valid range.
n_appliance_genes = self.appliance_layout.n_genes n_appliance_genes = self.appliance_layout.n_genes
if n_appliance_genes > 0: if n_appliance_genes > 0:
base = len(individual) - n_appliance_genes base = len(individual) - n_appliance_genes
appliance_mutation_probability = 0.2 mutable_positions = [
for position, gene in enumerate(self.appliance_layout.genes): (base + position, len(gene.allowed_start_slots) - 1)
if random.random() < appliance_mutation_probability: # noqa: S311 for position, gene in enumerate(self.appliance_layout.genes)
upper = len(gene.allowed_start_slots) - 1 if len(gene.allowed_start_slots) > 1
individual[base + position] = random.randint(0, upper) # noqa: S311 ]
if mutable_positions:
position, upper = random.choice(mutable_positions) # noqa: S311
old_value = int(individual[position])
replacement = random.randint(0, upper - 1) # noqa: S311
if replacement >= old_value:
replacement += 1
individual[position] = replacement
changed = True
return changed
def mutate(self, individual: list[int]) -> tuple[list[int]]:
"""Apply one coherent mutation family instead of stacking destructive changes."""
operation = random.random() # noqa: S311
changed = False
if operation < 0.50:
changed = self._mutate_point_controls(individual)
elif operation < 0.70:
before = list(individual)
self._mutate_battery_block(individual)
changed = individual != before
elif operation < 0.90:
changed = self._mutate_energy_shift(individual)
else:
changed = self._mutate_flexible_controls(individual)
# Some specialized moves are unavailable without EV, appliances or a
# viable grid-export opportunity. Always return a genuinely changed
# future control so an offspring budget is not silently wasted.
if not changed:
self._mutate_point_controls(individual)
if self.optimize_ev and self.fixed_eauto_hours > 0:
ev_end = self.total_slots * 2
individual[ev_end - self.fixed_eauto_hours : ev_end] = [0] * self.fixed_eauto_hours
return (individual,) return (individual,)
@@ -1198,9 +1265,10 @@ class GeneticOptimization(OptimizationBase):
# Add EV charge index values if optimize_ev is True # Add EV charge index values if optimize_ev is True
if self.optimize_ev: if self.optimize_ev:
individual_components += [ ev_controls = [self.toolbox.attr_ev_charge_index() for _ in range(self.total_slots)]
self.toolbox.attr_ev_charge_index() for _ in range(self.total_slots) if self.fixed_eauto_hours > 0:
] ev_controls[-self.fixed_eauto_hours :] = [0] * self.fixed_eauto_hours
individual_components += ev_controls
# Add one appliance start gene per scheduled run (index into that run's # Add one appliance start gene per scheduled run (index into that run's
# allowed_start_slots). No draws happen when there are no appliances, so # allowed_start_slots). No draws happen when there are no appliances, so
@@ -1714,6 +1782,269 @@ class GeneticOptimization(OptimizationBase):
final_value = float(best.fitness.values[0]) final_value = float(best.fitness.values[0])
return best, evaluations, improvements, initial_value, final_value return best, evaluations, improvements, initial_value, final_value
def _population_diversity(self, population: list[Any]) -> float:
"""Return the fraction of fitness-relevant unique genomes."""
if not population:
return 0.0
return len({self._fitness_key(individual) for individual in population}) / len(population)
def _invalidate_individual(self, individual: Any) -> None:
"""Invalidate inherited fitness and auxiliary simulation values."""
if individual.fitness.valid:
del individual.fitness.values
if hasattr(individual, "extra_data"):
del individual.extra_data
def _evaluate_invalid(self, population: list[Any]) -> int:
"""Evaluate invalid individuals and return the number of cache lookups."""
invalid = [individual for individual in population if not individual.fitness.valid]
fitnesses = self.toolbox.map(self.toolbox.evaluate, invalid)
for individual, fitness in zip(invalid, fitnesses):
individual.fitness.values = fitness
return len(invalid)
def _fresh_population(self, count: int, *, educated_fraction: float) -> list[Any]:
"""Create a mixed set of current educated guesses and random immigrants."""
if count <= 0:
return []
educated_target = min(count, int(count * educated_fraction + 0.5))
educated = self._educated_guess_individuals(educated_target)
fresh = [creator.Individual(genome) for genome in educated[:count]]
fresh.extend(self.toolbox.population(n=count - len(fresh)))
return fresh
def _best_unique(self, population: list[Any], count: int) -> list[Any]:
"""Return the best fitness-relevant unique candidates."""
selected: list[Any] = []
seen: set[tuple[int, ...]] = set()
for candidate in tools.selBest(population, len(population)):
key = self._fitness_key(candidate)
if key in seen:
continue
seen.add(key)
selected.append(candidate)
if len(selected) >= count:
break
return selected
def _select_diverse(self, candidates: list[Any], count: int) -> list[Any]:
"""Tournament-select while repairing only severe duplicate takeover."""
if not candidates or count <= 0:
return []
selected = tools.selTournament(candidates, count, tournsize=3)
best = tools.selBest(candidates, 1)[0]
best_key = self._fitness_key(best)
selected_keys = [self._fitness_key(candidate) for candidate in selected]
if best_key not in selected_keys:
worst_index = max(
range(len(selected)),
key=lambda index: selected[index].fitness.values[0],
)
selected[worst_index] = best
selected_keys[worst_index] = best_key
# Duplicates are useful for exploitation and cache hits. Replace only
# enough duplicate selections to keep a minimum search breadth.
target_unique = min(
count,
max(1, int(count * self.SELECTION_DIVERSITY_FLOOR + 0.999999)),
)
key_counts: dict[tuple[int, ...], int] = defaultdict(int)
for key in selected_keys:
key_counts[key] += 1
if len(key_counts) >= target_unique:
return selected
for candidate in tools.selBest(candidates, len(candidates)):
candidate_key = self._fitness_key(candidate)
if candidate_key in key_counts:
continue
replaceable = [index for index, key in enumerate(selected_keys) if key_counts[key] > 1]
if not replaceable:
break
replace_index = max(
replaceable,
key=lambda index: selected[index].fitness.values[0],
)
replaced_key = selected_keys[replace_index]
key_counts[replaced_key] -= 1
selected[replace_index] = candidate
selected_keys[replace_index] = candidate_key
key_counts[candidate_key] = 1
if len(key_counts) >= target_unique:
break
return selected
def _make_offspring(
self,
population: list[Any],
count: int,
*,
mutation_probability: float,
) -> list[Any]:
"""Create offspring where crossover and mutation can both be applied."""
offspring: list[Any] = []
for _ in range(count):
child = self.toolbox.clone(random.choice(population)) # noqa: S311
crossed = False
if len(population) > 1 and random.random() < self.CROSSOVER_PROBABILITY: # noqa: S311
partner = self.toolbox.clone(random.choice(population)) # noqa: S311
child, _ = self.toolbox.mate(child, partner)
crossed = True
# Non-crossover offspring are always mutated. Crossover children are
# independently mutated, preventing identical parents from turning
# most of the generation into unchanged copies.
if not crossed or random.random() < mutation_probability: # noqa: S311
(child,) = self.toolbox.mutate(child)
self._invalidate_individual(child)
offspring.append(child)
return offspring
def _evolve_population_adaptive(
self,
population: list[Any],
*,
mu: int,
lambda_: int,
ngen: int,
stats: Any,
halloffame: Any,
) -> tuple[list[Any], Any]:
"""Evolve with diversity boosts and incumbent-preserving soft restarts."""
logbook = tools.Logbook()
logbook.header = [
"gen",
"nevals",
*stats.fields,
"diversity",
"stagnation",
"immigrants",
"restart",
]
nevals = self._evaluate_invalid(population)
halloffame.update(population)
best_fitness = float(halloffame[0].fitness.values[0])
stagnation = 0
diversity = self._population_diversity(population)
record = stats.compile(population)
logbook.record(
gen=0,
nevals=nevals,
diversity=diversity,
stagnation=stagnation,
immigrants=0,
restart=0,
**record,
)
if self.verbose:
print(logbook.stream)
diversity_boost_active = False
soft_restarts = 0
total_immigrants = 0
minimum_diversity = diversity
for generation in range(1, ngen + 1):
diversity = self._population_diversity(population)
soft_restart = (
stagnation >= self.SOFT_RESTART_GENERATIONS
or diversity < self.SOFT_RESTART_DIVERSITY_THRESHOLD
)
immigrants = 0
if soft_restart:
survivor_count = max(1, int(mu * self.SOFT_RESTART_SURVIVOR_FRACTION))
survivors = self._best_unique(population, survivor_count)
immigrants = mu - len(survivors)
population = survivors + self._fresh_population(
immigrants,
educated_fraction=0.40,
)
nevals = self._evaluate_invalid(population)
halloffame.update(population)
soft_restarts += 1
total_immigrants += immigrants
stagnation = 0
diversity_boost_active = False
logger.info(
"Genetic soft restart at generation {}: kept {} unique survivors, "
"injected {} immigrants (diversity {:.1%}).",
generation,
len(survivors),
immigrants,
diversity,
)
else:
diversity_boost = (
stagnation >= self.STAGNATION_GENERATIONS
or diversity < self.DIVERSITY_BOOST_THRESHOLD
)
if diversity_boost and not diversity_boost_active:
logger.info(
"Genetic diversity boost at generation {}: stagnation {}, "
"diversity {:.1%}.",
generation,
stagnation,
diversity,
)
diversity_boost_active = diversity_boost
mutation_probability = (
self.STAGNATION_MUTATION_PROBABILITY
if diversity_boost
else self.MUTATION_PROBABILITY
)
if diversity_boost:
immigrants = max(1, int(lambda_ * self.IMMIGRANT_FRACTION + 0.5))
offspring = self._make_offspring(
population,
lambda_ - immigrants,
mutation_probability=mutation_probability,
)
offspring.extend(
self._fresh_population(
immigrants,
educated_fraction=0.50,
)
)
nevals = self._evaluate_invalid(offspring)
halloffame.update(offspring)
population = self._select_diverse(population + offspring, mu)
total_immigrants += immigrants
current_best = float(halloffame[0].fitness.values[0])
if current_best < best_fitness - 1e-9:
best_fitness = current_best
stagnation = 0
diversity_boost_active = False
elif not soft_restart:
stagnation += 1
diversity = self._population_diversity(population)
minimum_diversity = min(minimum_diversity, diversity)
record = stats.compile(population)
logbook.record(
gen=generation,
nevals=nevals,
diversity=diversity,
stagnation=stagnation,
immigrants=immigrants,
restart=int(soft_restart),
**record,
)
if self.verbose:
print(logbook.stream)
self._adaptive_evolution_metrics = {
"soft_restarts": soft_restarts,
"immigrants": total_immigrants,
"minimum_diversity": minimum_diversity,
"final_diversity": self._population_diversity(population),
"final_stagnation": stagnation,
}
return population, logbook
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
@@ -1756,10 +2087,15 @@ class GeneticOptimization(OptimizationBase):
self.toolbox.register("population", tools.initRepeat, list, self.toolbox.individual) self.toolbox.register("population", tools.initRepeat, list, self.toolbox.individual)
self.toolbox.register("mate", tools.cxTwoPoint) self.toolbox.register("mate", tools.cxTwoPoint)
# Mutation operator for battery charge/discharge states # Keep point mutations local enough to refine a mature schedule. The
# Keep the expected number of mutated genes per hour stable when the # expected number of changed controls remains close to three regardless
# interval becomes finer (0.2 hourly -> 0.05 on a quarter-hour grid). # of interval and elapsed slots; coherent block/energy moves are handled
mutation_probability = 0.2 / self.slots_per_hour # by separate mutation families.
active_slots = max(self.total_slots - self._start_day_slot(), 1)
mutation_probability = min(
0.10,
self.POINT_MUTATION_EXPECTED_GENES / active_slots,
)
self.toolbox.register( self.toolbox.register(
"mutate_charge_discharge", "mutate_charge_discharge",
tools.mutUniformInt, tools.mutUniformInt,
@@ -1838,7 +2174,7 @@ class GeneticOptimization(OptimizationBase):
if not getattr(self, "_fitness_cache_enabled", False): if not getattr(self, "_fitness_cache_enabled", False):
return self._evaluate_uncached(individual, parameters, start_hour, worst_case) return self._evaluate_uncached(individual, parameters, start_hour, worst_case)
original_key = tuple(int(value) for value in individual) original_key = self._fitness_key(individual)
cached = self._fitness_cache.get(original_key) cached = self._fitness_cache.get(original_key)
if cached is not None: if cached is not None:
individual[:] = cached.genome individual[:] = cached.genome
@@ -1855,10 +2191,10 @@ class GeneticOptimization(OptimizationBase):
# persistent result for the remainder of the run. # persistent result for the remainder of the run.
return fitness return fitness
canonical_key = tuple(int(value) for value in individual) canonical_key = self._fitness_key(individual)
extra_value1, extra_value2, extra_value3 = extra_data extra_value1, extra_value2, extra_value3 = extra_data
entry = FitnessCacheEntry( entry = FitnessCacheEntry(
genome=canonical_key, genome=tuple(int(value) for value in individual),
fitness=fitness, fitness=fitness,
extra_data=( extra_data=(
float(extra_value1), float(extra_value1),
@@ -1870,6 +2206,18 @@ class GeneticOptimization(OptimizationBase):
self._fitness_cache[canonical_key] = entry self._fitness_cache[canonical_key] = entry
return fitness return fitness
def _fitness_key(self, individual: list[int]) -> tuple[int, ...]:
"""Return the fitness-relevant genome, excluding elapsed control slots."""
start_slot = self._start_day_slot()
relevant = list(individual[start_slot : self.total_slots])
if self.optimize_ev:
ev_start = self.total_slots + start_slot
relevant.extend(individual[ev_start : self.total_slots * 2])
n_appliance_genes = self.appliance_layout.n_genes
if n_appliance_genes > 0:
relevant.extend(individual[-n_appliance_genes:])
return tuple(int(value) for value in relevant)
def _evaluate_uncached( def _evaluate_uncached(
self, self,
individual: list[int], individual: list[int],
@@ -1918,7 +2266,7 @@ class GeneticOptimization(OptimizationBase):
except Exception: except Exception:
# Return bad fitness score ("FitnessMin") in case of an exception # Return bad fitness score ("FitnessMin") in case of an exception
if hasattr(individual, "extra_data"): if hasattr(individual, "extra_data"):
del individual.extra_data # type: ignore[attr-defined] del individual.extra_data
return (100000.0,) return (100000.0,)
gesamtbilanz = simulation_result["Gesamtbilanz_Euro"] * (-1.0 if worst_case else 1.0) gesamtbilanz = simulation_result["Gesamtbilanz_Euro"] * (-1.0 if worst_case else 1.0)
@@ -2192,11 +2540,13 @@ class GeneticOptimization(OptimizationBase):
population.extend(self.toolbox.population(n=random_count)) population.extend(self.toolbox.population(n=random_count))
logger.info( logger.info(
"Genetic settings: {} individuals, {} generations, {} survivors, " "Genetic settings: {} individuals, {} generations, {} survivors, "
"{} offspring per generation.", "{} offspring per generation, adaptive mutation {:.0%}/{:.0%}.",
individuals, individuals,
ngen, ngen,
individuals, individuals,
individuals, individuals,
self.MUTATION_PROBABILITY,
self.STAGNATION_MUTATION_PROBABILITY,
) )
logger.info( logger.info(
"Initial population {}: {} exact warm starts, {} warm mutations, " "Initial population {}: {} exact warm starts, {} warm mutations, "
@@ -2219,19 +2569,17 @@ class GeneticOptimization(OptimizationBase):
local_improvements = 0 local_improvements = 0
local_initial_fitness = float("nan") local_initial_fitness = float("nan")
local_final_fitness = float("nan") local_final_fitness = float("nan")
self._adaptive_evolution_metrics = {}
try: try:
pop, log = algorithms.eaMuPlusLambda( pop, log = self._evolve_population_adaptive(
population, population,
self.toolbox,
mu=individuals, mu=individuals,
lambda_=individuals, lambda_=individuals,
cxpb=0.6,
mutpb=0.4,
ngen=ngen, ngen=ngen,
stats=stats, stats=stats,
halloffame=hof, halloffame=hof,
verbose=self.verbose,
) )
population = pop
( (
best_solution, best_solution,
local_evaluations, local_evaluations,
@@ -2245,6 +2593,9 @@ class GeneticOptimization(OptimizationBase):
max(individuals, 1), max(individuals, 1),
), ),
) )
except Exception:
self._fitness_cache.clear()
raise
finally: finally:
self._fitness_cache_enabled = False self._fitness_cache_enabled = False
@@ -2260,12 +2611,13 @@ class GeneticOptimization(OptimizationBase):
cache_lookups = self._fitness_cache_hits + self._fitness_cache_misses cache_lookups = self._fitness_cache_hits + self._fitness_cache_misses
cache_hit_rate = self._fitness_cache_hits / cache_lookups if cache_lookups > 0 else 0.0 cache_hit_rate = self._fitness_cache_hits / cache_lookups if cache_lookups > 0 else 0.0
cache_keys = len(self._fitness_cache)
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,
self._fitness_cache_misses, self._fitness_cache_misses,
cache_hit_rate, cache_hit_rate,
len(self._fitness_cache), cache_keys,
) )
# Store fitness history # Store fitness history
@@ -2274,12 +2626,17 @@ class GeneticOptimization(OptimizationBase):
"avg": log.select("avg"), # Average fitness for each generation (Y-axis) "avg": log.select("avg"), # Average fitness for each generation (Y-axis)
"max": log.select("max"), # Maximum fitness for each generation (Y-axis) "max": log.select("max"), # Maximum fitness for each generation (Y-axis)
"min": log.select("min"), # Minimum fitness for each generation (Y-axis) "min": log.select("min"), # Minimum fitness for each generation (Y-axis)
"diversity": log.select("diversity"),
"stagnation": log.select("stagnation"),
"immigrants": log.select("immigrants"),
"restart": log.select("restart"),
"fitness_cache": { "fitness_cache": {
"hits": self._fitness_cache_hits, "hits": self._fitness_cache_hits,
"misses": self._fitness_cache_misses, "misses": self._fitness_cache_misses,
"hit_rate": cache_hit_rate, "hit_rate": cache_hit_rate,
"keys": len(self._fitness_cache), "keys": cache_keys,
}, },
"adaptive_evolution": self._adaptive_evolution_metrics,
"local_search": { "local_search": {
"evaluations": local_evaluations, "evaluations": local_evaluations,
"improvements": local_improvements, "improvements": local_improvements,
@@ -2296,6 +2653,9 @@ class GeneticOptimization(OptimizationBase):
member["verluste"].append(extra_value2) member["verluste"].append(extra_value2)
member["nebenbedingung"].append(extra_value3) member["nebenbedingung"].append(extra_value3)
# Avoid retaining large genome tuples in a long-lived API process until
# cyclic garbage collection happens. Cache statistics above are scalar.
self._fitness_cache.clear()
return best_solution, member return best_solution, member
def optimierung_ems( def optimierung_ems(
+65 -5
View File
@@ -3,7 +3,7 @@ from unittest.mock import MagicMock, patch
import numpy as np import numpy as np
import pytest import pytest
from deap import creator from deap import creator, tools
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
@@ -142,6 +142,34 @@ def test_fitness_cache_never_stores_failed_evaluations(config_eos: ConfigEOS):
assert opt._fitness_cache == {} assert opt._fitness_cache == {}
def test_fitness_cache_ignores_elapsed_control_slots(config_eos: ConfigEOS):
_configure_hourly_grid(config_eos, start_hour=10)
opt = GeneticOptimization(fixed_seed=42)
opt.optimize_ev = False
opt.setup_deap_environment({"home_appliance": 0}, start_hour=10)
parameters = SimpleNamespace(
ems=SimpleNamespace(preis_euro_pro_wh_akku=0.0),
eauto=None,
)
result = {
"Gesamtbilanz_Euro": 1.0,
"Gesamt_Verluste": 0.0,
"EAuto_SoC_pro_Stunde": np.zeros(opt.total_slots),
}
first = creator.Individual([0] * opt.total_slots)
elapsed_variant = creator.Individual(first)
elapsed_variant[0] = 1
opt._fitness_cache_enabled = True
with patch.object(opt, "evaluate_inner", return_value=result) as evaluate:
first_fitness = opt.evaluate(first, parameters, 10, False) # type: ignore[arg-type]
variant_fitness = opt.evaluate(elapsed_variant, parameters, 10, False) # type: ignore[arg-type]
assert evaluate.call_count == 1
assert first_fitness == variant_fitness
assert opt._fitness_cache_hits == 1
def test_mutated_warm_start_neighbors_keep_elapsed_slots(config_eos: ConfigEOS): def test_mutated_warm_start_neighbors_keep_elapsed_slots(config_eos: ConfigEOS):
_configure_hourly_grid(config_eos, start_hour=10) _configure_hourly_grid(config_eos, start_hour=10)
opt = GeneticOptimization(fixed_seed=42) opt = GeneticOptimization(fixed_seed=42)
@@ -170,7 +198,7 @@ def test_initial_population_uses_fixed_seed_budget_and_configured_population(
educated = [[7] * opt.total_slots for _ in range(100)] educated = [[7] * opt.total_slots for _ in range(100)]
captured: dict[str, object] = {} captured: dict[str, object] = {}
def fake_ea(population, toolbox, **kwargs): def fake_evolution(population, **kwargs):
captured["population"] = list(population) captured["population"] = list(population)
captured["mu"] = kwargs["mu"] captured["mu"] = kwargs["mu"]
captured["lambda"] = kwargs["lambda_"] captured["lambda"] = kwargs["lambda_"]
@@ -188,7 +216,7 @@ def test_initial_population_uses_fixed_seed_budget_and_configured_population(
"population", "population",
side_effect=lambda n: [creator.Individual([9] * opt.total_slots) for _ in range(n)], side_effect=lambda n: [creator.Individual([9] * opt.total_slots) for _ in range(n)],
), ),
patch("akkudoktoreos.optimization.genetic.genetic.algorithms.eaMuPlusLambda", fake_ea), patch.object(opt, "_evolve_population_adaptive", side_effect=fake_evolution),
): ):
opt.optimize(start_solution=start_solution, ngen=1) opt.optimize(start_solution=start_solution, ngen=1)
@@ -220,7 +248,7 @@ def test_small_population_scales_warm_and_educated_seed_families(config_eos: Con
captured["educated_count"] = count captured["educated_count"] = count
return [[7] * opt.total_slots for _ in range(count)] return [[7] * opt.total_slots for _ in range(count)]
def fake_ea(population, toolbox, **kwargs): def fake_evolution(population, **kwargs):
captured["population"] = list(population) captured["population"] = list(population)
captured["mu"] = kwargs["mu"] captured["mu"] = kwargs["mu"]
captured["lambda"] = kwargs["lambda_"] captured["lambda"] = kwargs["lambda_"]
@@ -238,7 +266,7 @@ def test_small_population_scales_warm_and_educated_seed_families(config_eos: Con
"population", "population",
side_effect=lambda n: [creator.Individual([9] * opt.total_slots) for _ in range(n)], side_effect=lambda n: [creator.Individual([9] * opt.total_slots) for _ in range(n)],
), ),
patch("akkudoktoreos.optimization.genetic.genetic.algorithms.eaMuPlusLambda", fake_ea), patch.object(opt, "_evolve_population_adaptive", side_effect=fake_evolution),
): ):
opt.optimize(start_solution=start_solution, ngen=1) opt.optimize(start_solution=start_solution, ngen=1)
@@ -255,6 +283,38 @@ def test_small_population_scales_warm_and_educated_seed_families(config_eos: Con
assert captured["lambda"] == 100 assert captured["lambda"] == 100
def test_adaptive_evolution_soft_restarts_collapsed_population(config_eos: ConfigEOS):
_configure_hourly_grid(config_eos)
opt = GeneticOptimization(fixed_seed=42)
opt.optimize_ev = False
opt.setup_deap_environment({"home_appliance": 0}, start_hour=0)
opt.toolbox.register("evaluate", lambda individual: (float(sum(individual)),))
population = [creator.Individual([0] * opt.total_slots) for _ in range(20)]
stats = tools.Statistics(lambda individual: individual.fitness.values)
stats.register("min", np.min)
stats.register("avg", np.mean)
stats.register("max", np.max)
halloffame = tools.HallOfFame(1)
fresh = [creator.Individual([value] + [0] * (opt.total_slots - 1)) for value in range(1, 20)]
with patch.object(opt, "_fresh_population", return_value=fresh) as create_fresh:
evolved, log = opt._evolve_population_adaptive(
population,
mu=20,
lambda_=20,
ngen=1,
stats=stats,
halloffame=halloffame,
)
create_fresh.assert_called_once_with(19, educated_fraction=0.40)
assert log.select("restart") == [0, 1]
assert log.select("immigrants") == [0, 19]
assert opt._adaptive_evolution_metrics["soft_restarts"] == 1
assert opt._population_diversity(evolved) == pytest.approx(1.0)
assert halloffame[0].fitness.values == (0.0,)
def test_local_search_moves_weak_export_to_later_expensive_import(config_eos: ConfigEOS): def test_local_search_moves_weak_export_to_later_expensive_import(config_eos: ConfigEOS):
_configure_hourly_grid(config_eos) _configure_hourly_grid(config_eos)
opt = GeneticOptimization(fixed_seed=42) opt = GeneticOptimization(fixed_seed=42)
+25 -6
View File
@@ -227,8 +227,8 @@ def test_hourly_start_solution_is_expanded_to_slots(config_eos: ConfigEOS):
assert migrated[:8] == [0, 0, 0, 0, 1, 1, 1, 1] assert migrated[:8] == [0, 0, 0, 0, 1, 1, 1, 1]
def test_quarter_hour_mutation_probability_preserves_hourly_rate(config_eos: ConfigEOS): def test_quarter_hour_mutation_targets_three_future_controls(config_eos: ConfigEOS):
"""A finer genome does not mutate four times as many controls per hour.""" """Point mutation scales to roughly three effective future controls."""
config_eos.merge_settings_from_dict( config_eos.merge_settings_from_dict(
{ {
"prediction": {"hours": 48}, "prediction": {"hours": 48},
@@ -239,7 +239,28 @@ def test_quarter_hour_mutation_probability_preserves_hourly_rate(config_eos: Con
opt.optimize_ev = False opt.optimize_ev = False
opt.setup_deap_environment({"home_appliance": 0}, start_hour=0) opt.setup_deap_environment({"home_appliance": 0}, start_hour=0)
assert opt.toolbox.mutate_charge_discharge.keywords["indpb"] == pytest.approx(0.05) active_slots = opt.total_slots - opt._start_day_slot()
expected = min(0.10, opt.POINT_MUTATION_EXPECTED_GENES / active_slots)
assert opt.toolbox.mutate_charge_discharge.keywords["indpb"] == pytest.approx(expected)
def test_point_mutation_keeps_elapsed_slots_unchanged(config_eos: ConfigEOS):
config_eos.merge_settings_from_dict(
{
"prediction": {"hours": 48},
"optimization": {"horizon_hours": 48, "interval": 900},
}
)
get_ems(init=True).set_start_datetime(to_datetime().set(hour=10, minute=0))
opt = GeneticOptimization(fixed_seed=42)
opt.optimize_ev = False
opt.setup_deap_environment({"home_appliance": 0}, start_hour=10)
individual = [0] * opt.total_slots
changed = opt._mutate_point_controls(individual)
assert changed
assert individual[: opt._start_day_slot()] == [0] * opt._start_day_slot()
def test_sub_hourly_home_appliance_is_scheduled(config_eos: ConfigEOS): def test_sub_hourly_home_appliance_is_scheduled(config_eos: ConfigEOS):
@@ -253,9 +274,7 @@ def test_sub_hourly_home_appliance_is_scheduled(config_eos: ConfigEOS):
parameters = load_hourly_parameters().model_copy( parameters = load_hourly_parameters().model_copy(
update={ update={
"home_appliances": [ "home_appliances": [
HomeApplianceParameters( HomeApplianceParameters(device_id="dishwasher1", consumption_wh=1200, duration_h=2)
device_id="dishwasher1", consumption_wh=1200, duration_h=2
)
] ]
}, },
deep=True, deep=True,
+99 -99
View File
@@ -116,24 +116,24 @@
0, 0,
0, 0,
0, 0,
0, 1,
0, 1,
0, 1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1, 1,
1, 1,
1, 1,
0, 0,
0, 0,
1,
1,
1,
1,
1,
1,
1,
1,
1,
0,
0, 0,
0, 0,
0, 0,
@@ -144,10 +144,10 @@
0, 0,
0, 0,
1, 1,
1,
1,
0, 0,
0 1,
1,
1
], ],
"battery_grid_export_allowed": [], "battery_grid_export_allowed": [],
"eautocharge_hours_float": null, "eautocharge_hours_float": null,
@@ -239,9 +239,9 @@
0.0, 0.0,
0.022582049506752234, 0.022582049506752234,
0.3039575, 0.3039575,
0.19320652266312205, 0.1926250109597104,
0.1358062627100041, 0.13346423958241627,
0.0692592282596561, 0.053398267180554584,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -262,7 +262,7 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.09514375330616998, 0.024351216461056053,
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": 3421.6165248449383, "Gesamt_Verluste": 3807.1176630027076,
"Gesamtbilanz_Euro": 0.5951993628823129, "Gesamtbilanz_Euro": 0.22702041221600888,
"Gesamteinnahmen_Euro": 1.1298399163065491, "Gesamteinnahmen_Euro": 1.0402628835513343,
"Gesamtkosten_Euro": 1.725039279188862, "Gesamtkosten_Euro": 1.2672832957673432,
"Home_appliance_wh_per_hour": [ "Home_appliance_wh_per_hour": [
0.0, 0.0,
0.0, 0.0,
@@ -324,14 +324,6 @@
0.07557452231671152, 0.07557452231671152,
0.0, 0.0,
4.55656845588237e-17, 4.55656845588237e-17,
0.001414013162203277,
0.005881449073870462,
0.05258762370598476,
0.0,
0.0,
0.0,
0.26650619799999997,
0.19588158,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -341,6 +333,14 @@
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.25364873699864443,
0.1306329312971816, 0.1306329312971816,
0.07362195915902499, 0.07362195915902499,
0.060401289430882174, 0.060401289430882174,
@@ -352,10 +352,10 @@
0.013012984677295973, 0.013012984677295973,
0.08357424731947552, 0.08357424731947552,
0.0, 0.0,
0.19011028252189552,
0.0, 0.0,
0.0, 0.0,
0.214398479, 0.0
0.16484566
], ],
"Netzbezug_Wh_pro_Stunde": [ "Netzbezug_Wh_pro_Stunde": [
439.1848126015972, 439.1848126015972,
@@ -364,14 +364,6 @@
402.20607938643707, 402.20607938643707,
0.0, 0.0,
2.2737367544323206e-13, 2.2737367544323206e-13,
6.433180901743754,
25.909467285772962,
175.4675465665157,
0.0,
0.0,
0.0,
912.38,
704.61,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -381,6 +373,14 @@
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,
833.8222781020527,
537.58407941227, 537.58407941227,
322.9033296448464, 322.9033296448464,
273.0618871197205, 273.0618871197205,
@@ -392,10 +392,10 @@
57.32592368852852, 57.32592368852852,
278.85968408233407, 278.85968408233407,
0.0, 0.0,
617.0408390843736,
0.0, 0.0,
0.0, 0.0,
733.99, 0.0
592.97
], ],
"Netzeinspeisung_Wh_pro_Stunde": [ "Netzeinspeisung_Wh_pro_Stunde": [
0.0, 0.0,
@@ -404,9 +404,9 @@
0.0, 0.0,
322.60070723931767, 322.60070723931767,
4342.25, 4342.25,
2760.093180901744, 2751.785870853006,
1940.089467285773, 1906.6319940345184,
989.4175465665157, 762.832388293637,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -427,7 +427,7 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
1359.1964758024283, 347.87452087222937,
2176.6272473840627, 2176.6272473840627,
1473.7559236885286, 1473.7559236885286,
776.5396840823341, 776.5396840823341,
@@ -444,14 +444,14 @@
51.82392952637247, 51.82392952637247,
543.9059151312817, 543.9059151312817,
0.0, 0.0,
0.0, 1.8741291469953731,
0.0, 7.548005965483245,
0.0, 51.11761170636123,
108.98319763338179, 108.98319763338179,
106.80230977350088, 106.80230977350088,
133.7321802766326, 133.7321802766326,
0.0, 124.41545454545451,
0.0, 96.08318181818186,
70.41409090909087, 70.41409090909087,
118.37045454545455, 118.37045454545455,
94.68272727272722, 94.68272727272722,
@@ -460,22 +460,22 @@
66.66681818181814, 66.66681818181814,
69.12409090909085, 69.12409090909085,
109.07302361034766, 109.07302361034766,
116.99491129525349, 3.2918733722463145,
22.312089529472388, 22.312089529472388,
54.18759955738153, 54.18759955738153,
86.6234264543665, 86.6234264543665,
165.15986945297027, 165.15986945297027,
65.92300791736113, 65.92300791736113,
538.2984000000001, 538.2984000000001,
278.8343903340726, 400.1930249256966,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
111.78035844493081, 111.78035844493081,
90.18403329253945, 6.04210069012484,
134.59227272727276, 134.59227272727276,
0.0, 100.08954545454549,
0.0 80.85954545454547
], ],
"akku_soc_pro_stunde": [ "akku_soc_pro_stunde": [
80.0, 80.0,
@@ -491,31 +491,31 @@
98.93741213017658, 98.93741213017658,
95.76274429012544, 95.76274429012544,
91.54148603150185, 91.54148603150185,
91.54148603150185, 87.61423052185997,
91.54148603150185, 84.5813018028517,
89.31881902048255, 82.35863479183242,
85.58237790478007, 78.62219367612994,
82.59365545298944, 75.6334712243393,
79.97317508108861, 73.01299085243846,
77.57859002599218, 70.61840579734205,
75.47420813893983, 68.5140239102897,
73.29226082489025, 66.33207659624011,
69.84937058394196, 62.88918635529183,
66.35170046539932, 62.98062728229866,
66.97148073010689, 63.600407547006235,
68.47669182892304, 65.10561864582239,
70.8828981193221, 67.51182493622146,
75.4706722707935, 72.09959908769285,
77.30186693516465, 73.93079375206398,
92.254600268498, 88.88352708539732,
100.0, 100.0,
100.0, 100.0,
100.0, 100.0,
100.0, 100.0,
98.60068046043587, 98.60068046043587,
96.11252124341868, 98.76851659071711,
91.86402778611841, 94.52002313341684,
91.86402778611841 91.360630915786
], ],
"Electricity_price": [ "Electricity_price": [
0.000228, 0.000228,
@@ -709,15 +709,12 @@
"initial_soc_percentage": 54 "initial_soc_percentage": 54
}, },
"start_solution": [ "start_solution": [
2.0,
2.0,
1.0,
1.0,
0.0, 0.0,
0.0, 2.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,
@@ -731,18 +728,21 @@
1.0, 1.0,
1.0, 1.0,
1.0, 1.0,
1.0,
1.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, 0.0,
0.0, 0.0,
1.0,
1.0,
1.0,
1.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,
@@ -753,10 +753,10 @@
0.0, 0.0,
0.0, 0.0,
1.0, 1.0,
1.0,
1.0,
0.0, 0.0,
0.0 1.0,
1.0,
1.0
], ],
"washingstart": null, "washingstart": null,
"appliance_starts": {} "appliance_starts": {}
+56 -56
View File
@@ -110,11 +110,11 @@
0, 0,
0, 0,
0, 0,
0, 1,
0, 1,
0, 1,
0, 1,
0, 1,
0, 0,
0, 0,
0, 0,
@@ -128,7 +128,7 @@
1, 1,
1, 1,
1, 1,
1, 0,
1, 1,
1, 1,
1, 1,
@@ -237,8 +237,8 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.022582049506752234, 0.0,
0.3039575, 0.16427941326352855,
0.19320652266312205, 0.19320652266312205,
0.1358062627100041, 0.1358062627100041,
0.0692592282596561, 0.0692592282596561,
@@ -262,7 +262,7 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.09514375330616998, 0.14543003946319485,
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": 3421.6165248449383, "Gesamt_Verluste": 3782.4922474084588,
"Gesamtbilanz_Euro": 0.5951993628823129, "Gesamtbilanz_Euro": 0.5099857443907834,
"Gesamteinnahmen_Euro": 1.1298399163065491, "Gesamteinnahmen_Euro": 1.0178660662203505,
"Gesamtkosten_Euro": 1.725039279188862, "Gesamtkosten_Euro": 1.5278518106111338,
"Home_appliance_wh_per_hour": [ "Home_appliance_wh_per_hour": [
0.0, 0.0,
0.0, 0.0,
@@ -318,10 +318,10 @@
], ],
"home_appliance_energy_wh": {}, "home_appliance_energy_wh": {},
"Kosten_Euro_pro_Stunde": [ "Kosten_Euro_pro_Stunde": [
0.10013413727316416, 0.0,
0.09007999454232955, 0.0,
0.11436917344552285, 0.0,
0.07557452231671152, 0.0,
0.0, 0.0,
4.55656845588237e-17, 4.55656845588237e-17,
0.001414013162203277, 0.001414013162203277,
@@ -336,7 +336,7 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.182970359,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -358,10 +358,10 @@
0.16484566 0.16484566
], ],
"Netzbezug_Wh_pro_Stunde": [ "Netzbezug_Wh_pro_Stunde": [
439.1848126015972, 0.0,
407.23324838304495, 0.0,
546.436566868241, 0.0,
402.20607938643707, 0.0,
0.0, 0.0,
2.2737367544323206e-13, 2.2737367544323206e-13,
6.433180901743754, 6.433180901743754,
@@ -376,7 +376,7 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 556.31,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -402,8 +402,8 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
322.60070723931767, 0.0,
4342.25, 2346.848760907551,
2760.093180901744, 2760.093180901744,
1940.089467285773, 1940.089467285773,
989.4175465665157, 989.4175465665157,
@@ -427,7 +427,7 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
1359.1964758024283, 2077.5719923313554,
2176.6272473840627, 2176.6272473840627,
1473.7559236885286, 1473.7559236885286,
776.5396840823341, 776.5396840823341,
@@ -438,12 +438,12 @@
0.0 0.0
], ],
"Verluste_Pro_Stunde": [ "Verluste_Pro_Stunde": [
37.96737751219166, 97.85621559422765,
46.38878980596536, 101.92059640365329,
39.91398802418894, 114.42806532440363,
51.82392952637247, 106.67021307906845,
543.9059151312817, 582.6179999999995,
0.0, 239.44814869109382,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -456,7 +456,7 @@
118.37045454545455, 118.37045454545455,
94.68272727272722, 94.68272727272722,
83.01681818181817, 83.01681818181817,
75.86045454545456, 0.0,
66.66681818181814, 66.66681818181814,
69.12409090909085, 69.12409090909085,
109.07302361034766, 109.07302361034766,
@@ -467,7 +467,7 @@
165.15986945297027, 165.15986945297027,
65.92300791736113, 65.92300791736113,
538.2984000000001, 538.2984000000001,
278.8343903340726, 192.62932835060133,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -479,11 +479,11 @@
], ],
"akku_soc_pro_stunde": [ "akku_soc_pro_stunde": [
80.0, 80.0,
81.05464937533866, 79.16421888032488,
82.3432268699488, 78.69989843940196,
83.45194875950959, 77.45653455559739,
84.8915023574644, 77.16482920302518,
100.0, 93.3486625363585,
100.0, 100.0,
100.0, 100.0,
100.0, 100.0,
@@ -497,17 +497,17 @@
85.58237790478007, 85.58237790478007,
82.59365545298944, 82.59365545298944,
79.97317508108861, 79.97317508108861,
77.57859002599218, 79.97317508108861,
75.47420813893983, 77.86879319403626,
73.29226082489025, 75.68684587998666,
69.84937058394196, 72.24395563903838,
66.35170046539932, 68.74628552049575,
66.97148073010689, 69.36606578520332,
68.47669182892304, 70.87127688401948,
70.8828981193221, 73.27748317441853,
75.4706722707935, 77.86525732588994,
77.30186693516465, 79.69645199026107,
92.254600268498, 94.64918532359441,
100.0, 100.0,
100.0, 100.0,
100.0, 100.0,
@@ -719,11 +719,11 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 1.0,
0.0, 1.0,
0.0, 1.0,
0.0, 1.0,
0.0, 1.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -737,7 +737,7 @@
1.0, 1.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,
+331 -331
View File
@@ -11,10 +11,7 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 1.0,
0.0,
0.0,
0.0,
1.0, 1.0,
0.0, 0.0,
0.0, 0.0,
@@ -26,6 +23,9 @@
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,
@@ -110,23 +110,23 @@
0, 0,
0, 0,
0, 0,
0,
0,
0,
1, 1,
1, 0,
1,
1,
1, 1,
0, 0,
0, 0,
0, 0,
0,
1,
1,
1, 1,
0, 0,
0, 0,
1, 1,
1,
0, 0,
0,
0,
1,
1, 1,
1, 1,
1, 1,
@@ -135,17 +135,17 @@
1, 1,
0, 0,
0, 0,
0, 1,
0, 1,
0, 1,
0, 1,
0,
0,
0,
0,
1, 1,
1, 1,
0, 0,
1,
1,
1,
1,
0, 0,
0 0
], ],
@@ -161,37 +161,37 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.625,
1.0,
0.0, 0.0,
0.375, 0.0,
0.0,
0.625, 0.625,
1.0, 0.0,
0.75,
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.75,
0.0,
0.0,
0.625, 0.625,
0.0,
0.0,
0.0,
0.5,
0.875, 0.875,
0.0, 0.75,
0.3, 0.375,
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.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,
@@ -202,37 +202,37 @@
], ],
"result": { "result": {
"Last_Wh_pro_Stunde": [ "Last_Wh_pro_Stunde": [
7953.07, 1053.07,
12103.91, 5677.336751616955,
1320.56, 1320.56,
5272.03, 8032.03,
8063.67, 1163.67,
17059.173961709643, 9456.82,
8116.22, 1216.22,
10763.78, 1103.78,
1129.12, 1129.12,
4490.71, 1178.71,
1050.98, 3550.98,
988.56, 3488.56,
912.38, 912.38,
704.61, 2704.61,
516.37, 516.37,
868.05, 868.05,
694.34, 694.34,
608.79, 608.79,
556.31, 556.31,
488.89, 8768.89,
506.91, 506.91,
804.89, 804.89,
1141.98, 8041.98,
1056.97, 1056.97,
992.46, 992.46,
1155.99, 1155.99,
827.01, 6347.01,
1257.98, 10917.98,
1232.67, 9512.67,
3371.26, 5011.26,
3360.88, 1964.88,
1158.03, 1158.03,
1222.72, 1222.72,
1221.04, 1221.04,
@@ -242,44 +242,44 @@
592.97 592.97
], ],
"EAuto_SoC_pro_Stunde": [ "EAuto_SoC_pro_Stunde": [
5.0,
5.0,
5.0,
5.0, 5.0,
15.925, 15.925,
33.405, 15.925,
33.405, 29.035,
39.96, 29.035,
50.885000000000005, 29.035,
68.365, 29.035,
79.29, 29.035,
94.585, 29.035,
94.585, 29.035,
99.82900000000001, 29.035,
99.82900000000001, 29.035,
99.82900000000001, 29.035,
99.82900000000001, 29.035,
99.82900000000001, 29.035,
99.82900000000001, 29.035,
99.82900000000001, 29.035,
99.82900000000001, 42.144999999999996,
99.82900000000001, 42.144999999999996,
99.82900000000001, 42.144999999999996,
99.82900000000001, 53.06999999999999,
99.82900000000001, 53.06999999999999,
99.82900000000001, 53.06999999999999,
99.82900000000001, 53.06999999999999,
99.82900000000001, 61.809999999999995,
99.82900000000001, 77.105,
99.82900000000001, 90.215,
99.82900000000001, 96.77,
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,
99.82900000000001,
99.82900000000001
], ],
"Einnahmen_Euro_pro_Stunde": [ "Einnahmen_Euro_pro_Stunde": [
0.0, 0.0,
@@ -321,30 +321,11 @@
0.0, 0.0,
0.0 0.0
], ],
"Gesamt_Verluste": 8756.080717524794, "Gesamt_Verluste": 10997.311193085054,
"Gesamtbilanz_Euro": 9.985237573040141, "Gesamtbilanz_Euro": 9.400002543796743,
"Gesamteinnahmen_Euro": 0.0, "Gesamteinnahmen_Euro": 0.0,
"Gesamtkosten_Euro": 9.985237573040141, "Gesamtkosten_Euro": 9.400002543796743,
"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,
@@ -363,29 +344,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.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,
@@ -404,86 +385,105 @@
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": [
0.5980075076051746, 0.10013413727316416,
1.473337992, 1.110569992,
0.11436917344552285,
0.4642292905233547,
0.0, 0.0,
0.0, 0.0,
0.0, 0.001414013162203277,
2.3442290999999997, 0.005881449073870462,
0.9428514400845971,
1.762926136273946,
0.05258762370598476, 0.05258762370598476,
0.0, 0.0,
0.995566611,
1.033413892,
0.0,
0.7518815799999999,
0.174739608,
0.28801899,
0.0, 0.0,
0.0, 0.0,
0.26650619799999997, 0.0,
0.19588158, 1.4565879259999999,
0.0, 0.0,
0.0, 0.0,
0.22802125600000003, 1.0058038379999998,
0.0,
0.0,
0.08281196422730584,
0.16677339,
0.264113778992023,
0.2536463762855701,
0.1306329312971816, 0.1306329312971816,
0.07362195915902499, 0.07362195915902499,
0.060401289430882174, 0.0,
0.009619897970888898, 0.0,
0.07029121023060134, 0.9554559982283223,
4.179128154646605e-17, 0.1443477211581475,
0.04064749825228731, 0.14848868609267263,
0.1994538035279033, 0.0,
0.013012984677295973, 0.013012984677295973,
0.08357424731947552,
0.0, 0.0,
0.0, 0.0,
0.293043269, 0.0,
0.0,
0.214398479, 0.214398479,
0.16484566 0.16484566
], ],
"Netzbezug_Wh_pro_Stunde": [ "Netzbezug_Wh_pro_Stunde": [
2622.8399456367306, 439.1848126015972,
6660.66, 5020.66,
546.436566868241,
2470.61889581349,
0.0, 0.0,
0.0, 0.0,
0.0, 6.433180901743754,
11697.75, 25.909467285772962,
4289.587989465865,
7766.194432924872,
175.4675465665157, 175.4675465665157,
0.0, 0.0,
3231.31,
3480.68,
0.0,
2704.61,
516.37,
868.05,
0.0, 0.0,
0.0, 0.0,
912.38, 0.0,
704.61, 4368.889999999999,
0.0, 0.0,
0.0, 0.0,
694.34, 3306.3899999999994,
0.0,
0.0,
248.38621543882974,
506.91,
799.8600211751151,
833.8145177040436,
537.58407941227, 537.58407941227,
322.9033296448464, 322.9033296448464,
273.0618871197205, 0.0,
45.96224544141853, 0.0,
374.088399311343, 5084.917499884632,
2.2737367544323206e-13, 785.3521281727285,
202.83182760622412, 740.9615074484663,
907.4331370696236, 0.0,
57.32592368852852, 57.32592368852852,
278.85968408233407,
0.0, 0.0,
0.0, 0.0,
987.01, 0.0,
0.0,
733.99, 733.99,
592.97 592.97
], ],
@@ -528,84 +528,84 @@
0.0 0.0
], ],
"Verluste_Pro_Stunde": [ "Verluste_Pro_Stunde": [
945.0059934764078, 37.96737751219166,
1152.0, 599.9999999999998,
114.42806532440363, 39.91398802418894,
768.1700560862765, 945.0334674976189,
752.877211603942, 582.6179999999995,
1152.0, 1026.1905162926748,
362.1897587359037, 331.2111817082091,
485.44493195098454, 232.81073607429266,
118.73010558798194, 118.73010558798194,
641.287728412984, 108.98319763338179,
106.80230977350088,
133.7321802766326,
0.0, 0.0,
0.0, 0.0,
70.41409090909087, 124.41545454545451,
118.37045454545455, 240.0,
0.0, 0.0,
0.0,
94.68272727272722,
83.01681818181817, 83.01681818181817,
75.86045454545456, 75.86045454545456,
32.79597062197777, 1014.0,
0.0, 69.12409090909085,
0.0012025410138062752, 109.07302361034766,
3.292931608338464, 945.0,
22.312089529472388, 22.312089529472388,
54.18759955738153, 54.18759955738153,
86.6234264543665, 123.8591383343284,
165.15986945297027, 853.5941151729826,
65.92300791736113, 1083.022499986156,
538.2984000000001, 906.7055359846831,
166.26381931274682, 304.8583246953946,
68.89237644835487, 225.52229247529465,
176.85071084262336, 176.85071084262336,
93.18476208988011, 131.21108264656203,
111.78035844493081, 111.78035844493081,
90.18403329253945, 90.18403329253945,
0.0, 134.59227272727276,
0.0, 0.0,
0.0 0.0
], ],
"akku_soc_pro_stunde": [ "akku_soc_pro_stunde": [
80.0, 80.0,
61.060772546061834, 81.05464937533866,
42.12137860666789, 97.72131604200533,
40.878014722863334, 98.83003793156611,
23.18290087405168, 79.89157364488383,
13.89226749676402, 96.07540697821715,
30.55893416343069, 78.84078381044188,
31.036427461650234, 88.04109441344768,
31.104342238066472, 94.50805930440028,
34.40240072662152, 97.80611779295532,
19.405325997784466, 96.7435299231319,
16.23065815773333, 96.7435299231319,
12.00939989910972, 96.7435299231319,
12.00939989910972, 92.81627441349004,
12.00939989910972, 99.48294108015669,
9.786732888090437, 99.48294108015669,
6.050291772387957, 99.48294108015669,
6.050291772387957, 96.49421862836606,
3.429811400487131, 93.87373825646522,
1.0352263453907122, 91.4791532013688,
0.0, 72.53975926197485,
0.0, 70.35781194792527,
3.3403917050174315e-05, 66.91492170697698,
0.09144092700684212, 47.975527767583046,
0.7112211917144087, 48.59530803229061,
2.216432290530563, 50.10051913110676,
4.622638580929631, 51.331355728325214,
9.210412732401027, 33.27368862539725,
11.04160739677217, 14.334919685618749,
25.994340730105503, 1.0715355651189202,
30.612780155459586, 1.7753354997896456,
32.526457279024996, 5.155440452534742,
37.438977024653425, 10.06796019816317,
40.027442638261206, 11.456100036623162,
38.62812309869707, 10.05678049705903,
36.139963881679876, 7.568621280041836,
36.139963881679876, 3.32012782274156,
36.139963881679876 3.32012782274156
], ],
"Electricity_price": [ "Electricity_price": [
0.000228, 0.000228,
@@ -702,37 +702,37 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.625,
1.0,
0.0, 0.0,
0.375, 0.0,
0.0,
0.625, 0.625,
1.0, 0.0,
0.75,
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.75,
0.0,
0.0,
0.625, 0.625,
0.0,
0.0,
0.0,
0.5,
0.875, 0.875,
0.0, 0.75,
0.3, 0.375,
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.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,
@@ -795,53 +795,54 @@
"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": 59897.4, "soc_wh": 59110.8,
"initial_soc_percentage": 5 "initial_soc_percentage": 5
}, },
"start_solution": [ "start_solution": [
0.0,
0.0,
1.0,
2.0, 2.0,
1.0,
1.0,
0.0,
0.0,
0.0,
2.0, 2.0,
1.0, 1.0,
1.0, 1.0,
1.0, 1.0,
0.0,
1.0, 1.0,
2.0,
2.0,
0.0,
0.0,
2.0,
2.0,
1.0,
0.0,
1.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
1.0, 1.0,
2.0, 2.0,
0.0, 0.0,
0.0, 0.0,
0.0,
1.0, 1.0,
1.0, 1.0,
1.0, 1.0,
0.0,
0.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,
0.0, 0.0,
0.0, 0.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
0.0, 0.0,
0.0, 1.0,
0.0, 1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0, 1.0,
1.0, 1.0,
0.0, 0.0,
@@ -853,54 +854,53 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
2.0, 0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
3.0, 3.0,
1.0, 0.0,
4.0, 4.0,
3.0,
6.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,
0.0,
4.0,
0.0,
0.0,
3.0, 3.0,
6.0, 0.0,
3.0, 0.0,
5.0,
0.0, 0.0,
2.0, 2.0,
3.0,
3.0,
5.0, 5.0,
4.0, 4.0,
6.0,
3.0,
0.0,
0.0,
1.0, 1.0,
2.0,
6.0,
5.0, 5.0,
0.0,
3.0,
6.0, 6.0,
3.0,
1.0,
3.0,
2.0,
0.0,
2.0,
2.0,
4.0, 4.0,
5.0,
3.0, 3.0,
1.0, 1.0,
5.0, 10.0
5.0,
3.0,
0.0,
2.0,
29.0
], ],
"washingstart": 39, "washingstart": 20,
"appliance_starts": { "appliance_starts": {
"dishwasher1": [ "dishwasher1": [
"2025-01-16 15:00:00+01:00" "2025-01-15 20:00:00+01:00"
] ]
} }
} }
+294 -294
View File
@@ -15,7 +15,7 @@
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,
@@ -110,23 +110,23 @@
0, 0,
0, 0,
0, 0,
1, 0,
1, 0,
1, 0,
1, 0,
0,
0,
0,
1, 1,
0, 0,
1,
1,
0, 0,
0, 0,
0, 0,
1, 1,
1, 1,
1, 1,
0,
0,
1,
1,
0,
1, 1,
1, 1,
1, 1,
@@ -143,10 +143,10 @@
0, 0,
0, 0,
0, 0,
1,
1,
0, 0,
0, 0,
0,
1,
0 0
], ],
"battery_grid_export_allowed": [], "battery_grid_export_allowed": [],
@@ -161,37 +161,37 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.625,
1.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.625,
0.5,
0.75,
0.375, 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,
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.75,
0.5,
0.5,
0.0, 0.0,
0.5,
0.5,
0.4,
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": [
7953.07, 1053.07,
12103.91, 1063.91,
1320.56, 1320.56,
5272.03, 1132.03,
8063.67, 1163.67,
17059.173961709643, 1176.82,
8116.22, 1216.22,
10763.78, 1103.78,
1129.12, 1129.12,
4490.71, 1178.71,
1050.98, 1050.98,
988.56, 988.56,
912.38, 912.38,
@@ -221,19 +221,19 @@
694.34, 694.34,
608.79, 608.79,
556.31, 556.31,
488.89, 7388.89,
506.91, 6026.91,
804.89, 9084.89,
1141.98, 5281.98,
1056.97, 1056.97,
992.46, 9272.46,
1155.99, 6675.99,
827.01, 6347.01,
1257.98, 1257.98,
1232.67, 6752.67,
3371.26, 6391.26,
3360.88, 7776.88,
1158.03, 3658.0299999999997,
1222.72, 1222.72,
1221.04, 1221.04,
949.99, 949.99,
@@ -242,55 +242,55 @@
592.97 592.97
], ],
"EAuto_SoC_pro_Stunde": [ "EAuto_SoC_pro_Stunde": [
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0, 5.0,
15.925, 15.925,
33.405, 24.665,
33.405, 37.775,
39.96, 44.330000000000005,
50.885000000000005, 44.330000000000005,
68.365, 57.440000000000005,
79.29, 66.18,
94.585, 74.92,
94.585, 74.92,
99.82900000000001, 83.66,
99.82900000000001, 92.4,
99.82900000000001, 99.392,
99.82900000000001, 99.392,
99.82900000000001, 99.392,
99.82900000000001, 99.392,
99.82900000000001, 99.392,
99.82900000000001, 99.392,
99.82900000000001, 99.392
99.82900000000001,
99.82900000000001,
99.82900000000001,
99.82900000000001,
99.82900000000001,
99.82900000000001,
99.82900000000001,
99.82900000000001,
99.82900000000001,
99.82900000000001,
99.82900000000001,
99.82900000000001,
99.82900000000001,
99.82900000000001,
99.82900000000001,
99.82900000000001,
99.82900000000001,
99.82900000000001,
99.82900000000001
], ],
"Einnahmen_Euro_pro_Stunde": [ "Einnahmen_Euro_pro_Stunde": [
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.022582049506752234,
0.0, 0.3039575,
0.0, 0.19320652266312205,
0.0, 0.13346423958241627,
0.0, 0.0692592282596561,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -321,10 +321,10 @@
0.0, 0.0,
0.0 0.0
], ],
"Gesamt_Verluste": 8756.080717524794, "Gesamt_Verluste": 7236.761593008068,
"Gesamtbilanz_Euro": 9.985237573040141, "Gesamtbilanz_Euro": 10.765839318494601,
"Gesamteinnahmen_Euro": 0.0, "Gesamteinnahmen_Euro": 0.7224695400119466,
"Gesamtkosten_Euro": 9.985237573040141, "Gesamtkosten_Euro": 11.488308858506548,
"Home_appliance_wh_per_hour": [ "Home_appliance_wh_per_hour": [
0.0, 0.0,
0.0, 0.0,
@@ -355,9 +355,9 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
2500.0,
2500.0,
0.0, 0.0,
2500.0,
2500.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -396,9 +396,9 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
2500.0,
2500.0,
0.0, 0.0,
2500.0,
2500.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
@@ -408,83 +408,83 @@
] ]
}, },
"Kosten_Euro_pro_Stunde": [ "Kosten_Euro_pro_Stunde": [
0.5980075076051746, 0.10013413727316416,
1.473337992, 0.09007999454232955,
0.11436917344552285,
0.07557452231671152,
0.0, 0.0,
4.55656845588237e-17,
0.001414013162203277,
0.0, 0.0,
0.0,
2.3442290999999997,
0.9428514400845971,
1.762926136273946,
0.05258762370598476, 0.05258762370598476,
0.0, 0.0,
0.0, 0.0,
0.0, 0.29116746986009023,
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.08281196422730584, 0.0,
0.16677339, 0.9964959260000001,
0.264113778992023, 0.5352533899999999,
0.2536463762855701, 1.5452864699999995,
0.16621183799999983,
0.1306329312971816, 0.1306329312971816,
0.07362195915902499, 1.8585251365618425,
0.060401289430882174, 1.1230913228365589,
0.009619897970888898, 0.8820174288094884,
0.07029121023060134, 0.07029121023060134,
4.179128154646605e-17, 0.3006368911258292,
0.04064749825228731, 0.437965956045862,
0.1994538035279033, 1.0551552313933648,
0.013012984677295973, 0.2896168262092554,
0.08357424731947552, 0.08357424731947552,
0.0, 0.17784012884918773,
0.0, 0.19011028252189552,
0.293043269, 0.293043269,
0.214398479, 0.0,
0.16484566 0.16484566
], ],
"Netzbezug_Wh_pro_Stunde": [ "Netzbezug_Wh_pro_Stunde": [
2622.8399456367306, 439.1848126015972,
6660.66, 407.23324838304495,
546.436566868241,
402.20607938643707,
0.0, 0.0,
2.2737367544323206e-13,
6.433180901743754,
0.0, 0.0,
0.0,
11697.75,
4289.587989465865,
7766.194432924872,
175.4675465665157, 175.4675465665157,
0.0, 0.0,
0.0, 0.0,
0.0, 980.6920507244535,
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,
248.38621543882974, 0.0,
506.91, 2988.8900000000003,
799.8600211751151, 1626.9099999999999,
833.8145177040436, 4679.8499999999985,
546.3899999999994,
537.58407941227, 537.58407941227,
322.9033296448464, 8151.426037551941,
273.0618871197205, 5077.266378103792,
45.96224544141853, 4214.130094646385,
374.088399311343, 374.088399311343,
2.2737367544323206e-13, 1635.6740540034234,
202.83182760622412, 2185.4588625043016,
907.4331370696236, 4800.5242556568,
57.32592368852852, 1275.8450493799796,
278.85968408233407, 278.85968408233407,
0.0, 556.6201215937018,
0.0, 617.0408390843736,
987.01, 987.01,
733.99, 0.0,
592.97 592.97
], ],
"Netzeinspeisung_Wh_pro_Stunde": [ "Netzeinspeisung_Wh_pro_Stunde": [
@@ -492,11 +492,11 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 322.60070723931767,
0.0, 4342.25,
0.0, 2760.093180901744,
0.0, 1906.6319940345184,
0.0, 989.4175465665157,
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": [
945.0059934764078, 37.96737751219166,
1152.0, 46.38878980596536,
114.42806532440363, 39.91398802418894,
768.1700560862765, 51.82392952637247,
752.877211603942, 543.9059151312817,
1152.0, 0.0,
362.1897587359037, 0.0,
485.44493195098454, 7.548005965483245,
118.73010558798194, 0.0,
641.287728412984, 108.98319763338179,
106.80230977350088, 106.80230977350088,
133.7321802766326, 0.0014460869344160802,
0.0, 0.0,
0.0, 0.0,
70.41409090909087, 70.41409090909087,
118.37045454545455, 118.37045454545455,
0.0, 94.68272727272722,
83.01681818181817, 83.01681818181817,
75.86045454545456, 75.86045454545456,
32.79597062197777, 945.0,
0.0, 876.0,
0.0012025410138062752, 1014.0,
3.292931608338464, 807.0,
22.312089529472388, 22.312089529472388,
54.18759955738153, 414.01032450623296,
86.6234264543665, 276.72796537245506,
165.15986945297027, 278.9400113575662,
65.92300791736113, 65.92300791736113,
538.2984000000001, 348.1792864804107,
166.26381931274682, 317.77906350051614,
68.89237644835487, 226.9433106788162,
176.85071084262336, 23.073005925597585,
93.18476208988011, 93.18476208988011,
111.78035844493081, 35.877614591244196,
90.18403329253945, 6.04210069012484,
0.0,
0.0, 0.0,
100.08954545454549,
0.0 0.0
], ],
"akku_soc_pro_stunde": [ "akku_soc_pro_stunde": [
80.0, 80.0,
61.060772546061834, 81.05464937533866,
42.12137860666789, 82.3432268699488,
40.878014722863334, 83.45194875950959,
23.18290087405168, 84.8915023574644,
13.89226749676402, 100.0,
30.55893416343069, 100.0,
31.036427461650234, 100.0,
31.104342238066472, 100.0,
34.40240072662152, 100.0,
19.405325997784466, 98.93741213017658,
16.23065815773333, 95.76274429012544,
12.00939989910972, 95.76278445920696,
12.00939989910972, 95.76278445920696,
12.00939989910972, 95.76278445920696,
9.786732888090437, 93.54011744818767,
6.050291772387957, 89.80367633248518,
6.050291772387957, 86.81495388069455,
3.429811400487131, 84.19447350879372,
1.0352263453907122, 81.79988845369729,
0.0, 62.860494514303355,
0.0, 43.92110057490942,
3.3403917050174315e-05, 24.981706635515476,
0.09144092700684212, 6.042312696121536,
0.7112211917144087, 6.662092960829104,
2.216432290530563, 6.662379752668908,
4.622638580929631, 6.682601013014882,
9.210412732401027, 6.764267995169498,
11.04160739677217, 8.595462659540642,
25.994340730105503, 10.600442839552054,
30.612780155459586, 11.760972381233058,
32.526457279024996, 11.931619900089059,
37.438977024653425, 12.57253673135566,
40.027442638261206, 15.161002344963439,
38.62812309869707, 16.157602750275778,
36.139963881679876, 16.325438880557027,
36.139963881679876, 16.325438880557027,
36.139963881679876 13.16604666292617
], ],
"Electricity_price": [ "Electricity_price": [
0.000228, 0.000228,
@@ -702,37 +702,37 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.625,
1.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.625,
0.5,
0.75,
0.375, 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,
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.75,
0.5,
0.5,
0.0, 0.0,
0.5,
0.5,
0.4,
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": 59897.4, "soc_wh": 59635.2,
"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,
2.0,
1.0,
1.0,
1.0,
1.0,
1.0,
2.0, 2.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,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.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,
1.0, 1.0,
1.0, 1.0,
@@ -842,7 +842,9 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
1.0, 0.0,
0.0,
0.0,
1.0, 1.0,
0.0, 0.0,
0.0, 0.0,
@@ -853,54 +855,52 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
2.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,
3.0, 3.0,
1.0, 2.0,
4.0, 4.0,
3.0,
6.0,
0.0,
1.0, 1.0,
3.0,
6.0,
3.0,
5.0,
0.0, 0.0,
2.0,
3.0,
3.0,
5.0,
4.0, 4.0,
6.0, 2.0,
3.0,
0.0,
0.0,
1.0,
5.0,
0.0,
3.0,
6.0,
3.0,
1.0,
3.0,
2.0, 2.0,
0.0, 0.0,
2.0, 2.0,
2.0, 2.0,
4.0, 4.0,
3.0,
1.0,
5.0,
5.0,
3.0,
0.0, 0.0,
3.0,
2.0, 2.0,
29.0 0.0,
5.0,
2.0,
5.0,
30.0
], ],
"washingstart": 39, "washingstart": 40,
"appliance_starts": { "appliance_starts": {
"dishwasher1": [ "dishwasher1": [
"2025-01-16 15:00:00+01:00" "2025-01-16 16:00:00+01:00"
] ]
} }
} }