Skip to content

Commit 2a52ea5

Browse files
committed
feat(libecalc): add closed-form liquid pump process simulation
Introduce a liquid pump process graph under the PROCESS_SIMULATIONS domain, kept separate from the gas FluidStream pipeline. - Extend the closed-form Pump result with efficiency, specific shaft work, shaft power and speed at the operating point (PumpEvaluationResult). - Add PumpProcessSimulation: a fixed liquid graph of inlet, recirculation mixer, pump, recirculation splitter, choke and outlet with typed LiquidProcessUnits, connections and a recirculation loop, evaluating per period to a liquid stream on every connection plus the pump result. - Add the PROCESS_SIMULATIONS "TYPE: PUMP" YAML, its mapper and YamlModel.get_pump_process_simulations().
1 parent 2d0b33b commit 2a52ea5

14 files changed

Lines changed: 751 additions & 106 deletions

File tree

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
from libecalc.common.errors.ecalc_validation_error import EcalcValidationException
2+
from libecalc.common.errors.exceptions import InvalidResourceException
3+
from libecalc.common.time_utils import Period
4+
from libecalc.common.variables import ExpressionEvaluator
5+
from libecalc.domain.process.value_objects.chart import Chart
6+
from libecalc.domain.process.value_objects.chart.chart import ChartData
7+
from libecalc.domain.regularity import Regularity
8+
from libecalc.domain.resource import Resources
9+
from libecalc.presentation.yaml.domain.expression_time_series_flow_rate import ExpressionTimeSeriesFlowRate
10+
from libecalc.presentation.yaml.domain.expression_time_series_fluid_density import ExpressionTimeSeriesFluidDensity
11+
from libecalc.presentation.yaml.domain.expression_time_series_pressure import ExpressionTimeSeriesPressure
12+
from libecalc.presentation.yaml.domain.reference_service import ReferenceService
13+
from libecalc.presentation.yaml.domain.time_series_expression import TimeSeriesExpression
14+
from libecalc.presentation.yaml.mappers.charts.user_defined_chart_data import UserDefinedChartData
15+
from libecalc.presentation.yaml.yaml_types.facility_model.yaml_facility_model import (
16+
YamlPumpChartSingleSpeed,
17+
)
18+
from libecalc.presentation.yaml.yaml_types.process.yaml_process_simulation import YamlPumpProcessSimulation
19+
from libecalc.process.pump.liquid_stream import LiquidStream
20+
from libecalc.process.pump.pump_process_simulation import (
21+
PumpProcessInput,
22+
PumpProcessPeriodInput,
23+
PumpProcessSimulation,
24+
)
25+
26+
27+
class PumpProcessSimulationMapper:
28+
def __init__(
29+
self,
30+
expression_evaluator: ExpressionEvaluator,
31+
reference_service: ReferenceService,
32+
resources: Resources,
33+
process_simulation_period: Period,
34+
):
35+
self._expression_evaluator = expression_evaluator.get_subset_for_period(process_simulation_period)
36+
self._reference_service = reference_service
37+
self._resources = resources
38+
39+
def map(
40+
self,
41+
yaml_process_simulation: YamlPumpProcessSimulation,
42+
) -> tuple[PumpProcessSimulation, PumpProcessInput]:
43+
chart_data = self._get_chart_data(yaml_process_simulation.pump_model.chart)
44+
chart = Chart(chart_data)
45+
minimum_flow_rate = (
46+
yaml_process_simulation.pump_model.minimum_flow_rate
47+
if yaml_process_simulation.pump_model.minimum_flow_rate is not None
48+
else chart.minimum_rate
49+
)
50+
simulation = PumpProcessSimulation(
51+
name=yaml_process_simulation.name,
52+
chart=chart_data,
53+
minimum_flow_rate_m3_per_hour=minimum_flow_rate,
54+
)
55+
56+
regularity = Regularity(
57+
expression_evaluator=self._expression_evaluator,
58+
target_period=self._expression_evaluator.get_period(),
59+
)
60+
rate = ExpressionTimeSeriesFlowRate(
61+
time_series_expression=TimeSeriesExpression(
62+
expression_evaluator=self._expression_evaluator,
63+
expression=yaml_process_simulation.inlet.rate,
64+
),
65+
regularity=regularity,
66+
)
67+
rate_values = rate.get_stream_day_values()
68+
pressure_validation_mask = [value > 0 for value in rate_values]
69+
suction_pressure = ExpressionTimeSeriesPressure(
70+
time_series_expression=TimeSeriesExpression(
71+
expression_evaluator=self._expression_evaluator,
72+
expression=yaml_process_simulation.inlet.pressure,
73+
),
74+
validation_mask=pressure_validation_mask,
75+
)
76+
discharge_pressure = ExpressionTimeSeriesPressure(
77+
time_series_expression=TimeSeriesExpression(
78+
expression_evaluator=self._expression_evaluator,
79+
expression=yaml_process_simulation.required_discharge_pressure,
80+
),
81+
validation_mask=pressure_validation_mask,
82+
)
83+
density = ExpressionTimeSeriesFluidDensity(
84+
time_series_expression=TimeSeriesExpression(
85+
expression_evaluator=self._expression_evaluator,
86+
expression=yaml_process_simulation.inlet.density,
87+
)
88+
)
89+
90+
periods = list(rate.get_periods())
91+
period_inputs = tuple(
92+
PumpProcessPeriodInput(
93+
period=period,
94+
inlet_stream=LiquidStream.from_volumetric_rate(
95+
volumetric_rate_m3_per_day=rate_value,
96+
pressure_bara=suction,
97+
density_kg_per_m3=density_value,
98+
),
99+
required_discharge_pressure_bara=discharge,
100+
)
101+
for period, rate_value, suction, discharge, density_value in zip(
102+
periods,
103+
rate_values,
104+
suction_pressure.get_values(),
105+
discharge_pressure.get_values(),
106+
density.get_values(),
107+
strict=True,
108+
)
109+
)
110+
return (
111+
simulation,
112+
PumpProcessInput(period_inputs=period_inputs),
113+
)
114+
115+
def _get_chart_data(self, reference: str) -> ChartData:
116+
model = self._reference_service.get_pump_model(reference)
117+
if model.head_margin != 0.0:
118+
raise EcalcValidationException("HEAD_MARGIN is not supported by the new pump process domain.")
119+
resource = self._resources.get(model.file)
120+
if resource is None:
121+
raise EcalcValidationException(f"Pump chart resource '{model.file}' was not found.")
122+
try:
123+
return UserDefinedChartData.from_resource(
124+
resource,
125+
units=model.units,
126+
is_single_speed=isinstance(model, YamlPumpChartSingleSpeed),
127+
)
128+
except InvalidResourceException as error:
129+
raise EcalcValidationException(str(error)) from error

