Skip to content

Liquid-liquid & vapour-liquid-liquid equilibrium

Rigorous two-liquid (LLE) and three-phase (VLLE) flashes, heterogeneous azeotropes, and the tangent-plane machinery that decides when a second liquid appears.

Liquid-liquid equilibrium

lle

Liquid-liquid equilibrium (LLE): two-liquid flash, tie-lines, and binodals.

Partially miscible liquids split into two phases I and II whose component fugacities are equal. With an activity-coefficient model that equality is the isoactivity condition

x_i^I gamma_i(x^I, T) = x_i^II gamma_i(x^II, T),

i.e. K_i = x_i^II / x_i^I = gamma_i^I / gamma_i^II, closed by the same Rachford-Rice material balance as a vapour-liquid flash, with the phase fraction psi now the mole fraction in phase II:

sum_i z_i (K_i - 1) / (1 + psi (K_i - 1)) = 0,
x_i^I = z_i / (1 + psi (K_i - 1)),   x_i^II = K_i x_i^I.

The flash is stability-first. The tangent-plane search of fugacio.thermo.stability.liquid_stability decides whether a split exists: an established stable liquid is returned as one phase (psi = 0), and an unstable one seeds the isoactivity iteration from its most negative trial phase, which avoids the ever-present trivial solution x^I = x^II = z. An iteration that still collapses onto it is reported as TRIVIAL. The converged split is differentiable in temperature, feed, and the model parameters via implicit differentiation of the fixed point, so tie-lines move smoothly under a gradient, which matters for solvent-selection optimisation and parameter fitting to mutual-solubility data.

Only an activity model can describe an LLE; Wilson's model is excluded by construction (it has no miscibility gap), so use NRTL, UNIQUAC, or UNIFAC.

Classes:

Name Description
LLEResult

Result of a two-liquid (LLE) flash.

LLESolveResult

Two-liquid flash and its report.

Functions:

Name Description
flash_lle_with_info

Split feed z into two liquids at temperature t (isoactivity flash).

flash_lle

Two-liquid flash value; NaN when flash_lle_with_info reports failure.

binary_binodal

Mutual-solubility (binodal) compositions of a binary at temperature t.

tie_line

One ternary tie-line through feed z: (x_raffinate, x_extract, psi).

binodal_curve

Binary binodal branches over a temperature range.

LLEResult

Bases: NamedTuple

Result of a two-liquid (LLE) flash.

Attributes:

Name Type Description
psi Array

Mole fraction of the feed in liquid phase II.

x_i Array

Phase I (raffinate-like) mole fractions.

x_ii Array

Phase II (extract-like) mole fractions.

k Array

Distribution ratios K_i = x_i^II / x_i^I at the solution.

LLESolveResult

Bases: NamedTuple

Two-liquid flash and its report.

flash_lle_with_info

flash_lle_with_info(
    model: ActivityModel,
    t: ArrayLike,
    z: Array,
    *,
    k_guess: Array | None = None,
    tol: float = 1e-12,
    max_iter: int = 400,
) -> LLESolveResult

Split feed z into two liquids at temperature t (isoactivity flash).

Parameters:

Name Type Description Default
model ActivityModel

Liquid activity-coefficient model (must admit a miscibility gap).

required
t ArrayLike

Temperature (K).

required
z Array

Overall (feed) mole fractions; absent components stay absent.

required
k_guess Array | None

Optional initial distribution ratios K_i. If omitted, the tangent-plane stability test decides whether a split exists and seeds it.

None
tol float

Fixed-point convergence tolerance.

1e-12
max_iter int

Maximum number of fixed-point iterations.

400

Returns:

Type Description
LLESolveResult

The split and its report. An established stable liquid converges to one

LLESolveResult

phase (psi = 0, both compositions equal to z); a collapse onto the

LLESolveResult

trivial solution from an unstable feed is TRIVIAL.

flash_lle

flash_lle(
    model: ActivityModel,
    t: ArrayLike,
    z: Array,
    *,
    k_guess: Array | None = None,
    tol: float = 1e-12,
    max_iter: int = 400,
) -> LLEResult

Two-liquid flash value; NaN when flash_lle_with_info reports failure.

binary_binodal

binary_binodal(
    model: ActivityModel,
    t: ArrayLike,
    *,
    feed: ArrayLike = 0.5,
    tol: float = 1e-12,
    max_iter: int = 400,
) -> tuple[Array, Array]

Mutual-solubility (binodal) compositions of a binary at temperature t.

Returns (x1_phase_I, x1_phase_II), the mole fraction of component 1 in each conjugate liquid (the tie-line ends). Any feed inside the gap gives the same pair; the default 50/50 feed sits squarely in a symmetric gap. Outside the gap (no split) both are NaN.

tie_line

tie_line(
    model: ActivityModel,
    t: ArrayLike,
    z: Array,
    *,
    tol: float = 1e-12,
    max_iter: int = 400,
) -> tuple[Array, Array, Array]

One ternary tie-line through feed z: (x_raffinate, x_extract, psi).

A thin wrapper over flash_lle returning the two conjugate-phase compositions and the phase fraction, the unit of a ternary LLE diagram.

binodal_curve

binodal_curve(
    model: ActivityModel,
    temperatures: Array,
    *,
    feed: ArrayLike = 0.5,
    tol: float = 1e-12,
    max_iter: int = 400,
) -> tuple[Array, Array]

Binary binodal branches over a temperature range.

Maps binary_binodal across temperatures and returns (x1_phase_I, x1_phase_II) arrays aligned with the input, the two branches of the solubility envelope that meet at the upper (or lower) critical solution temperature (NaN beyond it).

Vapour-liquid-liquid equilibrium

vlle

Three-phase vapour-liquid-liquid equilibrium (VLLE).

