Skip to content
Open
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
1 change: 1 addition & 0 deletions docs/drafts/next.draft.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ STP: "flare" column has been added to STP Export - for `FIXED` installations onl
## Bug Fixes

- Hardened compressor PH flash handling so invalid thermodynamic states are no longer used in compressor outlet calculations.
- `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.

## Breaking changes

Expand Down
17 changes: 15 additions & 2 deletions src/libecalc/process/process_units/liquid_remover.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ def get_id(self) -> ProcessUnitId:

def propagate_stream(self, inlet_stream: FluidStream) -> FluidStream:
"""
Removes liquid from the fluid stream.
Removes liquid from the fluid stream. The new stream's mass rate is scaled
down by the gas mass fraction so the dropped-out liquid isn't re-injected
into the gas phase.

The removed liquid (mass = inlet.mass_rate * (1 - gas_mass_fraction),
composition = inlet - new_fluid) is currently discarded. It could later
be exposed as a separate outlet stream — e.g. routed to an oil pump,
accounted for in emissions, or reported back to the user.

Args:
inlet_stream: The fluid stream to be scrubbed.
Expand All @@ -26,6 +33,12 @@ def propagate_stream(self, inlet_stream: FluidStream) -> FluidStream:
"""
if inlet_stream.vapor_fraction_molar < ThermodynamicConstants.PURE_VAPOR_THRESHOLD:
new_fluid = self._fluid_service.remove_liquid(inlet_stream.fluid)
return inlet_stream.with_new_fluid(new_fluid)
inlet_molar_mass = inlet_stream.fluid.molar_mass
if inlet_molar_mass > 0.0:
gas_mass_fraction = inlet_stream.vapor_fraction_molar * new_fluid.molar_mass / inlet_molar_mass
else:
gas_mass_fraction = 1.0
Comment on lines +37 to +40
Copy link
Copy Markdown
Contributor

@kjbrak kjbrak May 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider failing explicitly when inlet_molar_mass <= 0.0 rather than preserving the full inlet mass rate. A non-positive molar mass is non-physical, and gas_mass_fraction = 1.0 would hide the invalid thermodynamic state while carrying all removed-liquid mass downstream again for that edge case.

If using InvalidStreamException, the matching import also needs to be added.

Suggested change
if inlet_molar_mass > 0.0:
gas_mass_fraction = inlet_stream.vapor_fraction_molar * new_fluid.molar_mass / inlet_molar_mass
else:
gas_mass_fraction = 1.0
if inlet_molar_mass <= 0.0:
raise InvalidStreamException(
f"Cannot remove liquid from stream with non-positive molar mass: {inlet_molar_mass}"
)
gas_mass_fraction = inlet_stream.vapor_fraction_molar * new_fluid.molar_mass / inlet_molar_mass

new_mass_rate = inlet_stream.mass_rate_kg_per_h * gas_mass_fraction
return inlet_stream.with_new_fluid(new_fluid).with_mass_rate(new_mass_rate)
else:
return inlet_stream
33 changes: 33 additions & 0 deletions tests/libecalc/process/process_units/test_liquid_remover.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,36 @@ def test_liquid_remover_removes_liquid(fluid_service, liquid_remover_factory):

assert inlet_stream.vapor_fraction_molar < 1.0
assert outlet_stream.vapor_fraction_molar == 1.0

expected_gas_mass_fraction = (
inlet_stream.vapor_fraction_molar * outlet_stream.fluid.molar_mass / inlet_stream.fluid.molar_mass
)
expected_mass_rate = inlet_stream.mass_rate_kg_per_h * expected_gas_mass_fraction
assert outlet_stream.mass_rate_kg_per_h < inlet_stream.mass_rate_kg_per_h
assert outlet_stream.mass_rate_kg_per_h == expected_mass_rate


def test_liquid_remover_passthrough_when_no_liquid(fluid_service, liquid_remover_factory):
composition = FluidComposition(
nitrogen=3,
CO2=1,
methane=80,
ethane=10,
propane=6,
)
fluid_model = FluidModel(eos_model=EoSModel.SRK, composition=composition)
fluid = fluid_service.create_fluid(
fluid_model=fluid_model,
pressure_bara=STANDARD_PRESSURE_BARA,
temperature_kelvin=STANDARD_TEMPERATURE_KELVIN,
)
inlet_stream = FluidStream.from_standard_rate(
standard_rate_m3_per_day=100000,
fluid_model=fluid.fluid_model,
fluid_properties=fluid.properties,
)
remover = liquid_remover_factory()
outlet_stream = remover.propagate_stream(inlet_stream)

assert inlet_stream.vapor_fraction_molar == 1.0
assert outlet_stream.mass_rate_kg_per_h == inlet_stream.mass_rate_kg_per_h
Loading