src/libecalc/presentation/yaml/model.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
from libecalc.presentation.yaml.domain.time_series_resource import TimeSeriesResource
4646
from libecalc.presentation.yaml.mappers.component_mapper import EcalcModelMapper
4747
from libecalc.presentation.yaml.mappers.process_simulation_mapper import ProcessSimulationMapper
48+
from libecalc.presentation.yaml.mappers.pump_process_simulation_mapper import PumpProcessSimulationMapper
4849
from libecalc.presentation.yaml.mappers.variables_mapper import map_yaml_to_variables
4950
from libecalc.presentation.yaml.mappers.variables_mapper.get_global_time_vector import (
5051
InvalidEndDate,
@@ -61,13 +62,21 @@
6162
)
6263
from libecalc.presentation.yaml.yaml_models.yaml_model import YamlValidator
6364
from libecalc.presentation.yaml.yaml_reference_service import YamlReferenceService
65+
from libecalc.presentation.yaml.yaml_types.process.yaml_process_simulation import (
66+
YamlProcessSimulation,
67+
YamlPumpProcessSimulation,
68+
)
6469
from libecalc.presentation.yaml.yaml_validation_context import (
6570
ModelContext,
6671
ModelName,
6772
YamlModelValidationContext,
6873
YamlModelValidationContextNames,
6974
)
7075
from libecalc.process.process_pipeline.process_pipeline import ProcessPipeline
76+
from libecalc.process.pump.pump_process_simulation import (
77+
PumpProcessInput,
78+
PumpProcessSimulation,
79+
)
7180

