Skip to content

Commit 1a745f3

Browse files
authored
fix(process): scale mass rate when LiquidRemover drops out liquid phase (#1546)
test(process): assert LiquidRemover scales mass rate by gas mass fraction docs: changelog entry for LiquidRemover mass-rate fix fix(process): raise on non-positive inlet molar mass in LiquidRemover test(process): assert LiquidRemover raises on non-positive inlet molar mass
1 parent f71f21c commit 1a745f3

3 files changed

Lines changed: 69 additions & 2 deletions

File tree

docs/drafts/next.draft.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ STP: "flare" column has been added to STP Export - for `FIXED` installations onl
1414
## Bug Fixes
1515

1616
- Hardened compressor PH flash handling so invalid thermodynamic states are no longer used in compressor outlet calculations.
17+
- `LiquidRemover` now scales the outlet mass rate by the gas mass fraction when liquid is dropped, so the removed liquid mass is no longer carried by the gas stream downstream.
1718

1819
## Breaking changes
1920

src/libecalc/process/process_units/liquid_remover.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Final
22

33
from libecalc.process.fluid_stream.constants import ThermodynamicConstants
4+
from libecalc.process.fluid_stream.exceptions import InvalidStreamException
45
from libecalc.process.fluid_stream.fluid_service import FluidService
56
from libecalc.process.fluid_stream.fluid_stream import FluidStream
67
from libecalc.process.process_pipeline.process_unit import ProcessUnit, ProcessUnitId
@@ -16,7 +17,14 @@ def get_id(self) -> ProcessUnitId:
1617

1718
def propagate_stream(self, inlet_stream: FluidStream) -> FluidStream:
1819
"""
19-
Removes liquid from the fluid stream.
20+
Removes liquid from the fluid stream. The new stream's mass rate is scaled
21+
down by the gas mass fraction so the dropped-out liquid isn't re-injected
22+
into the gas phase.
23+
24+
The removed liquid (mass = inlet.mass_rate * (1 - gas_mass_fraction),
25+
composition = inlet - new_fluid) is currently discarded. It could later
26+
be exposed as a separate outlet stream — e.g. routed to an oil pump,
27+
accounted for in emissions, or reported back to the user.
2028
2129
Args:
2230
inlet_stream: The fluid stream to be scrubbed.
@@ -26,6 +34,13 @@ def propagate_stream(self, inlet_stream: FluidStream) -> FluidStream:
2634
"""
2735
if inlet_stream.vapor_fraction_molar < ThermodynamicConstants.PURE_VAPOR_THRESHOLD:
2836
new_fluid = self._fluid_service.remove_liquid(inlet_stream.fluid)
29-
return inlet_stream.with_new_fluid(new_fluid)
37+
inlet_molar_mass = inlet_stream.fluid.molar_mass
38+
if inlet_molar_mass <= 0.0:
39+
raise InvalidStreamException(
40+
f"Cannot remove liquid from a degenerate stream with non-positive molar mass: {inlet_molar_mass}"
41+
)
42+
gas_mass_fraction = inlet_stream.vapor_fraction_molar * new_fluid.molar_mass / inlet_molar_mass
43+
new_mass_rate = inlet_stream.mass_rate_kg_per_h * gas_mass_fraction
44+
return inlet_stream.with_new_fluid(new_fluid).with_mass_rate(new_mass_rate)
3045
else:
3146
return inlet_stream

tests/libecalc/process/process_units/test_liquid_remover.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
from unittest.mock import MagicMock
2+
3+
import pytest
4+
15
from ecalc_neqsim_wrapper.thermo import STANDARD_PRESSURE_BARA, STANDARD_TEMPERATURE_KELVIN
6+
from libecalc.process.fluid_stream.exceptions import InvalidStreamException
27
from libecalc.process.fluid_stream.fluid_model import EoSModel, FluidComposition, FluidModel
38
from libecalc.process.fluid_stream.fluid_stream import FluidStream
9+
from libecalc.process.process_units.liquid_remover import LiquidRemover
410

511

612
def test_liquid_remover_removes_liquid(fluid_service, liquid_remover_factory):
@@ -33,3 +39,48 @@ def test_liquid_remover_removes_liquid(fluid_service, liquid_remover_factory):
3339

3440
assert inlet_stream.vapor_fraction_molar < 1.0
3541
assert outlet_stream.vapor_fraction_molar == 1.0
42+
43+
expected_gas_mass_fraction = (
44+
inlet_stream.vapor_fraction_molar * outlet_stream.fluid.molar_mass / inlet_stream.fluid.molar_mass
45+
)
46+
expected_mass_rate = inlet_stream.mass_rate_kg_per_h * expected_gas_mass_fraction
47+
assert outlet_stream.mass_rate_kg_per_h < inlet_stream.mass_rate_kg_per_h
48+
assert outlet_stream.mass_rate_kg_per_h == expected_mass_rate
49+
50+
51+
def test_liquid_remover_passthrough_when_no_liquid(fluid_service, liquid_remover_factory):
52+
composition = FluidComposition(
53+
nitrogen=3,
54+
CO2=1,
55+
methane=80,
56+
ethane=10,
57+
propane=6,
58+
)
59+
fluid_model = FluidModel(eos_model=EoSModel.SRK, composition=composition)
60+
fluid = fluid_service.create_fluid(
61+
fluid_model=fluid_model,
62+
pressure_bara=STANDARD_PRESSURE_BARA,
63+
temperature_kelvin=STANDARD_TEMPERATURE_KELVIN,
64+
)
65+
inlet_stream = FluidStream.from_standard_rate(
66+
standard_rate_m3_per_day=100000,
67+
fluid_model=fluid.fluid_model,
68+
fluid_properties=fluid.properties,
69+
)
70+
remover = liquid_remover_factory()
71+
outlet_stream = remover.propagate_stream(inlet_stream)
72+
73+
assert inlet_stream.vapor_fraction_molar == 1.0
74+
assert outlet_stream.mass_rate_kg_per_h == inlet_stream.mass_rate_kg_per_h
75+
76+
77+
def test_liquid_remover_raises_on_non_positive_inlet_molar_mass():
78+
inlet_stream = MagicMock()
79+
inlet_stream.vapor_fraction_molar = 0.5
80+
inlet_stream.fluid.molar_mass = 0.0
81+
82+
fluid_service = MagicMock()
83+
remover = LiquidRemover(fluid_service=fluid_service)
84+
85+
with pytest.raises(InvalidStreamException, match="non-positive molar mass"):
86+
remover.propagate_stream(inlet_stream)

0 commit comments

Comments
 (0)