Skip to content

Commit

Permalink
refactor: move validate operational conditions from compressor train,…
Browse files Browse the repository at this point in the history
… rename to validate model input (#256)

chore: add ModelInputFailureStatus

chore: fix tests
  • Loading branch information
olelod committed Oct 27, 2023
1 parent c7f5a99 commit 6b0c728
Show file tree
Hide file tree
Showing 7 changed files with 403 additions and 378 deletions.
1 change: 1 addition & 0 deletions src/libecalc/core/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .model_input_validation import ModelInputFailureStatus, validate_model_input
200 changes: 1 addition & 199 deletions src/libecalc/core/models/compressor/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from abc import abstractmethod
from functools import partial
from typing import List, Optional, Tuple
from typing import List

import numpy as np
from libecalc import dto
Expand All @@ -15,9 +15,6 @@
)
from libecalc.core.models.compressor.train.utils.numeric_methods import find_root
from libecalc.core.models.results import CompressorTrainResult
from libecalc.core.models.results.compressor import (
CompressorTrainCommonShaftFailureStatus,
)
from libecalc.core.models.turbine import TurbineModel
from numpy.typing import NDArray

Expand Down Expand Up @@ -71,201 +68,6 @@ def evaluate_streams(
):
raise NotImplementedError

def validate_operational_conditions(
self,
rate: NDArray[np.float64],
suction_pressure: NDArray[np.float64],
discharge_pressure: NDArray[np.float64],
intermediate_pressure: Optional[NDArray[np.float64]] = None,
) -> Tuple[
NDArray[np.float64],
NDArray[np.float64],
NDArray[np.float64],
NDArray[np.float64],
List[CompressorTrainCommonShaftFailureStatus],
]:
indices_to_validate = self._find_indices_to_validate(rate=rate)
validated_failure_status = [None] * len(suction_pressure)
validated_rate = rate.copy()
validated_suction_pressure = suction_pressure.copy()
validated_discharge_pressure = discharge_pressure.copy()
if intermediate_pressure is not None:
validated_intermediate_pressure = intermediate_pressure
if len(indices_to_validate) >= 1:
(
tmp_rate,
tmp_suction_pressure,
tmp_discharge_pressure,
tmp_intermediate_pressure,
tmp_failure_status,
) = self._validate_operational_conditions(
rate=rate[:, indices_to_validate] if np.ndim(rate) == 2 else rate[indices_to_validate],
suction_pressure=suction_pressure[indices_to_validate],
discharge_pressure=discharge_pressure[indices_to_validate],
intermediate_pressure=intermediate_pressure[indices_to_validate]
if intermediate_pressure is not None
else None,
)

if np.ndim(rate) == 2:
validated_rate[:, indices_to_validate] = tmp_rate
else:
validated_rate[indices_to_validate] = tmp_rate
validated_suction_pressure[indices_to_validate] = tmp_suction_pressure
validated_discharge_pressure[indices_to_validate] = tmp_discharge_pressure
if intermediate_pressure is not None:
validated_intermediate_pressure[indices_to_validate] = tmp_intermediate_pressure
for i, failure in enumerate(tmp_failure_status):
validated_failure_status[indices_to_validate[i]] = failure

# any remaining zero or negative suction/discharge pressures (for unvalidated time steps, others are already changed)
# must be set to 1 (for neqsim to initiate fluid streams)
validated_suction_pressure = np.where(validated_suction_pressure <= 0, 1, validated_suction_pressure)
validated_discharge_pressure = np.where(validated_discharge_pressure <= 0, 1, validated_discharge_pressure)

return (
validated_rate,
validated_suction_pressure,
validated_discharge_pressure,
validated_intermediate_pressure if intermediate_pressure is not None else None,
validated_failure_status,
)

@staticmethod
def _find_indices_to_validate(rate: NDArray[np.float64]) -> List[int]:
"""Find indices of array where rate(s) are positive.
For a 1D array, this means returning the indices where rate is positive.
For a 2D array, this means returning the indices where at least one rate is positive (along 0-axis).
"""
return np.where(np.any(rate != 0, axis=0) if np.ndim(rate) == 2 else rate != 0)[0].tolist()

