Skip to content

Rigorous distillation

The simultaneous-correction (Naphtali-Sandholm) MESH column: multiple feeds, side draws, stage duties, condenser and reboiler types, design specifications, and the absorber and stripper wrappers.

See the rigorous distillation guide for worked examples.

distillation

Rigorous equilibrium-stage columns: simultaneous-correction MESH solve.

The shortcut and constant-molar-overflow columns in fugacio.sim.column are design-screening tools. This module is the rigorous counterpart a process simulator is judged by: every stage carries its own Material balances, Equilibrium relations, Summation constraints, and energy (Heat) balance, and the whole set is solved simultaneously by Newton's method in the manner of Naphtali and Sandholm (1971), with the Jacobian supplied exactly by JAX autodiff. Because the stage energy balances are included, the internal vapour and liquid traffic varies up the column as the real heats of vaporisation and mixing dictate, which is what the constant-molar-overflow assumption discards.

The column is described generically enough to cover the separations that make up a plant:

  • any number of feeds, each on its own stage (ColumnFeed), entering at whatever thermal condition the feed stream carries;
  • liquid or vapour side draws (SideDraw) as a fraction of the phase leaving a stage, and per-stage heat duties (StageDuty) for intercoolers and interreboilers;
  • a total or partial condenser (or none, for an absorber), a kettle reboiler (or none, for a stripper), and an optional subcooled-reflux temperature;
  • Murphree vapour efficiencies per stage or for the whole column;
  • a linear pressure profile between the top and bottom stages;
  • any two column specifications (ColumnSpec) to close the condenser and reboiler degrees of freedom: reflux ratio, distillate or bottoms rate, boilup ratio, a duty, a product purity, a component recovery, or a stage temperature;
  • any property package (fugacio.sim.models.package_for), so an ethanol/water column runs on NRTL and a demethaniser on Peng-Robinson with the same code.

Unknowns are the logarithms of the component liquid and vapour flows leaving every stage (so flows stay positive through the Newton iteration) plus the stage temperatures and the condenser/reboiler duties; the equilibrium relations are written in logarithmic form so trace components are as well conditioned as bulk ones. A bubble-point (Wang-Henke) sweep seeds the iteration, then fugacio.thermo.implicit.newton_system converges it with a backtracking line search. The converged column is differentiable with respect to the feeds, the pressure profile, the specifications, the side-draw fractions, the duties, and the property package by the implicit function theorem, so column design variables can be optimised by gradient descent through a fully rigorous model.

absorber and stripper are thin wrappers for the two-feed, no-condenser, no-reboiler configurations.

Classes:

Name Description
ColumnFeed

A feed stream entering the column on a given stage.

SideDraw

A side product withdrawn from a stage.

StageDuty

An external heat duty on an intermediate stage (intercooler / interreboiler).

ColumnSpec

A column specification closing one condenser/reboiler degree of freedom.

RigorousColumnResult

Converged rigorous column.

Functions:

Name Description
reflux_ratio

Spec the molar reflux ratio L_1 / D.

reflux_rate

Spec the molar reflux flow L_1 (mol/s).

distillate_rate

Spec the total distillate flow D (mol/s).

bottoms_rate

Spec the total bottoms flow B (mol/s).

boilup_ratio

Spec the boilup ratio V_N / B.

condenser_duty

Spec the condenser duty (W, negative for heat removed).

reboiler_duty

Spec the reboiler duty (W, positive for heat added).

purity

Spec the mole fraction of component in product.

recovery

Spec the fraction of the fed component that leaves in product.

component_flow

Spec the molar flow of component in product (mol/s).

stage_temperature

Spec the temperature of stage stage (K).

rigorous_column

Solve a rigorous multistage column by simultaneous correction (MESH).

absorber

Countercurrent absorber: lean solvent to the top stage, rich gas to the bottom.

stripper

Countercurrent stripper: rich liquid to the top stage, stripping gas to the bottom.

ColumnFeed dataclass

ColumnFeed(stream: Stream, stage: int)

A feed stream entering the column on a given stage.

Attributes:

Name Type Description
stream Stream

The feed Stream (its temperature and pressure set its enthalpy, so a subcooled, saturated, or partially vaporised feed is handled without a separate quality parameter).

stage int

Stage number, counted from the top with the condenser (when present) as stage 1 and the reboiler (when present) as stage n_stages.

SideDraw dataclass

SideDraw(
    stage: int,
    phase: str = "liquid",
    fraction: ArrayLike = 0.1,
)

A side product withdrawn from a stage.

Attributes:

Name Type Description
stage int

Stage number (1 = top).

phase str

"liquid" or "vapor".

fraction ArrayLike

Fraction of that phase leaving the stage that is withdrawn (a ratio keeps the material balance well posed for any column traffic; differentiable).

StageDuty dataclass

StageDuty(stage: int, duty: ArrayLike)

An external heat duty on an intermediate stage (intercooler / interreboiler).

Attributes:

Name Type Description
stage int

Stage number (1 = top).

duty ArrayLike

Heat added to the stage (W); negative removes heat.

ColumnSpec dataclass

