|
| 1 | +import abc |
| 2 | +from collections.abc import Callable |
| 3 | + |
| 4 | +from libecalc.domain.process.compressor.core.train.utils.common import EPSILON |
| 5 | +from libecalc.domain.process.entities.process_units.recirculation_loop import RecirculationLoop |
| 6 | +from libecalc.domain.process.entities.shaft import Shaft |
| 7 | +from libecalc.domain.process.process_solver.boundary import Boundary |
| 8 | +from libecalc.domain.process.process_solver.float_constraint import FloatConstraint |
| 9 | +from libecalc.domain.process.process_solver.search_strategies import BinarySearchStrategy, ScipyRootFindingStrategy |
| 10 | +from libecalc.domain.process.process_solver.solver import Solution |
| 11 | +from libecalc.domain.process.process_solver.solvers.recirculation_solver import ( |
| 12 | + RecirculationConfiguration, |
| 13 | + RecirculationSolver, |
| 14 | +) |
| 15 | +from libecalc.domain.process.process_solver.solvers.speed_solver import SpeedConfiguration, SpeedSolver |
| 16 | +from libecalc.domain.process.process_system.process_error import RateTooLowError |
| 17 | +from libecalc.domain.process.process_system.process_system import ProcessSystem |
| 18 | +from libecalc.domain.process.process_system.process_unit import ProcessUnit |
| 19 | +from libecalc.domain.process.value_objects.fluid_stream import FluidService, FluidStream |
| 20 | + |
| 21 | + |
| 22 | +class CompressorStageProcessUnit(ProcessUnit): |
| 23 | + @abc.abstractmethod |
| 24 | + def get_speed_boundary(self) -> Boundary: ... |
| 25 | + |
| 26 | + @abc.abstractmethod |
| 27 | + def get_maximum_standard_rate(self, inlet_stream: FluidStream) -> float: |
| 28 | + """ |
| 29 | + Maximum standard rate ignoring speed |
| 30 | + """ |
| 31 | + ... |
| 32 | + |
| 33 | + |
| 34 | +class CommonASVSolver: |
| 35 | + def __init__( |
| 36 | + self, |
| 37 | + shaft: Shaft, |
| 38 | + compressors: list[CompressorStageProcessUnit], |
| 39 | + fluid_service: FluidService, |
| 40 | + ) -> None: |
| 41 | + self._shaft = shaft |
| 42 | + self._compressors = compressors |
| 43 | + self._fluid_service = fluid_service |
| 44 | + self._root_finding_strategy = ScipyRootFindingStrategy() |
| 45 | + self._recirculation_loop = RecirculationLoop( |
| 46 | + inner_process=ProcessSystem( |
| 47 | + process_units=self._compressors, |
| 48 | + ), |
| 49 | + fluid_service=self._fluid_service, |
| 50 | + ) |
| 51 | + |
| 52 | + def get_recirculation_loop(self) -> RecirculationLoop: |
| 53 | + return self._recirculation_loop |
| 54 | + |
| 55 | + def get_initial_speed_boundary(self) -> Boundary: |
| 56 | + speed_boundaries = [compressor.get_speed_boundary() for compressor in self._compressors] |
| 57 | + max_speed = max(speed_boundary.max for speed_boundary in speed_boundaries) |
| 58 | + min_speed = min(speed_boundary.min for speed_boundary in speed_boundaries) |
| 59 | + return Boundary( |
| 60 | + min=min_speed, |
| 61 | + max=max_speed, |
| 62 | + ) |
| 63 | + |
| 64 | + def get_maximum_recirculation_rate(self, inlet_stream: FluidStream) -> float: |
| 65 | + first_compressor = self._compressors[0] |
| 66 | + max_rate = first_compressor.get_maximum_standard_rate(inlet_stream=inlet_stream) |
| 67 | + return max(0.0, max_rate - inlet_stream.standard_rate_sm3_per_day) |
| 68 | + |
| 69 | + def get_initial_recirculation_rate_boundary( |
| 70 | + self, inlet_stream: FluidStream, minimum_recirculation_rate: float = EPSILON |
| 71 | + ) -> Boundary: |
| 72 | + return Boundary( |
| 73 | + min=minimum_recirculation_rate, |
| 74 | + max=self.get_maximum_recirculation_rate(inlet_stream=inlet_stream) * (1 - EPSILON), |
| 75 | + ) |
| 76 | + |
| 77 | + def get_recirculation_solver( |
| 78 | + self, |
| 79 | + boundary: Boundary, |
| 80 | + target_pressure: FloatConstraint | None = None, |
| 81 | + ) -> RecirculationSolver: |
| 82 | + return RecirculationSolver( |
| 83 | + root_finding_strategy=self._root_finding_strategy, |
| 84 | + search_strategy=BinarySearchStrategy(tolerance=10e-3), |
| 85 | + recirculation_rate_boundary=boundary, |
| 86 | + target_pressure=target_pressure, |
| 87 | + ) |
| 88 | + |
| 89 | + def get_recirculation_func(self, inlet_stream: FluidStream) -> Callable[[RecirculationConfiguration], FluidStream]: |
| 90 | + def recirculation_func(configuration: RecirculationConfiguration) -> FluidStream: |
| 91 | + self._recirculation_loop.set_recirculation_rate(configuration.recirculation_rate) |
| 92 | + return self._recirculation_loop.propagate_stream(inlet_stream=inlet_stream) |
| 93 | + |
| 94 | + return recirculation_func |
| 95 | + |
| 96 | + def find_common_asv_solution( |
| 97 | + self, pressure_constraint: FloatConstraint, inlet_stream: FluidStream |
| 98 | + ) -> tuple[Solution[SpeedConfiguration], Solution[RecirculationConfiguration]]: |
| 99 | + speed_solver = SpeedSolver( |
| 100 | + search_strategy=BinarySearchStrategy(), |
| 101 | + root_finding_strategy=self._root_finding_strategy, |
| 102 | + boundary=self.get_initial_speed_boundary(), |
| 103 | + target_pressure=pressure_constraint.value, |
| 104 | + ) |
| 105 | + recirculation_solver_to_capacity = self.get_recirculation_solver( |
| 106 | + boundary=self.get_initial_recirculation_rate_boundary(inlet_stream=inlet_stream) |
| 107 | + ) |
| 108 | + recirculation_func = self.get_recirculation_func(inlet_stream=inlet_stream) |
| 109 | + |
| 110 | + def speed_func(configuration: SpeedConfiguration) -> FluidStream: |
| 111 | + try: |
| 112 | + self._shaft.set_speed(configuration.speed) |
| 113 | + self._recirculation_loop.set_recirculation_rate(0) |
| 114 | + return self._recirculation_loop.propagate_stream(inlet_stream=inlet_stream) |
| 115 | + except RateTooLowError: |
| 116 | + solution = recirculation_solver_to_capacity.solve(recirculation_func) |
| 117 | + self._recirculation_loop.set_recirculation_rate(solution.configuration.recirculation_rate) |
| 118 | + return self._recirculation_loop.propagate_stream(inlet_stream=inlet_stream) |
| 119 | + |
| 120 | + speed_solution = speed_solver.solve(speed_func) |
| 121 | + self._shaft.set_speed(speed_solution.configuration.speed) |
| 122 | + recirculation_solver_with_target_pressure = self.get_recirculation_solver( |
| 123 | + boundary=self.get_initial_recirculation_rate_boundary( |
| 124 | + inlet_stream=inlet_stream, |
| 125 | + ), |
| 126 | + target_pressure=pressure_constraint, |
| 127 | + ) |
| 128 | + recirculation_solution = recirculation_solver_with_target_pressure.solve(recirculation_func) |
| 129 | + # Return solution with all configurations |
| 130 | + return speed_solution, recirculation_solution |
0 commit comments