Skip to content

Commit a006e1e

Browse files
committed
feat: add reusable fluid definitions for process simulations
1 parent da4cd0f commit a006e1e

15 files changed

Lines changed: 274 additions & 57 deletions
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from libecalc.common.ddd import value_object
2+
from libecalc.common.errors.exceptions import ProgrammingError
3+
from libecalc.common.time_utils import Period
4+
from libecalc.presentation.yaml.domain.time_series_expression import TimeSeriesExpression
5+
from libecalc.process.fluid_stream.fluid_model import EoSModel, FluidComposition, FluidModel
6+
7+
8+
@value_object
9+
class TimeSeriesFluidComposition:
10+
water: TimeSeriesExpression
11+
nitrogen: TimeSeriesExpression
12+
CO2: TimeSeriesExpression
13+
methane: TimeSeriesExpression
14+
ethane: TimeSeriesExpression
15+
propane: TimeSeriesExpression
16+
i_butane: TimeSeriesExpression
17+
n_butane: TimeSeriesExpression
18+
i_pentane: TimeSeriesExpression
19+
n_pentane: TimeSeriesExpression
20+
n_hexane: TimeSeriesExpression
21+
22+
def get_value(self, period: Period) -> FluidComposition:
23+
"""Evaluate all component expressions and materialize the fluid composition for the given period."""
24+
try:
25+
index = self.methane.get_periods().index(period)
26+
except ValueError as error:
27+
raise ProgrammingError(f"Period {period} is not available for the fluid composition.") from error
28+
29+
return FluidComposition(
30+
water=self.water.get_masked_values()[index],
31+
nitrogen=self.nitrogen.get_masked_values()[index],
32+
CO2=self.CO2.get_masked_values()[index],
33+
methane=self.methane.get_masked_values()[index],
34+
ethane=self.ethane.get_masked_values()[index],
35+
propane=self.propane.get_masked_values()[index],
36+
i_butane=self.i_butane.get_masked_values()[index],
37+
n_butane=self.n_butane.get_masked_values()[index],
38+
i_pentane=self.i_pentane.get_masked_values()[index],
39+
n_pentane=self.n_pentane.get_masked_values()[index],
40+
n_hexane=self.n_hexane.get_masked_values()[index],
41+
)
42+
43+
44+
@value_object
45+
class TimeSeriesFluidModel:
46+
eos_model: EoSModel
47+
composition: TimeSeriesFluidComposition
48+
49+
def get_value(self, period: Period) -> FluidModel:
50+
"""Materialize a fluid model with the composition evaluated for the given period."""
51+
return FluidModel(
52+
eos_model=self.eos_model,
53+
composition=self.composition.get_value(period),
54+
)
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from libecalc.common.ddd import value_object
2+
from libecalc.ecalc_model.time_series_fluid_model import TimeSeriesFluidModel
23
from libecalc.presentation.yaml.domain.expression_time_series_flow_rate import ExpressionTimeSeriesFlowRate
34
from libecalc.presentation.yaml.domain.time_series_expression import TimeSeriesExpression
4-
from libecalc.process.fluid_stream.fluid_model import FluidModel
55

66

77
@value_object
88
class TimeSeriesStream:
9-
fluid_model: FluidModel
9+
fluid_model: TimeSeriesFluidModel
1010
pressure_bara: TimeSeriesExpression
1111
temperature_kelvin: TimeSeriesExpression
1212
standard_rate_m3_per_day: ExpressionTimeSeriesFlowRate

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
YamlVariableSpeedCompressorTrain,
2525
YamlVariableSpeedCompressorTrainMultipleStreamsAndPressures,
2626
)
27+
from libecalc.presentation.yaml.yaml_types.process.yaml_fluid_definitions import YamlFluidDefinition
2728
from libecalc.presentation.yaml.yaml_types.process.yaml_process_pipeline import YamlProcessPipeline
2829
from libecalc.presentation.yaml.yaml_types.process.yaml_process_units import YamlProcessUnitDefinition
2930
from libecalc.presentation.yaml.yaml_types.streams.yaml_inlet_stream import YamlInletStream
@@ -55,6 +56,9 @@ def get_yaml_path(self, reference: str) -> YamlPath: ...
5556
@abc.abstractmethod
5657
def get_fluid(self, reference: str) -> YamlFluidModel: ...
5758

