Skip to content

Commit 0c7fb76

Browse files
committed
fix: only validate pressures when rates and regularity are positive
1 parent 553c477 commit 0c7fb76

4 files changed

Lines changed: 154 additions & 81 deletions

File tree

src/libecalc/presentation/yaml/domain/expression_time_series_flow_rate.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,14 @@ def __init__(
4040
assert isinstance(consumption_rate_type, RateType)
4141
self._consumption_rate_type = consumption_rate_type
4242
self._rate_values = self._get_stream_day_values()
43-
self._validate()
43+
if self._rate_values is not None:
44+
self._validate()
4445

4546
def _validate(self):
4647
"""Validate that all flow rate values are positive."""
4748
for rate in self._rate_values:
48-
if rate is not None and rate < 0:
49+
assert rate is not None
50+
if rate < 0:
4951
raise InvalidFlowRateException(rate, str(self._time_series_expression.get_expression()))
5052

5153
def _get_stream_day_values(self) -> list[float | None]:

src/libecalc/presentation/yaml/domain/expression_time_series_pressure.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,24 @@ class ExpressionTimeSeriesPressure(TimeSeriesPressure):
2424
def __init__(
2525
self,
2626
time_series_expression: TimeSeriesExpression,
27+
validation_mask: list[bool] | None = None,
2728
):
2829
self._time_series_expression = time_series_expression
2930
self._pressure_values = self._time_series_expression.get_evaluated_expression()
31+
self._validation_mask = validation_mask
3032
self._validate()
3133

3234
def _validate(self):
33-
"""Validate that all pressure values are non-negative."""
34-
# TODO: Currently all pressures must be non-negative, but in the future we want to only allow positive pressures
35-
# There are many situations where input values of zero means that equipment should be turned off
36-
# When the rest of the codebase is more mature with this respect (specific start/end dates for equipment,
37-
# instead of using rates/pressures of zero), we can tighten this validation
38-
for pressure in self._pressure_values:
39-
if pressure < 0:
40-
raise InvalidPressureException(pressure, str(self._time_series_expression.get_expression()))
35+
"""Validate that all pressure values are positive, except where masked by the condition."""
36+
if self._validation_mask is None:
37+
self._validation_mask = [True] * len(self._pressure_values)
38+
39+
for pressure, should_validate in zip(self._pressure_values, self._validation_mask):
40+
if should_validate:
41+
# TODO: this comparison should in reality be <= 0, but since there are slight confusion around units
42+
# bara vs barg in the input data, we allow 0 for now. Will be tightened up in future.
43+
if pressure < 0:
44+
raise InvalidPressureException(pressure, str(self._time_series_expression.get_expression()))
4145

4246
def get_periods(self) -> Periods:
4347
"""
@@ -56,3 +60,10 @@ def get_values(self) -> list[float]:
5660
pressure_values = self._pressure_values
5761

5862
return list(pressure_values)
63+
64+
def get_validation_mask(self) -> list[bool]:
65+
"""
66+
Returns the mask indicating which pressure values were validated.
67+
68+
"""
69+
return self._validation_mask

src/libecalc/presentation/yaml/mappers/consumer_function_mapper.py

Lines changed: 126 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -125,28 +125,36 @@ def map_rate_fractions(
125125

126126

127127
def validate_increasing_pressure(
128-
suction_pressure: list[float],
129-
discharge_pressure: list[float],
130-
intermediate_pressure: list[float] | None = None,
128+
suction_pressure: ExpressionTimeSeriesPressure,
129+
discharge_pressure: ExpressionTimeSeriesPressure,
130+
intermediate_pressure: ExpressionTimeSeriesPressure | None = None,
131131
):
132-
# TODO: Currently all pressures must be non-negative, meaning that we allow zero pressures. This also means that we
133-
# allow zero discharge pressure, which is not physically meaningful in most cases. The compressor does no work.
134-
# In the future we want to only allow positive pressures, and then we can tighten this validation to require
135-
# strictly increasing pressures (i.e. suction < discharge, and suction < intermediate < discharge).
136-
for i in range(len(suction_pressure)):
137-
sp = suction_pressure[i]
138-
dp = discharge_pressure[i]
139-
if intermediate_pressure:
140-
ip = intermediate_pressure[i]
141-
if not (sp <= ip <= dp):
142-
raise ProcessPressureRatioValidationException(
143-
message=f"Invalid pressures at index {i+1}: suction pressure ({sp}) must be less than intermediate pressure ({ip}), which must be less than discharge pressure ({dp})."
144-
)
145-
else:
146-
if not (sp <= dp):
147-
raise ProcessPressureRatioValidationException(
148-
message=f"Invalid pressures at index {i+1}: suction pressure ({sp}) must be less than discharge pressure ({dp})."
149-
)
132+
validation_mask = suction_pressure.get_validation_mask()
133+
assert validation_mask == discharge_pressure.get_validation_mask()
134+
suction_pressure_values = suction_pressure.get_values()
135+
discharge_pressure_values = discharge_pressure.get_values()
136+
137+
if intermediate_pressure is not None:
138+
assert validation_mask == intermediate_pressure.get_validation_mask()
139+
intermediate_pressure_values = intermediate_pressure.get_values()
140+
else:
141+
intermediate_pressure_values = None
142+
143+
for i in range(len(suction_pressure_values)):
144+
if validation_mask[i]:
145+
sp = suction_pressure_values[i]
146+
dp = discharge_pressure_values[i]
147+
if intermediate_pressure_values is not None:
148+
ip = intermediate_pressure_values[i]
149+
if not (sp <= ip <= dp):
150+
raise ProcessPressureRatioValidationException(
151+
message=f"Invalid pressures at index {i+1}: suction pressure ({sp}) must be less than intermediate pressure ({ip}), which must be less than discharge pressure ({dp})."
152+
)
153+
else:
154+
if not (sp <= dp):
155+
raise ProcessPressureRatioValidationException(
156+
message=f"Invalid pressures at index {i+1}: suction pressure ({sp}) must be less than discharge pressure ({dp})."
157+
)
150158

151159

152160
class ConsumerFunctionMapper:
@@ -289,19 +297,31 @@ def _map_pump(
289297
)
290298
fluid_density = ExpressionTimeSeriesFluidDensity(time_series_expression=fluid_density_expression)
291299

300+
pressure_validation_mask = [
301+
bool(_rate * _regularity > 0)
302+
for _rate, _regularity in zip(rate_standard_m3_day.get_stream_day_values(), period_regularity.values)
303+
if _rate is not None
304+
]
305+
292306
suction_pressure_expression = TimeSeriesExpression(
293307
expression=model.suction_pressure, expression_evaluator=period_evaluator
294308
)
295-
suction_pressure = ExpressionTimeSeriesPressure(time_series_expression=suction_pressure_expression)
309+
suction_pressure = ExpressionTimeSeriesPressure(
310+
time_series_expression=suction_pressure_expression,
311+
validation_mask=pressure_validation_mask,
312+
)
296313

297314
discharge_pressure_expression = TimeSeriesExpression(
298315
expression=model.discharge_pressure, expression_evaluator=period_evaluator
299316
)
300-
discharge_pressure = ExpressionTimeSeriesPressure(time_series_expression=discharge_pressure_expression)
317+
discharge_pressure = ExpressionTimeSeriesPressure(
318+
time_series_expression=discharge_pressure_expression,
319+
validation_mask=pressure_validation_mask,
320+
)
301321

302322
validate_increasing_pressure(
303-
suction_pressure=suction_pressure.get_values(),
304-
discharge_pressure=discharge_pressure.get_values(),
323+
suction_pressure=suction_pressure,
324+
discharge_pressure=discharge_pressure,
305325
)
306326

307327
return PumpConsumerFunction(
@@ -333,42 +353,52 @@ def _map_multiple_streams_compressor(
333353
if model.power_loss_factor is not None
334354
else None
335355
)
356+
357+
rates_per_stream: list[TimeSeriesFlowRate] = [
358+
ExpressionTimeSeriesFlowRate(
359+
time_series_expression=TimeSeriesExpression(
360+
rate_expression, expression_evaluator=expression_evaluator, condition=_map_condition(model)
361+
),
362+
regularity=regularity,
363+
consumption_rate_type=RateType.CALENDAR_DAY,
364+
)
365+
for rate_expression in model.rate_per_stream
366+
]
367+
368+
rates_per_stream_values = [rates.get_stream_day_values() for rates in rates_per_stream]
369+
sum_of_rates = [sum(values) for values in zip(*rates_per_stream_values)]
370+
371+
validation_mask = [bool(_rate * _regularity > 0) for _rate, _regularity in zip(sum_of_rates, regularity.values)]
372+
336373
suction_pressure = ExpressionTimeSeriesPressure(
337374
time_series_expression=TimeSeriesExpression(
338375
model.suction_pressure, expression_evaluator=expression_evaluator
339-
)
376+
),
377+
validation_mask=validation_mask,
340378
)
341379
discharge_pressure = ExpressionTimeSeriesPressure(
342380
time_series_expression=TimeSeriesExpression(
343381
model.discharge_pressure, expression_evaluator=expression_evaluator
344-
)
382+
),
383+
validation_mask=validation_mask,
345384
)
346385
interstage_control_pressure = (
347386
ExpressionTimeSeriesPressure(
348387
time_series_expression=TimeSeriesExpression(
349388
model.interstage_control_pressure, expression_evaluator=expression_evaluator
350-
)
389+
),
390+
validation_mask=validation_mask,
351391
)
352392
if model.interstage_control_pressure is not None
353393
else None
354394
)
355395

356396
validate_increasing_pressure(
357-
suction_pressure=suction_pressure.get_values(),
358-
discharge_pressure=discharge_pressure.get_values(),
359-
intermediate_pressure=interstage_control_pressure.get_values(),
397+
suction_pressure=suction_pressure,
398+
discharge_pressure=discharge_pressure,
399+
intermediate_pressure=interstage_control_pressure,
360400
)
361401

362-
rates_per_stream: list[TimeSeriesFlowRate] = [
363-
ExpressionTimeSeriesFlowRate(
364-
time_series_expression=TimeSeriesExpression(
365-
rate_expression, expression_evaluator=expression_evaluator, condition=_map_condition(model)
366-
),
367-
regularity=regularity,
368-
consumption_rate_type=RateType.CALENDAR_DAY,
369-
)
370-
for rate_expression in model.rate_per_stream
371-
]
372402
return CompressorConsumerFunction(
373403
power_loss_factor_expression=power_loss_factor,
374404
compressor_function=compressor_train_model,
@@ -402,20 +432,36 @@ def _map_compressor(
402432
else None
403433
)
404434

435+
stream_day_rate = ExpressionTimeSeriesFlowRate(
436+
time_series_expression=TimeSeriesExpression(
437+
model.rate, expression_evaluator=expression_evaluator, condition=_map_condition(model)
438+
),
439+
consumption_rate_type=RateType.CALENDAR_DAY,
440+
regularity=regularity,
441+
)
442+
443+
validation_mask = [
444+
bool(_rate * _regularity > 0)
445+
for _rate, _regularity in zip(stream_day_rate.get_stream_day_values(), regularity.values)
446+
if _rate is not None
447+
]
405448
suction_pressure = (
406449
ExpressionTimeSeriesPressure(
407450
time_series_expression=TimeSeriesExpression(
408451
model.suction_pressure, expression_evaluator=expression_evaluator
409-
)
452+
),
453+
validation_mask=validation_mask,
410454
)
411455
if model.suction_pressure
412456
else None
413457
)
458+
414459
discharge_pressure = (
415460
ExpressionTimeSeriesPressure(
416461
time_series_expression=TimeSeriesExpression(
417462
model.discharge_pressure, expression_evaluator=expression_evaluator
418-
)
463+
),
464+
validation_mask=validation_mask,
419465
)
420466
if model.discharge_pressure
421467
else None
@@ -425,20 +471,14 @@ def _map_compressor(
425471
suction_pressure is not None and discharge_pressure is not None
426472
): # to handle compressor sampled which may not have pressures
427473
validate_increasing_pressure(
428-
suction_pressure=suction_pressure.get_values(),
429-
discharge_pressure=discharge_pressure.get_values(),
474+
suction_pressure=suction_pressure,
475+
discharge_pressure=discharge_pressure,
430476
)
431477

432478
return CompressorConsumerFunction(
433479
power_loss_factor_expression=power_loss_factor,
434480
compressor_function=compressor_model,
435-
rate_expression=ExpressionTimeSeriesFlowRate(
436-
time_series_expression=TimeSeriesExpression(
437-
model.rate, expression_evaluator=expression_evaluator, condition=_map_condition(model)
438-
),
439-
consumption_rate_type=RateType.CALENDAR_DAY,
440-
regularity=regularity,
441-
),
481+
rate_expression=stream_day_rate,
442482
suction_pressure_expression=suction_pressure,
443483
discharge_pressure_expression=discharge_pressure,
444484
intermediate_pressure_expression=None,
@@ -510,25 +550,39 @@ def _map_compressor_system(
510550
for rate_expr in operational_setting.rates
511551
]
512552

513-
number_of_compressors = len(compressors)
553+
validation_mask = [
554+
[
555+
bool(_rate * _regularity > 0)
556+
for _rate, _regularity in zip(rate.get_stream_day_values(), regularity.values)
557+
if _rate is not None
558+
]
559+
for rate in rates
560+
]
514561

515562
if operational_setting.suction_pressure is not None:
516563
suction_pressures = [
517564
ExpressionTimeSeriesPressure(
518565
time_series_expression=TimeSeriesExpression(
519-
expression=operational_setting.suction_pressure, expression_evaluator=expression_evaluator
520-
)
566+
expression=operational_setting.suction_pressure,
567+
expression_evaluator=expression_evaluator,
568+
),
569+
validation_mask=mask,
521570
)
522-
] * number_of_compressors
571+
for mask in validation_mask
572+
]
523573
else:
524574
assert operational_setting.suction_pressures is not None
525575
suction_pressures = [
526576
ExpressionTimeSeriesPressure(
527577
time_series_expression=TimeSeriesExpression(
528578
expression=pressure_expr, expression_evaluator=expression_evaluator
529-
)
579+
),
580+
validation_mask=mask,
581+
)
582+
for pressure_expr, mask in zip(
583+
operational_setting.suction_pressures,
584+
validation_mask,
530585
)
531-
for pressure_expr in operational_setting.suction_pressures
532586
]
533587

534588
if operational_setting.discharge_pressure is not None:
@@ -537,24 +591,30 @@ def _map_compressor_system(
537591
time_series_expression=TimeSeriesExpression(
538592
expression=operational_setting.discharge_pressure,
539593
expression_evaluator=expression_evaluator,
540-
)
594+
),
595+
validation_mask=mask,
541596
)
542-
] * number_of_compressors
597+
for mask in validation_mask
598+
]
543599
else:
544600
assert operational_setting.discharge_pressures is not None
545601
discharge_pressures = [
546602
ExpressionTimeSeriesPressure(
547603
time_series_expression=TimeSeriesExpression(
548604
expression=pressure_expr, expression_evaluator=expression_evaluator
549-
)
605+
),
606+
validation_mask=mask,
607+
)
608+
for pressure_expr, mask in zip(
609+
operational_setting.discharge_pressures,
610+
validation_mask,
550611
)
551-
for pressure_expr in operational_setting.discharge_pressures
552612
]
553613

554614
for suction_pressure, discharge_pressure in zip(suction_pressures, discharge_pressures):
555615
validate_increasing_pressure(
556-
suction_pressure=suction_pressure.get_values(),
557-
discharge_pressure=discharge_pressure.get_values(),
616+
suction_pressure=suction_pressure,
617+
discharge_pressure=discharge_pressure,
558618
)
559619

560620
core_setting = CompressorSystemOperationalSettingExpressions(
@@ -683,8 +743,8 @@ def _map_pump_system(
683743

684744
for suction_pressure, discharge_pressure in zip(suction_pressures, discharge_pressures):
685745
validate_increasing_pressure(
686-
suction_pressure=suction_pressure.get_values(),
687-
discharge_pressure=discharge_pressure.get_values(),
746+
suction_pressure=suction_pressure,
747+
discharge_pressure=discharge_pressure,
688748
)
689749
operational_settings.append(
690750
PumpSystemOperationalSettingExpressions(

0 commit comments

Comments
 (0)