When a vapour coexists with two partially miscible liquids (the heterogeneous azeotropes that make water/organic distillation and decantation work), neither a two-phase VLE flash nor an LLE flash alone suffices. VLLE couples them: a vapour V and two liquids I and II all at equal temperature, pressure, and component fugacity.

Following Michelsen, both non-reference phases are referred to liquid I through two sets of K-values,

K_i^V = y_i / x_i^I = gamma_i^I f_i^{0,L} / (phi_i^V P),
K_i^L = x_i^II / x_i^I = gamma_i^I / gamma_i^II,

so the material balance gives x_i^I = z_i / D_i with D_i = 1 + beta_V (K_i^V - 1) + beta_II (K_i^L - 1). The two phase fractions (beta_V, beta_II) solve the pair of Rachford-Rice equations sum_i z_i (K_i^V - 1)/D_i = 0 and sum_i z_i (K_i^L - 1)/D_i = 0 (a small 2x2 Newton with an analytic Jacobian), and the K-values are updated from the fugacity equalities until consistent.

The flash is seeded from a VLE flash followed by a stability-driven split of its liquid. When no three-phase solution exists (the Rachford-Rice root leaves the phase-fraction simplex) the flash reports INFEASIBLE rather than clipping phase fractions away from their compositions: use the two-phase VLE or LLE flash there. A converged three-phase state is differentiable through the fixed point.

Classes:

Name Description
HeterogeneousAzeotrope

A binary heterogeneous (two-liquid) azeotrope at fixed pressure.

VLLEResult

Result of a three-phase vapour-liquid-liquid flash.

VLLESolveResult

Three-phase flash and its report.

Functions:

Name Description
flash_vlle_with_info

Isothermal-isobaric three-phase (V-L-L) flash of feed z at (T, P).

flash_vlle

Three-phase flash value; NaN when flash_vlle_with_info reports failure.

heterogeneous_azeotrope

Binary heterogeneous azeotrope: the boiling temperature of two conjugate liquids.

HeterogeneousAzeotrope

Bases: NamedTuple

A binary heterogeneous (two-liquid) azeotrope at fixed pressure.

Attributes:

Name Type Description
t Array

Azeotrope temperature (K).

x_i Array

Composition of liquid I.

x_ii Array

Composition of liquid II.

y Array

Common vapour composition in equilibrium with both liquids.

VLLEResult

Bases: NamedTuple

Result of a three-phase vapour-liquid-liquid flash.

Attributes:

Name Type Description
beta_v Array

Vapour mole fraction of the feed.

beta_l1 Array

Mole fraction in liquid I (the reference liquid).

beta_l2 Array

Mole fraction in liquid II.

y Array

Vapour composition.

x_i Array

Liquid I composition.

x_ii Array

Liquid II composition.

three_phase Array

True when all three phase fractions are strictly positive.

VLLESolveResult

Bases: NamedTuple

Three-phase flash and its report.

flash_vlle_with_info

flash_vlle_with_info(
    model: ActivityModel,
    t: ArrayLike,
    p: ArrayLike,
    z: Array,
    tc: Array,
    pc: Array,
    omega: Array,
    *,
    eos: CubicEOS = PR,
    kij: Array | None = None,
    vapor: str = "ideal",
    poynting: bool = False,
    phi_saturation: bool = False,
    tol: float = 1e-11,
    max_iter: int = 300,
) -> VLLESolveResult

Isothermal-isobaric three-phase (V-L-L) flash of feed z at (T, P).

Seeds a vapour/liquid split from the gamma-phi VLE flash and a liquid/liquid split of its liquid from fugacio.thermo.lle.flash_lle_with_info, then drives both K-value sets to consistency with a 2x2 Rachford-Rice inner solve. Phase fractions and compositions come from the same root, so the returned state always closes the material balance.

Returns:

Type Description
VLLESolveResult

The state and its report. The report is INFEASIBLE (and the value

VLLESolveResult

must not be used) when the converged root has a phase fraction outside

VLLESolveResult

[0, 1], i.e. the feed isn't three-phase at (T, P).

flash_vlle

flash_vlle(
    model: ActivityModel,
    t: ArrayLike,
    p: ArrayLike,
    z: Array,
    tc: Array,
    pc: Array,
    omega: Array,
    **options: Any,
) -> VLLEResult

Three-phase flash value; NaN when flash_vlle_with_info reports failure.

heterogeneous_azeotrope

heterogeneous_azeotrope(
    model: ActivityModel,
    p: ArrayLike,
    tc: Array,
    pc: Array,
    omega: Array,
    *,
    eos: CubicEOS = PR,
    feed: ArrayLike = 0.5,
    t_min: float = 250.0,
    t_max: float = 500.0,
    tol: float = 1e-09,
    max_iter: int = 200,
) -> HeterogeneousAzeotrope

Binary heterogeneous azeotrope: the boiling temperature of two conjugate liquids.

Inside a miscibility gap the two conjugate liquids have equal component activities (a_i = x_i gamma_i matches across the LLE tie-line), so they necessarily boil to the same vapour y_i = a_i Psat_i / P. The heterogeneous azeotrope is the temperature at which that shared vapour's total pressure sum_i a_i Psat_i reaches the system pressure P, a single, well-posed scalar root, located on a scanned bracket and differentiable in P and the model parameters.

Returns:

Type Description
HeterogeneousAzeotrope

A HeterogeneousAzeotrope (the temperature, both liquid

HeterogeneousAzeotrope

compositions, and the common vapour), or NaN when no two-liquid boiling

HeterogeneousAzeotrope

point exists on [t_min, t_max]. Use a feed mole fraction (of

HeterogeneousAzeotrope

component 1) that lies inside the miscibility gap.