Skip to content

Commit b6e06ce

Browse files
authored
chore: pinpoint outlet pressure target failures to connection (#1675)
Previously, an outlet pressure target failures was pinpointed to a pipeline. We have now implemented support for intermediate pressure targets, which means that there are more places than one place (outlet pressure + intermediate pressures) where we are not able to reach a target pressure. We therefore need to pinpoint that more specifically. It will now specify the section id of where it failed, which can unambigiously also pinpoint the last process unit and its outlet as where it actually failed. Refs: equinor/ecalc-internal#1992
1 parent d7b5527 commit b6e06ce

6 files changed

Lines changed: 39 additions & 20 deletions

File tree

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ repos:
3838
name: Check for typos in code
3939
additional_dependencies: [ tomli ]
4040
exclude: docs/docs/changelog/changelog.md|.*\.ipynb
41+
args: ["--write-changes"] # Instead of just complaining about typos, automatically fix them in the files.
4142
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
4243
rev: 1d7031e6d0bbfce76e5a625b0cec24fb22e517a0 # frozen: v9.25.0
4344

src/libecalc/process/process_pipeline/process_pipeline.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def __init__(
7777
):
7878
self._name = name
7979
self._process_pipeline_sections = process_pipeline_sections
80-
self._process_unit_connections = ProcessPipeline._create_process_unit_connections(
80+
self._process_unit_connections = ProcessPipeline._create_process_unit_connections( # NOTE: this can currently only be used in mapper, to create connections first time!
8181
process_pipeline_sections=process_pipeline_sections
8282
)
8383
self._process_pipeline_id = process_pipeline_id

src/libecalc/process/process_solver/multi_pressure_solver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def _find_solution(
139139
configuration=solution.configuration,
140140
achievable_pressure_bara=outlet.pressure_bara,
141141
target_pressure_bara=target.value,
142-
source_id=self._pipeline.get_id(), # TODO: Might want to change to section or unit id, or include both/thrith (yes, I just invented that word)
142+
source_id=pipeline_section.get_id(),
143143
direction=TargetDirection.MAX_BELOW_TARGET,
144144
)
145145

src/libecalc/process/process_solver/pipeline_section_solver.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def _find_solution(
130130
if isinstance(speed_finding.failure, TargetPressureUnreachableFailure) and (
131131
speed_finding.failure.direction == TargetDirection.MAX_BELOW_TARGET
132132
):
133-
failure = speed_finding.failure.with_source_id(self._pipeline_section.get_process_pipeline_id())
133+
failure = speed_finding.failure.with_source_id(self._pipeline_section.get_id())
134134
return Solution(configuration=speed_and_anti_surge_configurations, failure=failure)
135135

136136
outlet_at_chosen_speed = self._get_outlet_stream(
@@ -147,7 +147,7 @@ def _find_solution(
147147
)
148148
failure = pressure_control_solution.failure
149149
if isinstance(failure, TargetPressureUnreachableFailure) and failure.source_id is None:
150-
failure = failure.with_source_id(self._pipeline_section.get_process_pipeline_id())
150+
failure = failure.with_source_id(self._pipeline_section.get_id())
151151
return Solution(
152152
configuration=merge_configurations(
153153
speed_and_anti_surge_configurations, pressure_control_solution.configuration

src/libecalc/process/process_solver/solver.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
OfftakeExceedsInletError,
1818
ProcessError,
1919
)
20-
from libecalc.process.process_pipeline.process_pipeline import ProcessPipelineId
20+
from libecalc.process.process_pipeline.process_pipeline import ProcessPipelineId, ProcessPipelineSectionId
2121
from libecalc.process.process_pipeline.process_unit import ProcessUnitId
2222
from libecalc.process.process_solver.configuration import (
2323
Configuration,
@@ -157,9 +157,16 @@ class TargetPressureUnreachableFailure(SolverFailure):
157157
achievable_pressure_bara: float
158158
target_pressure_bara: float
159159
direction: TargetDirection
160-
source_id: ProcessPipelineId | None = None # TODO: Replace with failing process unit!
160+
source_id: ProcessPipelineSectionId | None = (
161+
None # NOTE: This currently uniquely identifies the unit and connection as well, the last one in the section
162+
)
161163

162-
def with_source_id(self, source_id: ProcessPipelineId) -> Self: # TODO: Change to processunitid? ...
164+
def with_source_id(self, source_id: ProcessPipelineSectionId) -> Self:
165+
"""
166+
This is a smart way of potentially adding more metadata to the failure as it is sent up the hierarchy. We
167+
do not necessarily need to send all kinds of metadata and irrelevant data to a given function just to
168+
add metadata to the failure. Instead, we can add more metadata as it is being sent up the hierarchy.
169+
"""
163170
return dataclasses.replace(self, source_id=source_id)
164171

165172

@@ -189,7 +196,7 @@ def target_pressure_unreachable[T](
189196
achievable_pressure_bara: float,
190197
target_pressure_bara: float,
191198
direction: TargetDirection,
192-
source_id: ProcessPipelineId | None = None,
199+
source_id: ProcessPipelineSectionId | None = None,
193200
) -> Solution[T]:
194201
"""Build an unsuccessful Solution carrying a TargetPressureUnreachableFailure."""
195202
return Solution(

tests/libecalc/process/process_solver/test_multi_pressure_solver.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from libecalc.common.fixed_speed_pressure_control import FixedSpeedPressureControl, InterstagePressureControl
44
from libecalc.domain.process.compressor.core.train.compressor_train_common_shaft import CompressorTrainCommonShaft
55
from libecalc.domain.process.compressor.core.train.train_evaluation_input import CompressorTrainEvaluationInput
6+
from libecalc.process.process_pipeline.process_pipeline import ProcessPipeline
67
from libecalc.process.process_solver.float_constraint import FloatConstraint
78
from libecalc.process.process_solver.multi_pressure_solver import MultiPressureSolver
89
from libecalc.process.process_solver.pipeline_section import Pipeline
@@ -380,15 +381,15 @@ def test_target_not_achievable_event_identifies_failing_segment(
380381
lp_units, lp_loops = with_individual_asv(lp_units_raw)
381382
lp_loop_ids = [loop.get_id() for loop in lp_loops]
382383
lp_runner = process_runner_factory(units=lp_units, configuration_handlers=[shaft, *lp_loops])
383-
lp_process_pipeline = process_pipeline_factory(units=lp_units)
384384

385385
hp_compressor = compressor_factory(chart_data=chart_data)
386386
hp_units_raw = stage_units_factory(compressor=hp_compressor, shaft=shaft, temperature_kelvin=temperature)
387387
hp_compressors = [hp_compressor]
388388
hp_units, hp_loops = with_individual_asv(hp_units_raw)
389389
hp_loop_ids = [loop.get_id() for loop in hp_loops]
390390
hp_runner = process_runner_factory(units=hp_units, configuration_handlers=[shaft, *hp_loops])
391-
hp_process_pipeline = process_pipeline_factory(units=hp_units)
391+
392+
process_pipeline_id = ProcessPipeline._create_id()
392393

393394
lp_segment = pipeline_section_factory(
394395
runner=lp_runner,
@@ -399,7 +400,7 @@ def test_target_not_achievable_event_identifies_failing_segment(
399400
runner=lp_runner, recirculation_loop_ids=lp_loop_ids, compressors=lp_compressors
400401
),
401402
shaft=shaft,
402-
process_pipeline_id=lp_process_pipeline.get_id(),
403+
process_pipeline_id=process_pipeline_id,
403404
)
404405
hp_segment = pipeline_section_factory(
405406
runner=hp_runner,
@@ -410,10 +411,13 @@ def test_target_not_achievable_event_identifies_failing_segment(
410411
runner=hp_runner, recirculation_loop_ids=hp_loop_ids, compressors=hp_compressors
411412
),
412413
shaft=shaft,
413-
process_pipeline_id=hp_process_pipeline.get_id(),
414+
process_pipeline_id=process_pipeline_id,
414415
)
415416

416-
solver = MultiPressureSolver(Pipeline(process_pipeline_sections=[lp_segment, hp_segment]))
417+
process_pipeline = Pipeline(
418+
process_pipeline_sections=[lp_segment, hp_segment], process_pipeline_id=process_pipeline_id
419+
)
420+
solver = MultiPressureSolver(pipeline=process_pipeline)
417421

418422
inlet_stream = stream_factory(standard_rate_m3_per_day=10_000, pressure_bara=30.0, temperature_kelvin=temperature)
419423

@@ -425,7 +429,9 @@ def test_target_not_achievable_event_identifies_failing_segment(
425429

426430
assert not solution.success
427431
assert isinstance(solution.failure, TargetPressureUnreachableFailure)
428-
assert solution.failure.source_id == hp_process_pipeline.get_id()
432+
assert (
433+
solution.failure.source_id == process_pipeline.get_process_pipeline_sections()[1].get_id()
434+
) # HP section fails
429435

430436

431437
def test_target_not_achievable_event_when_first_segment_fails(
@@ -465,15 +471,14 @@ def test_target_not_achievable_event_when_first_segment_fails(
465471
lp_units, lp_loops = with_individual_asv(lp_units_raw)
466472
lp_loop_ids = [loop.get_id() for loop in lp_loops]
467473
lp_runner = process_runner_factory(units=lp_units, configuration_handlers=[shaft, *lp_loops])
468-
lp_process_pipeline = process_pipeline_factory(units=lp_units)
469474

470475
hp_compressor = compressor_factory(chart_data=chart_data)
471476
hp_units_raw = stage_units_factory(compressor=hp_compressor, shaft=shaft, temperature_kelvin=temperature)
472477
hp_compressors = [hp_compressor]
473478
hp_units, hp_loops = with_individual_asv(hp_units_raw)
474479
hp_loop_ids = [loop.get_id() for loop in hp_loops]
475480
hp_runner = process_runner_factory(units=hp_units, configuration_handlers=[shaft, *hp_loops])
476-
hp_process_pipeline = process_pipeline_factory(units=hp_units)
481+
process_pipeline_id = ProcessPipeline._create_id()
477482

478483
lp_segment = pipeline_section_factory(
479484
runner=lp_runner,
@@ -484,7 +489,7 @@ def test_target_not_achievable_event_when_first_segment_fails(
484489
runner=lp_runner, recirculation_loop_ids=lp_loop_ids, compressors=lp_compressors
485490
),
486491
shaft=shaft,
487-
process_pipeline_id=lp_process_pipeline.get_id(),
492+
process_pipeline_id=process_pipeline_id,
488493
)
489494
hp_segment = pipeline_section_factory(
490495
runner=hp_runner,
@@ -495,10 +500,14 @@ def test_target_not_achievable_event_when_first_segment_fails(
495500
runner=hp_runner, recirculation_loop_ids=hp_loop_ids, compressors=hp_compressors
496501
),
497502
shaft=shaft,
498-
process_pipeline_id=hp_process_pipeline.get_id(),
503+
process_pipeline_id=process_pipeline_id,
504+
)
505+
506+
process_pipeline = Pipeline(
507+
process_pipeline_sections=[lp_segment, hp_segment], process_pipeline_id=process_pipeline_id
499508
)
500509

501-
solver = MultiPressureSolver(pipeline=Pipeline(process_pipeline_sections=[lp_segment, hp_segment]))
510+
solver = MultiPressureSolver(pipeline=process_pipeline)
502511

503512
inlet_stream = stream_factory(standard_rate_m3_per_day=10_000, pressure_bara=30.0, temperature_kelvin=temperature)
504513

@@ -510,4 +519,6 @@ def test_target_not_achievable_event_when_first_segment_fails(
510519

511520
assert not solution.success
512521
assert isinstance(solution.failure, TargetPressureUnreachableFailure)
513-
assert solution.failure.source_id == lp_process_pipeline.get_id()
522+
assert (
523+
solution.failure.source_id == process_pipeline.get_process_pipeline_sections()[0].get_id()
524+
) # LP section fails

0 commit comments

Comments
 (0)