From 4db62264c40ce01254990b7742f0c05afe48d7ff Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Wed, 11 Mar 2026 22:05:53 +0100 Subject: [PATCH 1/3] chore: init tracer bullet --- libecalc.iml | 14 ++ src/libecalc/domain/process/dummy.py | 158 ++++++++++++++++++ .../process/entities/process_units/choke.py | 19 ++- .../domain/process/process_simulation.py | 9 + src/libecalc/presentation/yaml/model.py | 12 +- 5 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 libecalc.iml create mode 100644 src/libecalc/domain/process/dummy.py create mode 100644 src/libecalc/domain/process/process_simulation.py diff --git a/libecalc.iml b/libecalc.iml new file mode 100644 index 0000000000..64976a1c33 --- /dev/null +++ b/libecalc.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/libecalc/domain/process/dummy.py b/src/libecalc/domain/process/dummy.py new file mode 100644 index 0000000000..a882a68487 --- /dev/null +++ b/src/libecalc/domain/process/dummy.py @@ -0,0 +1,158 @@ +""" +Dummy process implementation for testing purposes. +""" +""" +Prototyping... +""" +from ecalc_neqsim_wrapper import NeqSimFluidService, CacheConfig, NeqsimService, Py4JConfig +from libecalc.domain.process.compressor.core.train.stage import CompressorTrainStage +from libecalc.domain.process.entities.process_units.choke import Choke +from libecalc.domain.process.entities.process_units.compressor.compressor import Compressor +from libecalc.domain.process.entities.process_units.rate_modifier.rate_modifier import RateModifier +from libecalc.domain.process.entities.process_units.temperature_setter import TemperatureSetter +from libecalc.domain.process.entities.shaft import Shaft, VariableSpeedShaft +from libecalc.domain.process.process_solver.boundary import Boundary +from libecalc.domain.process.process_system.compressor_stage_process_unit import CompressorStageProcessUnit +from libecalc.domain.process.process_system.process_error import RateTooHighError, RateTooLowError, OutsideCapacityError +from libecalc.domain.process.process_system.process_system import ProcessSystem +from libecalc.domain.process.process_system.process_unit import create_process_unit_id, ProcessUnitId +from libecalc.domain.process.value_objects.chart import ChartCurve +from libecalc.domain.process.value_objects.chart.chart import ChartData +from libecalc.domain.process.value_objects.chart.chart_area_flag import ChartAreaFlag +from libecalc.domain.process.value_objects.fluid_stream import FluidModel, EoSModel, \ + FluidStream +from libecalc.presentation.yaml.mappers.charts.user_defined_chart_data import UserDefinedChartData +from libecalc.presentation.yaml.mappers.fluid_mapper import MEDIUM_MW_19P4 + +# Temporarily adding dummy/temp stage process unit here until we have one ready +class MyStageProcessUnit(CompressorStageProcessUnit): + def __init__(self, compressor_stage: CompressorTrainStage): + self._id = create_process_unit_id() + self._compressor_stage = compressor_stage + + def get_id(self) -> ProcessUnitId: + return self._id + + def get_speed_boundary(self) -> Boundary: + chart = self._compressor_stage.compressor.compressor_chart + return Boundary(min=chart.minimum_speed, max=chart.maximum_speed) + + def get_maximum_standard_rate(self, inlet_stream: FluidStream) -> float: + compressor_inlet_stream = self._compressor_stage.get_compressor_inlet_stream(inlet_stream_stage=inlet_stream) + density = compressor_inlet_stream.density + max_actual_rate = self._compressor_stage.compressor.compressor_chart.maximum_rate_as_function_of_speed( + self._compressor_stage.compressor.speed + ) + max_mass_rate = max_actual_rate * density + return self._compressor_stage.fluid_service.mass_rate_to_standard_rate( + fluid_model=compressor_inlet_stream.fluid_model, mass_rate_kg_per_h=max_mass_rate + ) + + def get_minimum_standard_rate(self, inlet_stream: FluidStream) -> float: + compressor_inlet_stream = self._compressor_stage.get_compressor_inlet_stream(inlet_stream_stage=inlet_stream) + density = compressor_inlet_stream.density + min_actual_rate = self._compressor_stage.compressor.compressor_chart.minimum_rate_as_function_of_speed( + self._compressor_stage.compressor.speed + ) + min_mass_rate = min_actual_rate * density + return self._compressor_stage.fluid_service.mass_rate_to_standard_rate( + fluid_model=compressor_inlet_stream.fluid_model, mass_rate_kg_per_h=min_mass_rate + ) + + def propagate_stream(self, inlet_stream: FluidStream) -> FluidStream: + result = self._compressor_stage.evaluate(inlet_stream_stage=inlet_stream) + if result.chart_area_flag == ChartAreaFlag.ABOVE_MAXIMUM_FLOW_RATE: + raise RateTooHighError() + if result.chart_area_flag == ChartAreaFlag.BELOW_MINIMUM_FLOW_RATE: + raise RateTooLowError() + + if not result.within_capacity: + raise OutsideCapacityError() + return result.outlet_stream + + +def process_system_dummy() -> ProcessSystem: + def chart_data() -> ChartData: + return UserDefinedChartData( + curves=[ + ChartCurve( + rate_actual_m3_hour=[3000.0, 3500.0, 4000.0, 4500.0], + polytropic_head_joule_per_kg=[8500.0,8000.0,7500.0,6500.0], + efficiency_fraction=[0.72, 0.75, 0.74, 0.70], + speed_rpm=7500.0, + ), + ChartCurve( + rate_actual_m3_hour=[4100.0, 4600.0, 5000.0, 5500.0, 6000.0, 6500.0], + polytropic_head_joule_per_kg=[16500.0,16500.0,15500.0,14500.0,13500.0,12000.0], + efficiency_fraction=[0.72, 0.73, 0.74, 0.74, 0.72, 0.70], + speed_rpm=10500.0, + ), + ], + control_margin=0.0, + ) + + def shaft() -> Shaft: + return VariableSpeedShaft(speed_rpm=10500.0) # TODO: Should not set speed here, but we may want to set min and max here ...(from data or explicit) + + ## e.g. loaded from db, after solving has taken place + def train() -> ProcessSystem: + common_shaft = shaft() + return ProcessSystem( + process_units=[ + MyStageProcessUnit( + compressor_stage=CompressorTrainStage( + compressor=Compressor( + compressor_chart=chart_data(), + fluid_service=NeqSimFluidService.instance(), + shaft=common_shaft + ), + temperature_setter=TemperatureSetter( + process_unit_id=create_process_unit_id(), + fluid_service=NeqSimFluidService.instance(), + required_temperature_kelvin=30+273.15 + ), + liquid_remover=None, + rate_modifier=RateModifier( + compressor_chart=chart_data(), + shaft=common_shaft + ), + fluid_service=NeqSimFluidService.instance(), + splitter=None, + mixer=None, + choke=None, + interstage_pressure_control=None, + ) + ), + MyStageProcessUnit( + compressor_stage=CompressorTrainStage( + compressor=Compressor( + compressor_chart=chart_data(), + fluid_service=NeqSimFluidService.instance(), + shaft=common_shaft + ), + temperature_setter=TemperatureSetter( + process_unit_id=create_process_unit_id(), + fluid_service=NeqSimFluidService.instance(), + required_temperature_kelvin=30+273.15 + ), + liquid_remover=None, + rate_modifier=RateModifier( + compressor_chart=chart_data(), + shaft=common_shaft + ), + fluid_service=NeqSimFluidService.instance(), + splitter=None, + mixer=None, + choke=None, + interstage_pressure_control=None, + ) + ), + Choke( # DownStreamChoke - default PressureControlMechanism when not specified + process_unit_id=create_process_unit_id(), + fluid_service=NeqSimFluidService.instance(), + pressure_change=0.0, # No need to choke...we meet outlet target pressure perfectly... + ) + ] + ) + + return train() diff --git a/src/libecalc/domain/process/entities/process_units/choke.py b/src/libecalc/domain/process/entities/process_units/choke.py index 1766392cb8..f145487e34 100644 --- a/src/libecalc/domain/process/entities/process_units/choke.py +++ b/src/libecalc/domain/process/entities/process_units/choke.py @@ -1,9 +1,14 @@ +from dataclasses import dataclass + from libecalc.domain.process.process_system.process_error import OutsideCapacityError -from libecalc.domain.process.process_system.process_unit import ProcessUnit, ProcessUnitId +from libecalc.domain.process.process_system.process_unit import ProcessUnit, ProcessUnitId, ProcessUnitProperties from libecalc.domain.process.value_objects.fluid_stream import FluidService, FluidStream +@dataclass(frozen=True) +class ChokeProperties(ProcessUnitProperties): + ... -class Choke(ProcessUnit): +class Choke(ProcessUnit[ChokeProperties]): def __init__( self, process_unit_id: ProcessUnitId, @@ -21,6 +26,16 @@ def get_id(self) -> ProcessUnitId: def pressure_change(self) -> float: return self._pressure_change + def get_properties(self) -> ChokeProperties: + return ChokeProperties() + + @classmethod + def from_properties(cls, properties: ChokeProperties) -> "Choke": + return cls( + process_unit_id=create_process_unit_id(), + fluid_service=FluidService(), + ) + def set_pressure_change(self, pressure_change: float) -> None: if pressure_change < 0: raise ValueError("Pressure_change cannot be negative") diff --git a/src/libecalc/domain/process/process_simulation.py b/src/libecalc/domain/process/process_simulation.py new file mode 100644 index 0000000000..8b00587529 --- /dev/null +++ b/src/libecalc/domain/process/process_simulation.py @@ -0,0 +1,9 @@ +from abc import ABC, abstractmethod + +from libecalc.domain.process.process_system.process_system import ProcessSystem + + +class ProcessSimulation(ABC): + + @abstractmethod + def get_process_systems(self) -> list[ProcessSystem]: ... \ No newline at end of file diff --git a/src/libecalc/presentation/yaml/model.py b/src/libecalc/presentation/yaml/model.py index a463c3abde..76deb078ac 100644 --- a/src/libecalc/presentation/yaml/model.py +++ b/src/libecalc/presentation/yaml/model.py @@ -27,11 +27,14 @@ from libecalc.domain.process.compressor.core.sampled import CompressorModelSampled from libecalc.domain.process.compressor.core.train.base import CompressorTrainModel from libecalc.domain.process.core.results import CompressorTrainResult, PumpModelResult +from libecalc.domain.process.dummy import process_system_dummy from libecalc.domain.process.evaluation_input import ( CompressorEvaluationInput, CompressorSampledEvaluationInput, PumpEvaluationInput, ) +from libecalc.domain.process.process_simulation import ProcessSimulation +from libecalc.domain.process.process_system.process_system import ProcessSystem from libecalc.domain.process.pump.pump import PumpModel from libecalc.domain.regularity import Regularity from libecalc.presentation.yaml.domain.category_service import CategoryService @@ -104,7 +107,7 @@ def get_fuel_usage(self) -> TimeSeriesStreamDayRate | None: return energy_usage -class YamlModel: +class YamlModel(ProcessSimulation): """ Class representing both the yaml and the resources. @@ -138,6 +141,13 @@ def __init__( self._id = uuid.uuid4() # ID used for "asset" energy container, which is the same as model? + def get_process_systems(self) -> list[ProcessSystem]: + self.validate_for_run() + # TODO: Do we want to get all process systems across different installations here, or dict per installation, or provide installation name/id? + return [ + process_system_dummy() # So, need to set up YAML for this one (or map from old yaml? and map to domain model) + ] + def get_emitter(self, container_id: uuid.UUID) -> Emitter | None: for installation in self.get_installations(): for emitter in installation.get_emitters(): From 5aa44421b6136f8dfab451d16bd6a9662a77937e Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Thu, 12 Mar 2026 14:35:52 +0100 Subject: [PATCH 2/3] chore: upd --- .../process/entities/process_units/choke.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/libecalc/domain/process/entities/process_units/choke.py b/src/libecalc/domain/process/entities/process_units/choke.py index f145487e34..ad0789c28b 100644 --- a/src/libecalc/domain/process/entities/process_units/choke.py +++ b/src/libecalc/domain/process/entities/process_units/choke.py @@ -4,11 +4,7 @@ from libecalc.domain.process.process_system.process_unit import ProcessUnit, ProcessUnitId, ProcessUnitProperties from libecalc.domain.process.value_objects.fluid_stream import FluidService, FluidStream -@dataclass(frozen=True) -class ChokeProperties(ProcessUnitProperties): - ... - -class Choke(ProcessUnit[ChokeProperties]): +class Choke(ProcessUnit): def __init__( self, process_unit_id: ProcessUnitId, @@ -26,16 +22,6 @@ def get_id(self) -> ProcessUnitId: def pressure_change(self) -> float: return self._pressure_change - def get_properties(self) -> ChokeProperties: - return ChokeProperties() - - @classmethod - def from_properties(cls, properties: ChokeProperties) -> "Choke": - return cls( - process_unit_id=create_process_unit_id(), - fluid_service=FluidService(), - ) - def set_pressure_change(self, pressure_change: float) -> None: if pressure_change < 0: raise ValueError("Pressure_change cannot be negative") From dfc1d3f418ff5441379d0248908655d7230457b6 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Thu, 12 Mar 2026 20:14:29 +0100 Subject: [PATCH 3/3] chore: upd --- src/libecalc/domain/process/dummy.py | 12 ++++++------ .../domain/process/entities/process_units/choke.py | 4 +--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/libecalc/domain/process/dummy.py b/src/libecalc/domain/process/dummy.py index a882a68487..82176dcbb1 100644 --- a/src/libecalc/domain/process/dummy.py +++ b/src/libecalc/domain/process/dummy.py @@ -1,10 +1,12 @@ """ Dummy process implementation for testing purposes. """ +from libecalc.domain.process.process_system.serial_process_system import SerialProcessSystem + """ Prototyping... """ -from ecalc_neqsim_wrapper import NeqSimFluidService, CacheConfig, NeqsimService, Py4JConfig +from ecalc_neqsim_wrapper import NeqSimFluidService from libecalc.domain.process.compressor.core.train.stage import CompressorTrainStage from libecalc.domain.process.entities.process_units.choke import Choke from libecalc.domain.process.entities.process_units.compressor.compressor import Compressor @@ -19,10 +21,8 @@ from libecalc.domain.process.value_objects.chart import ChartCurve from libecalc.domain.process.value_objects.chart.chart import ChartData from libecalc.domain.process.value_objects.chart.chart_area_flag import ChartAreaFlag -from libecalc.domain.process.value_objects.fluid_stream import FluidModel, EoSModel, \ - FluidStream +from libecalc.domain.process.value_objects.fluid_stream import FluidStream from libecalc.presentation.yaml.mappers.charts.user_defined_chart_data import UserDefinedChartData -from libecalc.presentation.yaml.mappers.fluid_mapper import MEDIUM_MW_19P4 # Temporarily adding dummy/temp stage process unit here until we have one ready class MyStageProcessUnit(CompressorStageProcessUnit): @@ -97,8 +97,8 @@ def shaft() -> Shaft: ## e.g. loaded from db, after solving has taken place def train() -> ProcessSystem: common_shaft = shaft() - return ProcessSystem( - process_units=[ + return SerialProcessSystem( + propagators=[ MyStageProcessUnit( compressor_stage=CompressorTrainStage( compressor=Compressor( diff --git a/src/libecalc/domain/process/entities/process_units/choke.py b/src/libecalc/domain/process/entities/process_units/choke.py index ad0789c28b..a6ee5bcc5f 100644 --- a/src/libecalc/domain/process/entities/process_units/choke.py +++ b/src/libecalc/domain/process/entities/process_units/choke.py @@ -1,7 +1,5 @@ -from dataclasses import dataclass - from libecalc.domain.process.process_system.process_error import OutsideCapacityError -from libecalc.domain.process.process_system.process_unit import ProcessUnit, ProcessUnitId, ProcessUnitProperties +from libecalc.domain.process.process_system.process_unit import ProcessUnit, ProcessUnitId from libecalc.domain.process.value_objects.fluid_stream import FluidService, FluidStream class Choke(ProcessUnit):