7281
DEFAULT_START_TIME = datetime(1900, 1, 1)
7382

@@ -155,6 +164,8 @@ def get_process_simulations(self) -> tuple[list[ProcessPipeline], list[ProcessSi
155164
)
156165
process_pipelines = []
157166
for yaml_process_simulation in self._configuration.process_simulations:
167+
if not isinstance(yaml_process_simulation, YamlProcessSimulation):
168+
continue
158169
process_pipeline, process_simulation = mapper.map_process_simulation(
159170
yaml_process_simulation=yaml_process_simulation, process_periods=self.get_periods()
160171
)
@@ -163,6 +174,23 @@ def get_process_simulations(self) -> tuple[list[ProcessPipeline], list[ProcessSi
163174

164175
return process_pipelines, process_simulations
165176

177+
def get_pump_process_simulations(
178+
self,
179+
) -> list[tuple[PumpProcessSimulation, PumpProcessInput]]:
180+
self.validate_for_run()
181+
facility_resources, _ = self._resource_service.get_facility_resources()
182+
mapper = PumpProcessSimulationMapper(
183+
expression_evaluator=self.get_expression_evaluator(),
184+
process_simulation_period=self.period,
185+
resources=facility_resources,
186+
reference_service=self._get_reference_service(),
187+
)
188+
return [
189+
mapper.map(yaml_process_simulation)
190+
for yaml_process_simulation in self._configuration.process_simulations
191+
if isinstance(yaml_process_simulation, YamlPumpProcessSimulation)
192+
]
193+
166194
def get_periods(self) -> list[Period]:
167195
"""
168196
Get the global timevector for this model

src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from libecalc.presentation.yaml.yaml_types.fuel_type.yaml_fuel_type import YamlFuelType
3333
from libecalc.presentation.yaml.yaml_types.models import YamlConsumerModel, YamlFluidModel
3434
from libecalc.presentation.yaml.yaml_types.process.yaml_process_pipeline import YamlProcessPipeline
35-
from libecalc.presentation.yaml.yaml_types.process.yaml_process_simulation import YamlProcessSimulation
35+
from libecalc.presentation.yaml.yaml_types.process.yaml_process_simulation import YamlProcessSimulationInput
3636
from libecalc.presentation.yaml.yaml_types.process.yaml_process_units import YamlProcessUnit
3737
from libecalc.presentation.yaml.yaml_types.streams.yaml_inlet_stream import YamlInletStream
3838
from libecalc.presentation.yaml.yaml_types.time_series.yaml_time_series import YamlTimeSeriesCollection
@@ -468,9 +468,9 @@ def process_pipelines(self) -> dict[str, YamlProcessPipeline]:
468468
return process_pipelines
469469

470470
@property
471-
def process_simulations(self) -> list[YamlProcessSimulation]:
472-
process_simulations: list[YamlProcessSimulation] = []
473-
adapter = TypeAdapter(YamlProcessSimulation)
471+
def process_simulations(self) -> list[YamlProcessSimulationInput]:
472+
process_simulations: list[YamlProcessSimulationInput] = []
473+
adapter = TypeAdapter(YamlProcessSimulationInput)
474474
for process_simulation in self._get_yaml_list_or_empty(_PROCESS_SIMULATIONS_KEY):
475475
try:
476476
process_simulations.append(adapter.validate_python(process_simulation))

src/libecalc/presentation/yaml/yaml_models/yaml_model.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from libecalc.presentation.yaml.yaml_types.fuel_type.yaml_fuel_type import YamlFuelType
1818
from libecalc.presentation.yaml.yaml_types.models import YamlConsumerModel, YamlFluidModel
1919
from libecalc.presentation.yaml.yaml_types.process.yaml_process_pipeline import YamlProcessPipeline
20-
from libecalc.presentation.yaml.yaml_types.process.yaml_process_simulation import YamlProcessSimulation
20+
from libecalc.presentation.yaml.yaml_types.process.yaml_process_simulation import YamlProcessSimulationInput
2121
from libecalc.presentation.yaml.yaml_types.process.yaml_process_units import YamlProcessUnit
2222
from libecalc.presentation.yaml.yaml_types.streams.yaml_inlet_stream import YamlInletStream
2323
from libecalc.presentation.yaml.yaml_types.time_series.yaml_time_series import (
@@ -95,7 +95,7 @@ def process_pipelines(self) -> dict[str, YamlProcessPipeline]:
9595

9696
@property
9797
@abc.abstractmethod
98-
def process_simulations(self) -> Iterable[YamlProcessSimulation]:
98+
def process_simulations(self) -> Iterable[YamlProcessSimulationInput]:
9999
pass
100100

101101
@property

src/libecalc/presentation/yaml/yaml_reference_service.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
)
2727
from libecalc.presentation.yaml.yaml_types.models.yaml_enums import YamlModelType
2828
from libecalc.presentation.yaml.yaml_types.process.yaml_process_pipeline import YamlProcessPipeline
29-
from libecalc.presentation.yaml.yaml_types.process.yaml_process_simulation import YamlProcessSimulation
29+
from libecalc.presentation.yaml.yaml_types.process.yaml_process_simulation import YamlProcessSimulationInput
3030
from libecalc.presentation.yaml.yaml_types.process.yaml_process_units import YamlProcessUnit
3131
from libecalc.presentation.yaml.yaml_types.streams.yaml_inlet_stream import YamlInletStream
3232

@@ -35,7 +35,7 @@
3535
YamlModel = YamlConsumerModel | YamlFacilityModel
3636

3737
ReferenceType = (
38-
YamlModel | YamlFuelType | YamlInletStream | YamlProcessPipeline | YamlProcessSimulation | YamlProcessUnit
38+
YamlModel | YamlFuelType | YamlInletStream | YamlProcessPipeline | YamlProcessSimulationInput | YamlProcessUnit
3939
)
4040

4141
# Some models are referenced by other models, for example a compressor model will reference compressor chart models

src/libecalc/presentation/yaml/yaml_types/components/yaml_asset.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from libecalc.presentation.yaml.yaml_types.fuel_type.yaml_fuel_type import YamlFuelType
99
from libecalc.presentation.yaml.yaml_types.models import YamlConsumerModel, YamlFluidModel
1010
from libecalc.presentation.yaml.yaml_types.process.yaml_process_pipeline import YamlProcessPipeline
11-
from libecalc.presentation.yaml.yaml_types.process.yaml_process_simulation import YamlProcessSimulation
11+
from libecalc.presentation.yaml.yaml_types.process.yaml_process_simulation import YamlProcessSimulationInput
1212
from libecalc.presentation.yaml.yaml_types.process.yaml_process_units import YamlProcessUnit
1313
from libecalc.presentation.yaml.yaml_types.streams.yaml_inlet_stream import YamlInletStream
1414
from libecalc.presentation.yaml.yaml_types.time_series.yaml_time_series import YamlTimeSeriesCollection
@@ -74,7 +74,7 @@ class YamlAsset(YamlBase):
7474
title="PROCESS_PIPELINES",
7575
description="Defines process pipelines to use in process simulations.",
7676
)
77-
process_simulations: list[YamlProcessSimulation] = Field(
77+
process_simulations: list[YamlProcessSimulationInput] = Field(
7878
default_factory=list,
7979
title="PROCESS_SIMULATIONS",
8080
description="Defines one or more process simulations to be run.",

src/libecalc/presentation/yaml/yaml_types/process/yaml_process_references.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
type StreamRef = str
66
type ProcessPipelineReference = str # TODO: validate correct reference
77
type ProcessUnitReference = str
8+
type PumpChartReference = str

src/libecalc/presentation/yaml/yaml_types/process/yaml_process_simulation.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Annotated
1+
from typing import Annotated, Literal
22

33
from pydantic import Field
44

@@ -11,6 +11,7 @@
1111
from libecalc.presentation.yaml.yaml_types.process.yaml_process_references import (
1212
ProcessPipelineReference,
1313
ProcessUnitReference,
14+
PumpChartReference,
1415
)
1516
from libecalc.presentation.yaml.yaml_types.process.yaml_stream_distribution import YamlStreamDistribution
1617
from libecalc.process.process_solver.anti_surge.anti_surge_strategy import AntiSurgeType
@@ -62,3 +63,58 @@ class YamlProcessSimulation(YamlBase):
6263
description="Constraints per target. Key is pipeline name, value is list of constraints.",
6364
),
6465
]
66+
67+
68+
class YamlPumpProcessModel(YamlBase):
69+
chart: Annotated[
70+
PumpChartReference,
71+
Field(
72+
title="CHART",
73+
description="Reference to a pump chart defined in FACILITY_INPUTS.",
74+
),
75+
]
76+
minimum_flow_rate: Annotated[
77+
float | None,
78+
Field(
79+
title="MINIMUM_FLOW_RATE",
80+
description="Minimum pump flow in m3/h. Defaults to the chart minimum.",
81+
),
82+
] = None
83+
84+
85+
class YamlPumpProcessInlet(YamlBase):
86+
rate: Annotated[
87+
YamlExpressionType,
88+
Field(title="RATE", description="Requested liquid rate in m3/day."),
89+
]
90+
pressure: Annotated[
91+
YamlExpressionType,
92+
Field(title="PRESSURE", description="Pump suction pressure in bara."),
93+
]
94+
density: Annotated[
95+
YamlExpressionType,
96+
Field(title="DENSITY", description="Liquid density in kg/m3."),
97+
]
98+
99+
100+
class YamlPumpProcessSimulation(YamlBase):
101+
type: Literal["PUMP"]
102+
name: str
103+
pump_model: Annotated[
104+
YamlPumpProcessModel,
105+
Field(title="PUMP_MODEL"),
106+
]
107+
inlet: Annotated[
108+
YamlPumpProcessInlet,
109+
Field(title="INLET"),
110+
]
111+
required_discharge_pressure: Annotated[
112+
YamlExpressionType,
113+
Field(
114+
title="REQUIRED_DISCHARGE_PRESSURE",
115+
description="Required pump discharge pressure in bara.",
116+
),
117+
]
118+
119+
120+
type YamlProcessSimulationInput = YamlProcessSimulation | YamlPumpProcessSimulation

src/libecalc/process/pump/liquid_stream.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def from_volumetric_rate(
6363
pressure_bara: float,
6464
density_kg_per_m3: float,
6565
) -> LiquidStream:
66-
"""Create a stream from an actual volumetric flow rate [m3/day]."""
66+
"""Create a stream from a volumetric flow rate [m3/day]."""
6767
mass_rate_kg_per_h = volumetric_rate_m3_per_day * density_kg_per_m3 / UnitConstants.HOURS_PER_DAY
6868
return cls(
6969
pressure_bara=pressure_bara,

0 commit comments

Comments
 (0)