Skip to content

Unit operations

Energy-balanced unit operations (flash drum, adiabatic flash, heater, valve, pump, compressor, turbine, mixer, splitter, component separator) and the non-ideal separation units (decanter, three-phase flash). Every unit is a compiled kernel with the property package and its operating values as dynamic arguments. An eager call raises on failure, and a traced call returns NaN outlets; see the reliability guide.

Core unit operations

units

Differentiable unit operations with rigorous material and energy balances.

Every block here is built on a fugacio.thermo.PropertyPackage, the object that owns both the phase equilibrium and the energy properties of the mixture, so each unit is differentiable end-to-end with respect to its operating conditions, its feed, and the thermodynamic parameters: you can take a gradient of a product purity, a duty, or a shaft power with respect to a drum temperature, an outlet pressure, a split fraction, a feed flow, or an NRTL parameter.

The library covers the staples of a process flowsheet:

  • flash_drum / flash_drum_with_info: isothermal-isobaric separator;
  • adiabatic_flash: pressure-and-duty separator (a PH flash), the right specification for a letdown into a drum or a pure fluid on its saturation line;
  • heater: heater/cooler on a temperature, duty, or outlet vapor-fraction spec;
  • valve: isenthalpic (Joule-Thomson) pressure letdown;
  • pump: incompressible-liquid pump with an efficiency;
  • compressor / turbine: isentropic machines with an efficiency;
  • mix: adiabatic mixer (energy-balanced);
  • splitter: flow splitter;
  • component_separator: idealised component split.

Contract. Every unit is one compiled kernel. The property package and every specification are dynamic arguments, so a unit compiles once per package structure and specification kind, then evaluates new operating points without recompiling. Each kernel returns a fugacio.thermo.diagnostics.SolveReport alongside its outlets, and a failed kernel's outlets are NaN. An eager call raises: fugacio.thermo.diagnostics.ConvergenceError for a failed solve and ValueError for a violated operating limit (fugacio.sim.unit_limits). A traced call (inside jax.jit, a flowsheet, or an optimizer) can't raise and instead returns NaN outlets with nonfinite derivatives.

The flashes here solve one liquid and one vapor. Whether a feed splits into two liquids is a separate, more expensive question: check it with flash_drum_checked (fugacio.sim.acceptance), a flowsheet's post-solve audit, or the package's stability method.

Classes:

Name Description
FlashDrumResult

Vapor and liquid products of a separator, its heat duty, and its report.

HeaterResult

Outlet of a heater/cooler together with the heat duty.

PumpResult

Outlet of a pump together with the shaft work.

WorkResult

Outlet of a compressor/turbine with actual and ideal shaft work.

Functions:

Name Description
flash_drum_with_info

Flash a feed at temperature t and pressure p, with duty and report.

flash_drum

Flash a feed stream at temperature t and pressure p.

adiabatic_flash

Separate a feed at pressure p with heat duty added (a PH flash drum).

heater

Heat or cool a stream on a temperature, duty, or vapor-fraction specification.

valve

Isenthalpic (Joule-Thomson) pressure letdown to p_out.

pump

Pump an (incompressible) liquid from feed.p to p_out.

compressor

Compress a stream to p_out with an isentropic efficiency (< 1).

turbine

Expand a stream to p_out with an isentropic efficiency (< 1).

splitter

Split feed into outlets that share its composition and state.

component_separator

Idealised separator with a per-component recovery to the top product.

mix

Combine streams with an exact material balance and an adiabatic energy balance.

FlashDrumResult

Bases: NamedTuple

Vapor and liquid products of a separator, its heat duty, and its report.

Attributes:

Name Type Description
vapor Stream

Vapor product Stream.

liquid Stream

Liquid product Stream.

duty Array

Heat added to hold the drum at its specification (W).

report SolveReport

Solve and specification report.

outlets property

outlets: tuple[Stream, Stream]

(vapor, liquid), the order a flowsheet assigns outputs.

heat property

heat: Array

Heat into the fluid (W), the flowsheet's energy-accounting name.

