Skip to content

Commit 9bbc05d

Browse files
committed
test(individual_asv_pressure): regression test for boundary collapse and success flag
Adds a test that fails on main with DidNotConvergeError([0, 0]) due to two bugs in IndividualASVPressureControlStrategy that are fixed in the companion commit: 1. Boundary collapse: _minimum_achievable_pressure sets recirculation to maximum, then run(to_id=compressor_id) traverses the mixer, returning a stream whose rate already includes max recirculation. This makes boundary.max = max_std - (inlet + max_recirc) = 0, so RecirculationSolver receives [0, 0] and raises DidNotConvergeError. 2. Success flag: outlet_stream == target_pressure compares FluidStream to FloatConstraint — the types are incompatible so it always returns False, making the strategy report failure even when the target is met.
1 parent fbbead0 commit 9bbc05d

2 files changed

Lines changed: 115 additions & 0 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"""Regression test for IndividualASVPressureControlStrategy success-flag bug.
2+
3+
Bug: IndividualASVPressureControlStrategy.apply returned
4+
Solution(success=outlet_stream == target_pressure, ...)
5+
where outlet_stream is a FluidStream and target_pressure is a FloatConstraint.
6+
Because these are different types, Python's equality check always returns False
7+
(no __eq__ override bridges them), so the strategy always reported success=False
8+
even when the recirculation was correctly adjusted and the target was met.
9+
10+
Fix: compare outlet_stream.pressure_bara == target_pressure, which uses
11+
FloatConstraint.__eq__ (isclose, abs_tol=1e-3).
12+
13+
Secondary bug also fixed (covered by the same test): the boundary for
14+
RecirculationSolver was computed via run(to_id=compressor_id), which traverses
15+
through the RecirculationLoop mixer and returns a stream whose rate already
16+
includes the current recirculation. After _minimum_achievable_pressure sets
17+
recirculation to its maximum, the boundary collapses to [0, 0], preventing
18+
the solver from finding a valid recirculation.
19+
Fix: use run(to_id=recirculation_loop_id) to stop before the mixer.
20+
"""
21+
22+
import pytest
23+
24+
from libecalc.domain.process.process_solver.float_constraint import FloatConstraint
25+
from libecalc.domain.process.value_objects.chart import ChartCurve
26+
27+
28+
@pytest.mark.parametrize("target_pressure_bara", [50.0, 70.0, 87.0])
29+
def test_individual_asv_pressure_control_reaches_target_pressure(
30+
target_pressure_bara,
31+
stream_factory,
32+
chart_data_factory,
33+
fluid_service,
34+
stage_units_factory,
35+
with_individual_asv,
36+
process_runner_factory,
37+
individual_asv_pressure_control_strategy_factory,
38+
):
39+
"""IndividualASVPressureControlStrategy must report success=True when the target is met.
40+
41+
On main: two bugs cause failure:
42+
1. Boundary collapse: the per-stage boundary is computed via run(to_id=compressor_id),
43+
which traverses through the RecirculationLoop mixer. After _minimum_achievable_pressure
44+
sets recirculation to its maximum, the stream at the compressor already includes that
45+
max recirculation, making boundary.max = max_std - (inlet + max_recirc) = 0. The
46+
RecirculationSolver receives boundary=[0, 0] and raises DidNotConvergeError.
47+
2. Wrong success flag: even if the solver ran, success=outlet_stream == target_pressure
48+
compares FluidStream to FloatConstraint — always False.
49+
50+
With fix: stable boundary (run stops before mixer), correct type comparison.
51+
"""
52+
temperature = 300.0
53+
inlet_standard_rate = 500_000.0 # sm3/day
54+
inlet_pressure = 30.0 # bara
55+
56+
inlet_stream = stream_factory(
57+
standard_rate_m3_per_day=inlet_standard_rate,
58+
pressure_bara=inlet_pressure,
59+
temperature_kelvin=temperature,
60+
)
61+
q0 = float(inlet_stream.volumetric_rate_m3_per_hour)
62+
63+
# Chart: min_rate = 2*q0 (inlet is BELOW min → ASV recirculation always needed).
64+
# At min speed (75 RPM):
65+
# - Outlet at surge flow (2*q0): head=head_hi=150k → outlet ≈ 89 bara
66+
# - Max recirculation: flow = 8*q0, head=head_lo=40k → outlet ≈ 41 bara
67+
# All three target pressures (50, 70, 87) lie strictly between 41 and 89 bara.
68+
chart_data = chart_data_factory.from_curves(
69+
curves=[
70+
ChartCurve(
71+
speed_rpm=75.0,
72+
rate_actual_m3_hour=[q0 * 2, q0 * 8],
73+
polytropic_head_joule_per_kg=[150_000.0, 40_000.0],
74+
efficiency_fraction=[0.75, 0.75],
75+
),
76+
ChartCurve(
77+
speed_rpm=105.0,
78+
rate_actual_m3_hour=[q0 * 2, q0 * 8],
79+
polytropic_head_joule_per_kg=[150_000.0 * 1.05, 40_000.0 * 1.05],
80+
efficiency_fraction=[0.75, 0.75],
81+
),
82+
],
83+
control_margin=0.0,
84+
)
85+
86+
from libecalc.domain.process.entities.shaft import VariableSpeedShaft
87+
88+
shaft = VariableSpeedShaft()
89+
shaft.set_speed(75.0)
90+
91+
units = stage_units_factory(chart_data=chart_data, shaft=shaft, temperature_kelvin=temperature)
92+
wrapped_units, loop_ids, compressors = with_individual_asv(units)
93+
runner = process_runner_factory(units=wrapped_units, shaft=shaft)
94+
95+
strategy = individual_asv_pressure_control_strategy_factory(
96+
runner=runner,
97+
recirculation_loop_ids=loop_ids,
98+
compressors=compressors,
99+
)
100+
101+
target = FloatConstraint(target_pressure_bara)
102+
solution = strategy.apply(target_pressure=target, inlet_stream=inlet_stream)
103+
104+
assert solution.success is True, (
105+
f"Expected success=True for target={target_pressure_bara} bara, got False. "
106+
"This indicates IndividualASVPressureControlStrategy compared FluidStream == FloatConstraint "
107+
"(always False) instead of outlet_stream.pressure_bara == target_pressure."
108+
)
109+
110+
runner.apply_configurations(solution.configuration)
111+
outlet = runner.run(inlet_stream=inlet_stream)
112+
113+
assert outlet.pressure_bara == pytest.approx(
114+
target_pressure_bara, rel=1e-3
115+
), f"Expected outlet {target_pressure_bara} bara but got {outlet.pressure_bara:.4f} bara."

tests/libecalc/domain/process/process_solver/pressure_control/test_individual_asv_rate_control_pressure_reduction.py renamed to tests/libecalc/domain/process/process_solver/pressure_control/test_individual_asv_rate_control_pressure_strategy.py

File renamed without changes.

0 commit comments

Comments
 (0)