Skip to content

Commit ecbacfa

Browse files
authored
refactor: assign ports to all compressor models with common shaft (#1291)
refactor: assign ports to all compressor models that receive fluid streams refactor: make ports from splitters and mixers
1 parent da0726f commit ecbacfa

File tree

8 files changed

+169
-250
lines changed

8 files changed

+169
-250
lines changed

src/libecalc/domain/process/compressor/core/train/base.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ def __init__(
4747
calculate_max_rate: bool | None = False,
4848
stage_number_interstage_pressure: int | None = None,
4949
):
50-
# self.data_transfer_object = data_transfer_object
5150
self.energy_usage_adjustment_constant = energy_usage_adjustment_constant
5251
self.energy_usage_adjustment_factor = energy_usage_adjustment_factor
5352
self.stages = stages

src/libecalc/domain/process/compressor/core/train/compressor_train_common_shaft.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from libecalc.domain.process.compressor.core.train.base import CompressorTrainModel
1515
from libecalc.domain.process.compressor.core.train.stage import CompressorTrainStage
1616
from libecalc.domain.process.compressor.core.train.train_evaluation_input import CompressorTrainEvaluationInput
17+
from libecalc.domain.process.compressor.core.train.types import StreamPort
1718
from libecalc.domain.process.compressor.core.train.utils.common import (
1819
EPSILON,
1920
POWER_CALCULATION_TOLERANCE,
@@ -88,6 +89,14 @@ def __init__(
8889
maximum_discharge_pressure=maximum_discharge_pressure,
8990
stage_number_interstage_pressure=stage_number_interstage_pressure,
9091
)
92+
93+
self.ports: list[StreamPort] = [
94+
StreamPort(is_inlet_port=True, connected_to_stage_no=0)
95+
] # inlet port for first stage
96+
self.inlet_port_connected_to_stage: dict[int, list[int]] = {key: [] for key in range(len(self.stages))}
97+
self.outlet_port_connected_to_stage: dict[int, list[int]] = {key: [] for key in range(len(self.stages))}
98+
99+
self._make_ports_from_splitters_and_mixers()
91100
self._validate_maximum_discharge_pressure()
92101
self._validate_stages(stages)
93102
self._validate_shaft()
@@ -96,6 +105,24 @@ def __init__(
96105
def is_variable_speed(self):
97106
return all(stage.compressor.compressor_chart.is_variable_speed for stage in self.stages)
98107

108+
def _make_ports_from_splitters_and_mixers(self):
109+
"""Create stream ports based on the splitters and mixers defined in each stage."""
110+
for i, stage in enumerate(self.stages):
111+
# Outlet ports from splitters
112+
if stage.splitter:
113+
for _ in range(stage.splitter.number_of_outputs - 1):
114+
self.ports.append(StreamPort(is_inlet_port=False, connected_to_stage_no=i))
115+
# Inlet ports from mixers
116+
if stage.mixer:
117+
for _ in range(stage.mixer.number_of_inputs - 1):
118+
self.ports.append(StreamPort(is_inlet_port=True, connected_to_stage_no=i))
119+
120+
for i, port in enumerate(self.ports):
121+
if port.is_inlet_port:
122+
self.inlet_port_connected_to_stage[port.connected_to_stage_no].append(i)
123+
else:
124+
self.outlet_port_connected_to_stage[port.connected_to_stage_no].append(i)
125+
99126
def evaluate_given_constraints(
100127
self,
101128
constraints: CompressorTrainEvaluationInput,

src/libecalc/domain/process/compressor/core/train/compressor_train_common_shaft_multiple_streams_and_pressures.py

Lines changed: 30 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@
77
from libecalc.common.errors.exceptions import IllegalStateException
88
from libecalc.common.fixed_speed_pressure_control import FixedSpeedPressureControl
99
from libecalc.common.logger import logger
10-
from libecalc.domain.component_validation_error import DomainValidationException, ProcessChartTypeValidationException
10+
from libecalc.domain.component_validation_error import DomainValidationException
1111
from libecalc.domain.process.compressor.core.results import CompressorTrainResultSingleTimeStep
1212
from libecalc.domain.process.compressor.core.train.compressor_train_common_shaft import CompressorTrainCommonShaft
1313
from libecalc.domain.process.compressor.core.train.stage import CompressorTrainStage
1414
from libecalc.domain.process.compressor.core.train.train_evaluation_input import CompressorTrainEvaluationInput
15-
from libecalc.domain.process.compressor.core.train.types import FluidStreamObjectForMultipleStreams
1615
from libecalc.domain.process.compressor.core.train.utils.common import EPSILON
1716
from libecalc.domain.process.compressor.core.train.utils.numeric_methods import (
1817
maximize_x_given_boolean_condition_function,
@@ -56,7 +55,6 @@ class CompressorTrainCommonShaftMultipleStreamsAndPressures(CompressorTrainCommo
5655

5756
def __init__(
5857
self,
59-
streams: list[FluidStreamObjectForMultipleStreams],
6058
energy_usage_adjustment_constant: float,
6159
energy_usage_adjustment_factor: float,
6260
stages: list[CompressorTrainStage],
@@ -67,7 +65,6 @@ def __init__(
6765
pressure_control: FixedSpeedPressureControl | None = None,
6866
stage_number_interstage_pressure: int | None = None,
6967
):
70-
logger.debug(f"Creating {type(self).__name__} with\nn_stages: {len(stages)} and n_streams: {len(streams)}")
7168
super().__init__(
7269
energy_usage_adjustment_constant=energy_usage_adjustment_constant,
7370
energy_usage_adjustment_factor=energy_usage_adjustment_factor,
@@ -79,19 +76,10 @@ def __init__(
7976
calculate_max_rate=calculate_max_rate,
8077
stage_number_interstage_pressure=stage_number_interstage_pressure,
8178
)
79+
logger.debug(f"Creating {type(self).__name__} with\n" f"n_stages: {len(stages)} and n_ports: {len(self.ports)}")
8280
if pressure_control and not isinstance(pressure_control, FixedSpeedPressureControl):
8381
raise TypeError(f"pressure_control must be of type FixedSpeedPressureControl, got {type(pressure_control)}")
8482
self._validate_stages(stages)
85-
self.streams = streams
86-
self.number_of_compressor_streams = len(self.streams)
87-
88-
self.inlet_stream_connected_to_stage: dict[int, list[int]] = {key: [] for key in range(len(self.stages))}
89-
self.outlet_stream_connected_to_stage: dict[int, list[int]] = {key: [] for key in range(len(self.stages))}
90-
for i, stream in enumerate(self.streams):
91-
if stream.is_inlet_stream:
92-
self.inlet_stream_connected_to_stage[stream.connected_to_stage_no].append(i)
93-
else:
94-
self.outlet_stream_connected_to_stage[stream.connected_to_stage_no].append(i)
9583

9684
@property
9785
def pressure_control_first_part(self) -> FixedSpeedPressureControl:
@@ -161,20 +149,6 @@ def _check_intermediate_pressure_stage_number_is_valid(
161149
logger.exception(msg)
162150
raise IllegalStateException(msg)
163151

164-
def _validate_stages(self, stages: list[CompressorTrainStage]):
165-
min_speed_per_stage = []
166-
max_speed_per_stage = []
167-
for stage in stages:
168-
max_speed_per_stage.append(stage.compressor.compressor_chart.maximum_speed)
169-
min_speed_per_stage.append(stage.compressor.compressor_chart.minimum_speed)
170-
171-
if max(min_speed_per_stage) > min(max_speed_per_stage):
172-
msg = "Variable speed compressors in compressor train have incompatible compressor charts."
173-
f" Stage {min_speed_per_stage.index(max(min_speed_per_stage)) + 1}'s minimum speed is higher"
174-
f" than max speed of stage {max_speed_per_stage.index(min(max_speed_per_stage)) + 1}"
175-
176-
raise ProcessChartTypeValidationException(message=str(msg))
177-
178152
def train_inlet_stream(
179153
self,
180154
pressure: float,
@@ -235,7 +209,7 @@ def evaluate_given_constraints(
235209
# At this point, stream_rates is confirmed to be not None
236210
assert constraints.stream_rates is not None
237211

238-
inlet_rates = [constraints.stream_rates[i] for (i, stream) in enumerate(self.streams) if stream.is_inlet_stream]
212+
inlet_rates = [constraints.stream_rates[i] for (i, port) in enumerate(self.ports) if port.is_inlet_port]
239213
# Enough with one positive ingoing stream. Compressors with possible zero rates will recirculate
240214
positive_ingoing_streams = list(filter(lambda x: x > 0, list(inlet_rates)))
241215
if not any(positive_ingoing_streams):
@@ -298,13 +272,13 @@ def check_that_ingoing_streams_are_larger_than_or_equal_to_outgoing_streams(
298272
sum_of_ingoing_volume_up_to_current_stage_number += sum(
299273
[
300274
std_rates_std_m3_per_day_per_stream[inlet_stream_number]
301-
for inlet_stream_number in self.inlet_stream_connected_to_stage[stage_number]
275+
for inlet_stream_number in self.inlet_port_connected_to_stage[stage_number]
302276
]
303277
)
304278
sum_of_outgoing_volume_up_to_current_stage_number += sum(
305279
[
306280
std_rates_std_m3_per_day_per_stream[inlet_stream_number]
307-
for inlet_stream_number in self.outlet_stream_connected_to_stage[stage_number]
281+
for inlet_stream_number in self.outlet_port_connected_to_stage[stage_number]
308282
]
309283
)
310284
if sum_of_outgoing_volume_up_to_current_stage_number > sum_of_ingoing_volume_up_to_current_stage_number:
@@ -334,19 +308,19 @@ def convert_to_rates_for_each_compressor_train_stages(self, rates_per_stream: li
334308
stage_rates = []
335309
current_compressor_stage = 0
336310
previous_compressor_stage = 0
337-
for i, stream in enumerate(self.streams):
311+
for i, port in enumerate(self.ports):
338312
# the next stage with an additional ingoing/outgoing stream.
339313
# current_compressor_stage is not necessarily previous_compressor_stage + 1, unless streams are entering
340314
# or leaving the train between all stages
341-
current_compressor_stage = stream.connected_to_stage_no
315+
current_compressor_stage = port.connected_to_stage_no
342316
if i == 0:
343317
stage_rates.append(rates_per_stream[i])
344318
else:
345319
# if no ingoing/outgoing streams between compressor train stages,
346320
# keep the rate coming out of the previous compressor train stage
347321
for _ in range(previous_compressor_stage, current_compressor_stage):
348322
stage_rates.append(stage_rates[previous_compressor_stage])
349-
if stream.is_inlet_stream: # if there is an extra ingoing rate, then add it
323+
if port.is_inlet_port: # if there is an extra ingoing rate, then add it
350324
stage_rates[current_compressor_stage] += rates_per_stream[i]
351325
else: # if there is an extra outgoing rate, then add it
352326
stage_rates[current_compressor_stage] -= rates_per_stream[i]
@@ -377,14 +351,14 @@ def _get_max_std_rate_single_timestep(
377351
Returns:
378352
float: The maximum standard volume rate in Sm3/day for the specified stream. Returns 0.0 if no valid rate is found.
379353
"""
380-
constraints = constraints.create_conditions_with_new_input(new_stream_rates=[EPSILON] * len(self.streams))
354+
constraints = constraints.create_conditions_with_new_input(new_stream_rates=[EPSILON] * len(self.ports))
381355
assert constraints.stream_rates is not None
382356

383-
stream_to_maximize_connected_to_stage_no = self.streams[constraints.stream_to_maximize].connected_to_stage_no
357+
stream_to_maximize_connected_to_stage_no = self.ports[constraints.stream_to_maximize].connected_to_stage_no
384358

385359
# if it is not an ingoing stream --> currently no calculations done
386360
# Fixme: what should be returned? 0.0, NaN or something else?
387-
if not self.streams[constraints.stream_to_maximize].is_inlet_stream:
361+
if not self.ports[constraints.stream_to_maximize].is_inlet_port:
388362
return 0.0
389363

390364
std_rates_std_m3_per_day_per_stream = constraints.stream_rates.copy()
@@ -447,9 +421,9 @@ def calculate_compressor_train(
447421
stream, per-stage results (including conditions and power), and overall train performance.
448422
449423
Typical usage scenarios:
450-
1. Standard train: self.streams[0] is the main inlet.
451-
2. First subtrain (with intermediate pressure target): self.streams[0] is the inlet.
452-
3. Last subtrain (after a split): self.streams[0] may be an entering or leaving stream, and the inlet fluid
424+
1. Standard train: ports[0] is the main inlet.
425+
2. First subtrain (with intermediate pressure target): self.ports[0] is the inlet.
426+
3. Last subtrain (after a split): self.ports[0] may be an entering or leaving stream, and the inlet fluid
453427
is set from the first subtrain.
454428
455429
Args:
@@ -465,8 +439,8 @@ def calculate_compressor_train(
465439
# Make list of fluid streams for the ingoing streams
466440
assert isinstance(self._fluid_model, list) # for mypy
467441
fluid_streams = []
468-
for i, stream in enumerate(self.streams):
469-
if stream.is_inlet_stream:
442+
for i, port in enumerate(self.ports):
443+
if port.is_inlet_port:
470444
stream_fluid_model = self._fluid_model[i]
471445
if stream_fluid_model is not None:
472446
fluid_streams.append(
@@ -487,10 +461,10 @@ def calculate_compressor_train(
487461

488462
rates_out_of_splitter = [
489463
constraints.stream_rates[stream_number]
490-
for stream_number in self.outlet_stream_connected_to_stage.get(stage_number, [])
464+
for stream_number in self.outlet_port_connected_to_stage.get(stage_number, [])
491465
]
492466
streams_in_to_mixer = []
493-
for stream_number in self.inlet_stream_connected_to_stage.get(stage_number, []):
467+
for stream_number in self.inlet_port_connected_to_stage.get(stage_number, []):
494468
if stream_number > 0:
495469
if inlet_stream_counter < len(fluid_streams):
496470
# Create fluid at stage inlet conditions using fluid service
@@ -605,8 +579,8 @@ def find_and_calculate_for_compressor_train_with_two_pressure_requirements(
605579
)
606580
)
607581

608-
# set self.inlet_fluid based on outlet_stream_first_part
609-
compressor_train_last_part.streams[0].fluid_model = FluidModel(
582+
assert isinstance(compressor_train_last_part._fluid_model, list) # for mypy
583+
compressor_train_last_part._fluid_model[0] = FluidModel(
610584
composition=compressor_train_results_first_part_with_optimal_speed_result.stage_results[
611585
-1
612586
].outlet_stream.composition,
@@ -615,11 +589,6 @@ def find_and_calculate_for_compressor_train_with_two_pressure_requirements(
615589
].outlet_stream.fluid_model.eos_model,
616590
)
617591

618-
# Update fluid model to match the new composition
619-
# This ensures base class methods use the correct fluid properties/composition
620-
assert isinstance(compressor_train_last_part._fluid_model, list) # for mypy
621-
compressor_train_last_part._fluid_model[0] = compressor_train_last_part.streams[0].fluid_model
622-
623592
# Get the speed found for the last part - will be used to compare with the first part
624593
shaft_speed_last_part = compressor_train_last_part.find_fixed_shaft_speed_given_constraints(
625594
constraints=constraints_last_part,
@@ -720,16 +689,16 @@ def split_rates_on_stage_number(
720689
"""
721690
rates_first_part = [
722691
rates_per_stream[i]
723-
for i, stream in enumerate(compressor_train.streams)
724-
if stream.connected_to_stage_no < stage_number
692+
for i, port in enumerate(compressor_train.ports)
693+
if port.connected_to_stage_no < stage_number
725694
]
726695

727696
rates_last_part = [
728697
compressor_train.convert_to_rates_for_each_compressor_train_stages(rates_per_stream)[stage_number - 1]
729698
]
730699

731-
for i, stream in enumerate(compressor_train.streams):
732-
if stream.connected_to_stage_no >= stage_number:
700+
for i, port in enumerate(compressor_train.ports):
701+
if port.connected_to_stage_no >= stage_number:
733702
rates_last_part.append(rates_per_stream[i])
734703

735704
return rates_first_part, rates_last_part
@@ -763,16 +732,14 @@ def split_train_on_stage_number(
763732
"""
764733

765734
# Create streams for first part
766-
streams_first_part = [stream for stream in compressor_train.streams if stream.connected_to_stage_no < stage_number]
767735
assert isinstance(compressor_train._fluid_model, list) # for mypy
768736
fluid_model_first_part = [
769737
fluid_model
770-
for stream, fluid_model in zip(compressor_train.streams, compressor_train._fluid_model)
771-
if stream.connected_to_stage_no < stage_number
738+
for port, fluid_model in zip(compressor_train.ports, compressor_train._fluid_model)
739+
if port.connected_to_stage_no < stage_number
772740
]
773741

774742
compressor_train_first_part = CompressorTrainCommonShaftMultipleStreamsAndPressures(
775-
streams=streams_first_part,
776743
shaft=compressor_train.shaft,
777744
energy_usage_adjustment_constant=compressor_train.energy_usage_adjustment_constant,
778745
energy_usage_adjustment_factor=compressor_train.energy_usage_adjustment_factor,
@@ -789,34 +756,20 @@ def split_train_on_stage_number(
789756
compressor_train_first_part._fluid_model = fluid_model_first_part
790757

791758
# Create streams for last part
792-
streams_last_part = [
793-
deepcopy(compressor_train.streams[compressor_train.inlet_stream_connected_to_stage.get(0)[0]]) # type: ignore[index]
794-
] # placeholder for stream coming out of first train, updated at runtime
795-
streams_last_part.extend(
796-
[
797-
FluidStreamObjectForMultipleStreams(
798-
fluid_model=stream.fluid_model,
799-
is_inlet_stream=stream.is_inlet_stream,
800-
connected_to_stage_no=stream.connected_to_stage_no - stage_number,
801-
)
802-
for stream in compressor_train.streams
803-
if stream.connected_to_stage_no >= stage_number
804-
]
805-
)
806-
# Last part initially uses the main fluid model (placeholder)
759+
760+
# Last part initially uses the main fluid factory (placeholder)
807761
# This will be updated at runtime after the fluid model (composition) is changed
808762

809763
fluid_model_last_part = [deepcopy(compressor_train._fluid_model[0])]
810764
fluid_model_last_part.extend(
811765
[
812766
fluid_model
813-
for stream, fluid_model in zip(compressor_train.streams, compressor_train._fluid_model)
814-
if stream.connected_to_stage_no >= stage_number
767+
for port, fluid_model in zip(compressor_train.ports, compressor_train._fluid_model)
768+
if port.connected_to_stage_no >= stage_number
815769
]
816770
)
817771

818772
compressor_train_last_part = CompressorTrainCommonShaftMultipleStreamsAndPressures(
819-
streams=streams_last_part,
820773
energy_usage_adjustment_constant=compressor_train.energy_usage_adjustment_constant,
821774
energy_usage_adjustment_factor=compressor_train.energy_usage_adjustment_factor,
822775
stages=compressor_train.stages[stage_number:],
Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,11 @@
11
from __future__ import annotations
22

3-
from libecalc.common.logger import logger
4-
from libecalc.domain.component_validation_error import ProcessFluidModelValidationException
5-
from libecalc.domain.process.value_objects.fluid_stream.fluid_model import FluidModel
6-
7-
8-
class FluidStreamObjectForMultipleStreams:
9-
"""Inlet streams needs a fluid with composition attached
10-
Outlet stream is what comes out of the compressor.
11-
"""
123

4+
class StreamPort:
135
def __init__(
146
self,
15-
is_inlet_stream: bool,
16-
name: str | None = None,
17-
fluid_model: FluidModel | None = None,
7+
is_inlet_port: bool,
188
connected_to_stage_no: int = 0,
199
):
20-
self.name = name
21-
self.fluid_model = fluid_model
22-
self.is_inlet_stream = is_inlet_stream
10+
self.is_inlet_port = is_inlet_port
2311
self.connected_to_stage_no = connected_to_stage_no
24-
self.check_valid_input()
25-
26-
def check_valid_input(self):
27-
if not self.is_inlet_stream and self.fluid_model:
28-
msg = "Outgoing stream should not have a fluid model defined"
29-
logger.error(msg)
30-
31-
raise ProcessFluidModelValidationException(message=str(msg))
32-
33-
if self.is_inlet_stream and not self.fluid_model:
34-
msg = "Ingoing stream needs a fluid model to be defined"
35-
logger.error(msg)
36-
raise ProcessFluidModelValidationException(message=str(msg))

0 commit comments

Comments
 (0)