HeaterResult

Bases: NamedTuple

Outlet of a heater/cooler together with the heat duty.

Attributes:

Name Type Description
outlet Stream

Product Stream.

duty Array

Heat duty (W). Positive means heat added; negative means cooling.

report SolveReport

Solve and specification report.

outlets property

outlets: tuple[Stream]

The single outlet, as a flowsheet output tuple.

heat property

heat: Array

Heat into the fluid (W).

PumpResult

Bases: NamedTuple

Outlet of a pump together with the shaft work.

Attributes:

Name Type Description
outlet Stream

Product Stream.

work Array

Shaft power delivered to the fluid (W).

report SolveReport

Solve and specification report.

outlets property

outlets: tuple[Stream]

The single outlet, as a flowsheet output tuple.

WorkResult

Bases: NamedTuple

Outlet of a compressor/turbine with actual and ideal shaft work.

Attributes:

Name Type Description
outlet Stream

Product Stream.

work Array

Actual shaft power into the fluid (W); negative for a turbine (the fluid does work on the surroundings).

ideal_work Array

Reversible (isentropic) shaft power into the fluid (W).

report SolveReport

Solve and specification report.

outlets property

outlets: tuple[Stream]

The single outlet, as a flowsheet output tuple.

flash_drum_with_info

flash_drum_with_info(
    feed: Stream,
    t: ArrayLike,
    p: ArrayLike,
    *,
    model: Model = None,
) -> FlashDrumResult

Flash a feed at temperature t and pressure p, with duty and report.

Parameters:

Name Type Description Default
feed Stream

Inlet Stream.

required
t ArrayLike

Drum temperature (K).

required
p ArrayLike

Drum pressure (Pa).

required
model Model

Property package; defaults to Peng-Robinson over the feed.

None

Returns:

Type Description
FlashDrumResult

The vapor and liquid products, the duty to hold the drum at t, and

FlashDrumResult

the report. A pure fluid specified at its own resolved saturation state

FlashDrumResult

keeps its phase inventory.

Raises:

Type Description
ConvergenceError

If an eager flash fails.

ValueError