ColumnSpec(
    kind: str,
    value: ArrayLike,
    component: int | None = None,
    product: str = "distillate",
    stage: int | None = None,
)

A column specification closing one condenser/reboiler degree of freedom.

Build these with the helper constructors (reflux_ratio, distillate_rate, purity, ...) rather than by hand.

Attributes:

Name Type Description
kind str

One of "reflux_ratio", "reflux_rate", "distillate_rate", "bottoms_rate", "boilup_ratio", "condenser_duty", "reboiler_duty", "mole_fraction", "recovery", "component_flow", "stage_temperature".

value ArrayLike

Target value (differentiable).

component int | None

Component index for composition / recovery / flow specs.

product str

"distillate", "bottoms", or "draw:<k>" (the k-th side draw, zero-based) for product-based specs.

stage int | None

Stage number for "stage_temperature".

RigorousColumnResult

Bases: NamedTuple

Converged rigorous column.

Attributes:

Name Type Description
distillate Stream

Top product (liquid for a total condenser, vapour for a partial condenser, the overhead vapour for an absorber).

bottoms Stream

Bottom liquid product.

side_draws tuple[Stream, ...]

Side products in the order the SideDraw entries were given.

condenser_duty Array

Condenser duty (W, negative = heat removed); zero if none.

reboiler_duty Array

Reboiler duty (W); zero if none.

t Array

Stage temperatures (K), top to bottom.

p Array

Stage pressures (Pa).

x Array

Liquid mole fractions per stage, shape (n_stages, n_components).

y Array

Vapour mole fractions per stage.

k Array

K-values per stage.

liquid_flow Array

Total liquid leaving each stage (mol/s), including any draw.

vapor_flow Array

Total vapour leaving each stage (mol/s), including any draw.

reflux_ratio Array

L_1 / D at the solution.

boilup_ratio Array

V_N / B at the solution.

residual_norm Array

Max-norm of the scaled MESH residual at the solution.

report SolveReport

Numerical convergence report in the original equation order.

stage_liquid Array

Internal liquid component flows, excluding side products.

stage_vapor Array

Internal vapor component flows, excluding side products.

solver_code Array

Array code for the selected linear strategy, 1 for block and 0 for dense.

jacobian_size Array

Scalar number of simultaneous unknowns.

generation Array

Per-stage component generation (mol/s), zero without reactions.

reaction_rates Array

Per-stage intensive reaction rates (mol/(m^3 s)).

reaction_heat Array

Formation-energy source (W); already included in stage balances.

reaction_volumes Array

Reacting-phase volume on each stage (m^3).

Methods:

Name Description
warm_start

Full stage state for rigorous_column(..., guess=result.warm_start()).

solver_info

Describe the selected Jacobian strategy for this concrete column.

converged property

converged: Array

Whether the stage balances and specifications converged.

warm_start

warm_start() -> dict[str, Array]

Full stage state for rigorous_column(..., guess=result.warm_start()).

solver_info

solver_info() -> dict[str, Any]

Describe the selected Jacobian strategy for this concrete column.

Block solves independently check their linear residual and may use a pivoted dense fallback. This description isn't a count of fallbacks.

reflux_ratio

reflux_ratio(value: ArrayLike) -> ColumnSpec

Spec the molar reflux ratio L_1 / D.

reflux_rate

reflux_rate(value: ArrayLike) -> ColumnSpec

Spec the molar reflux flow L_1 (mol/s).

distillate_rate

distillate_rate(value: ArrayLike) -> ColumnSpec

Spec the total distillate flow D (mol/s).

bottoms_rate

bottoms_rate(value: ArrayLike) -> ColumnSpec

Spec the total bottoms flow B (mol/s).

boilup_ratio

boilup_ratio(value: ArrayLike) -> ColumnSpec

Spec the boilup ratio V_N / B.

condenser_duty

condenser_duty(value: ArrayLike) -> ColumnSpec

Spec the condenser duty (W, negative for heat removed).

reboiler_duty

reboiler_duty(value: ArrayLike) -> ColumnSpec

Spec the reboiler duty (W, positive for heat added).

purity

purity(
    product: str, component: int, value: ArrayLike
) -> ColumnSpec

Spec the mole fraction of component in product.

recovery

recovery(
    component: int, product: str, value: ArrayLike
) -> ColumnSpec

Spec the fraction of the fed component that leaves in product.

component_flow

component_flow(
    product: str, component: int, value: ArrayLike
) -> ColumnSpec

Spec the molar flow of component in product (mol/s).

stage_temperature

stage_temperature(
    stage: int, value: ArrayLike
) -> ColumnSpec

Spec the temperature of stage stage (K).

rigorous_column