59+
@abc.abstractmethod
60+
def get_fluid_definition(self, reference: str) -> YamlFluidDefinition: ...
61+
5862
@abc.abstractmethod
5963
def get_turbine(self, reference: str) -> YamlTurbine: ...
6064

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

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
1+
from typing import assert_never
2+
13
from libecalc.common.errors.ecalc_validation_error import EcalcValidationException
4+
from libecalc.common.variables import ExpressionEvaluator
5+
from libecalc.ecalc_model.time_series_fluid_model import TimeSeriesFluidComposition, TimeSeriesFluidModel
6+
from libecalc.expression.expression import ExpressionType
7+
from libecalc.presentation.yaml.domain.time_series_expression import TimeSeriesExpression
28
from libecalc.presentation.yaml.yaml_keywords import EcalcYamlKeywords
3-
from libecalc.presentation.yaml.yaml_types.models.yaml_fluid import YamlCompositionFluidModel, YamlPredefinedFluidModel
9+
from libecalc.presentation.yaml.yaml_types.models.yaml_fluid import (
10+
YamlCompositionFluidModel,
11+
YamlEosModel,
12+
YamlPredefinedFluidModel,
13+
)
14+
from libecalc.presentation.yaml.yaml_types.process.yaml_fluid_definitions import (
15+
YamlCompositionFluidDefinition,
16+
YamlFluidComposition,
17+
YamlFluidDefinition,
18+
YamlPredefinedFluidDefinition,
19+
)
420
from libecalc.process.fluid_stream.fluid_model import EoSModel, FluidComposition, FluidModel
521

