Skip to content
Closed
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
21 changes: 21 additions & 0 deletions libecalc.iml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/.venv" />
<excludeFolder url="file://$MODULE_DIR$/docs" />
</content>
<orderEntry type="jdk" jdkName="Python 3.12 (libecalc)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="GOOGLE" />
<option name="myDocStringFormat" value="Google" />
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="py.test" />
</component>
</module>
198 changes: 198 additions & 0 deletions src/libecalc/domain/process/dummy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
"""
Dummy process implementation for testing purposes.
"""
import uuid
from datetime import datetime

from libecalc.domain.process.entities.process_units.compressor import Compressor
from libecalc.domain.process.entities.process_units.legacy_compressor.legacy_compressor import LegacyCompressor
from libecalc.domain.process.process_simulation import ProcessScenario, PressureControlConfig, AntiSurgeConfig, \
Constraint, IndividualStreamDistributionConfig, ProcessPipeline, create_process_scenario_id, ProcessSimulation
from libecalc.domain.process.process_solver.anti_surge import anti_surge_strategy
from libecalc.domain.process.process_system.serial_process_system import SerialProcessSystem
from libecalc.domain.process.value_objects.fluid_stream.fluid_stream import SimpleStream
from libecalc.presentation.yaml.domain.time_series_expression import TimeSeriesExpression
from libecalc.presentation.yaml.mappers.fluid_mapper import MEDIUM_MW_19P4

"""
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.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.process_error import OutsideCapacityError, RateTooHighError, RateTooLowError
from libecalc.domain.process.process_system.process_system import ProcessSystem, create_process_system_id, \
ProcessSystemId
from libecalc.domain.process.process_system.process_unit import ProcessUnitId, create_process_unit_id
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, FluidModel, EoSModel
from libecalc.presentation.yaml.mappers.charts.user_defined_chart_data import UserDefinedChartData


dummy_process_pipeline_id = uuid.uuid4()
process_scenario_id = create_process_scenario_id()
process_simulation_id = uuid.uuid4()

"""def process_solution_dummy() -> ProcessSolution:
# Find solution! (For now we "say" that we only have 1 solution, and we return the first good solution we find (or no solution, if exhaustive search
# for a solution yields no solution)

# TODO: We could and should load domain model from db, but for simplicity we wait


return ProcessSolution(
id=uuid.uuid4(),
process_problem_id=process_problem_id,
configuration={}
)
"""

def process_stream_dummy() -> SimpleStream:
fluid_model = FluidModel(eos_model=EoSModel.SRK, composition=MEDIUM_MW_19P4)
pressure = 20.0
temperature_kelvin = 273.15 + 30
standard_rate_m3_per_day = 4000000

return SimpleStream(
fluid_model=fluid_model,
pressure_bara=pressure,
temperature_kelvin=temperature_kelvin,
standard_rate_m3_per_day=standard_rate_m3_per_day,
)

def process_stream_distribution_dummy() -> IndividualStreamDistributionConfig:
return IndividualStreamDistributionConfig(
inlet_streams=[process_stream_dummy()]
)

def process_simulation_dummy() -> ProcessSimulation:
return ProcessSimulation(
id=process_simulation_id,
stream_distribution=process_stream_distribution_dummy(),
process_scenarios=[process_scenario_dummy()]
)

def process_scenario_dummy() -> ProcessScenario:
return ProcessScenario(
id=process_scenario_id,
process_pipeline_id=dummy_process_pipeline_id,
pressure_control_strategy=PressureControlConfig(
type="DOWNSTREAM_CHOKE"
),
anti_surge_strategy=AntiSurgeConfig(
type="COMMON_ASV",
),
constraint=Constraint(
outlet_pressure=200.0
),
# inlet_stream=process_system_dummy_stream()
)

def shaft_dummy() -> 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)


def chart_data_dummy() -> ChartData:
# TODO: 2 compressors use this chart data - is it sharable in db; but in domain objs it is VO?
# Should we enforce this in YAML too?
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 compressors_dummy() -> list[Compressor]:
common_shaft = shaft_dummy()
return [
Compressor(
process_unit_id=create_process_unit_id(),
compressor_chart=chart_data_dummy(),
fluid_service=NeqSimFluidService.instance(),
shaft=common_shaft,
),
Compressor(
process_unit_id=create_process_unit_id(),
compressor_chart=chart_data_dummy(),
fluid_service=NeqSimFluidService.instance(),
shaft=common_shaft,
)
]

def process_pipeline_dummy() -> ProcessPipeline:
# TODO: Process system ..?
propagators = [*compressors_dummy(),
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 ProcessPipeline(
id=dummy_process_pipeline_id,
stream_propagators=propagators
)


def process_system_dummy_streams() -> dict[datetime, SimpleStream | FluidStream]:
fluid_model = FluidModel(eos_model=EoSModel.SRK, composition=MEDIUM_MW_19P4)
pressure = 20.0
temperature_kelvin = 273.15 + 30
standard_rates_m3_per_day = [
4000000,
4000000,
4000000,
4000000,
4500000,
5000000,
5500000,
6000000,
6000000,
5500000,
5000000,
3000000,
3000000,
2000000,
1000000,
1000000,
500000,
500000,
500000,
200000,
200000,
0
]
# 1st of january every year from 2020 to 2040
timestamps = [
datetime(year, 1, 1) for year in range(2020, 2040)
]

return {
timestamp: SimpleStream(
fluid_model=fluid_model,
pressure_bara=pressure,
temperature_kelvin=temperature_kelvin,
standard_rate_m3_per_day=standard_rate,
)
for timestamp, standard_rate in zip(timestamps, standard_rates_m3_per_day)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 9 additions & 3 deletions src/libecalc/domain/process/entities/shaft/shaft.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,27 @@

ShaftId = NewType("ShaftId", UUID)


def create_shaft_id() -> ShaftId:
return ShaftId(uuid.uuid4())


class Shaft(ABC):
"""Abstract base class for a shaft.

Can be expanded to include more properties and methods as needed.
Name, id, units connected to it, etc

TODO: Instead of "that a compressor has a shaft", should we flip it and say that a shaft has compressors? Or the unit it is attached to?
ie, the shaft needs to know when a solver or algorithm changes it, and it needs to make sure that all connected units are updated w. same speed ...
should we then also have a "mechanical system", that is related to the shaft, motor, gear box etc? and also the compressor - but from a mechanical perspective
"""

def __init__(self, speed_rpm: float | None = None):
self._id = create_shaft_id()
self._speed_rpm = speed_rpm
self._id = create_shaft_id()
# TODO: The speed should not be set when creating a shaft, it is a part of the config/state of the process system,
# and can be changed by the solver/algorithm. We can have a "config" or "state" object that is passed to the shaft,
# and the shaft can update it when the speed is changed. TOTHINK: Would shared state where solver can change, and system must read
# from state be a better solution - ie inspired by react state management?

def get_id(self) -> ShaftId:
return self._id
Expand Down
Loading
Loading