From f1af9e6c701d8899450aacaa94ba02071b032dc6 Mon Sep 17 00:00:00 2001 From: Jostein Solaas Date: Mon, 13 Nov 2023 14:02:34 +0100 Subject: [PATCH] refactor: use PriorityOptimizer outside ConsumerSystem ConsumerSystem should not be restricted to only work with priorities. Trying to strip ConsumerSystem down to the essentials, although this leads to a bit more setup in ecalc.py --- src/libecalc/common/priority_optimizer.py | 6 +- .../core/consumers/consumer_system.py | 91 ++++++++----------- src/libecalc/core/ecalc.py | 31 ++++++- 3 files changed, 71 insertions(+), 57 deletions(-) diff --git a/src/libecalc/common/priority_optimizer.py b/src/libecalc/common/priority_optimizer.py index 3d4c2d7889..f9e0696ab3 100644 --- a/src/libecalc/common/priority_optimizer.py +++ b/src/libecalc/common/priority_optimizer.py @@ -92,14 +92,16 @@ def optimize( We process each timestep separately. + It will default to the last priority if all settings fails + Args: timesteps: The timesteps that we want to figure out which priority to use for. priorities: Dict of priorities, key is used to identify the priority in the results. evaluator: The evaluator function gives a list of results back, each result with its own unique id. Returns: - PriorityOptimizerResult: result containing priorities used and a map of the calculated results. The keys of - the results map are the timestep used, the priority index and the id of the result. + PriorityOptimizerResult: result containing priorities used and a list of the results merged on priorities + used, """ is_valid = TimeSeriesBoolean(timesteps=timesteps, values=[False] * len(timesteps), unit=Unit.NONE) diff --git a/src/libecalc/core/consumers/consumer_system.py b/src/libecalc/core/consumers/consumer_system.py index 927a1a15be..e5a56615f5 100644 --- a/src/libecalc/core/consumers/consumer_system.py +++ b/src/libecalc/core/consumers/consumer_system.py @@ -7,17 +7,15 @@ import networkx as nx import numpy as np -from libecalc.common.priorities import Priorities -from libecalc.common.priority_optimizer import EvaluatorResult, PriorityOptimizer +from libecalc.common.priority_optimizer import EvaluatorResult from libecalc.common.stream_conditions import StreamConditions from libecalc.common.utils.rates import ( TimeSeriesInt, ) -from libecalc.core.consumers.base import BaseConsumer from libecalc.core.consumers.compressor import Compressor from libecalc.core.consumers.factory import create_consumer from libecalc.core.consumers.pump import Pump -from libecalc.core.result import ConsumerSystemResult, EcalcModelResult +from libecalc.core.result import ComponentResult, ConsumerSystemResult, EcalcModelResult from libecalc.dto.components import ConsumerComponent Consumer = TypeVar("Consumer", bound=Union[Compressor, Pump]) @@ -33,7 +31,7 @@ class SystemComponentConditions(Protocol): crossover: List[Crossover] -class ConsumerSystem(BaseConsumer): +class ConsumerSystem: """ A system of possibly interdependent consumers and or other consumer systems. @@ -100,62 +98,51 @@ def _get_stream_conditions_adjusted_for_crossover( return adjusted_stream_conditions - def evaluate( + def evaluator( + self, timestep: datetime, system_stream_condition: Dict[str, List[StreamConditions]] + ) -> List[EvaluatorResult]: + """ + Function to evaluate the consumers in the system given stream conditions + + Args: + timestep: + system_stream_condition: + + Returns: list of evaluator results + + """ + stream_conditions_for_timestep = { + component_id: [stream_condition.get_subset_for_timestep(timestep) for stream_condition in stream_conditions] + for component_id, stream_conditions in system_stream_condition.items() + } + adjusted_system_stream_conditions = self._get_stream_conditions_adjusted_for_crossover( + stream_conditions=stream_conditions_for_timestep, + ) + consumer_results_for_priority = [ + consumer.evaluate(adjusted_system_stream_conditions[consumer.id]) for consumer in self._consumers + ] + return [ + EvaluatorResult( + id=consumer_result_for_priority.component_result.id, + result=consumer_result_for_priority, + is_valid=consumer_result_for_priority.component_result.is_valid, + ) + for consumer_result_for_priority in consumer_results_for_priority + ] + + def get_system_result( self, - timesteps: List[datetime], - system_stream_conditions_priorities: Priorities[Dict[str, List[StreamConditions]]], + consumer_results: List[ComponentResult], + operational_settings_used: TimeSeriesInt, ) -> EcalcModelResult: """ - Evaluating a consumer system that may be composed of both consumers and other consumer systems. It will default - to the last operational setting if all settings fails. + Get system result based on consumer results Notes: - We use 1-indexed operational settings output. We should consider changing this to default 0-index, and only convert when presenting results to the end-user. """ - optimizer = PriorityOptimizer() - - def evaluator( - timestep: datetime, system_stream_condition: Dict[str, List[StreamConditions]] - ) -> List[EvaluatorResult]: - stream_conditions_for_timestep = { - component_id: [ - stream_condition.get_subset_for_timestep(timestep) for stream_condition in stream_conditions - ] - for component_id, stream_conditions in system_stream_condition.items() - } - adjusted_system_stream_conditions = self._get_stream_conditions_adjusted_for_crossover( - stream_conditions=stream_conditions_for_timestep, - ) - consumer_results_for_priority = [ - consumer.evaluate(adjusted_system_stream_conditions[consumer.id]) for consumer in self._consumers - ] - return [ - EvaluatorResult( - id=consumer_result_for_priority.component_result.id, - result=consumer_result_for_priority, - is_valid=consumer_result_for_priority.component_result.is_valid, - ) - for consumer_result_for_priority in consumer_results_for_priority - ] - - optimizer_result = optimizer.optimize( - timesteps=timesteps, priorities=system_stream_conditions_priorities, evaluator=evaluator - ) - - consumer_results = optimizer_result.priority_results - - # Convert to legacy compatible operational_settings_used - priorities_to_int_map = { - priority_name: index + 1 for index, priority_name in enumerate(system_stream_conditions_priorities.keys()) - } - operational_settings_used = TimeSeriesInt( - timesteps=optimizer_result.priorities_used.timesteps, - values=[priorities_to_int_map[priority_name] for priority_name in optimizer_result.priorities_used.values], - unit=optimizer_result.priorities_used.unit, - ) - consumer_system_result = ConsumerSystemResult( id=self.id, is_valid=reduce( diff --git a/src/libecalc/core/ecalc.py b/src/libecalc/core/ecalc.py index f27c86f802..b4e31d40af 100644 --- a/src/libecalc/core/ecalc.py +++ b/src/libecalc/core/ecalc.py @@ -5,6 +5,8 @@ import libecalc.dto.components from libecalc import dto from libecalc.common.list.list_utils import elementwise_sum +from libecalc.common.priority_optimizer import PriorityOptimizer +from libecalc.common.utils.rates import TimeSeriesInt from libecalc.core.consumers.consumer_system import ConsumerSystem from libecalc.core.consumers.direct_emitter import DirectEmitter from libecalc.core.consumers.generator_set import Genset @@ -58,14 +60,37 @@ def evaluate_energy_usage(self, variables_map: dto.VariablesMap) -> Dict[str, Ec consumers=component_dto.consumers, component_conditions=component_dto.component_conditions, ) + evaluated_stream_conditions = component_dto.evaluate_stream_conditions( variables_map=variables_map, ) - system_result = consumer_system.evaluate( - timesteps=variables_map.time_vector, system_stream_conditions_priorities=evaluated_stream_conditions + optimizer = PriorityOptimizer() + + optimizer_result = optimizer.optimize( + timesteps=variables_map.time_vector, + priorities=evaluated_stream_conditions, + evaluator=consumer_system.evaluator, + ) + + # Convert to legacy compatible operational_settings_used + priorities_to_int_map = { + priority_name: index + 1 for index, priority_name in enumerate(evaluated_stream_conditions.keys()) + } + operational_settings_used = TimeSeriesInt( + timesteps=optimizer_result.priorities_used.timesteps, + values=[ + priorities_to_int_map[priority_name] + for priority_name in optimizer_result.priorities_used.values + ], + unit=optimizer_result.priorities_used.unit, + ) + + system_result = consumer_system.get_system_result( + consumer_results=optimizer_result.priority_results, + operational_settings_used=operational_settings_used, ) consumer_results[component_dto.id] = system_result - for consumer_result in system_result.sub_components: + for consumer_result in optimizer_result.priority_results: consumer_results[consumer_result.id] = EcalcModelResult( component_result=consumer_result, sub_components=[],