1+ from collections .abc import Callable
12from typing import Final
23
34import pytest
@@ -92,41 +93,43 @@ def speed_func(configuration: SpeedConfiguration):
9293 assert outlet_stream .pressure_bara == expected_pressure
9394
9495
95- class FluidNotAchievableProcessUnit (ProcessUnit ):
96- """Process unit that raises OutletFluidNotAchievableError based on speed.
97-
98- Behaves like SpeedProcessUnit (pressure = inlet_pressure + speed) except when
99- the speed matches the failure condition. Supports two modes:
100- - fails_at_or_above: raises when speed >= threshold (for max-speed failure tests)
101- - fails_at_or_below: raises when speed <= threshold (for min-speed failure tests)
102- """
96+ class FakeProcessUnit (ProcessUnit ):
97+ """Test double whose propagate_stream is supplied as a callable."""
10398
10499 def __init__ (
105100 self ,
106- shaft : Shaft ,
107- fluid_service : FluidService ,
108- * ,
109- fails_at_or_above : float | None = None ,
110- fails_at_or_below : float | None = None ,
101+ propagate_stream : Callable [[FluidStream ], FluidStream ],
111102 process_unit_id : ProcessUnitId | None = None ,
112103 ):
113104 self ._id : Final [ProcessUnitId ] = process_unit_id or ProcessUnit ._create_id ()
114- self ._shaft = shaft
115- self ._fluid_service = fluid_service
116- self ._fails_at_or_above = fails_at_or_above
117- self ._fails_at_or_below = fails_at_or_below
105+ self ._propagate_stream = propagate_stream
118106
119107 def get_id (self ) -> ProcessUnitId :
120108 return self ._id
121109
122110 def propagate_stream (self , inlet_stream : FluidStream ) -> FluidStream :
123- speed = self ._shaft .get_speed ()
124- should_fail = (self ._fails_at_or_above is not None and speed >= self ._fails_at_or_above ) or (
125- self ._fails_at_or_below is not None and speed <= self ._fails_at_or_below
111+ return self ._propagate_stream (inlet_stream )
112+
113+
114+ def _failing_speed_unit (
115+ shaft : Shaft ,
116+ fluid_service : FluidService ,
117+ * ,
118+ fails_at_or_above : float | None = None ,
119+ fails_at_or_below : float | None = None ,
120+ ) -> FakeProcessUnit :
121+ """Build a FakeProcessUnit that mirrors `pressure = inlet + speed` but raises
122+ OutletFluidNotAchievableError when the shaft speed crosses the given threshold."""
123+ unit_id = ProcessUnit ._create_id ()
124+
125+ def _propagate (inlet_stream : FluidStream ) -> FluidStream :
126+ speed = shaft .get_speed ()
127+ should_fail = (fails_at_or_above is not None and speed >= fails_at_or_above ) or (
128+ fails_at_or_below is not None and speed <= fails_at_or_below
126129 )
127130 if should_fail :
128131 raise OutletFluidNotAchievableError (
129- process_unit_id = self . _id ,
132+ process_unit_id = unit_id ,
130133 unachievable_operating_point = CompressorOperatingPoint (
131134 inlet_pressure_bara = inlet_stream .pressure_bara ,
132135 inlet_temperature_kelvin = inlet_stream .temperature_kelvin ,
@@ -136,13 +139,15 @@ def propagate_stream(self, inlet_stream: FluidStream) -> FluidStream:
136139 speed = speed ,
137140 ),
138141 )
139- return self . _fluid_service .create_stream_from_standard_rate (
142+ return fluid_service .create_stream_from_standard_rate (
140143 fluid_model = inlet_stream .fluid_model ,
141144 pressure_bara = inlet_stream .pressure_bara + speed ,
142145 standard_rate_m3_per_day = inlet_stream .standard_rate_sm3_per_day ,
143146 temperature_kelvin = inlet_stream .temperature_kelvin ,
144147 )
145148
149+ return FakeProcessUnit (propagate_stream = _propagate , process_unit_id = unit_id )
150+
146151
147152def test_min_speed_fluid_not_achievable_target_achievable (
148153 search_strategy_factory ,
@@ -152,7 +157,7 @@ def test_min_speed_fluid_not_achievable_target_achievable(
152157 fluid_service ,
153158):
154159 """OutletFluidNotAchievableError at min speed: binary search finds higher effective min; target still reachable."""
155- unit = FluidNotAchievableProcessUnit (shaft = shaft , fluid_service = fluid_service , fails_at_or_below = 300 )
160+ unit = _failing_speed_unit (shaft = shaft , fluid_service = fluid_service , fails_at_or_below = 300 )
156161 speed_solver = SpeedSolver (
157162 search_strategy = search_strategy_factory (),
158163 root_finding_strategy = root_finding_strategy ,
@@ -179,7 +184,7 @@ def test_min_speed_fluid_not_achievable_target_not_achievable(
179184 fluid_service ,
180185):
181186 """OutletFluidNotAchievableError at min speed: effective min too high → target below minimum achievable."""
182- unit = FluidNotAchievableProcessUnit (shaft = shaft , fluid_service = fluid_service , fails_at_or_below = 400 )
187+ unit = _failing_speed_unit (shaft = shaft , fluid_service = fluid_service , fails_at_or_below = 400 )
183188 speed_solver = SpeedSolver (
184189 search_strategy = search_strategy_factory (),
185190 root_finding_strategy = root_finding_strategy ,
@@ -209,7 +214,7 @@ def test_max_speed_fluid_not_achievable_target_achievable(
209214 fluid_service ,
210215):
211216 """OutletFluidNotAchievableError at max speed: binary search finds lower effective max; target still reachable."""
212- unit = FluidNotAchievableProcessUnit (shaft = shaft , fluid_service = fluid_service , fails_at_or_above = 500 )
217+ unit = _failing_speed_unit (shaft = shaft , fluid_service = fluid_service , fails_at_or_above = 500 )
213218 speed_solver = SpeedSolver (
214219 search_strategy = search_strategy_factory (),
215220 root_finding_strategy = root_finding_strategy ,
@@ -236,7 +241,7 @@ def test_max_speed_fluid_not_achievable_target_not_achievable(
236241 fluid_service ,
237242):
238243 """OutletFluidNotAchievableError at max speed: binary search finds lower effective max; target not reachable."""
239- unit = FluidNotAchievableProcessUnit (shaft = shaft , fluid_service = fluid_service , fails_at_or_above = 300 )
244+ unit = _failing_speed_unit (shaft = shaft , fluid_service = fluid_service , fails_at_or_above = 300 )
240245 speed_solver = SpeedSolver (
241246 search_strategy = search_strategy_factory (),
242247 root_finding_strategy = root_finding_strategy ,
@@ -266,7 +271,7 @@ def test_all_speeds_fluid_not_achievable_from_max(
266271 fluid_service ,
267272):
268273 """All speeds fail EOS when searching from max: pre-check at boundary.min catches it immediately."""
269- unit = FluidNotAchievableProcessUnit (shaft = shaft , fluid_service = fluid_service , fails_at_or_above = 0 )
274+ unit = _failing_speed_unit (shaft = shaft , fluid_service = fluid_service , fails_at_or_above = 0 )
270275 speed_solver = SpeedSolver (
271276 search_strategy = search_strategy_factory (),
272277 root_finding_strategy = root_finding_strategy ,
@@ -293,7 +298,7 @@ def test_all_speeds_fluid_not_achievable_from_min(
293298 fluid_service ,
294299):
295300 """All speeds fail EOS when searching from min: pre-check at boundary.max catches it immediately."""
296- unit = FluidNotAchievableProcessUnit (shaft = shaft , fluid_service = fluid_service , fails_at_or_below = 600 )
301+ unit = _failing_speed_unit (shaft = shaft , fluid_service = fluid_service , fails_at_or_below = 600 )
297302 speed_solver = SpeedSolver (
298303 search_strategy = search_strategy_factory (),
299304 root_finding_strategy = root_finding_strategy ,
0 commit comments