Skip to content

Commit 7aa0a8a

Browse files
committed
chore: introduce StreamPropagator
Common StreamPropagator interface to better capture propagate_stream behavior. Also make ProcessSystem an interface, since we want different kinds of ProcessSystems, recirculation loop and compressor train.
1 parent 4c404c2 commit 7aa0a8a

9 files changed

Lines changed: 98 additions & 81 deletions

File tree

src/libecalc/domain/process/entities/process_units/recirculation_loop.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1+
from libecalc.domain.component_validation_error import DomainValidationException
12
from libecalc.domain.process.entities.process_units.mixer.mixer import Mixer
23
from libecalc.domain.process.entities.process_units.splitter.splitter import Splitter
34
from libecalc.domain.process.process_system.process_system import ProcessSystem
4-
from libecalc.domain.process.process_system.process_unit import ProcessUnit, ProcessUnitId
5+
from libecalc.domain.process.process_system.process_unit import ProcessUnitId
56
from libecalc.domain.process.value_objects.fluid_stream import FluidService, FluidStream
67

7-
InnerProcess = ProcessSystem | ProcessUnit
88

9-
10-
class RecirculationLoop(ProcessUnit):
9+
class RecirculationLoop(ProcessSystem):
1110
def __init__(
1211
self,
1312
process_unit_id: ProcessUnitId,
14-
inner_process: InnerProcess,
13+
inner_process: ProcessSystem,
1514
fluid_service: FluidService,
1615
recirculation_rate: float = 0,
1716
):
@@ -22,18 +21,16 @@ def __init__(
2221
self._validate_inner_process()
2322

2423
def _validate_inner_process(self):
25-
if isinstance(self._inner_process, Splitter):
26-
raise ValueError("Inner process cannot be a splitter")
27-
if isinstance(self._inner_process, Mixer):
28-
raise ValueError("Inner process cannot be a mixer")
29-
if isinstance(self._inner_process, ProcessSystem) and self._inner_process.has_multiple_streams:
30-
raise ValueError("Inner process cannot have multiple streams")
24+
assert isinstance(self._inner_process, ProcessSystem), "Recirculation loop should contain a ProcessSystem"
25+
for process_unit in self._inner_process.get_process_units():
26+
if isinstance(process_unit, Splitter | Mixer):
27+
raise DomainValidationException("Recirculation loop cannot contain splitters or mixers")
3128

3229
def get_id(self) -> ProcessUnitId:
3330
return self._id
3431

35-
def get_inner_process(self) -> InnerProcess:
36-
return self._inner_process
32+
def get_process_units(self):
33+
return self._inner_process.get_process_units()
3734

3835
def set_recirculation_rate(self, rate: float):
3936
self._recirculation_rate = rate

src/libecalc/domain/process/process_solver/asv_solvers.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
from libecalc.domain.process.process_solver.solvers.speed_solver import SpeedConfiguration, SpeedSolver
2727
from libecalc.domain.process.process_system.compressor_stage_process_unit import CompressorStageProcessUnit
2828
from libecalc.domain.process.process_system.process_error import RateTooLowError
29-
from libecalc.domain.process.process_system.process_system import ProcessSystem
3029
from libecalc.domain.process.process_system.process_unit import create_process_unit_id
30+
from libecalc.domain.process.process_system.serial_process_system import SerialProcessSystem
3131
from libecalc.domain.process.value_objects.fluid_stream import FluidService, FluidStream
3232

3333

@@ -63,8 +63,8 @@ def __init__(
6363
[
6464
RecirculationLoop(
6565
process_unit_id=create_process_unit_id(),
66-
inner_process=ProcessSystem(
67-
process_units=[compressor],
66+
inner_process=SerialProcessSystem(
67+
propagators=[compressor],
6868
),
6969
fluid_service=self._fluid_service,
7070
)
@@ -74,8 +74,8 @@ def __init__(
7474
else [
7575
RecirculationLoop(
7676
process_unit_id=create_process_unit_id(),
77-
inner_process=ProcessSystem(
78-
process_units=self._compressors,
77+
inner_process=SerialProcessSystem(
78+
propagators=self._compressors,
7979
),
8080
fluid_service=self._fluid_service,
8181
)
Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,10 @@
1-
from collections.abc import Sequence
1+
import abc
22

3-
from libecalc.domain.process.entities.process_units.mixer.mixer import Mixer
4-
from libecalc.domain.process.entities.process_units.splitter.splitter import Splitter
5-
from libecalc.domain.process.process_system.process_unit import ProcessUnit
6-
from libecalc.domain.process.value_objects.fluid_stream import FluidStream
3+
from libecalc.domain.process.process_system.stream_propagator import StreamPropagator
74

85
PORT_ID = str
96

107

11-
class ProcessSystem:
12-
def __init__(
13-
self,
14-
process_units: Sequence[ProcessUnit],
15-
):
16-
self._process_units = process_units
17-
18-
def get_process_units(self):
19-
return self._process_units
20-
21-
def propagate_stream(self, inlet_stream: FluidStream) -> FluidStream:
22-
current_inlet = inlet_stream
23-
for process_unit in self._process_units:
24-
current_inlet = process_unit.propagate_stream(inlet_stream=current_inlet)
25-
return current_inlet
26-
27-
@property
28-
def contains_splitter(self):
29-
return any(isinstance(process_unit, Splitter) for process_unit in self._process_units)
30-
31-
@property
32-
def contains_mixer(self):
33-
return any(isinstance(process_unit, Mixer) for process_unit in self._process_units)
34-
35-
@property
36-
def has_multiple_streams(self):
37-
return self.contains_splitter or self.contains_mixer
8+
class ProcessSystem(StreamPropagator, abc.ABC):
9+
@abc.abstractmethod
10+
def get_process_units(self): ...

src/libecalc/domain/process/process_system/process_unit.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import NewType
44
from uuid import UUID
55

6-
from libecalc.domain.process.value_objects.fluid_stream import FluidStream
6+
from libecalc.domain.process.process_system.stream_propagator import StreamPropagator
77

88
ProcessUnitId = NewType("ProcessUnitId", UUID)
99

@@ -12,9 +12,6 @@ def create_process_unit_id() -> ProcessUnitId:
1212
return ProcessUnitId(uuid.uuid4())
1313

1414

15-
class ProcessUnit(abc.ABC):
15+
class ProcessUnit(StreamPropagator, abc.ABC):
1616
@abc.abstractmethod
1717
def get_id(self) -> ProcessUnitId: ...
18-
19-
@abc.abstractmethod
20-
def propagate_stream(self, inlet_stream: FluidStream) -> FluidStream: ...
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from collections.abc import Sequence
2+
3+
from libecalc.domain.process.process_system.process_system import ProcessSystem
4+
from libecalc.domain.process.process_system.process_unit import ProcessUnit
5+
from libecalc.domain.process.value_objects.fluid_stream import FluidStream
6+
7+
8+
class SerialProcessSystem(ProcessSystem):
9+
def __init__(
10+
self,
11+
propagators: Sequence[ProcessUnit | ProcessSystem],
12+
):
13+
self._propagators = propagators
14+
15+
def get_process_units(self):
16+
process_units = []
17+
for propagator in self._propagators:
18+
if isinstance(propagator, ProcessSystem):
19+
process_units.extend(propagator.get_process_units())
20+
else:
21+
process_units.append(propagator)
22+
return process_units
23+
24+
def propagate_stream(self, inlet_stream: FluidStream) -> FluidStream:
25+
current_inlet = inlet_stream
26+
for process_unit in self._propagators:
27+
current_inlet = process_unit.propagate_stream(inlet_stream=current_inlet)
28+
return current_inlet
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import abc
2+
3+
from libecalc.domain.process.value_objects.fluid_stream import FluidStream
4+
5+
6+
class StreamPropagator(abc.ABC):
7+
@abc.abstractmethod
8+
def propagate_stream(self, inlet_stream: FluidStream) -> FluidStream: ...

tests/libecalc/conftest.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
from libecalc.domain.process.entities.process_units.temperature_setter import TemperatureSetter
2323
from libecalc.domain.process.entities.shaft import Shaft, SingleSpeedShaft, VariableSpeedShaft
2424
from libecalc.domain.process.process_system.process_system import ProcessSystem
25-
from libecalc.domain.process.process_system.process_unit import ProcessUnitId
25+
from libecalc.domain.process.process_system.process_unit import ProcessUnit, ProcessUnitId
26+
from libecalc.domain.process.process_system.serial_process_system import SerialProcessSystem
2627
from libecalc.domain.process.value_objects.chart import ChartCurve
2728
from libecalc.domain.process.value_objects.chart.chart import Chart, ChartData
2829
from libecalc.domain.process.value_objects.chart.compressor import CompressorChart
@@ -202,12 +203,26 @@ def create_liquid_remover(process_unit_id: ProcessUnitId = None):
202203

203204

204205
@pytest.fixture
205-
def recirculation_loop_factory(fluid_service):
206+
def process_system_factory(compressor_stage_factory, fluid_service):
207+
def create_process_system(
208+
process_units: list[ProcessUnit],
209+
):
210+
return SerialProcessSystem(
211+
propagators=process_units,
212+
)
213+
214+
return create_process_system
215+
216+
217+
@pytest.fixture
218+
def recirculation_loop_factory(fluid_service, process_system_factory):
206219
def create_recirculation_loop(
207-
inner_process: ProcessSystem,
220+
inner_process: ProcessSystem | ProcessUnit,
208221
process_unit_id: ProcessUnitId = None,
209222
recirculation_rate: float = 0,
210223
):
224+
if isinstance(inner_process, ProcessUnit):
225+
inner_process = process_system_factory([inner_process])
211226
return RecirculationLoop(
212227
inner_process=inner_process,
213228
process_unit_id=process_unit_id or uuid.uuid4(),

tests/libecalc/domain/process/conftest.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from libecalc.domain.process.process_solver.stream_constraint import PressureStreamConstraint
1414
from libecalc.domain.process.process_system.compressor_stage_process_unit import CompressorStageProcessUnit
1515
from libecalc.domain.process.process_system.process_error import OutsideCapacityError, RateTooHighError, RateTooLowError
16-
from libecalc.domain.process.process_system.process_system import ProcessSystem
1716
from libecalc.domain.process.process_system.process_unit import ProcessUnit, ProcessUnitId, create_process_unit_id
1817
from libecalc.domain.process.value_objects.chart.chart import ChartData
1918
from libecalc.domain.process.value_objects.chart.chart_area_flag import ChartAreaFlag
@@ -235,20 +234,6 @@ def propagate_stream(self, inlet_stream: FluidStream) -> FluidStream:
235234
return result.outlet_stream
236235

237236

238-
@pytest.fixture
239-
def process_system_factory(compressor_stage_factory, fluid_service):
240-
def create_process_system(
241-
process_units: list[ProcessUnit] | None = None,
242-
):
243-
if process_units is None:
244-
process_units = [simple_process_unit_factory(fluid_service)]
245-
return ProcessSystem(
246-
process_units=process_units,
247-
)
248-
249-
return create_process_system
250-
251-
252237
@pytest.fixture
253238
def stream_constraint_factory():
254239
def create_stream_constraint(pressure: float):

tests/libecalc/domain/process/entities/process_units/test_recirculation_loop.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import pytest
2+
from inline_snapshot import snapshot
23

4+
from libecalc.domain.component_validation_error import DomainValidationException
35
from libecalc.domain.process.entities.process_units.mixer.mixer import Mixer
46
from libecalc.domain.process.entities.process_units.splitter.splitter import Splitter
5-
from libecalc.domain.process.process_system.process_system import ProcessSystem
67
from libecalc.domain.process.value_objects.fluid_stream import (
78
Fluid,
89
FluidModel,
@@ -47,30 +48,43 @@ def fluid_service() -> FluidService:
4748
return DummyFluidService()
4849

4950

50-
def test_recirculation_loop_around_splitter_raises_valueerror(fluid_service, recirculation_loop_factory):
51+
@pytest.mark.inlinesnapshot
52+
@pytest.mark.snapshot
53+
def test_recirculation_loop_around_splitter_raises_exception(fluid_service, recirculation_loop_factory):
5154
process_unit = Splitter(number_of_outputs=2)
52-
with pytest.raises(ValueError, match="Inner process cannot be a splitter"):
55+
with pytest.raises(Exception) as exc_info:
5356
recirculation_loop_factory(inner_process=process_unit)
5457

58+
assert str(exc_info.value) == snapshot("Recirculation loop should contain a ProcessSystem")
5559

56-
def test_recirculation_loop_around_mixer_raises_valueerror(
60+
61+
@pytest.mark.inlinesnapshot
62+
@pytest.mark.snapshot
63+
def test_recirculation_loop_around_mixer_raises_exception(
5764
fluid_service,
5865
recirculation_loop_factory,
5966
):
6067
process_unit = Mixer(number_of_inputs=2, fluid_service=fluid_service)
61-
with pytest.raises(ValueError, match="Inner process cannot be a mixer"):
68+
with pytest.raises(Exception) as exc_info:
6269
recirculation_loop_factory(inner_process=process_unit)
6370

71+
assert str(exc_info.value) == snapshot("Recirculation loop should contain a ProcessSystem")
72+
6473

65-
def test_recirculation_loop_around_process_system_with_multiple_streams_raises_valueerror(
74+
@pytest.mark.inlinesnapshot
75+
@pytest.mark.snapshot
76+
def test_recirculation_loop_around_process_system_with_multiple_streams_raises_exception(
6677
choke_factory,
6778
liquid_remover_factory,
6879
recirculation_loop_factory,
6980
fluid_service,
81+
process_system_factory,
7082
):
7183
liquid_remover = liquid_remover_factory()
7284
choke = choke_factory(pressure_change=2)
7385
mixer = Mixer(number_of_inputs=2, fluid_service=fluid_service)
74-
process_system = ProcessSystem(process_units=[liquid_remover, choke, mixer])
75-
with pytest.raises(ValueError, match="Inner process cannot have multiple streams"):
86+
process_system = process_system_factory(process_units=[liquid_remover, choke, mixer])
87+
with pytest.raises(DomainValidationException) as exc_info:
7688
recirculation_loop_factory(inner_process=process_system)
89+
90+
assert str(exc_info.value) == snapshot("Recirculation loop cannot contain splitters or mixers")

0 commit comments

Comments
 (0)