diff --git a/docs/drafts/next.draft.md b/docs/drafts/next.draft.md index 88ef8ad32b..4c526bf154 100644 --- a/docs/drafts/next.draft.md +++ b/docs/drafts/next.draft.md @@ -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 diff --git a/src/libecalc/process/process_units/liquid_remover.py b/src/libecalc/process/process_units/liquid_remover.py index f75a5ddf8d..28c9acd784 100644 --- a/src/libecalc/process/process_units/liquid_remover.py +++ b/src/libecalc/process/process_units/liquid_remover.py @@ -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. @@ -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 + 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 diff --git a/tests/libecalc/process/process_units/test_liquid_remover.py b/tests/libecalc/process/process_units/test_liquid_remover.py index 3d433c0e69..62f5bfdab7 100644 --- a/tests/libecalc/process/process_units/test_liquid_remover.py +++ b/tests/libecalc/process/process_units/test_liquid_remover.py @@ -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