Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,17 @@ def get_id(self) -> ProcessUnitId:
def propagate_stream(self, inlet_stream: FluidStream) -> FluidStream:
actual_rate = inlet_stream.volumetric_rate_m3_per_hour
if actual_rate < self.minimum_flow_rate:
raise RateTooLowError()
raise RateTooLowError(
actual_rate=actual_rate,
boundary_rate=self.minimum_flow_rate,
process_unit_id=self._id,
)
if actual_rate > self.maximum_flow_rate:
raise RateTooHighError()
raise RateTooHighError(
actual_rate=actual_rate,
boundary_rate=self.maximum_flow_rate,
process_unit_id=self._id,
)

chart_curve_at_given_speed = self.compressor_chart.get_curve_by_speed(speed=self.speed)
if chart_curve_at_given_speed is not None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
from libecalc.domain.process.process_solver.anti_surge.anti_surge_strategy import AntiSurgeStrategy
from libecalc.domain.process.process_solver.configuration import Configuration
from libecalc.domain.process.process_solver.process_runner import ProcessRunner
from libecalc.domain.process.process_solver.solver import Solution
from libecalc.domain.process.process_solver.solver import (
OutsideCapacityEvent,
Solution,
SolverFailureStatus,
)
from libecalc.domain.process.process_solver.solvers.recirculation_solver import RecirculationConfiguration
from libecalc.domain.process.process_system.process_error import RateTooHighError
from libecalc.domain.process.process_system.process_system import ProcessSystemId
Expand Down Expand Up @@ -62,12 +66,29 @@ def apply(self, inlet_stream: FluidStream) -> Solution[Sequence[Configuration[Re
for loop_id, compressor in zip(self._recirculation_loop_ids, self._compressors, strict=True):
try:
inlet_stream_compressor = self._simulator.run(inlet_stream=inlet_stream, to_id=compressor.get_id())
except RateTooHighError:
return Solution(success=False, configuration=configurations)
if inlet_stream_compressor.standard_rate_sm3_per_day > compressor.get_maximum_standard_rate(
inlet_stream_compressor
):
return Solution(success=False, configuration=configurations)
except RateTooHighError as e:
return Solution(
success=False,
configuration=configurations,
failure_event=OutsideCapacityEvent(
status=SolverFailureStatus.ABOVE_MAXIMUM_FLOW_RATE,
actual_value=e.actual_rate,
boundary_value=e.boundary_rate,
source_id=e.process_unit_id,
),
)
max_actual_rate = compressor.maximum_flow_rate
if inlet_stream_compressor.volumetric_rate_m3_per_hour > max_actual_rate:
return Solution(
success=False,
configuration=configurations,
failure_event=OutsideCapacityEvent(
status=SolverFailureStatus.ABOVE_MAXIMUM_FLOW_RATE,
actual_value=inlet_stream_compressor.volumetric_rate_m3_per_hour,
boundary_value=max_actual_rate,
source_id=compressor.get_id(),
),
)
boundary = compressor.get_recirculation_range(inlet_stream=inlet_stream_compressor)
configuration: Configuration[RecirculationConfiguration] = Configuration(
simulation_unit_id=loop_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
DownstreamChokePressureControlStrategy,
)
from libecalc.domain.process.process_solver.pressure_control.upstream_choke import UpstreamChokePressureControlStrategy
from libecalc.domain.process.process_solver.solver import Solution
from libecalc.domain.process.process_solver.solver import (
Solution,
SolverFailureEvent,
SolverFailureStatus,
TargetNotAchievableEvent,
)
from libecalc.domain.process.process_solver.solvers.speed_solver import SpeedConfiguration
from libecalc.domain.process.value_objects.fluid_stream import FluidStream

Expand Down Expand Up @@ -89,6 +94,7 @@ def find_solution(

current_inlet = inlet_stream
overall_success = True
failure_event: SolverFailureEvent | None = None
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only first failure that occurs


for segment, target in zip(self._segments, pressure_targets):
segment.runner.apply_configuration(shaft_config)
Expand All @@ -112,12 +118,22 @@ def find_solution(
outlet = segment.runner.run(inlet_stream=current_inlet)
if not pressure_control_solution.success:
overall_success = False
if failure_event is None:
failure_event = pressure_control_solution.failure_event
elif outlet.pressure_bara < target:
overall_success = False
if failure_event is None:
failure_event = TargetNotAchievableEvent(
status=SolverFailureStatus.MAXIMUM_ACHIEVABLE_DISCHARGE_PRESSURE_BELOW_TARGET,
achievable_value=outlet.pressure_bara,
target_value=target.value,
source_id=segment.process_system_id,
)

current_inlet = outlet

return Solution(
success=overall_success,
configuration=list(all_configurations.values()),
failure_event=failure_event,
)
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
from libecalc.domain.process.process_solver.pressure_control.pressure_control_strategy import PressureControlStrategy
from libecalc.domain.process.process_solver.process_runner import ProcessRunner
from libecalc.domain.process.process_solver.search_strategies import BinarySearchStrategy, RootFindingStrategy
from libecalc.domain.process.process_solver.solver import Solution
from libecalc.domain.process.process_solver.solver import (
Solution,
SolverFailureStatus,
TargetNotAchievableEvent,
)
from libecalc.domain.process.process_solver.solvers.recirculation_solver import (
RecirculationConfiguration,
)
Expand Down Expand Up @@ -38,13 +42,15 @@ class OutletPressureSolver:
def __init__(
self,
shaft_id: ShaftId,
process_system_id: ProcessSystemId,
runner: ProcessRunner,
anti_surge_strategy: AntiSurgeStrategy,
pressure_control_strategy: PressureControlStrategy,
root_finding_strategy: RootFindingStrategy,
speed_boundary: Boundary,
) -> None:
self._shaft_id: Final = shaft_id
self._process_system_id: Final = process_system_id
self._root_finding_strategy: Final = root_finding_strategy
self._anti_surge_strategy: Final = anti_surge_strategy
self._simulator: Final = runner
Expand All @@ -69,6 +75,10 @@ def pressure_control_strategy(self) -> PressureControlStrategy:
def shaft_id(self) -> ShaftId:
return self._shaft_id

@property
def process_system_id(self) -> ProcessSystemId:
return self._process_system_id

def _get_initial_speed_boundary(self) -> Boundary:
return self._speed_boundary

Expand Down Expand Up @@ -135,7 +145,11 @@ def find_solution(
)

if not self._anti_surge_solution.success:
return Solution(success=False, configuration=list(configurations.values()))
return Solution(
success=False,
configuration=list(configurations.values()),
failure_event=self._anti_surge_solution.failure_event,
)

outlet_at_chosen_speed = self._get_outlet_stream(
inlet_stream=inlet_stream,
Expand All @@ -146,6 +160,12 @@ def find_solution(
return Solution(
success=False,
configuration=list(configurations.values()),
failure_event=TargetNotAchievableEvent(
status=SolverFailureStatus.MAXIMUM_ACHIEVABLE_DISCHARGE_PRESSURE_BELOW_TARGET,
achievable_value=outlet_at_chosen_speed.pressure_bara,
target_value=pressure_constraint.value,
source_id=self._process_system_id,
),
)

pressure_control_solution = self._pressure_control_strategy.apply(
Expand All @@ -156,7 +176,12 @@ def find_solution(
for pressure_control_configuration in pressure_control_solution.configuration:
configurations[pressure_control_configuration.simulation_unit_id] = pressure_control_configuration

failure_event = pressure_control_solution.failure_event
if isinstance(failure_event, TargetNotAchievableEvent) and failure_event.source_id is None:
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the failure occurs within common_asv or individual_asv there is no process_system_id available when the failure occurs. add it here.

failure_event = failure_event.with_source_id(self._process_system_id)

return Solution(
success=pressure_control_solution.success,
configuration=list(configurations.values()),
failure_event=failure_event,
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
from libecalc.domain.process.process_solver.pressure_control.pressure_control_strategy import PressureControlStrategy
from libecalc.domain.process.process_solver.process_runner import ProcessRunner
from libecalc.domain.process.process_solver.search_strategies import BinarySearchStrategy, RootFindingStrategy
from libecalc.domain.process.process_solver.solver import Solution
from libecalc.domain.process.process_solver.solver import (
Solution,
SolverFailureStatus,
TargetNotAchievableEvent,
)
from libecalc.domain.process.process_solver.solvers.downstream_choke_solver import ChokeConfiguration
from libecalc.domain.process.process_solver.solvers.recirculation_solver import (
RecirculationConfiguration,
Expand Down Expand Up @@ -61,6 +65,11 @@ def recirculation_func(config: RecirculationConfiguration) -> FluidStream:
value=min_configuration,
)
],
failure_event=TargetNotAchievableEvent(
status=SolverFailureStatus.MINIMUM_ACHIEVABLE_DISCHARGE_PRESSURE_ABOVE_TARGET,
achievable_value=min_pressure_stream.pressure_bara,
target_value=target_pressure.value,
),
)

solver = RecirculationSolver(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
from libecalc.domain.process.process_solver.pressure_control.pressure_control_strategy import PressureControlStrategy
from libecalc.domain.process.process_solver.process_runner import ProcessRunner
from libecalc.domain.process.process_solver.search_strategies import BinarySearchStrategy, RootFindingStrategy
from libecalc.domain.process.process_solver.solver import Solution
from libecalc.domain.process.process_solver.solver import (
Solution,
SolverFailureStatus,
TargetNotAchievableEvent,
)
from libecalc.domain.process.process_solver.solvers.downstream_choke_solver import ChokeConfiguration
from libecalc.domain.process.process_solver.solvers.recirculation_solver import (
RecirculationConfiguration,
Expand Down Expand Up @@ -57,6 +61,11 @@ def apply(
return Solution(
success=False,
configuration=minimum_achievable_pressure_configurations,
failure_event=TargetNotAchievableEvent(
status=SolverFailureStatus.MINIMUM_ACHIEVABLE_DISCHARGE_PRESSURE_ABOVE_TARGET,
achievable_value=minimum_achievable_pressure_stream.pressure_bara,
target_value=target_pressure.value,
),
)

n_stages = len(self._recirculation_loop_ids)
Expand Down Expand Up @@ -100,6 +109,7 @@ def recirculation_func(config: RecirculationConfiguration):
return Solution(
success=False,
configuration=configurations,
failure_event=solution.failure_event,
)

self._simulator.apply_configurations(configurations)
Expand Down Expand Up @@ -174,6 +184,11 @@ def apply(
return Solution(
success=False,
configuration=minimum_achievable_pressure_configurations,
failure_event=TargetNotAchievableEvent(
status=SolverFailureStatus.MINIMUM_ACHIEVABLE_DISCHARGE_PRESSURE_ABOVE_TARGET,
achievable_value=minimum_achievable_pressure_stream.pressure_bara,
target_value=target_pressure.value,
),
)

def get_outlet_stream(rate_fraction: float) -> FluidStream:
Expand Down
38 changes: 36 additions & 2 deletions src/libecalc/domain/process/process_solver/solver.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,52 @@
import abc
import dataclasses
from collections.abc import Callable, Sequence
from dataclasses import dataclass
from typing import Generic, TypeVar
from dataclasses import dataclass, field
from enum import Enum
from typing import Generic, Self, TypeVar

from libecalc.domain.process.process_solver.configuration import Configuration, OperatingConfiguration, SimulationUnitId
from libecalc.domain.process.process_system.process_system import ProcessSystemId
from libecalc.domain.process.process_system.process_unit import ProcessUnitId
from libecalc.domain.process.value_objects.fluid_stream import FluidStream

TConfiguration = TypeVar("TConfiguration")


class SolverFailureStatus(str, Enum):
ABOVE_MAXIMUM_FLOW_RATE = "ABOVE_MAXIMUM_FLOW_RATE"
BELOW_MINIMUM_FLOW_RATE = "BELOW_MINIMUM_FLOW_RATE"
MAXIMUM_ACHIEVABLE_DISCHARGE_PRESSURE_BELOW_TARGET = "MAXIMUM_ACHIEVABLE_DISCHARGE_PRESSURE_BELOW_TARGET"
MINIMUM_ACHIEVABLE_DISCHARGE_PRESSURE_ABOVE_TARGET = "MINIMUM_ACHIEVABLE_DISCHARGE_PRESSURE_ABOVE_TARGET"


@dataclass
class OutsideCapacityEvent:
Comment thread
tj098895 marked this conversation as resolved.
status: SolverFailureStatus
source_id: ProcessUnitId
actual_value: float | None = None
boundary_value: float | None = None


@dataclass
class TargetNotAchievableEvent:
status: SolverFailureStatus
achievable_value: float
target_value: float
source_id: ProcessSystemId | None = None

def with_source_id(self, source_id: ProcessSystemId) -> Self:
return dataclasses.replace(self, source_id=source_id)


SolverFailureEvent = OutsideCapacityEvent | TargetNotAchievableEvent


@dataclass
class Solution(Generic[TConfiguration]):
success: bool
configuration: TConfiguration
failure_event: SolverFailureEvent | None = field(default=None)

def get_configuration(
self: "Solution[Sequence[Configuration[OperatingConfiguration]]]",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
from libecalc.domain.process.process_solver.configuration import RecirculationConfiguration
from libecalc.domain.process.process_solver.float_constraint import FloatConstraint
from libecalc.domain.process.process_solver.search_strategies import RootFindingStrategy, SearchStrategy
from libecalc.domain.process.process_solver.solver import Solution, Solver
from libecalc.domain.process.process_solver.solver import (
OutsideCapacityEvent,
Solution,
Solver,
SolverFailureStatus,
TargetNotAchievableEvent,
)
from libecalc.domain.process.process_system.process_error import RateTooHighError, RateTooLowError
from libecalc.domain.process.value_objects.fluid_stream import FluidStream

Expand Down Expand Up @@ -50,9 +56,18 @@ def bool_func(x: float, mode: Literal["minimize", "maximize"]) -> tuple[bool, bo
boundary=self._recirculation_rate_boundary,
func=lambda x: bool_func(x, mode="minimize"),
)
except RateTooHighError:
except RateTooHighError as e:
# Flow is above stonewall at zero recirculation; adding recirculation cannot help.
return Solution(success=False, configuration=RecirculationConfiguration(recirculation_rate=minimum_rate))
return Solution(
success=False,
configuration=RecirculationConfiguration(recirculation_rate=minimum_rate),
failure_event=OutsideCapacityEvent(
status=SolverFailureStatus.ABOVE_MAXIMUM_FLOW_RATE,
actual_value=e.actual_rate,
boundary_value=e.boundary_rate,
source_id=e.process_unit_id,
),
)

target_pressure = self._target_pressure
if target_pressure is None:
Expand All @@ -73,16 +88,32 @@ def bool_func(x: float, mode: Literal["minimize", "maximize"]) -> tuple[bool, bo
minimum_outlet_stream = func(RecirculationConfiguration(recirculation_rate=minimum_rate))
if minimum_outlet_stream.pressure_bara <= target_pressure:
# Highest possible pressure is too low
is_success = minimum_outlet_stream.pressure_bara == target_pressure
return Solution(
success=minimum_outlet_stream.pressure_bara == target_pressure,
success=is_success,
configuration=RecirculationConfiguration(recirculation_rate=minimum_rate),
failure_event=None
if is_success
else TargetNotAchievableEvent(
status=SolverFailureStatus.MAXIMUM_ACHIEVABLE_DISCHARGE_PRESSURE_BELOW_TARGET,
achievable_value=minimum_outlet_stream.pressure_bara,
target_value=target_pressure.value,
),
)
maximum_outlet_stream = func(RecirculationConfiguration(recirculation_rate=maximum_rate))
if maximum_outlet_stream.pressure_bara >= target_pressure:
# Lowest possible pressure is too high
is_success = maximum_outlet_stream.pressure_bara == self._target_pressure
return Solution(
success=maximum_outlet_stream.pressure_bara == self._target_pressure,
success=is_success,
configuration=RecirculationConfiguration(recirculation_rate=maximum_rate),
failure_event=None
if is_success
else TargetNotAchievableEvent(
status=SolverFailureStatus.MINIMUM_ACHIEVABLE_DISCHARGE_PRESSURE_ABOVE_TARGET,
achievable_value=maximum_outlet_stream.pressure_bara,
target_value=target_pressure.value,
),
)

recirculation_rate = self._root_finding_strategy.find_root(
Expand Down
Loading
Loading