rigorous_column(
    feeds: Sequence[ColumnFeed],
    n_stages: int,
    *,
    p: ArrayLike | None = None,
    p_top: ArrayLike | None = None,
    p_bottom: ArrayLike | None = None,
    condenser: str | None = "total",
    reboiler: str | None = "kettle",
    specs: Sequence[ColumnSpec] = (),
    side_draws: Sequence[SideDraw] = (),
    stage_duties: Sequence[StageDuty] = (),
    efficiency: ArrayLike = 1.0,
    reflux_temperature: ArrayLike | None = None,
    reactions: ReactionSet | None = None,
    reaction_volumes: ArrayLike = 0.0,
    model: Model = None,
    eos: CubicEOS = PR,
    kij: Array | None = None,
    guess: dict[str, Any] | None = None,
    sweeps: int = 4,
    tol: float = 1e-09,
    max_iter: int = 80,
    check: bool = True,
    homotopy: bool = True,
    linear_solver: str = "block",
) -> RigorousColumnResult

Solve a rigorous multistage column by simultaneous correction (MESH).

Parameters:

Name Type Description Default
feeds Sequence[ColumnFeed]

One or more ColumnFeed entries (stream + stage).

required
n_stages int

Total number of equilibrium stages including the condenser and reboiler when present (stage 1 is the top).

required
p ArrayLike | None

Uniform column pressure (Pa); or give p_top and p_bottom for a linear profile.

None
p_top ArrayLike | None

Top-stage pressure (Pa) when a profile is wanted.

None
p_bottom ArrayLike | None

Bottom-stage pressure (Pa) when a profile is wanted.

None
condenser str | None

"total" (liquid distillate, saturated or subcooled reflux), "partial" (vapour distillate from an equilibrium stage), or None (no condenser; the top stage is an ordinary stage, as in an absorber).

'total'
reboiler str | None

"kettle" (an equilibrium stage with a heat duty) or None.

'kettle'
specs Sequence[ColumnSpec]

Column specifications; exactly one per condenser/reboiler present (two for a conventional column, one for a reboiled absorber, none for an absorber or stripper).

()
side_draws Sequence[SideDraw]

Side products as fractions of the phase leaving a stage.

()
stage_duties Sequence[StageDuty]

Intermediate heat duties (W).

()
efficiency ArrayLike

Murphree vapour efficiency, a scalar or one value per stage.

1.0
reflux_temperature ArrayLike | None

Subcooled reflux temperature (K) for a total condenser; None returns saturated reflux.

None
reactions ReactionSet | None

Optional validated kinetic reaction set. Local reaction sources enter material and energy equations without changing MESH unknowns.

None
reaction_volumes ArrayLike

Reacting-phase volumes (m^3). A scalar applies to interior stages; an n_stages vector explicitly selects every stage. Vapor-phase reaction in a total condenser is rejected.

0.0
model Model

Property package (see fugacio.sim.models.package_for); defaults to Peng-Robinson.

None
eos CubicEOS

Cubic EOS for the default package.

PR
kij Array | None

Binary interaction matrix for the default package.

None
guess dict[str, Any] | None

Optional seeding hints: "reflux_ratio", "distillate_rate" (used for the internal-traffic seed) and "t" (a stage temperature profile). Specs of those kinds are used automatically.

None
sweeps int

Bubble-point sweeps used to seed the Newton solve.

4
check bool

Raise for a failed concrete solve; compiled failures return NaNs.

True
tol float

Maximum accepted scaled equation residual.

1e-09
max_iter int

Iteration cap for each Newton solve, including homotopy increments.

80
homotopy bool

Retry by gradually restoring NRTL activity effects and reaction volumes. The final report checks the original full model.

True
linear_solver str

"block" uses colored stage Jacobians and checked block elimination with a pivoted dense fallback. "dense" selects the original dense reference equations and linearization.

'block'

Returns:

Type Description
RigorousColumnResult

A RigorousColumnResult with the products, duties, and stage profiles, all

RigorousColumnResult

differentiable with respect to the feeds, pressures, specifications,

RigorousColumnResult

draw fractions, duties, and the package parameters.

Raises:

Type Description
ValueError

for an inconsistent description (bad stage numbers, wrong number of specs, unknown condenser/reboiler type, mismatched components).

absorber

absorber(
    gas: Stream,
    liquid: Stream,
    n_stages: int,
    *,
    p: ArrayLike | None = None,
    p_top: ArrayLike | None = None,
    p_bottom: ArrayLike | None = None,
    efficiency: ArrayLike = 1.0,
    model: Model = None,
    eos: CubicEOS = PR,
    kij: Array | None = None,
    **kwargs: Any,
) -> RigorousColumnResult

Countercurrent absorber: lean solvent to the top stage, rich gas to the bottom.

No condenser, no reboiler, no specifications: the column is fully determined by its two feeds. distillate in the result is the treated gas leaving the top and bottoms the rich solvent leaving the bottom.

stripper

stripper(
    liquid: Stream,
    gas: Stream,
    n_stages: int,
    *,
    p: ArrayLike | None = None,
    p_top: ArrayLike | None = None,
    p_bottom: ArrayLike | None = None,
    efficiency: ArrayLike = 1.0,
    model: Model = None,
    eos: CubicEOS = PR,
    kij: Array | None = None,
    **kwargs: Any,
) -> RigorousColumnResult

Countercurrent stripper: rich liquid to the top stage, stripping gas to the bottom.

The same topology as absorber with the roles of the feeds swapped in the signature; distillate is the loaded stripping gas and bottoms the stripped liquid.