622
"""
@@ -121,3 +137,69 @@ def composition_fluid_model_mapper(
121137
n_hexane=user_defined_composition.n_hexane,
122138
),
123139
)
140+
141+
142+
def fluid_definition_mapper(
143+
definition: YamlFluidDefinition,
144+
expression_evaluator: ExpressionEvaluator,
145+
) -> TimeSeriesFluidModel:
146+
match definition:
147+
case YamlPredefinedFluidDefinition():
148+
return predefined_fluid_definition_mapper(definition, expression_evaluator)
149+
case YamlCompositionFluidDefinition():
150+
return composition_fluid_definition_mapper(definition, expression_evaluator)
151+
case _:
152+
assert_never(definition)
153+
154+
155+
def predefined_fluid_definition_mapper(
156+
model_config: YamlPredefinedFluidDefinition,
157+
expression_evaluator: ExpressionEvaluator,
158+
) -> TimeSeriesFluidModel:
159+
predefined_composition = _predefined_fluid_composition_mapper[model_config.gas_type]
160+
161+
return _create_time_series_fluid_model(
162+
eos_model_type=model_config.eos_model,
163+
composition=predefined_composition,
164+
expression_evaluator=expression_evaluator,
165+
)
166+
167+
168+
def composition_fluid_definition_mapper(
169+
model_config: YamlCompositionFluidDefinition,
170+
expression_evaluator: ExpressionEvaluator,
171+
) -> TimeSeriesFluidModel:
172+
return _create_time_series_fluid_model(
173+
eos_model_type=model_config.eos_model,
174+
composition=model_config.composition,
175+
expression_evaluator=expression_evaluator,
176+
)
177+
178+
179+
def _create_time_series_fluid_model(
180+
eos_model_type: YamlEosModel,
181+
composition: FluidComposition | YamlFluidComposition,
182+
expression_evaluator: ExpressionEvaluator,
183+
) -> TimeSeriesFluidModel:
184+
def time_series_expression(expression: ExpressionType) -> TimeSeriesExpression:
185+
return TimeSeriesExpression(
186+
expression=expression,
187+
expression_evaluator=expression_evaluator,
188+
)
189+
190+
return TimeSeriesFluidModel(
191+
eos_model=_eos_model_mapper[eos_model_type],
192+
composition=TimeSeriesFluidComposition(
193+
water=time_series_expression(composition.water),
194+
nitrogen=time_series_expression(composition.nitrogen),
195+
CO2=time_series_expression(composition.CO2),
196+
methane=time_series_expression(composition.methane),
197+
ethane=time_series_expression(composition.ethane),
198+
propane=time_series_expression(composition.propane),
199+
i_butane=time_series_expression(composition.i_butane),
200+
n_butane=time_series_expression(composition.n_butane),
201+
i_pentane=time_series_expression(composition.i_pentane),
202+
n_pentane=time_series_expression(composition.n_pentane),
203+
n_hexane=time_series_expression(composition.n_hexane),
204+
),
205+
)

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

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,23 @@
2626
TimeSeriesSplitterConfiguration,
2727
TimeSeriesTemperatureSetterConfiguration,
2828
)
29+
from libecalc.ecalc_model.time_series_fluid_model import TimeSeriesFluidModel
2930
from libecalc.ecalc_model.time_series_stream import TimeSeriesStream
3031
from libecalc.expression.expression import ExpressionType
3132
from libecalc.presentation.yaml.domain.expression_time_series_flow_rate import ExpressionTimeSeriesFlowRate
3233
from libecalc.presentation.yaml.domain.reference_service import ReferenceService
3334
from libecalc.presentation.yaml.domain.time_series_expression import TimeSeriesExpression
3435
from libecalc.presentation.yaml.mappers.charts.user_defined_chart_data import UserDefinedChartData
3536
from libecalc.presentation.yaml.mappers.consumer_function_mapper import handle_condition_list
36-
from libecalc.presentation.yaml.mappers.fluid_mapper import (
37-
composition_fluid_model_mapper,
38-
predefined_fluid_model_mapper,
39-
)
37+
from libecalc.presentation.yaml.mappers.fluid_mapper import fluid_definition_mapper
4038
from libecalc.presentation.yaml.mappers.model import InvalidChartResourceException
4139
from libecalc.presentation.yaml.mappers.process.build_sections import ProcessSectionBuilder
4240
from libecalc.presentation.yaml.yaml_types.components.yaml_expression_type import YamlExpressionType
43-
from libecalc.presentation.yaml.yaml_types.models import YamlFluidModel
4441
from libecalc.presentation.yaml.yaml_types.models.yaml_compressor_stages import YamlControlMarginUnits
45-
from libecalc.presentation.yaml.yaml_types.models.yaml_fluid import YamlCompositionFluidModel, YamlPredefinedFluidModel
42+
from libecalc.presentation.yaml.yaml_types.process.yaml_fluid_definitions import YamlFluidDefinition
4643
from libecalc.presentation.yaml.yaml_types.process.yaml_process_pipeline import YamlProcessPipeline
4744
from libecalc.presentation.yaml.yaml_types.process.yaml_process_references import (
45+
DefinitionReference,
4846
InstanceReference,
4947
)
5048
from libecalc.presentation.yaml.yaml_types.process.yaml_process_simulation import (
@@ -62,7 +60,6 @@
6260
)
6361
from libecalc.presentation.yaml.yaml_types.streams.yaml_inlet_stream import YamlInletStream, YamlInletStreamRate
6462
from libecalc.presentation.yaml.yaml_types.yaml_data_or_file import YamlFile
65-
from libecalc.process.fluid_stream.fluid_model import FluidModel
6663
from libecalc.process.fluid_stream.fluid_service import FluidService
6764
from libecalc.process.fluid_stream.fluid_stream import FluidStream
6865
from libecalc.process.process_pipeline.process_pipeline import (
@@ -186,11 +183,13 @@ def _resolve_stream_reference(self, ref: str | YamlInletStream) -> YamlInletStre
186183
else:
187184
return ref
188185

189-
def _resolve_fluid_model_reference(self, ref: str | YamlFluidModel) -> YamlFluidModel:
190-
if isinstance(ref, str):
191-
return self._reference_service.get_fluid(reference=ref)
192-
else:
193-
return ref
186+
def _resolve_fluid_definition(
187+
self,
188+
reference: DefinitionReference | YamlFluidDefinition,
189+
) -> YamlFluidDefinition:
190+
if isinstance(reference, str):
191+
return self._reference_service.get_fluid_definition(reference)
192+
return reference
194193

195194
def _map_conditions(
196195
self, conditions: YamlExpressionType | list[YamlExpressionType] | None
@@ -235,14 +234,14 @@ def _map_temperature(self, temperature: ExpressionType) -> TimeSeriesExpression:
235234
expression=temperature,
236235
)
237236

238-
def _map_fluid_model(self, yaml_fluid_model: YamlFluidModel) -> FluidModel:
239-
match yaml_fluid_model:
240-
case YamlPredefinedFluidModel():
241-
return predefined_fluid_model_mapper(yaml_fluid_model)
242-
case YamlCompositionFluidModel():
243-
return composition_fluid_model_mapper(yaml_fluid_model)
244-
case _:
245-
assert_never(yaml_fluid_model)
237+
def _map_fluid_definition(
238+
self,
239+
fluid_definition: YamlFluidDefinition,
240+
) -> TimeSeriesFluidModel:
241+
return fluid_definition_mapper(
242+
fluid_definition,
243+
expression_evaluator=self._expression_evaluator,
244+
)
246245

247246
def _validate_and_map_pipeline_events(
248247
self,
@@ -347,14 +346,14 @@ def map_process_simulation(
347346

348347
case YamlMixerDefinition():
349348
yaml_stream = self._resolve_stream_reference(yaml_process_unit.sidestream)
350-
yaml_fluid_model = self._resolve_fluid_model_reference(yaml_stream.fluid_model)
349+
yaml_fluid_definition = self._resolve_fluid_definition(yaml_stream.fluid)
351350
unit = Mixer(fluid_service=self._fluid_service)
352351
problem_time_series_configurations[unit.get_id()] = TimeSeriesMixerConfiguration(
353352
sidestream=TimeSeriesStream(
354353
pressure_bara=self._map_pressure(yaml_stream.pressure),
355354
standard_rate_m3_per_day=self._map_rate(yaml_stream.rate),
356355
temperature_kelvin=self._map_temperature(yaml_stream.temperature),
357-
fluid_model=self._map_fluid_model(yaml_fluid_model),
356+
fluid_model=self._map_fluid_definition(yaml_fluid_definition),
358357
)
359358
)
360359

@@ -539,12 +538,12 @@ def map_process_simulation(
539538
)
540539

541540
yaml_stream = self._resolve_stream_reference(yaml_stream_distribution.inlet_stream)
542-
yaml_fluid_model = self._resolve_fluid_model_reference(yaml_stream.fluid_model)
541+
yaml_fluid_definition = self._resolve_fluid_definition(yaml_stream.fluid)
543542
inlet_stream = TimeSeriesStream(
544543
pressure_bara=self._map_pressure(yaml_stream.pressure),
545544
standard_rate_m3_per_day=self._map_rate(yaml_stream.rate),
546545
temperature_kelvin=self._map_temperature(yaml_stream.temperature),
547-
fluid_model=self._map_fluid_model(yaml_fluid_model),
546+
fluid_model=self._map_fluid_definition(yaml_fluid_definition),
548547
)
549548
stream_distribution = CommonStreamDistributionConfig(
550549
inlet_stream=inlet_stream,
@@ -554,13 +553,13 @@ def map_process_simulation(
554553
inlet_streams = []
555554
for inlet_stream in yaml_stream_distribution.inlet_streams:
556555
yaml_stream = self._resolve_stream_reference(inlet_stream)
557-
yaml_fluid_model = self._resolve_fluid_model_reference(yaml_stream.fluid_model)
556+
yaml_fluid_definition = self._resolve_fluid_definition(yaml_stream.fluid)
558557
inlet_streams.append(
559558
TimeSeriesStream(
560559
pressure_bara=self._map_pressure(yaml_stream.pressure),
561560
standard_rate_m3_per_day=self._map_rate(yaml_stream.rate),
562561
temperature_kelvin=self._map_temperature(yaml_stream.temperature),
563-
fluid_model=self._map_fluid_model(yaml_fluid_model),
562+
fluid_model=self._map_fluid_definition(yaml_fluid_definition),
564563
)
565564
)
566565
stream_distribution = IndividualStreamDistributionConfig(

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from libecalc.presentation.yaml.yaml_types.facility_model.yaml_facility_model import YamlFacilityModel
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
34+
from libecalc.presentation.yaml.yaml_types.process.yaml_fluid_definitions import YamlFluidDefinition
3435
from libecalc.presentation.yaml.yaml_types.process.yaml_process_pipeline import YamlProcessPipeline
3536
from libecalc.presentation.yaml.yaml_types.process.yaml_process_simulation import (
3637
YamlEcalcEvent,
@@ -51,6 +52,7 @@
5152
_PROCESS_PIPELINES_KEY = "PROCESS_PIPELINES"
5253
_INLET_STREAMS_KEY = "INLET_STREAMS"
5354
_FLUID_MODELS_KEY = "FLUID_MODELS"
55+
_FLUIDS_KEY = "FLUIDS"
5456
_PROCESS_SIMULATIONS_KEY = "PROCESS_SIMULATIONS"
5557
_ECALC_EVENTS_KEY = "ECALC_EVENTS"
5658
_PROCESS_EVENTS_KEY = "PROCESS_EVENTS"
@@ -456,17 +458,25 @@ def fluid_models(self) -> dict[str, YamlFluidModel]:
456458
@property
457459
def definitions(self) -> YamlDefinitions:
458460
process_units: dict[str, YamlProcessUnitDefinition] = {}
461+
fluids: dict[str, YamlFluidDefinition] = {}
459462
definitions = (
460463
self._internal_datamodel.get(_DEFINITIONS_KEY, {}) if isinstance(self._internal_datamodel, dict) else {}
461464
)
462-
raw = definitions.get(_PROCESS_UNITS_KEY, {}) if isinstance(definitions, dict) else {}
465+
raw_process_units = definitions.get(_PROCESS_UNITS_KEY, {}) if isinstance(definitions, dict) else {}
466+
raw_fluids = definitions.get(_FLUIDS_KEY, {}) if isinstance(definitions, dict) else {}
463467

464-
for name, unit_data in raw.items():
468+
for name, unit_data in raw_process_units.items():
465469
try:
466470
process_units[name] = TypeAdapter(YamlProcessUnitDefinition).validate_python(unit_data)
467471
except PydanticValidationError:
468472
pass
469-
return YamlDefinitions(process_units=process_units)
473+
for name, fluid_data in raw_fluids.items():
474+
try:
475+
fluids[name] = TypeAdapter(YamlFluidDefinition).validate_python(fluid_data)
476+
except PydanticValidationError:
477+
pass
478+
479+
return YamlDefinitions(process_units=process_units, fluids=fluids)
470480

471481
@property
472482
def process_pipelines(self) -> dict[str, YamlProcessPipeline]:

src/libecalc/presentation/yaml/yaml_reference_service.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
YamlTurbine,
2626
)
2727
from libecalc.presentation.yaml.yaml_types.models.yaml_enums import YamlModelType
28+
from libecalc.presentation.yaml.yaml_types.process.yaml_fluid_definitions import YamlFluidDefinition
2829
from libecalc.presentation.yaml.yaml_types.process.yaml_process_pipeline import YamlProcessPipeline
2930
from libecalc.presentation.yaml.yaml_types.process.yaml_process_simulation import (
3031
YamlProcessSimulation,
@@ -45,6 +46,7 @@
4546
| YamlProcessSimulation
4647
| YamlPumpProcessSimulation
4748
| YamlProcessUnitDefinition
49+
| YamlFluidDefinition
4850
)
4951

5052
# Some models are referenced by other models, for example a compressor model will reference compressor chart models
@@ -117,6 +119,12 @@ def __init__(
117119
references[process_unit_key] = process_unit
118120
reference_yaml_context[process_unit_key] = process_unit_path
119121

122+
fluids_path = YamlPath(keys=("DEFINITIONS", "FLUIDS"))
123+
for fluid_key, fluid in configuration.definitions.fluids.items():
124+
fluid_path = fluids_path.append(fluid_key)
125+
references[fluid_key] = fluid
126+
reference_yaml_context[fluid_key] = fluid_path
127+
120128
process_pipelines_path = YamlPath(keys=("PROCESS_PIPELINES",))
121129
for process_pipeline_key, process_pipeline in configuration.process_pipelines.items():
122130
process_pipeline_path = process_pipelines_path.append(process_pipeline_key)
@@ -154,6 +162,12 @@ def get_fluid(self, reference: str) -> YamlFluidModel:
154162
raise InvalidReferenceException("fluid model", reference)
155163
return model
156164

165+
def get_fluid_definition(self, reference: str) -> YamlFluidDefinition:
166+
fluid = self._resolve_yaml_reference(reference, "fluid definition")
167+
if not isinstance(fluid, get_args(get_args(YamlFluidDefinition)[0])):
168+
raise InvalidReferenceException("fluid definition", reference)
169+
return fluid
170+
157171
def get_turbine(self, reference: str) -> YamlTurbine:
158172
model = self._resolve_yaml_reference(reference, "turbine model")
159173
if not isinstance(model, YamlTurbine):

0 commit comments

Comments
 (0)