@staticmethod
def _validate_operational_conditions(
rate: NDArray[np.float64],
suction_pressure: NDArray[np.float64],
discharge_pressure: NDArray[np.float64],
intermediate_pressure: Optional[NDArray[np.float64]] = None,
) -> Tuple[
NDArray[np.float64],
NDArray[np.float64],
NDArray[np.float64],
NDArray[np.float64],
List[CompressorTrainCommonShaftFailureStatus],
]:
"""
Checks for negative or zero values in the input values to the compressor train.
The following is done:
- Time steps where rate is zero are not checked for validity
(but zero or negative pressures will still be changed to 1)
- Any pressures that are negative or zero are set to one, and all rates for that time step are set to zero
- Any negative rates are set to zero
- A failure_status describing the first failure encountered is returned
Returns only one failure_status. Checks the potential failures at each time step in the following order:
suction pressure, intermediate_pressure, discharge pressure and rate. If there are multiple failures,
only the first one will be returned. When the input is changed to fix the first failure, the next failure
will be reported, and so on.
Args:
rate: Input rate(s) to the compressor train
suction_pressure: Suction pressures for the compressor train
discharge_pressure: Discharge pressures for the compressor train
intermediate_pressure: Intermediate pressures for the compressor train (if any)
Returns:
Tuple with the (potentially) updated input arrays and a failure_status describing if any input is invalid
"""
validation_failures = [
CompressorTrainCommonShaftFailureStatus.INVALID_SUCTION_PRESSURE_INPUT,
CompressorTrainCommonShaftFailureStatus.INVALID_INTERMEDIATE_PRESSURE_INPUT,
CompressorTrainCommonShaftFailureStatus.INVALID_DISCHARGE_PRESSURE_INPUT,
CompressorTrainCommonShaftFailureStatus.INVALID_RATE_INPUT,
None,
]

input_rate = rate.copy()
input_suction_pressure = suction_pressure.copy()
input_intermediate_pressure = intermediate_pressure.copy() if intermediate_pressure is not None else None
input_discharge_pressure = discharge_pressure.copy()

if not np.all(rate >= 0):
logger.warning(
f"The rate(s) in the compressor train must have non negative values. Given values [Sm3/sd]: "
f"{rate.tolist()}. The affected time steps will not be calculated, and rate is set to zero."
)
rate = (
np.where(np.any(rate < 0, axis=0), 0, rate) if np.ndim(rate) == 2 else np.where(rate < 0, 0, rate)
) # if the rate for one stream is negative, set the rates for all streams to zero for that time step
if intermediate_pressure is not None:
if not np.all(intermediate_pressure > 0):
logger.warning(
f"Interstage pressure needs to be a positive value. Given values: {intermediate_pressure.tolist()}."
f" The affected time steps will not be calculated, and rate is set to zero."
)
rate = np.where(intermediate_pressure <= 0, 0, rate)
intermediate_pressure = np.where(intermediate_pressure <= 0, 1, intermediate_pressure)
if not np.all(suction_pressure > 0):
logger.warning(
f"Inlet pressure needs to be a positive value. Given values: {suction_pressure.tolist()}."
f" The affected time steps will not be calculated, and rate is set to zero."
)
rate = np.where(suction_pressure <= 0, 0, rate)
suction_pressure = np.where(suction_pressure <= 0, 1, suction_pressure)
if not np.all(discharge_pressure > 0):
logger.warning(
f"Outlet pressure needs to be a positive value. Given values: {discharge_pressure.tolist()}"
f" The affected time steps will not be calculated, and rate is set to zero."
)
rate = np.where(discharge_pressure <= 0, 0, rate)
discharge_pressure = np.where(discharge_pressure <= 0, 1, discharge_pressure)
if not np.all(discharge_pressure >= suction_pressure):
logger.warning(
f"Inlet pressure needs to be a less than or equal to outlet pressure. Given values for inlet"
f" pressure: {suction_pressure.tolist()}. Given values for outlet pressure:"
f" {discharge_pressure.tolist()}. The affected time steps will not be calculated,"
f" and rate is set to zero."
)
rate = np.where(discharge_pressure < suction_pressure, 0, rate)
# for multiple stream train, rate is 2D
if np.ndim(rate) == 2:
# check if any of the streams have changed value during validation, streams along axis 0, time along axis 1
invalid_rate_input = np.any(rate != input_rate, axis=0)
else:
invalid_rate_input = np.where(rate != input_rate, True, False)