If p exceeds the feed pressure (a drum can't raise it).

flash_drum

flash_drum(
    feed: Stream,
    t: ArrayLike,
    p: ArrayLike,
    *,
    model: Model = None,
) -> tuple[Stream, Stream]

Flash a feed stream at temperature t and pressure p.

Parameters:

Name Type Description Default
feed Stream

Inlet Stream.

required
t ArrayLike

Drum temperature (K).

required
p ArrayLike

Drum pressure (Pa).

required
model Model

Property package; defaults to Peng-Robinson over the feed.

None

Returns:

Type Description
Stream

(vapor, liquid) product streams. Their flows are differentiable with

Stream

respect to t, p, the feed, and the package parameters.

Raises:

Type Description
ConvergenceError

If an eager flash fails (NaN products when traced).

ValueError

If p exceeds the feed pressure.

Warns:

Type Description
PhysicalAcceptanceWarning

If an eager call's outlet phase is unstable (the feed forms more phases than a vapor-liquid flash represents).

adiabatic_flash

adiabatic_flash(
    feed: Stream,
    p: ArrayLike,
    *,
    duty: ArrayLike = 0.0,
    model: Model = None,
    t_init: ArrayLike = 300.0,
    allow_extrapolation: bool = False,
) -> FlashDrumResult

Separate a feed at pressure p with heat duty added (a PH flash drum).

The drum temperature follows from the energy balance, so this is the separator for a letdown into a drum, a partial condenser on a duty, or a pure fluid on its saturation line (where a temperature can't fix the phase amounts).

Parameters:

Name Type Description Default
feed Stream

Inlet Stream.

required
p ArrayLike

Drum pressure (Pa).

required
duty ArrayLike

Heat added (W); zero for an adiabatic drum.

0.0
model Model

Property package; defaults to Peng-Robinson over the feed.

None
t_init ArrayLike

Initial temperature guess (K) for the energy solve.

300.0
allow_extrapolation bool

Accept states outside the parameters' observed bounds.

False

Returns:

Type Description
FlashDrumResult

The vapor and liquid products, the duty, and the report.

Raises:

Type Description
ConvergenceError

If an eager solve fails.

ValueError

If p exceeds the feed pressure.

heater

heater(
    feed: Stream,
    *,
    t_out: ArrayLike | None = None,
    duty: ArrayLike | None = None,
    vapor_fraction: ArrayLike | None = None,
    dp: ArrayLike = 0.0,
    model: Model = None,
    t_init: ArrayLike = 300.0,
    allow_extrapolation: bool = False,
) -> HeaterResult

Heat or cool a stream on a temperature, duty, or vapor-fraction specification.

Provide exactly one of:

  • t_out: outlet temperature (K); the duty follows from the enthalpy change;
  • duty: signed heat added (W); the outlet temperature follows from an isenthalpic solve, so partial vaporization/condensation is handled;
  • vapor_fraction: outlet molar vapor fraction at its saturation temperature, any value in [0, 1] for a pure fluid (a condenser to saturated liquid is 0) and 0 (bubble point) or 1 (dew point) for a mixture.

dp is the (non-negative) pressure drop across the block. A pure fluid specified by t_out exactly at its saturation temperature is rejected: its quality is undetermined, so use vapor_fraction or duty.

Raises:

Type Description
ValueError

If not exactly one specification is given, or dp < 0.

ConvergenceError

If an eager solve fails.

valve

valve(
    feed: Stream,
    p_out: ArrayLike,
    *,
    model: Model = None,
    t_init: ArrayLike = 300.0,
    allow_extrapolation: bool = False,
) -> Stream

Isenthalpic (Joule-Thomson) pressure letdown to p_out.

Enthalpy is conserved, so the outlet temperature (and any flashing that results from the pressure drop) follows from an isenthalpic solve. The outlet may be two-phase; its phase inventory is retained on the stream.

Raises:

Type Description
ValueError

If p_out exceeds the inlet pressure.

ConvergenceError

If an eager solve fails.

pump

pump(
    feed: Stream,
    p_out: ArrayLike,
    *,
    efficiency: ArrayLike = 0.75,
    model: Model = None,
    t_init: ArrayLike = 300.0,
) -> PumpResult

Pump an (incompressible) liquid from feed.p to p_out.

The reversible work is v_L (p_out - p_in) per mole using the liquid molar volume; the actual work is divided by efficiency and the inefficiency is deposited as heat, so the outlet temperature is found from an isenthalpic balance on H_out = H_in + W_actual.

Raises:

Type Description
ValueError

For an efficiency outside (0, 1], a pressure decrease, or a feed that isn't liquid (use compressor for a vapor).

ConvergenceError

If an eager solve fails.

compressor

compressor(
    feed: Stream,
    p_out: ArrayLike,
    *,
    efficiency: ArrayLike = 0.75,
    model: Model = None,
    t_init: ArrayLike = 300.0,
) -> WorkResult

Compress a stream to p_out with an isentropic efficiency (< 1).

The reversible outlet is the isentropic state at p_out; the actual work is W_ideal / efficiency and the extra enthalpy sets the (higher) real outlet temperature via an isenthalpic solve.

Raises:

Type Description
ValueError

For an efficiency outside (0, 1] or p_out below the inlet.

ConvergenceError

If an eager solve fails.

turbine

turbine(
    feed: Stream,
    p_out: ArrayLike,
    *,
    efficiency: ArrayLike = 0.85,
    model: Model = None,
    t_init: ArrayLike = 300.0,
) -> WorkResult

Expand a stream to p_out with an isentropic efficiency (< 1).

The fluid recovers efficiency of the reversible work; the returned work is negative (power delivered to the shaft) and the real outlet is warmer than the isentropic outlet because of the lost work.

Raises:

Type Description
ValueError

For an efficiency outside (0, 1] or p_out above the inlet.

ConvergenceError

If an eager solve fails.

splitter

splitter(
    feed: Stream, fractions: ArrayLike
) -> tuple[Stream, ...]

Split feed into outlets that share its composition and state.

fractions holds one split fraction per outlet; each outlet carries that fraction of every component flow (and of the vapor inventory). The fractions must each lie in [0, 1] and sum to one, so the split conserves matter.

Raises:

Type Description
ValueError

For invalid fractions (NaN outlets when traced).

component_separator

component_separator(
    feed: Stream,
    split_to_top: ArrayLike,
    *,
    top_t: ArrayLike | None = None,
    top_p: ArrayLike | None = None,
    bottom_t: ArrayLike | None = None,
    bottom_p: ArrayLike | None = None,
) -> tuple[Stream, Stream]

Idealised separator with a per-component recovery to the top product.

split_to_top is a per-component fraction in [0, 1] (aligned with feed.components) sent to the top outlet; the remainder leaves in the bottom. This is the workhorse "spec" separator for conceptual flowsheets, a stand-in for a column or absorber whose recoveries are known. Product temperatures and pressures default to the feed's; product pressures can't exceed it.

Raises:

Type Description
ValueError

For recoveries outside [0, 1] or a raised pressure.

mix

mix(
    streams: list[Stream],
    *,
    t: ArrayLike | None = None,
    p: ArrayLike | None = None,
    model: Model = None,
    t_init: ArrayLike = 300.0,
) -> Stream

Combine streams with an exact material balance and an adiabatic energy balance.

Flows add component-by-component. By default the outlet temperature is found from an adiabatic energy balance (total enthalpy in equals total enthalpy out) via an isenthalpic solve, so heat of mixing and any phase change are accounted for; pass t to fix the outlet temperature instead (its heat is then the enthalpy change). Outlet pressure defaults to the lowest inlet pressure and can't exceed it.

Raises:

Type Description
ValueError

if the streams are empty, don't share a component list, or p exceeds the lowest inlet pressure.

ConvergenceError

If an eager energy solve fails.

Operating limits

The predicates that every unit and the saved-case audit enforce.

unit_limits

Operating limits shared by the unit kernels and the process-case audit.

Each rule is a traceable predicate, so a unit evaluated inside a compiled flowsheet reports a violated limit (and returns NaN outputs) while an eager call raises a ValueError naming the rule. The saved-case audit (fugacio.sim.cases.results) evaluates the same predicates on solved streams, so a limit can't be enforced in one place and forgotten in another.

Functions:

Name Description
pressure_not_raised

Valves, expanders, separators, and mixers can't raise pressure.

pressure_not_lowered

Pumps and compressors can't lower pressure.

split_fractions_valid

Conservative split: fractions in [0, 1] summing to one.

recoveries_valid

Per-component recoveries in [0, 1] (the complement leaves the other outlet).

liquid_inlet

A liquid-only machine's inlet is (within roundoff) all liquid.

require

Raise ValueError for a concrete violated rule; return the flag for tracing.

pressure_not_raised

pressure_not_raised(
    p_in: ArrayLike, p_out: ArrayLike
) -> Array

Valves, expanders, separators, and mixers can't raise pressure.

pressure_not_lowered

pressure_not_lowered(
    p_in: ArrayLike, p_out: ArrayLike
) -> Array

Pumps and compressors can't lower pressure.

split_fractions_valid

split_fractions_valid(fractions: ArrayLike) -> Array

Conservative split: fractions in [0, 1] summing to one.

recoveries_valid

recoveries_valid(recoveries: ArrayLike) -> Array

Per-component recoveries in [0, 1] (the complement leaves the other outlet).

liquid_inlet

liquid_inlet(vapor_fraction: ArrayLike) -> Array

A liquid-only machine's inlet is (within roundoff) all liquid.

require

require(ok: Array, message: str) -> Array

Raise ValueError for a concrete violated rule; return the flag for tracing.

Raises:

Type Description
ValueError

If ok is concretely false.

Persistent compilation cache

compilation

Opt-in persistent compilation cache for repeated process studies.

Every Fugacio kernel compiles once per process (see fugacio.sim.units), but a fresh process compiles again. enable_compilation_cache points JAX's persistent compilation cache at a directory so a second process (a CLI run, a worker in a sweep, a notebook restart) loads the compiled executables instead.

The cache is keyed by the JAX version, the backend, and the exact program, so a stale entry is never reused for a different computation. It's opt-in because the directory grows with every distinct program compiled.

Functions:

Name Description
enable_compilation_cache

Persist compiled JAX programs in directory for reuse across processes.

enable_compilation_cache

enable_compilation_cache(
    directory: str | Path,
    *,
    min_compile_time_s: float = 0.0,
) -> Path

Persist compiled JAX programs in directory for reuse across processes.

Call it before the first computation when possible. Calling it later also works: the cache is re-initialized so the next compilation uses it.

Parameters:

Name Type Description Default
directory str | Path

Cache directory. It's created if missing.

required
min_compile_time_s float

Only programs that took at least this long to compile are stored. The default, zero, stores every program, since process kernels are small individually but numerous.

0.0

Returns:

Type Description
Path

The resolved cache directory.

Non-ideal separations

separations

Liquid-liquid and three-phase separators on an activity-coefficient package.

These complement the vapor-liquid blocks in fugacio.sim.units with the non-ideal phase behavior an activity-coefficient (gamma-phi) package describes:

  • decanter: a liquid-liquid separator (settling tank) that splits one feed into two conjugate liquid products via the stability-first isoactivity flash;
  • three_phase_flash: a vapor + two-liquid (V-L-L) separator for heterogeneous systems (water/organic decantation, heteroazeotropic columns).

Both follow the unit contract: an eager call raises on failure (with a hint naming the separator that does apply), and a traced call returns NaN products. Every product is a differentiable Stream; flows carry gradients with respect to the operating T, P, the feed, and the package's activity parameters.

Functions:

Name Description
decanter

Liquid-liquid settler: split feed into two conjugate liquid products.

three_phase_flash

Vapor-liquid-liquid (V-L-L) flash of feed at (T, P).

decanter

decanter(
    feed: Stream,
    model: GammaPhiPackage,
    *,
    t: ArrayLike | None = None,
    tol: float = 1e-12,
    max_iter: int = 400,
) -> tuple[Stream, Stream]

Liquid-liquid settler: split feed into two conjugate liquid products.

Solves the stability-first isoactivity LLE flash (fugacio.thermo.lle.flash_lle_with_info) with the package's activity model at temperature t (default: the feed temperature) and the feed pressure. A feed outside any miscibility gap is one liquid: liquid_I carries it all and liquid_II is empty.

Returns:

Type Description
Stream

(liquid_I, liquid_II) product streams. The two isoactivity roots are

Stream

symmetric, so the order is made deterministic by returning the product

tuple[Stream, Stream]

richest in the first component (index 0) as liquid_I.

Raises:

Type Description
TypeError

If model isn't a gamma-phi package.

ConvergenceError

If an eager split fails.

three_phase_flash

three_phase_flash(
    feed: Stream,
    t: ArrayLike,
    p: ArrayLike,
    model: GammaPhiPackage,
    *,
    tol: float = 1e-11,
    max_iter: int = 300,
) -> tuple[Stream, Stream, Stream]

Vapor-liquid-liquid (V-L-L) flash of feed at (T, P).

Drives the three-phase flash (fugacio.thermo.vlle.flash_vlle_with_info) with the package's activity liquid and its EOS or ideal vapor. Use it where a vapor coexists with two liquids (water/organic decantation, heteroazeotropic distillation).

Returns:

Type Description
tuple[Stream, Stream, Stream]

(vapor, liquid_I, liquid_II) product streams.

Raises:

Type Description
TypeError

If model isn't a gamma-phi package.

ConvergenceError

If an eager flash fails. A feed that isn't three-phase at (T, P) is reported as infeasible, with a hint to use fugacio.sim.flash_drum (vapor-liquid) or decanter (liquid-liquid).