mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-08-31 12:46:38 +00:00
Improve genetic optimizer convergence
This commit is contained in:
+14
-4
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}.")
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user