Skip to content

Commit 68c430e

Browse files
committed
fix(process): speed solver tolerates infeasible EOS probes
Treat speeds that make the EOS / fluid layer fail as infeasible probes rather than letting the exception crash the run. Bisect down from the max-speed probe when it's rejected by the EOS, and add an outer guard that marks otherwise-unrecoverable timesteps as NOT_CALCULATED.
1 parent 62940be commit 68c430e

2 files changed

Lines changed: 92 additions & 3 deletions

File tree

src/libecalc/domain/process/compressor/core/results.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,33 @@ def create_empty(cls) -> CompressorTrainStageResultSingleTimeStep:
8080
point_is_valid=True,
8181
)
8282

83+
@classmethod
84+
def create_infeasible(cls) -> CompressorTrainStageResultSingleTimeStep:
85+
"""Sentinel for an infeasible operating point (EOS / chart-extrapolation failure).
86+
87+
Differs from ``create_empty`` in that ``point_is_valid=False`` and
88+
``rate_exceeds_maximum=True``, so the train reports
89+
``within_capacity=False`` / ``is_valid=False`` and an upstream solver
90+
treats the proposal as infeasible rather than as a zero-power success.
91+
"""
92+
return cls(
93+
inlet_stream=None,
94+
outlet_stream=None,
95+
inlet_stream_including_asv=None,
96+
outlet_stream_including_asv=None,
97+
polytropic_head_kJ_per_kg=0.0,
98+
polytropic_efficiency=1.0,
99+
polytropic_enthalpy_change_kJ_per_kg=0.0,
100+
polytropic_enthalpy_change_before_choke_kJ_per_kg=0.0,
101+
power_megawatt=0.0,
102+
chart_area_flag=ChartAreaFlag.NOT_CALCULATED,
103+
rate_has_recirculation=False,
104+
rate_exceeds_maximum=True,
105+
pressure_is_choked=False,
106+
head_exceeds_maximum=True,
107+
point_is_valid=False,
108+
)
109+
83110
@property
84111
def inlet_actual_rate_m3_per_hour(self) -> float:
85112
"""Actual inlet rate in Am3/hour."""
@@ -576,3 +603,18 @@ def create_empty(cls, number_of_stages: int) -> CompressorTrainResultSingleTimeS
576603
stage_results=[CompressorTrainStageResultSingleTimeStep.create_empty()] * number_of_stages,
577604
target_pressure_status=TargetPressureStatus.NOT_CALCULATED,
578605
)
606+
607+
@classmethod
608+
def create_infeasible(cls, number_of_stages: int) -> CompressorTrainResultSingleTimeStep:
609+
"""Train-level counterpart of :meth:`CompressorTrainStageResultSingleTimeStep.create_infeasible`.
610+
611+
Reports ``within_capacity=False`` and ``is_valid=False`` so an upstream
612+
speed/rate solver treats the proposal as infeasible.
613+
"""
614+
return cls(
615+
inlet_stream=None,
616+
outlet_stream=None,
617+
speed=np.nan,
618+
stage_results=[CompressorTrainStageResultSingleTimeStep.create_infeasible()] * max(number_of_stages, 1),
619+
target_pressure_status=TargetPressureStatus.NOT_CALCULATED,
620+
)

src/libecalc/domain/process/compressor/core/train/compressor_train_common_shaft.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,22 @@ def evaluate_given_constraints(
142142
self,
143143
constraints: CompressorTrainEvaluationInput,
144144
fixed_speed: float | None = None, # TODO: not used?
145+
) -> CompressorTrainResultSingleTimeStep:
146+
try:
147+
return self._evaluate_given_constraints_impl(constraints=constraints, fixed_speed=fixed_speed)
148+
except IllegalStateException as exc:
149+
# Unrecoverable EOS / flash failure bubbled up — mark this timestep
150+
# NOT_CALCULATED rather than crash the whole timeseries.
151+
logger.warning(
152+
"Compressor train evaluation failed for a timestep and was treated as NOT_CALCULATED: %s",
153+
exc,
154+
)
155+
return CompressorTrainResultSingleTimeStep.create_empty(number_of_stages=len(self.stages))
156+
157+
def _evaluate_given_constraints_impl(
158+
self,
159+
constraints: CompressorTrainEvaluationInput,
160+
fixed_speed: float | None = None,
145161
) -> CompressorTrainResultSingleTimeStep:
146162
self.reset_rate_modifiers()
147163
self._validate_nonnegative_stage_rates(constraints)
@@ -1127,12 +1143,43 @@ def find_fixed_shaft_speed_given_constraints(
11271143

11281144
def _calculate_compressor_train(_speed: float) -> CompressorTrainResultSingleTimeStep:
11291145
self.shaft.set_speed(_speed)
1130-
return self.calculate_compressor_train(
1131-
constraints=constraints,
1132-
)
1146+
try:
1147+
return self.calculate_compressor_train(
1148+
constraints=constraints,
1149+
)
1150+
except IllegalStateException as exc:
1151+
# EOS / fluid layer rejected this speed — surface as infeasible
1152+
# so the speed solver treats it like an out-of-capacity probe
1153+
# and tries something else.
1154+
logger.warning(
1155+
"Compressor speed %.1f produced an infeasible state and will be treated "
1156+
"as out-of-capacity (%s). The shaft-speed solver will try a different proposal.",
1157+
_speed,
1158+
exc,
1159+
)
1160+
return CompressorTrainResultSingleTimeStep.create_infeasible(number_of_stages=len(self.stages))
11331161

11341162
train_result_for_maximum_speed = _calculate_compressor_train(_speed=maximum_speed)
11351163

1164+
# If the EOS rejected the max-speed probe (chart_area_flag NOT_CALCULATED
1165+
# rather than a real capacity flag), bisect down to find the highest
1166+
# speed we can actually evaluate at and continue the normal speed search
1167+
# from there.
1168+
max_speed_eos_failed = (
1169+
not train_result_for_maximum_speed.within_capacity
1170+
and len(train_result_for_maximum_speed.stage_results) > 0
1171+
and train_result_for_maximum_speed.stage_results[0].chart_area_flag == ChartAreaFlag.NOT_CALCULATED
1172+
)
1173+
if max_speed_eos_failed:
1174+
feasible_max_speed = maximize_x_given_boolean_condition_function(
1175+
x_min=minimum_speed,
1176+
x_max=maximum_speed,
1177+
bool_func=lambda s: _calculate_compressor_train(_speed=s).within_capacity,
1178+
)
1179+
if feasible_max_speed > minimum_speed:
1180+
maximum_speed = feasible_max_speed
1181+
train_result_for_maximum_speed = _calculate_compressor_train(_speed=maximum_speed)
1182+
11361183
if not train_result_for_maximum_speed.within_capacity:
11371184
# will not find valid result - the rate is above maximum rate, return invalid results at maximum speed
11381185
self.shaft.set_speed(maximum_speed)

0 commit comments

Comments
 (0)