From 6c067071d2c0c115d31298f85f18cc2db1829e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Petter=20L=C3=B8d=C3=B8en?= Date: Thu, 26 Mar 2026 09:50:43 +0100 Subject: [PATCH] refactor: add get_configuration to Solution refactor: extract Configurations and SimulationUnitId from ProcessRunner to break import cycle refactor: introduce OpertaingConfiguration as union of speed-, choke-, and recircconfigurations --- .../anti_surge/anti_surge_strategy.py | 2 +- .../process_solver/anti_surge/common_asv.py | 3 +- .../anti_surge/individual_asv.py | 3 +- .../process/process_solver/configuration.py | 49 +++++++++++++++++++ .../process_solver/multi_pressure_solver.py | 14 ++---- .../process_solver/outlet_pressure_solver.py | 3 +- .../pressure_control/common_asv.py | 3 +- .../pressure_control/downstream_choke.py | 3 +- .../pressure_control/individual_asv.py | 3 +- .../pressure_control_strategy.py | 2 +- .../pressure_control/upstream_choke.py | 3 +- .../process/process_solver/process_runner.py | 28 ++--------- .../process_solver/process_system_runner.py | 3 +- .../domain/process/process_solver/solver.py | 13 ++++- .../solvers/downstream_choke_solver.py | 7 +-- .../solvers/recirculation_solver.py | 10 +--- .../process_solver/solvers/speed_solver.py | 19 +------ ...t_individual_asv_solver_vs_legacy_train.py | 2 +- 18 files changed, 90 insertions(+), 80 deletions(-) create mode 100644 src/libecalc/domain/process/process_solver/configuration.py diff --git a/src/libecalc/domain/process/process_solver/anti_surge/anti_surge_strategy.py b/src/libecalc/domain/process/process_solver/anti_surge/anti_surge_strategy.py index 182c759f2b..3d59d92c5f 100644 --- a/src/libecalc/domain/process/process_solver/anti_surge/anti_surge_strategy.py +++ b/src/libecalc/domain/process/process_solver/anti_surge/anti_surge_strategy.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod from collections.abc import Sequence -from libecalc.domain.process.process_solver.process_runner import Configuration +from libecalc.domain.process.process_solver.configuration import Configuration from libecalc.domain.process.process_solver.solver import Solution from libecalc.domain.process.process_solver.solvers.recirculation_solver import RecirculationConfiguration from libecalc.domain.process.value_objects.fluid_stream import FluidStream diff --git a/src/libecalc/domain/process/process_solver/anti_surge/common_asv.py b/src/libecalc/domain/process/process_solver/anti_surge/common_asv.py index b8022048cf..c7acbce9de 100644 --- a/src/libecalc/domain/process/process_solver/anti_surge/common_asv.py +++ b/src/libecalc/domain/process/process_solver/anti_surge/common_asv.py @@ -2,7 +2,8 @@ from libecalc.domain.process.entities.process_units.compressor import Compressor from libecalc.domain.process.process_solver.anti_surge.anti_surge_strategy import AntiSurgeStrategy -from libecalc.domain.process.process_solver.process_runner import Configuration, ProcessRunner +from libecalc.domain.process.process_solver.configuration import Configuration +from libecalc.domain.process.process_solver.process_runner import ProcessRunner from libecalc.domain.process.process_solver.search_strategies import BinarySearchStrategy, RootFindingStrategy from libecalc.domain.process.process_solver.solver import Solution from libecalc.domain.process.process_solver.solvers.recirculation_solver import ( diff --git a/src/libecalc/domain/process/process_solver/anti_surge/individual_asv.py b/src/libecalc/domain/process/process_solver/anti_surge/individual_asv.py index 9f7c16b274..bb00a22766 100644 --- a/src/libecalc/domain/process/process_solver/anti_surge/individual_asv.py +++ b/src/libecalc/domain/process/process_solver/anti_surge/individual_asv.py @@ -3,7 +3,8 @@ from libecalc.domain.process.entities.process_units.compressor import Compressor from libecalc.domain.process.process_solver.anti_surge.anti_surge_strategy import AntiSurgeStrategy -from libecalc.domain.process.process_solver.process_runner import Configuration, ProcessRunner +from libecalc.domain.process.process_solver.configuration import Configuration +from libecalc.domain.process.process_solver.process_runner import ProcessRunner from libecalc.domain.process.process_solver.solver import Solution from libecalc.domain.process.process_solver.solvers.recirculation_solver import RecirculationConfiguration from libecalc.domain.process.process_system.process_error import RateTooHighError diff --git a/src/libecalc/domain/process/process_solver/configuration.py b/src/libecalc/domain/process/process_solver/configuration.py new file mode 100644 index 0000000000..9e257b3d6b --- /dev/null +++ b/src/libecalc/domain/process/process_solver/configuration.py @@ -0,0 +1,49 @@ +from dataclasses import dataclass +from functools import total_ordering +from typing import Generic, TypeVar + +from libecalc.domain.process.entities.shaft.shaft import ShaftId +from libecalc.domain.process.process_system.process_system import ProcessSystemId +from libecalc.domain.process.process_system.process_unit import ProcessUnitId + +T_co = TypeVar("T_co", covariant=True) + +SimulationUnitId = ProcessUnitId | ProcessSystemId | ShaftId + + +@dataclass(frozen=True) +class Configuration(Generic[T_co]): + simulation_unit_id: SimulationUnitId + value: T_co + + +@total_ordering +@dataclass +class SpeedConfiguration: + speed: float + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SpeedConfiguration): + return NotImplemented + return self.speed == other.speed + + def __lt__(self, other: object) -> bool: + if not isinstance(other, SpeedConfiguration): + return NotImplemented + return self.speed < other.speed + + +@dataclass +class ChokeConfiguration: + delta_pressure: float + + +@dataclass +class RecirculationConfiguration: + recirculation_rate: float + + def __post_init__(self): + self.recirculation_rate = float(self.recirculation_rate) + + +OperatingConfiguration = SpeedConfiguration | ChokeConfiguration | RecirculationConfiguration diff --git a/src/libecalc/domain/process/process_solver/multi_pressure_solver.py b/src/libecalc/domain/process/process_solver/multi_pressure_solver.py index 70e8ac7ac2..9844944d06 100644 --- a/src/libecalc/domain/process/process_solver/multi_pressure_solver.py +++ b/src/libecalc/domain/process/process_solver/multi_pressure_solver.py @@ -2,13 +2,13 @@ from typing import Final from libecalc.domain.component_validation_error import DomainValidationException +from libecalc.domain.process.process_solver.configuration import Configuration from libecalc.domain.process.process_solver.float_constraint import FloatConstraint from libecalc.domain.process.process_solver.outlet_pressure_solver import OutletPressureSolver from libecalc.domain.process.process_solver.pressure_control.downstream_choke import ( DownstreamChokePressureControlStrategy, ) from libecalc.domain.process.process_solver.pressure_control.upstream_choke import UpstreamChokePressureControlStrategy -from libecalc.domain.process.process_solver.process_runner import Configuration from libecalc.domain.process.process_solver.solver import Solution from libecalc.domain.process.process_solver.solvers.speed_solver import SpeedConfiguration from libecalc.domain.process.value_objects.fluid_stream import FluidStream @@ -60,14 +60,6 @@ def _validate_pressure_control_placement(segments: list[OutletPressureSolver]) - f"(segment {i} of {len(segments)})." ) - @staticmethod - def _extract_speed_configuration(solution: Solution[Sequence[Configuration]]) -> SpeedConfiguration: - """Find speed configuration in a sequence of configurations.""" - for config in solution.configuration: - if isinstance(config.value, SpeedConfiguration): - return config.value - raise DomainValidationException("No SpeedConfiguration found in solution.") - def find_solution( self, pressure_targets: list[FloatConstraint], @@ -83,7 +75,9 @@ def find_solution( current_inlet = inlet_stream for segment, target in zip(self._segments, pressure_targets): solution_for_segment = segment.find_solution(pressure_constraint=target, inlet_stream=current_inlet) - speed_configurations.append(self._extract_speed_configuration(solution_for_segment)) + speed_configuration = solution_for_segment.get_configuration(self._shaft_id) + assert isinstance(speed_configuration, SpeedConfiguration) + speed_configurations.append(speed_configuration) segment.runner.apply_configurations(solution_for_segment.configuration) current_inlet = segment.runner.run(inlet_stream=current_inlet) diff --git a/src/libecalc/domain/process/process_solver/outlet_pressure_solver.py b/src/libecalc/domain/process/process_solver/outlet_pressure_solver.py index 8ef0928afc..cb3336ef13 100644 --- a/src/libecalc/domain/process/process_solver/outlet_pressure_solver.py +++ b/src/libecalc/domain/process/process_solver/outlet_pressure_solver.py @@ -4,9 +4,10 @@ from libecalc.domain.process.entities.shaft.shaft import ShaftId from libecalc.domain.process.process_solver.anti_surge.anti_surge_strategy import AntiSurgeStrategy from libecalc.domain.process.process_solver.boundary import Boundary +from libecalc.domain.process.process_solver.configuration import Configuration from libecalc.domain.process.process_solver.float_constraint import FloatConstraint from libecalc.domain.process.process_solver.pressure_control.pressure_control_strategy import PressureControlStrategy -from libecalc.domain.process.process_solver.process_runner import Configuration, ProcessRunner +from libecalc.domain.process.process_solver.process_runner import ProcessRunner from libecalc.domain.process.process_solver.search_strategies import BinarySearchStrategy, RootFindingStrategy from libecalc.domain.process.process_solver.solver import Solution from libecalc.domain.process.process_solver.solvers.recirculation_solver import ( diff --git a/src/libecalc/domain/process/process_solver/pressure_control/common_asv.py b/src/libecalc/domain/process/process_solver/pressure_control/common_asv.py index b8abe855b5..653cf07193 100644 --- a/src/libecalc/domain/process/process_solver/pressure_control/common_asv.py +++ b/src/libecalc/domain/process/process_solver/pressure_control/common_asv.py @@ -1,9 +1,10 @@ from collections.abc import Sequence from libecalc.domain.process.entities.process_units.compressor import Compressor +from libecalc.domain.process.process_solver.configuration import Configuration from libecalc.domain.process.process_solver.float_constraint import FloatConstraint from libecalc.domain.process.process_solver.pressure_control.pressure_control_strategy import PressureControlStrategy -from libecalc.domain.process.process_solver.process_runner import Configuration, ProcessRunner +from libecalc.domain.process.process_solver.process_runner import ProcessRunner from libecalc.domain.process.process_solver.search_strategies import BinarySearchStrategy, RootFindingStrategy from libecalc.domain.process.process_solver.solver import Solution from libecalc.domain.process.process_solver.solvers.downstream_choke_solver import ChokeConfiguration diff --git a/src/libecalc/domain/process/process_solver/pressure_control/downstream_choke.py b/src/libecalc/domain/process/process_solver/pressure_control/downstream_choke.py index 6e30131acf..0d9af57904 100644 --- a/src/libecalc/domain/process/process_solver/pressure_control/downstream_choke.py +++ b/src/libecalc/domain/process/process_solver/pressure_control/downstream_choke.py @@ -1,8 +1,9 @@ from collections.abc import Sequence +from libecalc.domain.process.process_solver.configuration import Configuration from libecalc.domain.process.process_solver.float_constraint import FloatConstraint from libecalc.domain.process.process_solver.pressure_control.pressure_control_strategy import PressureControlStrategy -from libecalc.domain.process.process_solver.process_runner import Configuration, ProcessRunner +from libecalc.domain.process.process_solver.process_runner import ProcessRunner from libecalc.domain.process.process_solver.solver import Solution from libecalc.domain.process.process_solver.solvers.downstream_choke_solver import ( ChokeConfiguration, diff --git a/src/libecalc/domain/process/process_solver/pressure_control/individual_asv.py b/src/libecalc/domain/process/process_solver/pressure_control/individual_asv.py index 406594216e..cd8c0b5673 100644 --- a/src/libecalc/domain/process/process_solver/pressure_control/individual_asv.py +++ b/src/libecalc/domain/process/process_solver/pressure_control/individual_asv.py @@ -2,9 +2,10 @@ from libecalc.domain.process.compressor.core.train.utils.numeric_methods import find_root from libecalc.domain.process.entities.process_units.compressor import Compressor +from libecalc.domain.process.process_solver.configuration import Configuration from libecalc.domain.process.process_solver.float_constraint import FloatConstraint from libecalc.domain.process.process_solver.pressure_control.pressure_control_strategy import PressureControlStrategy -from libecalc.domain.process.process_solver.process_runner import Configuration, ProcessRunner +from libecalc.domain.process.process_solver.process_runner import ProcessRunner from libecalc.domain.process.process_solver.search_strategies import BinarySearchStrategy, RootFindingStrategy from libecalc.domain.process.process_solver.solver import Solution from libecalc.domain.process.process_solver.solvers.downstream_choke_solver import ChokeConfiguration diff --git a/src/libecalc/domain/process/process_solver/pressure_control/pressure_control_strategy.py b/src/libecalc/domain/process/process_solver/pressure_control/pressure_control_strategy.py index 585c5d5c58..4edec1d95d 100644 --- a/src/libecalc/domain/process/process_solver/pressure_control/pressure_control_strategy.py +++ b/src/libecalc/domain/process/process_solver/pressure_control/pressure_control_strategy.py @@ -1,8 +1,8 @@ from abc import ABC, abstractmethod from collections.abc import Sequence +from libecalc.domain.process.process_solver.configuration import Configuration from libecalc.domain.process.process_solver.float_constraint import FloatConstraint -from libecalc.domain.process.process_solver.process_runner import Configuration from libecalc.domain.process.process_solver.solver import Solution from libecalc.domain.process.process_solver.solvers.downstream_choke_solver import ChokeConfiguration from libecalc.domain.process.process_solver.solvers.recirculation_solver import RecirculationConfiguration diff --git a/src/libecalc/domain/process/process_solver/pressure_control/upstream_choke.py b/src/libecalc/domain/process/process_solver/pressure_control/upstream_choke.py index 4131534dac..41ef9e2d5a 100644 --- a/src/libecalc/domain/process/process_solver/pressure_control/upstream_choke.py +++ b/src/libecalc/domain/process/process_solver/pressure_control/upstream_choke.py @@ -2,9 +2,10 @@ from libecalc.domain.process.compressor.core.train.utils.common import PRESSURE_CALCULATION_TOLERANCE from libecalc.domain.process.process_solver.boundary import Boundary +from libecalc.domain.process.process_solver.configuration import Configuration from libecalc.domain.process.process_solver.float_constraint import FloatConstraint from libecalc.domain.process.process_solver.pressure_control.pressure_control_strategy import PressureControlStrategy -from libecalc.domain.process.process_solver.process_runner import Configuration, ProcessRunner +from libecalc.domain.process.process_solver.process_runner import ProcessRunner from libecalc.domain.process.process_solver.search_strategies import RootFindingStrategy from libecalc.domain.process.process_solver.solver import Solution from libecalc.domain.process.process_solver.solvers.downstream_choke_solver import ChokeConfiguration diff --git a/src/libecalc/domain/process/process_solver/process_runner.py b/src/libecalc/domain/process/process_solver/process_runner.py index 260e07031e..1c894c0609 100644 --- a/src/libecalc/domain/process/process_solver/process_runner.py +++ b/src/libecalc/domain/process/process_solver/process_runner.py @@ -1,39 +1,17 @@ import abc from collections.abc import Sequence -from dataclasses import dataclass -from typing import Generic, TypeVar -from libecalc.domain.process.entities.shaft.shaft import ShaftId -from libecalc.domain.process.process_solver.solvers.downstream_choke_solver import ChokeConfiguration -from libecalc.domain.process.process_solver.solvers.recirculation_solver import RecirculationConfiguration -from libecalc.domain.process.process_solver.solvers.speed_solver import SpeedConfiguration -from libecalc.domain.process.process_system.process_system import ProcessSystemId -from libecalc.domain.process.process_system.process_unit import ProcessUnitId +from libecalc.domain.process.process_solver.configuration import Configuration, OperatingConfiguration, SimulationUnitId from libecalc.domain.process.value_objects.fluid_stream import FluidStream -T_co = TypeVar("T_co", covariant=True) - -SimulationUnitId = ProcessUnitId | ProcessSystemId | ShaftId - - -@dataclass(frozen=True) -class Configuration(Generic[T_co]): - simulation_unit_id: SimulationUnitId - value: T_co - class ProcessRunner(abc.ABC): @abc.abstractmethod - def apply_configuration( - self, configuration: Configuration[ChokeConfiguration | RecirculationConfiguration | SpeedConfiguration] - ): + def apply_configuration(self, configuration: Configuration[OperatingConfiguration]): """Apply the given configuration to the process system.""" ... - def apply_configurations( - self, - configurations: Sequence[Configuration[ChokeConfiguration | RecirculationConfiguration | SpeedConfiguration]], - ): + def apply_configurations(self, configurations: Sequence[Configuration[OperatingConfiguration]]): """Apply the given configurations to the process system.""" for configuration in configurations: self.apply_configuration(configuration) diff --git a/src/libecalc/domain/process/process_solver/process_system_runner.py b/src/libecalc/domain/process/process_solver/process_system_runner.py index e418a3bddf..2b1a47a010 100644 --- a/src/libecalc/domain/process/process_solver/process_system_runner.py +++ b/src/libecalc/domain/process/process_solver/process_system_runner.py @@ -4,7 +4,8 @@ from libecalc.domain.process.entities.process_units.recirculation_loop import RecirculationLoop from libecalc.domain.process.entities.shaft import Shaft from libecalc.domain.process.entities.shaft.shaft import ShaftId -from libecalc.domain.process.process_solver.process_runner import Configuration, ProcessRunner, SimulationUnitId +from libecalc.domain.process.process_solver.configuration import Configuration, SimulationUnitId +from libecalc.domain.process.process_solver.process_runner import ProcessRunner from libecalc.domain.process.process_solver.solvers.downstream_choke_solver import ChokeConfiguration from libecalc.domain.process.process_solver.solvers.recirculation_solver import RecirculationConfiguration from libecalc.domain.process.process_solver.solvers.speed_solver import SpeedConfiguration diff --git a/src/libecalc/domain/process/process_solver/solver.py b/src/libecalc/domain/process/process_solver/solver.py index 99c9a17d4b..d5831f57ed 100644 --- a/src/libecalc/domain/process/process_solver/solver.py +++ b/src/libecalc/domain/process/process_solver/solver.py @@ -1,8 +1,9 @@ import abc -from collections.abc import Callable +from collections.abc import Callable, Sequence from dataclasses import dataclass from typing import Generic, TypeVar +from libecalc.domain.process.process_solver.configuration import Configuration, OperatingConfiguration, SimulationUnitId from libecalc.domain.process.value_objects.fluid_stream import FluidStream TConfiguration = TypeVar("TConfiguration") @@ -13,6 +14,16 @@ class Solution(Generic[TConfiguration]): success: bool configuration: TConfiguration + def get_configuration( + self: "Solution[Sequence[Configuration[OperatingConfiguration]]]", + unit_id: SimulationUnitId, + ) -> OperatingConfiguration: + """Find a configuration value by unit ID.""" + for config in self.configuration: + if config.simulation_unit_id == unit_id: + return config.value # type: ignore[return-value] + raise ValueError(f"No configuration found for unit {unit_id}.") + class Solver(abc.ABC, Generic[TConfiguration]): @abc.abstractmethod diff --git a/src/libecalc/domain/process/process_solver/solvers/downstream_choke_solver.py b/src/libecalc/domain/process/process_solver/solvers/downstream_choke_solver.py index 807cef709d..01460f56f4 100644 --- a/src/libecalc/domain/process/process_solver/solvers/downstream_choke_solver.py +++ b/src/libecalc/domain/process/process_solver/solvers/downstream_choke_solver.py @@ -1,15 +1,10 @@ from collections.abc import Callable -from dataclasses import dataclass +from libecalc.domain.process.process_solver.configuration import ChokeConfiguration from libecalc.domain.process.process_solver.solver import Solution, Solver from libecalc.domain.process.value_objects.fluid_stream import FluidStream -@dataclass -class ChokeConfiguration: - delta_pressure: float - - class DownstreamChokeSolver(Solver[ChokeConfiguration]): def __init__(self, target_pressure: float): self._target_pressure = target_pressure diff --git a/src/libecalc/domain/process/process_solver/solvers/recirculation_solver.py b/src/libecalc/domain/process/process_solver/solvers/recirculation_solver.py index e942cef94e..848b334fbd 100644 --- a/src/libecalc/domain/process/process_solver/solvers/recirculation_solver.py +++ b/src/libecalc/domain/process/process_solver/solvers/recirculation_solver.py @@ -1,8 +1,8 @@ from collections.abc import Callable -from dataclasses import dataclass from typing import Literal from libecalc.domain.process.process_solver.boundary import Boundary +from libecalc.domain.process.process_solver.configuration import RecirculationConfiguration from libecalc.domain.process.process_solver.float_constraint import FloatConstraint from libecalc.domain.process.process_solver.search_strategies import RootFindingStrategy, SearchStrategy from libecalc.domain.process.process_solver.solver import Solution, Solver @@ -10,14 +10,6 @@ from libecalc.domain.process.value_objects.fluid_stream import FluidStream -@dataclass -class RecirculationConfiguration: - recirculation_rate: float - - def __post_init__(self): - self.recirculation_rate = float(self.recirculation_rate) - - class RecirculationSolver(Solver): def __init__( self, diff --git a/src/libecalc/domain/process/process_solver/solvers/speed_solver.py b/src/libecalc/domain/process/process_solver/solvers/speed_solver.py index 02f897b231..747abfb984 100644 --- a/src/libecalc/domain/process/process_solver/solvers/speed_solver.py +++ b/src/libecalc/domain/process/process_solver/solvers/speed_solver.py @@ -1,9 +1,8 @@ import logging from collections.abc import Callable -from dataclasses import dataclass -from functools import total_ordering from libecalc.domain.process.process_solver.boundary import Boundary +from libecalc.domain.process.process_solver.configuration import SpeedConfiguration from libecalc.domain.process.process_solver.search_strategies import RootFindingStrategy, SearchStrategy from libecalc.domain.process.process_solver.solver import Solution, Solver from libecalc.domain.process.process_system.process_error import ProcessError, RateTooHighError, RateTooLowError @@ -12,22 +11,6 @@ logger = logging.getLogger(__name__) -@total_ordering -@dataclass -class SpeedConfiguration: - speed: float - - def __eq__(self, other: object) -> bool: - if not isinstance(other, SpeedConfiguration): - return NotImplemented - return self.speed == other.speed - - def __lt__(self, other: object) -> bool: - if not isinstance(other, SpeedConfiguration): - return NotImplemented - return self.speed < other.speed - - class SpeedSolver(Solver[SpeedConfiguration]): def __init__( self, diff --git a/tests/libecalc/domain/process/process_solver/test_individual_asv_solver_vs_legacy_train.py b/tests/libecalc/domain/process/process_solver/test_individual_asv_solver_vs_legacy_train.py index 26835e1b46..9a74b093d1 100644 --- a/tests/libecalc/domain/process/process_solver/test_individual_asv_solver_vs_legacy_train.py +++ b/tests/libecalc/domain/process/process_solver/test_individual_asv_solver_vs_legacy_train.py @@ -4,8 +4,8 @@ from libecalc.domain.process.compressor.core.train.train_evaluation_input import CompressorTrainEvaluationInput from libecalc.domain.process.entities.shaft import VariableSpeedShaft from libecalc.domain.process.process_solver.boundary import Boundary +from libecalc.domain.process.process_solver.configuration import Configuration from libecalc.domain.process.process_solver.float_constraint import FloatConstraint -from libecalc.domain.process.process_solver.process_runner import Configuration from libecalc.domain.process.process_solver.solvers.recirculation_solver import RecirculationConfiguration from libecalc.domain.process.process_solver.solvers.speed_solver import SpeedConfiguration from libecalc.domain.process.value_objects.chart import ChartCurve