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 catch is the ever-present trivial solution x^I = x^II = z (K = 1); we avoid it by seeding the iteration from the unstable trial phase found by the tangent-plane test in fugacio.thermo.stability. 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.

Functions:

Name Description
flash_lle

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

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.

flash_lle

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

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.

required
k_guess Array | None

Optional initial distribution ratios K_i. If omitted, the tangent-plane stability test seeds a non-trivial split automatically.

None
tol float

Fixed-point convergence tolerance.

1e-12
max_iter int

Maximum number of fixed-point iterations.

400

Returns:

Type Description
LLEResult

An LLEResult. If the feed is actually miscible the iteration

LLEResult

collapses toward the trivial K = 1 (psi at a bound); call

LLEResult

fugacio.thermo.stability.liquid_stability first to decide whether a

LLEResult

split exists.

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.

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.

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, which is what makes the non-trivial three-phase root reliably found. The converged 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.

Functions:

Name Description
flash_vlle

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

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.

flash_vlle

flash_vlle(
    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,
) -> VLLEResult

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

Seeds a vapour/liquid split from fugacio.thermo.gammaphi.flash_pt_gamma and a liquid/liquid split from fugacio.thermo.lle.flash_lle, then drives both K-value sets to consistency with a 2x2 Rachford-Rice inner solve. Inspect three_phase to confirm a genuine three-phase root (otherwise one phase fraction has collapsed and the relevant two-phase flash applies).

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, solved here with the bracketed solver and differentiable in P and the model parameters.

Returns:

Type Description
HeterogeneousAzeotrope

A HeterogeneousAzeotrope (the temperature, both liquid

HeterogeneousAzeotrope

compositions, and the common vapour). Use a feed mole fraction (of

HeterogeneousAzeotrope

component 1) that lies inside the miscibility gap.