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..82176dcbb1 --- /dev/null +++ b/src/libecalc/domain/process/dummy.py @@ -0,0 +1,158 @@ +""" +Dummy process implementation for testing purposes. +""" +from libecalc.domain.process.process_system.serial_process_system import SerialProcessSystem + +""" +Prototyping... +""" +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 +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 FluidStream +from libecalc.presentation.yaml.mappers.charts.user_defined_chart_data import UserDefinedChartData + +# 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 SerialProcessSystem( + propagators=[ + 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..a6ee5bcc5f 100644 --- a/src/libecalc/domain/process/entities/process_units/choke.py +++ b/src/libecalc/domain/process/entities/process_units/choke.py @@ -2,7 +2,6 @@ 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): def __init__( self, 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():