invalid_suction_pressure_input = np.logical_or(
np.where(suction_pressure != input_suction_pressure, True, False),
np.where(suction_pressure > discharge_pressure, True, False),
)
invalid_discharge_pressure_input = np.where(discharge_pressure != input_discharge_pressure, True, False)
invalid_intermediate_pressure_input = (
np.where(intermediate_pressure != input_intermediate_pressure, True, False)
if intermediate_pressure is not None
else np.asarray([False] * len(suction_pressure))
)

failure_status = [
validation_failures[
[
invalid_suction_pressure,
invalid_intermediate_pressure,
invalid_discharge_pressure,
invalid_rate,
True, # This is to also pick up failure_status None
].index(True)
]
for invalid_rate, invalid_suction_pressure, invalid_intermediate_pressure, invalid_discharge_pressure in zip(
invalid_rate_input,
invalid_suction_pressure_input,
invalid_intermediate_pressure_input,
invalid_discharge_pressure_input,
)
]

return rate, suction_pressure, discharge_pressure, intermediate_pressure, failure_status


class CompressorWithTurbineModel(CompressorModel):
def __init__(
Expand Down
5 changes: 3 additions & 2 deletions src/libecalc/core/models/compressor/train/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from libecalc.common.logger import logger
from libecalc.common.stream import Stream
from libecalc.common.units import Unit
from libecalc.core.models import ModelInputFailureStatus, validate_model_input
from libecalc.core.models.compressor.base import CompressorModel
from libecalc.core.models.compressor.results import CompressorTrainResultSingleTimeStep
from libecalc.core.models.compressor.train.fluid import FluidStream
Expand Down Expand Up @@ -84,7 +85,7 @@ def evaluate_rate_ps_pd(
"""
logger.debug(f"Evaluating {type(self).__name__} given rate, suction and discharge pressure.")

rate, suction_pressure, discharge_pressure, _, input_failure_status = self.validate_operational_conditions(
rate, suction_pressure, discharge_pressure, _, input_failure_status = validate_model_input(
rate=rate,
suction_pressure=suction_pressure,
discharge_pressure=discharge_pressure,
Expand Down Expand Up @@ -125,7 +126,7 @@ def evaluate_rate_ps_pd(
)

for i, train_result in enumerate(train_results):
if input_failure_status[i]:
if input_failure_status[i] is not ModelInputFailureStatus.NO_FAILURE:
train_result.failure_status = input_failure_status[i]

return CompressorTrainResult(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from libecalc.common.logger import logger
from libecalc.common.stream import Stream
from libecalc.common.units import Unit, UnitConstants
from libecalc.core.models import ModelInputFailureStatus, validate_model_input
from libecalc.core.models.compressor.results import CompressorTrainResultSingleTimeStep
from libecalc.core.models.compressor.train.base import CompressorTrainModel
from libecalc.core.models.compressor.train.fluid import FluidStream
Expand Down Expand Up @@ -774,7 +775,7 @@ def evaluate_rate_ps_pint_pd(
discharge_pressure,
intermediate_pressure,
input_failure_status,
) = self.validate_operational_conditions(
) = validate_model_input(
rate=rate,
suction_pressure=suction_pressure,
discharge_pressure=discharge_pressure,
Expand Down Expand Up @@ -825,7 +826,7 @@ def evaluate_rate_ps_pint_pd(
)

for i, train_result in enumerate(train_results):
if input_failure_status[i]:
if input_failure_status[i] is not ModelInputFailureStatus.NO_FAILURE:
train_result.failure_status = input_failure_status[i]

return CompressorTrainResult(
Expand Down
Loading

0 comments on commit 6b0c728

Please sign in to comment.