EN | JP | Last updated: 2026-08

Chapter 8: Transport Properties of Supercritical Fluids

Viscosity, Diffusivity, Thermal Conductivity, and Choosing a Supercritical Solvent

Reading Time: 35-40 minutes Difficulty: Advanced Code Examples: 9

Chapters 6 and 7 handled equilibrium: what the state of a supercritical fluid is, and how to compute it. This chapter handles the rates: how fast momentum, mass and heat actually move. Viscosity, diffusivity and thermal conductivity are what set pressure drop, extraction time and heat-exchanger area, and near the critical point they behave in ways that equilibrium intuition gets wrong — thermal conductivity diverges while thermal diffusivity collapses, and viscosity sails straight through the critical point without noticing it. We finish by turning the transport picture into a solvent-selection procedure across the full supercritical solvent set, not just CO₂ and water. Every printed output below was produced by running the code.

Learning Objectives

After completing this chapter, you will be able to:


8.1 Why Transport Properties Need Their Own Chapter

Equilibrium Tells You Whether, Rates Tell You How Long

An equation of state answers the question "at this temperature and pressure, what is the density, and how much solute will dissolve?" It says nothing at all about how long the extraction takes, how much pumping power the bed consumes, or how much heat-exchanger area the cycle needs. Those follow from three coefficients, each of which relates a flux to a gradient:

Coefficient Constitutive law Transports Sets
Dynamic viscosity $\eta$ (Pa·s) $\tau = -\eta \, \partial u/\partial y$ Momentum Pressure drop, pumping power, flow regime
Binary diffusivity $D_{12}$ (m²/s) $J = -D_{12} \, \partial c/\partial y$ Mass Extraction time, chromatographic band width
Thermal conductivity $\lambda$ (W/(m·K)) $q = -\lambda \, \partial T/\partial y$ Heat Heat-exchanger area, recuperator effectiveness

Chapters 1 and 2 gave the qualitative headline: a supercritical fluid has liquid-like density with gas-like viscosity and intermediate diffusivity. That headline is correct as an order of magnitude and it is what makes supercritical processing attractive. It is also, as this chapter will show numerically, considerably less dramatic at the high-density operating conditions industry actually uses than the usual "10-100 times" phrasing suggests.

The Three Regimes of a Transport Coefficient

All three coefficients can be written as a sum of contributions with different physical origins. For viscosity and thermal conductivity the standard decomposition used by every reference correlation is

$$ \eta(T,\rho) = \eta_0(T) + \Delta\eta(T,\rho) + \eta_c(T,\rho) $$ $$ \lambda(T,\rho) = \lambda_0(T) + \Delta\lambda(T,\rho) + \lambda_c(T,\rho) $$

The asymmetry that surprises everyone

The critical enhancement is not the same size for the three coefficients. In mode-coupling theory the divergences carry very different exponents: thermal conductivity diverges strongly (roughly as $\xi$), viscosity diverges only logarithmically-weakly (an exponent of order 0.04, which amounts to a few percent within millikelvins of $T_c$), and the mutual diffusion coefficient goes to zero. So at the same state point one coefficient blows up, one is unaffected, and one vanishes. Sections 8.2-8.4 demonstrate all three numerically.


8.2 Viscosity

The Dilute-Gas Term and the Lucas Correlation

At zero density the viscosity of a gas follows from kinetic theory. The practical engineering route is the Lucas corresponding-states correlation, which needs only $T_c$, $P_c$, $M$, $Z_c$ and the dipole moment:

$$ \eta_0 \xi = \left[0.807 T_r^{0.618} - 0.357 e^{-0.449 T_r} + 0.340 e^{-4.058 T_r} + 0.018\right] F_P^{\circ} F_Q^{\circ} $$ $$ \xi = 0.176 \left(\frac{T_c}{M^3 P_c^4}\right)^{1/6} $$

with $T_c$ in K, $M$ in g/mol, $P_c$ in bar, $\xi$ in $(\mu\text{P})^{-1}$ and $\eta_0$ in micropoise ($1\,\mu\text{P} = 10^{-7}$ Pa·s). $F_P^{\circ}$ corrects for molecular polarity through the reduced dipole moment $\mu_r = 52.46\, \mu^2 P_c / T_c^2$, and $F_Q^{\circ}$ is a quantum correction needed only for helium, hydrogen and deuterium.

Watch the direction of $\xi$. The group $\xi$ is an inverse viscosity scale, so the correlation is $\eta_0 = [\ldots]/\xi$, not $\eta_0 = [\ldots]\times\xi$. Multiplying instead of dividing gives an answer roughly four orders of magnitude too small — and because the result still looks like a small number with the right kind of exponent, the error survives casual inspection. This is exactly the defect found in the archived source material this chapter was built from.

Code Example 1: Lucas Dilute-Gas Viscosity, Checked Against a Reference Correlation
"""Example 1: dilute-gas viscosity from the Lucas correlation, checked against CoolProp."""
import numpy as np
import CoolProp.CoolProp as CP

R = 8.314462618  # J/(mol.K)


def lucas_dilute_gas_viscosity(T, Tc, Pc_bar, M, Zc, mu_debye=0.0):
    """Lucas correlation for the low-pressure (dilute-gas) viscosity.

    Poling, Prausnitz & O'Connell, "The Properties of Gases and Liquids",
    5th ed., eqs. 9-4.15 to 9-4.18.

    Parameters
    ----------
    T        : temperature, K
    Tc       : critical temperature, K
    Pc_bar   : critical pressure, bar
    M        : molar mass, g/mol
    Zc       : critical compressibility factor, -
    mu_debye : dipole moment, debye (0 for non-polar fluids)

    Returns
    -------
    eta : dynamic viscosity, Pa.s
    """
    Tr = T / Tc
    # Inverse viscosity scaling group, units of 1/micropoise
    xi = 0.176 * (Tc / (M ** 3 * Pc_bar ** 4)) ** (1.0 / 6.0)

    # Reduced dipole moment and the polarity correction factor F_P
    mu_r = 52.46 * mu_debye ** 2 * Pc_bar / Tc ** 2
    if mu_r < 0.022:
        F_P = 1.0
    elif mu_r < 0.075:
        F_P = 1.0 + 30.55 * max(0.0, 0.292 - Zc) ** 1.72
    else:
        F_P = 1.0 + 30.55 * max(0.0, 0.292 - Zc) ** 1.72 * abs(0.96 + 0.1 * (Tr - 0.7))

    bracket = (0.807 * Tr ** 0.618 - 0.357 * np.exp(-0.449 * Tr)
               + 0.340 * np.exp(-4.058 * Tr) + 0.018)

    eta_micropoise = bracket * F_P / xi
    return eta_micropoise * 1e-7  # 1 micropoise = 1e-7 Pa.s


# Dipole moments (debye) from the CRC Handbook
FLUIDS = {
    'CO2':      0.0,
    'Nitrogen': 0.0,
    'Propane':  0.084,
    'Ethanol':  1.69,
    'Water':    1.85,
}

print("=== Lucas dilute-gas viscosity vs CoolProp (evaluated at Tr = 1.05) ===")
print(f"{'Fluid':10s} {'T (K)':>8s} {'Lucas':>12s} {'CoolProp':>12s} {'error':>8s}  {'F_P':>5s}")
print(f"{'':10s} {'':>8s} {'(uPa.s)':>12s} {'(uPa.s)':>12s} {'(%)':>8s}")
print("-" * 62)

for fluid, mu in FLUIDS.items():
    Tc = CP.PropsSI('Tcrit', fluid)
    Pc = CP.PropsSI('pcrit', fluid)
    rho_c = CP.PropsSI('rhocrit', fluid)
    M = CP.PropsSI('molar_mass', fluid) * 1000.0        # g/mol
    Zc = Pc * (M / 1000.0) / (rho_c * R * Tc)
    T = 1.05 * Tc

    eta_lucas = lucas_dilute_gas_viscosity(T, Tc, Pc / 1e5, M, Zc, mu)
    # Dilute-gas limit of the reference correlation: evaluate at ~zero density
    eta_ref = CP.PropsSI('V', 'T', T, 'D', 1e-6, fluid)
    err = 100.0 * (eta_lucas - eta_ref) / eta_ref

    mu_r = 52.46 * mu ** 2 * (Pc / 1e5) / Tc ** 2
    F_P = eta_lucas / lucas_dilute_gas_viscosity(T, Tc, Pc / 1e5, M, Zc, 0.0)

    print(f"{fluid:10s} {T:8.2f} {eta_lucas*1e6:12.3f} {eta_ref*1e6:12.3f} "
          f"{err:+8.2f}  {F_P:5.3f}")

print()
print("Reduced dipole moments (mu_r) and the polarity branch taken:")
for fluid, mu in FLUIDS.items():
    Tc = CP.PropsSI('Tcrit', fluid)
    Pc_bar = CP.PropsSI('pcrit', fluid) / 1e5
    mu_r = 52.46 * mu ** 2 * Pc_bar / Tc ** 2
    branch = 'non-polar' if mu_r < 0.022 else ('weakly polar' if mu_r < 0.075 else 'polar')
    print(f"  {fluid:10s} mu = {mu:4.2f} D  ->  mu_r = {mu_r:8.5f}  ({branch})")
=== Lucas dilute-gas viscosity vs CoolProp (evaluated at Tr = 1.05) === Fluid T (K) Lucas CoolProp error F_P (uPa.s) (uPa.s) (%) -------------------------------------------------------------- CO2 319.33 16.151 15.912 +1.50 1.000 Nitrogen 132.50 8.895 8.985 -1.01 1.000 Propane 388.38 10.836 10.510 +3.10 1.000 Ethanol 540.44 15.583 15.898 -1.98 1.148 Water 679.45 23.803 24.716 -3.69 1.259 Reduced dipole moments (mu_r) and the polarity branch taken: CO2 mu = 0.00 D -> mu_r = 0.00000 (non-polar) Nitrogen mu = 0.00 D -> mu_r = 0.00000 (non-polar) Propane mu = 0.08 D -> mu_r = 0.00012 (non-polar) Ethanol mu = 1.69 D -> mu_r = 0.03545 (weakly polar) Water mu = 1.85 D -> mu_r = 0.09461 (polar)

Three to four percent for a correlation that needs nothing but the critical constants and a dipole moment is a good result, and it degrades in exactly the expected direction: the two hydrogen-bonding fluids are the worst, and both are handled by the polarity factor rather than by the underlying kinetic theory. Note also that all five dilute-gas viscosities land in the narrow band 9-25 µPa·s. At zero density, all gases are much the same.

Density Dependence: the Residual Term Does the Work

Real supercritical operation is at $\rho/\rho_c \approx 1$-$2$, where the residual term dominates. Subtracting the dilute-gas term isolates it, and the result is close to a function of density alone:

Code Example 2: Residual Viscosity of CO₂, and the Absent Critical Spike
"""Example 2: residual viscosity of CO2 and the (absent) critical spike."""
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import CoolProp.CoolProp as CP

FLUID = 'CO2'
Tc = CP.PropsSI('Tcrit', FLUID)
Pc = CP.PropsSI('pcrit', FLUID)
rho_c = CP.PropsSI('rhocrit', FLUID)


def eta0(T):
    """Dilute-gas viscosity: the reference correlation at ~zero density."""
    return CP.PropsSI('V', 'T', T, 'D', 1e-6, FLUID)


def eta(T, rho):
    return CP.PropsSI('V', 'T', T, 'D', rho, FLUID)


print(f"CO2: Tc = {Tc:.2f} K, Pc = {Pc/1e6:.3f} MPa, rho_c = {rho_c:.1f} kg/m3")
print()
print("=== Residual viscosity  d_eta = eta(T, rho) - eta0(T) ===")
print("If d_eta collapses onto a single curve in rho, it is density-controlled,")
print("not temperature-controlled.")
print()
header = f"{'rho/rho_c':>10s}" + ''.join(f"{f'{t-273.15:.0f} C':>12s}" for t in
                                        [310.0, 320.0, 340.0, 380.0, 450.0])
print(header)
print(f"{'':>10s}" + ''.join(f"{'(uPa.s)':>12s}" for _ in range(5)))
print("-" * (10 + 12 * 5))
for rho_r in [0.2, 0.5, 1.0, 1.5, 2.0]:
    rho = rho_r * rho_c
    row = f"{rho_r:10.1f}"
    for T in [310.0, 320.0, 340.0, 380.0, 450.0]:
        row += f"{(eta(T, rho) - eta0(T)) * 1e6:12.3f}"
    print(row)

print()
print("Spread of d_eta across the 310-450 K range, at fixed density:")
for rho_r in [0.2, 0.5, 1.0, 1.5, 2.0]:
    rho = rho_r * rho_c
    vals = np.array([eta(T, rho) - eta0(T) for T in [310.0, 320.0, 340.0, 380.0, 450.0]])
    print(f"  rho/rho_c = {rho_r:3.1f}:  mean = {vals.mean()*1e6:7.3f} uPa.s, "
          f"spread = {(vals.max()-vals.min())/vals.mean()*100:5.1f} % of mean")

print()
print("=== Is there a viscosity spike at the critical point? ===")
print("Walking an isotherm at Tr = 1.001 straight through rho_c:")
T_near = 1.001 * Tc
print(f"{'rho/rho_c':>10s} {'eta (uPa.s)':>14s} {'d(eta)/d(rho) (uPa.s per kg/m3)':>34s}")
print("-" * 60)
rhos = rho_c * np.array([0.90, 0.95, 0.99, 1.00, 1.01, 1.05, 1.10])
etas = np.array([eta(T_near, r) for r in rhos])
grad = np.gradient(etas, rhos)
for r, e, g in zip(rhos, etas, grad):
    print(f"{r/rho_c:10.2f} {e*1e6:14.4f} {g*1e6:34.5f}")

print()
print("Compare with the isobaric heat capacity on the same isotherm, which does diverge:")
for rho_r in [0.90, 1.00, 1.10]:
    rho = rho_r * rho_c
    cp = CP.PropsSI('C', 'T', T_near, 'D', rho, FLUID)
    print(f"  rho/rho_c = {rho_r:4.2f}:  cp = {cp:10.1f} J/(kg.K),  "
          f"eta = {eta(T_near, rho)*1e6:7.3f} uPa.s")

# Figure: residual viscosity collapse
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5))
rho_grid = np.linspace(1.0, 2.2 * rho_c, 300)
for T in [310.0, 320.0, 340.0, 380.0, 450.0]:
    e0 = eta0(T)
    ax1.plot(rho_grid / rho_c, [(eta(T, r)) * 1e6 for r in rho_grid],
             label=f'{T - 273.15:.0f} °C')
    ax2.plot(rho_grid / rho_c, [(eta(T, r) - e0) * 1e6 for r in rho_grid],
             label=f'{T - 273.15:.0f} °C')
ax1.set_xlabel(r'$\rho/\rho_c$'); ax1.set_ylabel(r'$\eta$ (µPa·s)')
ax1.set_title('Total viscosity'); ax1.legend(); ax1.grid(alpha=0.3)
ax2.set_xlabel(r'$\rho/\rho_c$'); ax2.set_ylabel(r'$\eta-\eta_0(T)$ (µPa·s)')
ax2.set_title('Residual viscosity collapses onto a density curve')
ax2.legend(); ax2.grid(alpha=0.3)
plt.tight_layout()
plt.savefig('co2_residual_viscosity.png', dpi=150)
print("\nFigure saved to 'co2_residual_viscosity.png'")
CO2: Tc = 304.13 K, Pc = 7.377 MPa, rho_c = 467.6 kg/m3 === Residual viscosity d_eta = eta(T, rho) - eta0(T) === If d_eta collapses onto a single curve in rho, it is density-controlled, not temperature-controlled. rho/rho_c 37 C 47 C 67 C 107 C 177 C (uPa.s) (uPa.s) (uPa.s) (uPa.s) (uPa.s) ---------------------------------------------------------------------- 0.2 1.029 1.079 1.164 1.292 1.421 0.5 4.594 4.670 4.800 4.986 5.156 1.0 17.182 17.213 17.267 17.350 17.448 1.5 41.018 40.924 40.785 40.669 40.839 2.0 86.379 85.881 85.087 84.153 83.996 Spread of d_eta across the 310-450 K range, at fixed density: rho/rho_c = 0.2: mean = 1.197 uPa.s, spread = 32.7 % of mean rho/rho_c = 0.5: mean = 4.841 uPa.s, spread = 11.6 % of mean rho/rho_c = 1.0: mean = 17.292 uPa.s, spread = 1.5 % of mean rho/rho_c = 1.5: mean = 40.847 uPa.s, spread = 0.9 % of mean rho/rho_c = 2.0: mean = 85.099 uPa.s, spread = 2.8 % of mean === Is there a viscosity spike at the critical point? === Walking an isotherm at Tr = 1.001 straight through rho_c: rho/rho_c eta (uPa.s) d(eta)/d(rho) (uPa.s per kg/m3) ------------------------------------------------------------ 0.90 29.0797 0.06808 0.95 30.6715 0.07032 0.99 32.0203 0.07394 1.00 32.3682 0.07487 1.01 32.7204 0.07580 1.05 34.1736 0.07963 1.10 36.0921 0.08206 Compare with the isobaric heat capacity on the same isotherm, which does diverge: rho/rho_c = 0.90: cp = 295511.7 J/(kg.K), eta = 29.080 uPa.s rho/rho_c = 1.00: cp = 579540.1 J/(kg.K), eta = 32.368 uPa.s rho/rho_c = 1.10: cp = 252983.5 J/(kg.K), eta = 36.092 uPa.s Figure saved to 'co2_residual_viscosity.png'

What Example 2 establishes

The practical consequence is convenient: you can build a supercritical hydraulic model with a viscosity correlation that ignores criticality entirely, and be wrong by less than the uncertainty in your bed voidage. The same shortcut applied to thermal conductivity would be a serious error, as Section 8.4 shows.


8.3 Diffusivity

Three Different Diffusion Coefficients

"Diffusivity" is ambiguous, and in the near-critical region the ambiguity matters:

Coefficient Physical meaning Behaviour near $T_c$
Self-diffusion $D_s$ Random walk of a molecule among identical molecules Smooth; no anomaly
Tracer diffusion $D_{12}^{\infty}$ Dilute solute in the solvent — the extraction-relevant one Weak anomaly at infinite dilution
Mutual diffusion $D_{12}$ Relaxation of a composition gradient at finite concentration Vanishes at the mixture critical locus

For supercritical extraction at the dilute concentrations typical of a solubility-limited process, the tracer coefficient is the right one, and it is well described hydrodynamically.

Stokes-Einstein: Why Low Viscosity Buys Fast Diffusion

The hydrodynamic result for a sphere of radius $r$ in a continuum of viscosity $\eta$ is

$$ D_{12} = \frac{k_B T}{n \pi \eta r}, \qquad n = 6 \ \text{(no slip)}, \quad n = 4 \ \text{(perfect slip)} $$

which contains the entire supercritical selling point in one line: $D \propto T/\eta$, and $\eta$ in a supercritical fluid is well below that of any liquid. It also makes the temperature dependence non-obvious, because raising $T$ raises the numerator and lowers $\eta$ through the density drop, so $D$ rises faster than linearly in $T$.

The classical engineering alternative is the Wilke-Chang correlation quoted in Chapter 2,

$$ D_{AB} = \frac{7.4\times10^{-8}\,(\phi M_B)^{0.5}\,T}{\eta\,V_A^{0.6}} \quad [\text{cm}^2/\text{s},\ \eta \text{ in cP}] $$

which was fitted to liquid solvents and is being extrapolated when applied to a supercritical fluid. Comparing the two is a cheap and honest uncertainty estimate.

Code Example 3: Naphthalene Diffusivity in scCO₂ and the Schmidt Number
"""Example 3: solute diffusivity in scCO2 - Stokes-Einstein, Wilke-Chang, Schmidt number."""
import numpy as np
import CoolProp.CoolProp as CP

k_B = 1.380649e-23      # J/K
N_A = 6.02214076e23     # 1/mol

# Naphthalene: the canonical solute for scCO2 diffusion measurements.
V_A = 147.6      # Le Bas molar volume, cm3/mol
M_B = 44.01      # CO2 molar mass, g/mol


def hydrodynamic_radius(V_le_bas_cm3):
    """Equivalent hard-sphere radius from a Le Bas molar volume."""
    V_m3 = V_le_bas_cm3 * 1e-6                       # m3/mol
    return (3.0 * V_m3 / (4.0 * np.pi * N_A)) ** (1.0 / 3.0)


def stokes_einstein(T, eta, radius, n=6.0):
    """D = k_B T / (n pi eta r).  n = 6 -> no slip, n = 4 -> perfect slip."""
    return k_B * T / (n * np.pi * eta * radius)


def wilke_chang(T, eta_Pa_s, phi=1.0):
    """Wilke-Chang correlation, returned in m2/s.

    D_AB = 7.4e-8 (phi M_B)^0.5 T / (eta V_A^0.6)   [cm2/s, eta in cP]
    """
    eta_cP = eta_Pa_s * 1e3
    D_cm2_s = 7.4e-8 * np.sqrt(phi * M_B) * T / (eta_cP * V_A ** 0.6)
    return D_cm2_s * 1e-4


r_A = hydrodynamic_radius(V_A)
print(f"Naphthalene: Le Bas molar volume {V_A:.1f} cm3/mol "
      f"-> equivalent radius {r_A * 1e10:.2f} A")
print()

print("=== Diffusivity of naphthalene in scCO2 at 40 C ===")
print(f"{'P':>6s} {'rho':>9s} {'eta':>10s} {'D (S-E, 6pi)':>14s} "
      f"{'D (Wilke-Chang)':>17s} {'Sc':>7s}")
print(f"{'(MPa)':>6s} {'(kg/m3)':>9s} {'(uPa.s)':>10s} {'(1e-8 m2/s)':>14s} "
      f"{'(1e-8 m2/s)':>17s} {'(-)':>7s}")
print("-" * 68)

T = 40 + 273.15
for P_MPa in [8, 10, 15, 20, 25, 30]:
    P = P_MPa * 1e6
    rho = CP.PropsSI('D', 'T', T, 'P', P, 'CO2')
    eta = CP.PropsSI('V', 'T', T, 'P', P, 'CO2')
    D_se = stokes_einstein(T, eta, r_A)
    D_wc = wilke_chang(T, eta)
    Sc = (eta / rho) / D_se
    print(f"{P_MPa:6d} {rho:9.1f} {eta*1e6:10.2f} {D_se*1e8:14.3f} "
          f"{D_wc*1e8:17.3f} {Sc:7.2f}")

print()
print("=== How do you actually raise D? Two routes, at 15 MPa ===")
print(f"{'T (C)':>7s} {'rho (kg/m3)':>12s} {'eta (uPa.s)':>12s} "
      f"{'D (1e-8 m2/s)':>15s} {'Sc':>7s}")
print("-" * 56)
for T_C in [35, 40, 50, 60, 80, 100]:
    T_k = T_C + 273.15
    rho = CP.PropsSI('D', 'T', T_k, 'P', 15e6, 'CO2')
    eta = CP.PropsSI('V', 'T', T_k, 'P', 15e6, 'CO2')
    D_se = stokes_einstein(T_k, eta, r_A)
    Sc = (eta / rho) / D_se
    print(f"{T_C:7d} {rho:12.1f} {eta*1e6:12.2f} {D_se*1e8:15.3f} {Sc:7.2f}")

print()
print("=== Comparison with liquid solvents, same solute ===")
print(f"{'Solvent':22s} {'eta (uPa.s)':>12s} {'D (1e-8 m2/s)':>15s} {'Sc':>9s}")
print("-" * 61)
cases = [
    ('scCO2, 40 C, 8 MPa',  'CO2',       313.15,  8.0e6),
    ('scCO2, 40 C, 20 MPa', 'CO2',       313.15, 20.0e6),
    ('n-Hexane, 25 C',      'n-Hexane',  298.15, 101325.0),
    ('Ethanol, 25 C',       'Ethanol',   298.15, 101325.0),
    ('Water, 25 C',         'Water',     298.15, 101325.0),
]
store = {}
for label, fluid, T_l, P_l in cases:
    rho_l = CP.PropsSI('D', 'T', T_l, 'P', P_l, fluid)
    eta_l = CP.PropsSI('V', 'T', T_l, 'P', P_l, fluid)
    D_l = stokes_einstein(T_l, eta_l, r_A)
    Sc_l = (eta_l / rho_l) / D_l
    store[label] = (eta_l, D_l)
    print(f"{label:22s} {eta_l*1e6:12.1f} {D_l*1e8:15.4f} {Sc_l:9.1f}")

print()
print("The 'diffusivity is 10-100x that of a liquid' claim, checked:")
eta_hex, D_hex = store['n-Hexane, 25 C']
for label in ['scCO2, 40 C, 8 MPa', 'scCO2, 40 C, 20 MPa']:
    eta_s, D_s = store[label]
    print(f"  {label:22s}: D / D_hexane = {D_s/D_hex:5.1f} x, "
          f"eta_hexane / eta = {eta_hex/eta_s:5.1f} x")
Naphthalene: Le Bas molar volume 147.6 cm3/mol -> equivalent radius 3.88 A === Diffusivity of naphthalene in scCO2 at 40 C === P rho eta D (S-E, 6pi) D (Wilke-Chang) Sc (MPa) (kg/m3) (uPa.s) (1e-8 m2/s) (1e-8 m2/s) (-) -------------------------------------------------------------------- 8 277.9 21.93 2.694 3.502 2.93 10 628.6 47.65 1.240 1.611 6.11 15 780.2 68.46 0.863 1.122 10.17 20 839.8 79.38 0.744 0.967 12.70 25 879.5 87.86 0.672 0.874 14.85 30 909.9 95.15 0.621 0.807 16.84 === How do you actually raise D? Two routes, at 15 MPa === T (C) rho (kg/m3) eta (uPa.s) D (1e-8 m2/s) Sc -------------------------------------------------------- 35 815.1 74.49 0.781 11.71 40 780.2 68.46 0.863 10.17 50 699.8 56.77 1.074 7.55 60 604.1 45.88 1.370 5.54 80 427.2 31.98 2.084 3.59 100 332.3 27.52 2.559 3.24 === Comparison with liquid solvents, same solute === Solvent eta (uPa.s) D (1e-8 m2/s) Sc ------------------------------------------------------------- scCO2, 40 C, 8 MPa 21.9 2.6944 2.9 scCO2, 40 C, 20 MPa 79.4 0.7443 12.7 n-Hexane, 25 C 298.0 0.1888 241.0 Ethanol, 25 C 1082.4 0.0520 2652.5 Water, 25 C 890.0 0.0632 1412.4 The 'diffusivity is 10-100x that of a liquid' claim, checked: scCO2, 40 C, 8 MPa : D / D_hexane = 14.3 x, eta_hexane / eta = 13.6 x scCO2, 40 C, 20 MPa : D / D_hexane = 3.9 x, eta_hexane / eta = 3.8 x

The Schmidt number is the number that matters

$Sc = \nu/D_{12}$ compares momentum transport with mass transport, and it is the group that enters every mass-transfer correlation. In scCO₂ it is 3-17. In n-hexane it is 241; in ethanol, 2650. Two to three orders of magnitude in the group that controls the concentration boundary layer is the real quantitative statement behind "supercritical mass transfer is fast", and it is a much sharper statement than a diffusivity ratio, because $Sc$ already accounts for the fact that the supercritical fluid is also less dense.

The "10-100× a liquid" claim needs a pressure attached to it. The last block of Example 3 checks it against n-hexane. At 8 MPa and 40 °C, scCO₂ gives 14× the diffusivity — comfortably inside the usual claim. At 20 MPa and 40 °C, the figure is 3.9×. Both are the same fluid at the same temperature; the difference is 560 kg/m³ of density. Since real extractions run at 15-30 MPa precisely because they need the density for solubility, the modest end of the range is the one that applies to most industrial practice. The advantage is real, but it is a factor of a few, bought at the cost of a pressure vessel.

Where the Hydrodynamic Picture Fails

Two caveats bound the numbers above:


8.4 Thermal Conductivity and the Critical Enhancement

The Term the Other Two Coefficients Do Not Have

Thermal conductivity is the coefficient for which the critical enhancement is unmistakable. Physically, heat is carried not only by molecular collisions but by the collective relaxation of the long-lived density fluctuations whose correlation length $\xi$ grows without bound at the critical point. In the mode-coupling result the enhancement scales roughly as

$$ \lambda_c \sim \frac{k_B T \, \rho \, c_p}{6\pi \eta \, \xi} \cdot \xi \propto \frac{k_B T \rho c_p}{6 \pi \eta} $$

so it inherits the divergence of $c_p$ — which Chapter 6 derived — and is large wherever $c_p$ is large. Reference correlations for CO₂ and water include this term explicitly, so it is present in every CoolProp thermal-conductivity call.

Code Example 4: Thermal Conductivity of CO₂ and the Critical Enhancement
"""Example 4: thermal conductivity of CO2 and the critical enhancement."""
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import CoolProp.CoolProp as CP

FLUID = 'CO2'
Tc = CP.PropsSI('Tcrit', FLUID)
Pc = CP.PropsSI('pcrit', FLUID)
rho_c = CP.PropsSI('rhocrit', FLUID)


def lam_TP(T, P):
    return CP.PropsSI('L', 'T', T, 'P', P, FLUID)


def lam_Trho(T, rho):
    return CP.PropsSI('L', 'T', T, 'D', rho, FLUID)


def lam0(T):
    """Dilute-gas thermal conductivity: the correlation at ~zero density."""
    return CP.PropsSI('L', 'T', T, 'D', 1e-6, FLUID)


print(f"CO2: Tc = {Tc:.2f} K, Pc = {Pc / 1e6:.3f} MPa, "
      f"rho_c = {rho_c:.1f} kg/m3")
print()
print("=== Thermal conductivity along isotherms, mW/(m.K) ===")
temps = [32, 35, 40, 50, 80, 150]
press = [7.5, 8, 9, 10, 15, 20, 30]
print(f"{'P (MPa)':>8s}" + ''.join(f"{f'{t} C':>10s}" for t in temps))
print("-" * (8 + 10 * len(temps)))
for P_MPa in press:
    row = f"{P_MPa:8.1f}"
    for T_C in temps:
        row += f"{lam_TP(T_C + 273.15, P_MPa * 1e6) * 1e3:10.2f}"
    print(row)

print()
print("=== The local peak on each isotherm (7.0-13 MPa, 0.002 MPa grid) ===")
print(f"{'T (C)':>7s} {'Tr':>7s} {'P_peak':>9s} {'rho_peak':>10s} "
      f"{'lam_peak':>10s} {'lam_min':>10s} {'peak/min':>10s}")
print(f"{'':>7s} {'':>7s} {'(MPa)':>9s} {'(kg/m3)':>10s} "
      f"{'(mW/m/K)':>10s} {'(mW/m/K)':>10s} {'':>10s}")
print("-" * 66)

P_scan = np.arange(7.0, 13.0 + 1e-9, 0.002)
for T_C in [31.5, 32, 33, 35, 40, 50, 80]:
    T_k = T_C + 273.15
    lams = np.array([lam_TP(T_k, p * 1e6) for p in P_scan])
    i_pk = int(np.argmax(lams))
    if i_pk == 0 or i_pk == len(P_scan) - 1:
        print(f"{T_C:7.1f} {T_k / Tc:7.4f} {'--':>9s} {'--':>10s} "
              f"{'--':>10s} {'--':>10s} {'monotonic':>10s}")
        continue
    i_min = i_pk + int(np.argmin(lams[i_pk:]))
    rho_peak = CP.PropsSI('D', 'T', T_k, 'P', P_scan[i_pk] * 1e6, FLUID)
    print(f"{T_C:7.1f} {T_k / Tc:7.4f} {P_scan[i_pk]:9.3f} {rho_peak:10.1f} "
          f"{lams[i_pk] * 1e3:10.2f} {lams[i_min] * 1e3:10.2f} "
          f"{lams[i_pk] / lams[i_min]:10.2f}")

print()
print("=== The critical enhancement, isolated at rho = rho_c ===")
print("Approaching Tc from above at fixed critical density:")
print(f"{'T/Tc - 1':>10s} {'T (K)':>9s} {'P (MPa)':>9s} {'lam':>10s} "
      f"{'lam0':>9s} {'lam/lam0':>9s}")
print(f"{'':>10s} {'':>9s} {'':>9s} {'(mW/m/K)':>10s} {'(mW/m/K)':>9s} {'':>9s}")
print("-" * 58)
for eps in [1e-4, 3e-4, 1e-3, 3e-3, 1e-2, 3e-2, 0.1, 0.5, 1.0]:
    T_k = Tc * (1 + eps)
    l = lam_Trho(T_k, rho_c)
    l0 = lam0(T_k)
    P = CP.PropsSI('P', 'T', T_k, 'D', rho_c, FLUID)
    print(f"{eps:10.4f} {T_k:9.3f} {P / 1e6:9.3f} {l * 1e3:10.2f} "
          f"{l0 * 1e3:9.2f} {l / l0:9.2f}")

lam_water = CP.PropsSI('L', 'T', 298.15, 'P', 101325.0, 'Water')
lam_near = lam_Trho(Tc * 1.0001, rho_c)
print()
print(f"For scale: liquid water at 25 C conducts at "
      f"{lam_water * 1e3:.1f} mW/(m.K).")
print(f"CO2 at Tc + 0.03 K and rho_c reaches {lam_near * 1e3:.1f} mW/(m.K), "
      f"i.e. {lam_near / lam_water:.2f}x water,")
print(f"while a gas at the same temperature and zero density manages only "
      f"{lam0(Tc * 1.0001) * 1e3:.1f} mW/(m.K).")

# Figure
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5))
P_grid = np.linspace(6.5, 25, 500)
for T_C in [32, 35, 40, 50, 80]:
    T_k = T_C + 273.15
    ax1.plot(P_grid, [lam_TP(T_k, p * 1e6) * 1e3 for p in P_grid],
             label=f'{T_C} °C')
ax1.axvline(Pc / 1e6, color='k', ls=':', lw=1, label=r'$P_c$')
ax1.set_xlabel('Pressure (MPa)')
ax1.set_ylabel(r'$\lambda$ (mW/(m·K))')
ax1.set_title('Isotherms: a local spike near $P_c$')
ax1.legend(); ax1.grid(alpha=0.3)

eps_grid = np.logspace(-4, 0, 60)
ax2.loglog(eps_grid, [lam_Trho(Tc * (1 + e), rho_c) * 1e3 for e in eps_grid],
           'o-', ms=3)
ax2.set_xlabel(r'$T/T_c - 1$')
ax2.set_ylabel(r'$\lambda$ at $\rho_c$ (mW/(m·K))')
ax2.set_title('The critical enhancement diverges')
ax2.grid(alpha=0.3, which='both')
plt.tight_layout()
plt.savefig('co2_thermal_conductivity.png', dpi=150)
print("\nFigure saved to 'co2_thermal_conductivity.png'")
CO2: Tc = 304.13 K, Pc = 7.377 MPa, rho_c = 467.6 kg/m3 === Thermal conductivity along isotherms, mW/(m.K) === P (MPa) 32 C 35 C 40 C 50 C 80 C 150 C -------------------------------------------------------------------- 7.5 89.03 45.85 36.20 30.64 27.86 30.71 8.0 76.92 84.91 43.87 33.38 28.76 31.07 9.0 78.51 74.95 72.18 41.59 30.84 31.84 10.0 81.64 77.77 71.87 53.98 33.37 32.68 15.0 93.29 90.30 85.33 75.58 51.06 37.79 20.0 101.41 98.79 94.49 86.19 64.91 44.28 30.0 113.73 111.41 107.64 100.45 82.02 57.83 === The local peak on each isotherm (7.0-13 MPa, 0.002 MPa grid) === T (C) Tr P_peak rho_peak lam_peak lam_min peak/min (MPa) (kg/m3) (mW/m/K) (mW/m/K) ------------------------------------------------------------------ 31.5 1.0017 7.464 449.8 188.41 76.89 2.45 32.0 1.0034 7.548 454.6 143.42 76.59 1.87 33.0 1.0066 7.714 453.5 110.98 75.99 1.46 35.0 1.0132 8.052 457.1 88.62 74.77 1.19 40.0 1.0297 -- -- -- -- monotonic 50.0 1.0625 -- -- -- -- monotonic 80.0 1.1612 -- -- -- -- monotonic === The critical enhancement, isolated at rho = rho_c === Approaching Tc from above at fixed critical density: T/Tc - 1 T (K) P (MPa) lam lam0 lam/lam0 (mW/m/K) (mW/m/K) ---------------------------------------------------------- 0.0001 304.159 7.382 632.40 17.04 37.11 0.0003 304.219 7.393 386.01 17.04 22.65 0.0010 304.432 7.429 229.97 17.06 13.48 0.0030 305.041 7.533 147.33 17.11 8.61 0.0100 307.169 7.897 96.17 17.27 5.57 0.0300 313.252 8.952 71.39 17.74 4.02 0.1000 334.541 12.691 56.71 19.41 2.92 0.5000 456.192 34.070 56.70 29.26 1.94 1.0000 608.256 60.063 68.43 41.58 1.65 For scale: liquid water at 25 C conducts at 606.5 mW/(m.K). CO2 at Tc + 0.03 K and rho_c reaches 632.4 mW/(m.K), i.e. 1.04x water, while a gas at the same temperature and zero density manages only 17.0 mW/(m.K). Figure saved to 'co2_thermal_conductivity.png'

Reading the numbers


8.5 Prandtl Number, Thermal Diffusivity and Critical Slowing Down

Conductivity Is Not the Same Thing as Heat Penetration

A large $\lambda$ is not automatically good news, because what determines how fast a temperature front moves is the thermal diffusivity

$$ a = \frac{\lambda}{\rho c_p}, \qquad Pr = \frac{\nu}{a} = \frac{\eta c_p}{\lambda} $$

Both $\lambda$ and $c_p$ diverge at the critical point, but $c_p$ diverges faster. Their ratio therefore goes to zero: heat diffusion stops precisely where conduction is best. This is critical slowing down, the same phenomenon that makes near-critical temperature control so difficult, and it is directly computable.

Code Example 5: Prandtl Number, Thermal Diffusivity and Critical Slowing Down
"""Example 5: Prandtl number, thermal diffusivity, and critical slowing down."""
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import CoolProp.CoolProp as CP

FLUID = 'CO2'
Tc = CP.PropsSI('Tcrit', FLUID)
Pc = CP.PropsSI('pcrit', FLUID)
rho_c = CP.PropsSI('rhocrit', FLUID)


def transport_group(T, P):
    """Return rho, eta, lam, cp, nu, a, Pr at (T, P)."""
    rho = CP.PropsSI('D', 'T', T, 'P', P, FLUID)
    eta = CP.PropsSI('V', 'T', T, 'P', P, FLUID)
    lam = CP.PropsSI('L', 'T', T, 'P', P, FLUID)
    cp = CP.PropsSI('C', 'T', T, 'P', P, FLUID)
    nu = eta / rho                       # kinematic viscosity, m2/s
    a = lam / (rho * cp)                 # thermal diffusivity, m2/s
    Pr = nu / a                          # = eta cp / lam
    return rho, eta, lam, cp, nu, a, Pr


print("=== Transport groups for CO2 at 40 C ===")
print(f"{'P':>6s} {'rho':>8s} {'eta':>9s} {'lam':>9s} {'cp':>10s} "
      f"{'nu':>11s} {'a':>11s} {'Pr':>7s}")
print(f"{'(MPa)':>6s} {'(kg/m3)':>8s} {'(uPa.s)':>9s} {'(mW/m/K)':>9s} "
      f"{'(J/kg/K)':>10s} {'(1e-8 m2/s)':>11s} {'(1e-8 m2/s)':>11s} {'(-)':>7s}")
print("-" * 76)
for P_MPa in [8, 10, 15, 20, 25, 30]:
    rho, eta, lam, cp, nu, a, Pr = transport_group(313.15, P_MPa * 1e6)
    print(f"{P_MPa:6d} {rho:8.1f} {eta*1e6:9.2f} {lam*1e3:9.2f} {cp:10.1f} "
          f"{nu*1e8:11.2f} {a*1e8:11.2f} {Pr:7.3f}")

print()
print("=== Critical slowing down of heat transport ===")
print("Thermal diffusivity a = lam / (rho cp) at rho = rho_c, approaching Tc:")
print(f"{'T/Tc - 1':>10s} {'lam':>10s} {'cp':>12s} {'a':>13s} {'Pr':>10s}")
print(f"{'':>10s} {'(mW/m/K)':>10s} {'(J/kg/K)':>12s} {'(1e-8 m2/s)':>13s} {'(-)':>10s}")
print("-" * 58)
for eps in [1e-4, 3e-4, 1e-3, 3e-3, 1e-2, 3e-2, 0.1, 0.5]:
    T_k = Tc * (1 + eps)
    lam = CP.PropsSI('L', 'T', T_k, 'D', rho_c, FLUID)
    cp = CP.PropsSI('C', 'T', T_k, 'D', rho_c, FLUID)
    eta = CP.PropsSI('V', 'T', T_k, 'D', rho_c, FLUID)
    a = lam / (rho_c * cp)
    Pr = eta * cp / lam
    print(f"{eps:10.4f} {lam*1e3:10.2f} {cp:12.1f} {a*1e8:13.4f} {Pr:10.2f}")

print()
print("Both lambda and cp diverge, but cp diverges faster, so a -> 0.")
print("Heat stops moving at exactly the point where the fluid conducts best.")

print()
print("=== What that means for a heat exchanger ===")
print("Time for heat to diffuse across a 1 mm channel, t ~ L^2 / a:")
L = 1e-3  # m
for label, T_k, P in [
        ('CO2, 40 C, 20 MPa', 313.15, 20e6),
        ('CO2, 35 C, 8 MPa (near-critical)', 308.15, 8e6),
        ('CO2, 32 C, 7.5 MPa (very near-critical)', 305.15, 7.5e6),
        ('Liquid water, 25 C, 0.1 MPa', 298.15, 101325.0)]:
    fluid = 'Water' if 'water' in label else FLUID
    lam = CP.PropsSI('L', 'T', T_k, 'P', P, fluid)
    rho = CP.PropsSI('D', 'T', T_k, 'P', P, fluid)
    cp = CP.PropsSI('C', 'T', T_k, 'P', P, fluid)
    a = lam / (rho * cp)
    print(f"  {label:42s} a = {a*1e8:8.2f}e-8 m2/s -> t = {L**2/a:7.2f} s")

print()
print("=== The pseudo-critical (Widom) locus: where cp peaks along an isobar ===")
print(f"{'P (MPa)':>9s} {'T_pc (C)':>10s} {'cp_max (J/kg/K)':>17s} "
      f"{'Pr at T_pc':>12s} {'a (1e-8 m2/s)':>15s}")
print("-" * 66)
for P_MPa in [7.5, 8, 9, 10, 12, 15]:
    P = P_MPa * 1e6
    T_scan = np.arange(300.0, 400.0, 0.01)
    cps = np.array([CP.PropsSI('C', 'T', t, 'P', P, FLUID) for t in T_scan])
    i = int(np.argmax(cps))
    T_pc = T_scan[i]
    rho, eta, lam, cp, nu, a, Pr = transport_group(T_pc, P)
    print(f"{P_MPa:9.1f} {T_pc - 273.15:10.2f} {cps[i]:17.1f} {Pr:12.3f} "
          f"{a*1e8:15.3f}")

# Figure
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5))
P_grid = np.linspace(7.5, 25, 300)
for T_C in [35, 40, 50, 80]:
    T_k = T_C + 273.15
    ax1.plot(P_grid, [transport_group(T_k, p * 1e6)[6] for p in P_grid],
             label=f'{T_C} °C')
ax1.set_xlabel('Pressure (MPa)'); ax1.set_ylabel('Pr (-)')
ax1.set_title('Prandtl number of CO$_2$'); ax1.legend(); ax1.grid(alpha=0.3)

eps_grid = np.logspace(-4, -0.3, 50)
ax2.loglog(eps_grid,
           [CP.PropsSI('L', 'T', Tc*(1+e), 'D', rho_c, FLUID)
            / (rho_c * CP.PropsSI('C', 'T', Tc*(1+e), 'D', rho_c, FLUID)) * 1e8
            for e in eps_grid], 'o-', ms=3)
ax2.set_xlabel(r'$T/T_c - 1$')
ax2.set_ylabel(r'$a$ at $\rho_c$ ($10^{-8}$ m$^2$/s)')
ax2.set_title('Critical slowing down of heat diffusion')
ax2.grid(alpha=0.3, which='both')
plt.tight_layout()
plt.savefig('co2_prandtl_thermal_diffusivity.png', dpi=150)
print("\nFigure saved to 'co2_prandtl_thermal_diffusivity.png'")
=== Transport groups for CO2 at 40 C === P rho eta lam cp nu a Pr (MPa) (kg/m3) (uPa.s) (mW/m/K) (J/kg/K) (1e-8 m2/s) (1e-8 m2/s) (-) ---------------------------------------------------------------------------- 8 277.9 21.93 43.87 4950.1 7.89 3.19 2.474 10 628.6 47.65 71.87 5657.5 7.58 2.02 3.751 15 780.2 68.46 85.33 2674.7 8.77 4.09 2.146 20 839.8 79.38 94.49 2253.9 9.45 4.99 1.894 25 879.5 87.86 101.60 2066.1 9.99 5.59 1.787 30 909.9 95.15 107.64 1955.6 10.46 6.05 1.729 === Critical slowing down of heat transport === Thermal diffusivity a = lam / (rho cp) at rho = rho_c, approaching Tc: T/Tc - 1 lam cp a Pr (mW/m/K) (J/kg/K) (1e-8 m2/s) (-) ---------------------------------------------------------- 0.0001 632.40 6288566.8 0.0215 321.73 0.0003 386.01 2052206.7 0.0402 172.02 0.0010 229.97 579540.1 0.0849 81.57 0.0030 147.33 171292.8 0.1839 37.67 0.0100 96.17 42910.8 0.4793 14.51 0.0300 71.39 13101.9 1.1653 6.02 0.1000 56.71 4240.5 2.8599 2.53 0.5000 56.70 1619.3 7.4887 1.13 Both lambda and cp diverge, but cp diverges faster, so a -> 0. Heat stops moving at exactly the point where the fluid conducts best. === What that means for a heat exchanger === Time for heat to diffuse across a 1 mm channel, t ~ L^2 / a: CO2, 40 C, 20 MPa a = 4.99e-8 m2/s -> t = 20.03 s CO2, 35 C, 8 MPa (near-critical) a = 0.68e-8 m2/s -> t = 146.07 s CO2, 32 C, 7.5 MPa (very near-critical) a = 0.65e-8 m2/s -> t = 154.57 s Liquid water, 25 C, 0.1 MPa a = 14.55e-8 m2/s -> t = 6.87 s === The pseudo-critical (Widom) locus: where cp peaks along an isobar === P (MPa) T_pc (C) cp_max (J/kg/K) Pr at T_pc a (1e-8 m2/s) ------------------------------------------------------------------ 7.5 31.71 228063.4 43.984 0.157 8.0 34.67 35266.1 12.405 0.560 9.0 40.01 12833.1 6.073 1.159 10.0 45.01 8081.3 4.331 1.642 12.0 53.97 4986.4 3.029 2.392 15.0 64.33 3495.7 2.329 3.199 Figure saved to 'co2_prandtl_thermal_diffusivity.png'

The three design consequences

Property-independent control tuning will not work near the critical point. Between 8 and 15 MPa on the pseudo-critical line, $a$ changes by a factor of six and $Pr$ by a factor of five. A thermal loop tuned at one operating point will be badly detuned two megapascals away. This is the same conclusion Chapter 6 reached from the $c_p$ divergence, arrived at from the transport side.


8.6 Transport Properties in a Working Extraction Bed

Momentum: the Ergun Equation

A supercritical extraction vessel is a packed bed, and its pressure drop follows the Ergun equation — a viscous term (Kozeny-Carman) plus an inertial term (Burke-Plummer):

$$ \frac{\Delta P}{L} = \underbrace{\frac{150\,\eta\,(1-\varepsilon)^2 u}{\varepsilon^3 d_p^2}}_{\text{viscous}} + \underbrace{\frac{1.75\,\rho\,(1-\varepsilon)u^2}{\varepsilon^3 d_p}}_{\text{inertial}} $$

with $u$ the superficial velocity and $\varepsilon$ the bed voidage. Note the two competing density effects: at fixed mass flow, a denser fluid moves more slowly, which cuts both terms, but a denser supercritical fluid is also more viscous.

Code Example 6: Pressure Drop Through a Packed Extraction Bed
"""Example 6: pressure drop through a packed extraction bed (Ergun equation)."""
import numpy as np
import CoolProp.CoolProp as CP

# Bed geometry: a laboratory-to-pilot scale extraction vessel
d_p = 1.0e-3      # particle diameter, m (ground coffee / milled plant material)
eps = 0.40        # bed voidage, -
L_bed = 1.0       # bed height, m
D_bed = 0.10      # bed diameter, m
A_bed = np.pi * D_bed ** 2 / 4.0
m_dot = 20.0 / 3600.0     # solvent mass flow, kg/s (20 kg/h)


def ergun(rho, eta, u):
    """Ergun pressure gradient, Pa/m.  u = superficial velocity, m/s."""
    viscous = 150.0 * eta * (1 - eps) ** 2 * u / (eps ** 3 * d_p ** 2)
    inertial = 1.75 * rho * (1 - eps) * u ** 2 / (eps ** 3 * d_p)
    return viscous, inertial


print(f"Bed: D = {D_bed*1e3:.0f} mm, L = {L_bed:.1f} m, d_p = {d_p*1e3:.1f} mm, "
      f"voidage = {eps:.2f}")
print(f"Solvent mass flow: {m_dot*3600:.0f} kg/h "
      f"(cross-section {A_bed*1e4:.1f} cm2)")
print()
print("=== scCO2 at 40 C ===")
print(f"{'P':>6s} {'rho':>8s} {'eta':>9s} {'u':>10s} {'Re_p':>8s} "
      f"{'dP visc':>10s} {'dP inert':>10s} {'dP total':>10s}")
print(f"{'(MPa)':>6s} {'(kg/m3)':>8s} {'(uPa.s)':>9s} {'(mm/s)':>10s} {'(-)':>8s} "
      f"{'(kPa)':>10s} {'(kPa)':>10s} {'(kPa)':>10s}")
print("-" * 76)
for P_MPa in [8, 10, 15, 20, 30]:
    rho = CP.PropsSI('D', 'T', 313.15, 'P', P_MPa * 1e6, 'CO2')
    eta = CP.PropsSI('V', 'T', 313.15, 'P', P_MPa * 1e6, 'CO2')
    u = m_dot / (rho * A_bed)
    Re_p = rho * u * d_p / eta
    dv, di = ergun(rho, eta, u)
    print(f"{P_MPa:6d} {rho:8.1f} {eta*1e6:9.2f} {u*1e3:10.3f} {Re_p:8.2f} "
          f"{dv*L_bed/1e3:10.4f} {di*L_bed/1e3:10.4f} "
          f"{(dv+di)*L_bed/1e3:10.4f}")

print()
print("=== Same bed, same mass flow, conventional liquid solvents at 25 C ===")
print(f"{'Solvent':12s} {'rho':>8s} {'eta':>9s} {'u':>10s} {'Re_p':>8s} "
      f"{'dP total':>10s} {'ratio to':>10s}")
print(f"{'':12s} {'(kg/m3)':>8s} {'(uPa.s)':>9s} {'(mm/s)':>10s} {'(-)':>8s} "
      f"{'(kPa)':>10s} {'scCO2':>10s}")
print("-" * 71)

rho_ref = CP.PropsSI('D', 'T', 313.15, 'P', 20e6, 'CO2')
eta_ref = CP.PropsSI('V', 'T', 313.15, 'P', 20e6, 'CO2')
u_ref = m_dot / (rho_ref * A_bed)
dP_ref = sum(ergun(rho_ref, eta_ref, u_ref)) * L_bed

for label, fluid in [('n-Hexane', 'n-Hexane'), ('Ethanol', 'Ethanol'),
                     ('Water', 'Water')]:
    rho = CP.PropsSI('D', 'T', 298.15, 'P', 101325.0, fluid)
    eta = CP.PropsSI('V', 'T', 298.15, 'P', 101325.0, fluid)
    u = m_dot / (rho * A_bed)
    Re_p = rho * u * d_p / eta
    dP = sum(ergun(rho, eta, u)) * L_bed
    print(f"{label:12s} {rho:8.1f} {eta*1e6:9.1f} {u*1e3:10.3f} {Re_p:8.2f} "
          f"{dP/1e3:10.4f} {dP/dP_ref:10.2f}")

print()
print(f"Reference: scCO2 at 40 C / 20 MPa gives dP = {dP_ref/1e3:.4f} kPa over "
      f"{L_bed:.1f} m of bed.")
print()
print("=== The pumping-power view ===")
print("Hydraulic power = volumetric flow x pressure drop.")
print(f"{'Case':26s} {'Q (L/h)':>10s} {'dP (kPa)':>10s} {'P_hyd (mW)':>12s}")
print("-" * 60)
cases = [('scCO2, 40 C, 10 MPa', 'CO2', 313.15, 10e6),
         ('scCO2, 40 C, 20 MPa', 'CO2', 313.15, 20e6),
         ('n-Hexane, 25 C', 'n-Hexane', 298.15, 101325.0),
         ('Water, 25 C', 'Water', 298.15, 101325.0)]
for label, fluid, T, P in cases:
    rho = CP.PropsSI('D', 'T', T, 'P', P, fluid)
    eta = CP.PropsSI('V', 'T', T, 'P', P, fluid)
    u = m_dot / (rho * A_bed)
    Q = m_dot / rho                    # m3/s
    dP = sum(ergun(rho, eta, u)) * L_bed
    print(f"{label:26s} {Q*3.6e6:10.2f} {dP/1e3:10.4f} {Q*dP*1e3:12.4f}")

print()
print("=== Where the bed itself stops being the limitation ===")
print("Fine particles improve mass transfer but the viscous term scales as 1/d_p^2:")
print(f"{'d_p (um)':>10s} {'dP total (kPa)':>16s} {'viscous share':>15s}")
print("-" * 43)
for d_p_um in [2000, 1000, 500, 200, 100, 50]:
    d_p = d_p_um * 1e-6
    rho, eta = rho_ref, eta_ref
    u = m_dot / (rho * A_bed)
    dv, di = ergun(rho, eta, u)
    print(f"{d_p_um:10d} {(dv+di)*L_bed/1e3:16.3f} "
          f"{dv/(dv+di)*100:14.1f}%")
Bed: D = 100 mm, L = 1.0 m, d_p = 1.0 mm, voidage = 0.40 Solvent mass flow: 20 kg/h (cross-section 78.5 cm2) === scCO2 at 40 C === P rho eta u Re_p dP visc dP inert dP total (MPa) (kg/m3) (uPa.s) (mm/s) (-) (kPa) (kPa) (kPa) ---------------------------------------------------------------------------- 8 277.9 21.93 2.545 32.26 0.0471 0.0295 0.0766 10 628.6 47.65 1.125 14.84 0.0452 0.0131 0.0583 15 780.2 68.46 0.907 10.33 0.0524 0.0105 0.0629 20 839.8 79.38 0.842 8.91 0.0564 0.0098 0.0662 30 909.9 95.15 0.777 7.43 0.0624 0.0090 0.0714 === Same bed, same mass flow, conventional liquid solvents at 25 C === Solvent rho eta u Re_p dP total ratio to (kg/m3) (uPa.s) (mm/s) (-) (kPa) scCO2 ----------------------------------------------------------------------- n-Hexane 654.9 298.0 1.080 2.37 0.2841 4.29 Ethanol 785.1 1082.4 0.901 0.65 0.8332 12.59 Water 997.0 890.0 0.709 0.79 0.5410 8.17 Reference: scCO2 at 40 C / 20 MPa gives dP = 0.0662 kPa over 1.0 m of bed. === The pumping-power view === Hydraulic power = volumetric flow x pressure drop. Case Q (L/h) dP (kPa) P_hyd (mW) ------------------------------------------------------------ scCO2, 40 C, 10 MPa 31.82 0.0583 0.5153 scCO2, 40 C, 20 MPa 23.81 0.0662 0.4378 n-Hexane, 25 C 30.54 0.2841 2.4102 Water, 25 C 20.06 0.5410 3.0145 === Where the bed itself stops being the limitation === Fine particles improve mass transfer but the viscous term scales as 1/d_p^2: d_p (um) dP total (kPa) viscous share ------------------------------------------- 2000 0.019 74.3% 1000 0.066 85.2% 500 0.245 92.0% 200 1.459 96.7% 100 5.739 98.3% 50 22.761 99.1%

Two things stand out. First, the pressure drop is flat in pressure: 0.058-0.077 kPa/m across the whole 8-30 MPa range, because rising viscosity and falling velocity nearly cancel. Bed hydraulics is not a reason to pick an operating pressure. Second, the ratio to a liquid solvent at the same mass flow is 4.3× for hexane and 12.6× for ethanol — real, but four times smaller than a naive comparison of viscosities alone would give, again because the liquid also moves more slowly. And the last block shows where the trade-off bites: milling to 50 µm to speed up intraparticle diffusion raises $\Delta P$ by a factor of 344 and pushes the viscous term to 99% of the total.

Mass: Sherwood Numbers and Which Resistance Controls

External (film) mass transfer in a packed bed is correlated through the Sherwood number. The Wakao-Kaguei correlation is the standard choice:

$$ Sh = \frac{k_f d_p}{D_{12}} = 2 + 1.1\, Sc^{1/3} Re^{0.6}, \qquad Re = \frac{\rho u d_p}{\eta} $$

The constant 2 is the stagnant-film limit for an isolated sphere. Comparing the resulting film time constant with the intraparticle diffusion time constant then answers the question that decides how to run the plant.

Code Example 7: Film Mass Transfer and the Controlling Resistance
"""Example 7: external mass transfer in a packed scCO2 extraction bed."""
import numpy as np
import CoolProp.CoolProp as CP

k_B = 1.380649e-23
N_A = 6.02214076e23

# Solute: naphthalene (Le Bas volume 147.6 cm3/mol) -> equivalent radius
r_A = (3.0 * 147.6e-6 / (4.0 * np.pi * N_A)) ** (1.0 / 3.0)

# Bed and particle geometry
d_p = 1.0e-3
eps = 0.40
D_bed = 0.10
A_bed = np.pi * D_bed ** 2 / 4.0
m_dot = 20.0 / 3600.0        # kg/s
tortuosity = 3.0             # typical for a milled plant matrix
porosity_p = 0.5             # intraparticle porosity


def sherwood_wakao_kaguei(Re, Sc):
    """Wakao-Kaguei packed-bed correlation: Sh = 2 + 1.1 Sc^(1/3) Re^0.6.

    Valid roughly for 3 < Re < 10000; the constant 2 is the stagnant-film limit.
    """
    return 2.0 + 1.1 * Sc ** (1.0 / 3.0) * Re ** 0.6


print("Solute: naphthalene, equivalent radius "
      f"{r_A*1e10:.2f} A; bed d_p = {d_p*1e3:.1f} mm, voidage {eps:.2f}")
print("Diffusivity from Stokes-Einstein, D = kT / (6 pi eta r)")
print()
print("=== scCO2 at 40 C, 20 kg/h through a 100 mm bed ===")
print(f"{'P':>6s} {'D':>13s} {'Sc':>7s} {'Re_p':>7s} {'Sh':>7s} "
      f"{'k_f':>12s} {'k_f a_v':>11s} {'tau_film':>10s}")
print(f"{'(MPa)':>6s} {'(1e-8 m2/s)':>13s} {'(-)':>7s} {'(-)':>7s} {'(-)':>7s} "
      f"{'(1e-5 m/s)':>12s} {'(1/s)':>11s} {'(s)':>10s}")
print("-" * 78)

a_v = 6.0 * (1 - eps) / d_p          # interfacial area per unit bed volume, 1/m
rows = []
for P_MPa in [8, 10, 15, 20, 30]:
    P = P_MPa * 1e6
    rho = CP.PropsSI('D', 'T', 313.15, 'P', P, 'CO2')
    eta = CP.PropsSI('V', 'T', 313.15, 'P', P, 'CO2')
    D12 = k_B * 313.15 / (6 * np.pi * eta * r_A)
    nu = eta / rho
    Sc = nu / D12
    u = m_dot / (rho * A_bed)
    Re = rho * u * d_p / eta
    Sh = sherwood_wakao_kaguei(Re, Sc)
    k_f = Sh * D12 / d_p
    tau_film = 1.0 / (k_f * a_v)
    rows.append((P_MPa, D12, Sc, Re, Sh, k_f, tau_film))
    print(f"{P_MPa:6d} {D12*1e8:13.3f} {Sc:7.2f} {Re:7.2f} {Sh:7.2f} "
          f"{k_f*1e5:12.3f} {k_f*a_v:11.4f} {tau_film:10.1f}")

print()
print(f"Interfacial area a_v = 6(1-eps)/d_p = {a_v:.0f} m2 per m3 of bed")
print()
print("=== Which resistance controls: film or intraparticle? ===")
print("Intraparticle effective diffusivity D_eff = eps_p D / tau,")
print("internal time constant tau_int ~ (d_p/2)^2 / (15 D_eff) for a sphere.")
print(f"{'P (MPa)':>8s} {'D_eff':>13s} {'tau_film':>10s} {'tau_int':>10s} "
      f"{'Bi_m':>8s} {'controlling':>14s}")
print(f"{'':>8s} {'(1e-9 m2/s)':>13s} {'(s)':>10s} {'(s)':>10s} {'(-)':>8s}")
print("-" * 68)
for P_MPa, D12, Sc, Re, Sh, k_f, tau_film in rows:
    D_eff = porosity_p * D12 / tortuosity
    tau_int = (d_p / 2) ** 2 / (15 * D_eff)
    Bi_m = k_f * (d_p / 2) / D_eff
    which = 'intraparticle' if tau_int > tau_film else 'film'
    print(f"{P_MPa:8d} {D_eff*1e9:13.3f} {tau_film:10.1f} {tau_int:10.1f} "
          f"{Bi_m:8.1f} {which:>14s}")

print()
print("=== Same bed with a liquid solvent, for scale ===")
print(f"{'Solvent':14s} {'D (1e-8 m2/s)':>15s} {'Sc':>8s} {'Re_p':>7s} "
      f"{'Sh':>7s} {'k_f (1e-5 m/s)':>16s}")
print("-" * 70)
for label, fluid, T, P in [('scCO2 (20 MPa)', 'CO2', 313.15, 20e6),
                           ('n-Hexane', 'n-Hexane', 298.15, 101325.0),
                           ('Ethanol', 'Ethanol', 298.15, 101325.0)]:
    rho = CP.PropsSI('D', 'T', T, 'P', P, fluid)
    eta = CP.PropsSI('V', 'T', T, 'P', P, fluid)
    D12 = k_B * T / (6 * np.pi * eta * r_A)
    Sc = (eta / rho) / D12
    u = m_dot / (rho * A_bed)
    Re = rho * u * d_p / eta
    Sh = sherwood_wakao_kaguei(Re, Sc)
    k_f = Sh * D12 / d_p
    print(f"{label:14s} {D12*1e8:15.4f} {Sc:8.1f} {Re:7.2f} {Sh:7.2f} "
          f"{k_f*1e5:16.3f}")

print()
print("Note: Re_p here is 0.6-2.4 for the liquids and 8.9-32 for scCO2,")
print("so the Wakao-Kaguei correlation is used slightly below its stated")
print("Re > 3 range for the liquid cases. Treat those two rows as indicative.")
Solute: naphthalene, equivalent radius 3.88 A; bed d_p = 1.0 mm, voidage 0.40 Diffusivity from Stokes-Einstein, D = kT / (6 pi eta r) === scCO2 at 40 C, 20 kg/h through a 100 mm bed === P D Sc Re_p Sh k_f k_f a_v tau_film (MPa) (1e-8 m2/s) (-) (-) (-) (1e-5 m/s) (1/s) (s) ------------------------------------------------------------------------------ 8 2.694 2.93 32.26 14.65 39.477 1.4212 0.7 10 1.240 6.11 14.84 12.15 15.063 0.5423 1.8 15 0.863 10.17 10.33 11.67 10.075 0.3627 2.8 20 0.744 12.70 8.91 11.53 8.585 0.3090 3.2 30 0.621 16.84 7.43 11.40 7.076 0.2547 3.9 Interfacial area a_v = 6(1-eps)/d_p = 3600 m2 per m3 of bed === Which resistance controls: film or intraparticle? === Intraparticle effective diffusivity D_eff = eps_p D / tau, internal time constant tau_int ~ (d_p/2)^2 / (15 D_eff) for a sphere. P (MPa) D_eff tau_film tau_int Bi_m controlling (1e-9 m2/s) (s) (s) (-) -------------------------------------------------------------------- 8 4.491 0.7 3.7 44.0 intraparticle 10 2.066 1.8 8.1 36.4 intraparticle 15 1.438 2.8 11.6 35.0 intraparticle 20 1.240 3.2 13.4 34.6 intraparticle 30 1.035 3.9 16.1 34.2 intraparticle === Same bed with a liquid solvent, for scale === Solvent D (1e-8 m2/s) Sc Re_p Sh k_f (1e-5 m/s) ---------------------------------------------------------------------- scCO2 (20 MPa) 0.7443 12.7 8.91 11.53 8.585 n-Hexane 0.1888 241.0 2.37 13.50 2.549 Ethanol 0.0520 2652.5 0.65 13.80 0.717 Note: Re_p here is 0.6-2.4 for the liquids and 8.9-32 for scCO2, so the Wakao-Kaguei correlation is used slightly below its stated Re > 3 range for the liquid cases. Treat those two rows as indicative.

Intraparticle diffusion controls, and that changes what you optimise

The mass Biot number is 34-44 across the whole operating range, and the intraparticle time constant exceeds the film time constant by a factor of 4-5 everywhere. In this bed the solvent film is not the bottleneck. The consequences are concrete: raising the CO₂ flow rate mostly wastes solvent, while halving the particle size cuts the internal time constant fourfold. That is why industrial supercritical extraction specifies particle size and matrix pretreatment far more carefully than flow rate — and why Example 6's pressure-drop penalty for fine grinding is the constraint that actually sets the particle size.

Where these numbers stop being trustworthy. $D_{\mathrm{eff}} = \varepsilon_p D_{12}/\tau$ with an assumed intraparticle porosity of 0.5 and tortuosity of 3 is a placeholder for a measurement; real plant matrices vary by an order of magnitude and are frequently anisotropic. The Wakao-Kaguei correlation is also used slightly below its stated $Re > 3$ validity for the liquid comparison rows. The conclusion — intraparticle control, by a comfortable margin — survives these uncertainties, since it would take a 5× error in $D_{\mathrm{eff}}$ to reverse it. The individual time constants do not.


8.7 The Supercritical Solvent Set Beyond CO₂ and Water

Chapters 2 and 3 covered scCO₂ and supercritical water in depth. The remaining candidates get named in comparison tables and then dropped, which understates a useful point: at matched reduced conditions their transport properties are remarkably similar, and the reasons they are not used are almost never transport reasons.

Supercritical Ethanol

$T_c = 241.6$ °C, $P_c = 6.27$ MPa, $\rho_c = 273$ kg/m³. Ethanol occupies the polarity gap between CO₂ and water, retains partial hydrogen-bonding capability, and is renewable and food-grade. Its critical pressure is the lowest of the polar options.

The limitation is flammability at 267 °C: the vessel needs inerting, and the safety case is materially more expensive than for CO₂.

Supercritical Propane

$T_c = 96.7$ °C, $P_c = 4.25$ MPa — the lowest critical pressure of any practical candidate. Propane is a distinctly better lipid solvent than CO₂ at comparable conditions, which is why it survives in vegetable-oil extraction and petroleum deasphalting despite being flammable and explosive. Example 8 shows it has the lowest viscosity and the highest solute diffusivity of the practical set at matched reduced conditions.

Supercritical Nitrogen and Xenon

Nitrogen ($T_c = -147$ °C, $P_c = 3.40$ MPa) is chemically ideal — completely inert, cheap, non-toxic — and practically unusable for extraction, because reaching the supercritical state requires cryogenic plant. It appears in inert-atmosphere processing rather than as a solvent.

Xenon ($T_c = 16.6$ °C, $P_c = 5.84$ MPa) has the most attractive critical temperature of anything on this list, is inert and non-toxic, and is used in specialised pharmaceutical and radiopharmaceutical work. Its cost — thousands of dollars per kilogram — confines it to applications where nothing else will do. It also, as Example 8 demonstrates, has no reference transport model in CoolProp at all, which is itself a lesson: the availability of reference-quality property data is a real engineering constraint on solvent choice, not an afterthought.

Fluorinated Fluids

R-134a ($T_c = 101$ °C, $P_c = 4.06$ MPa) and SF₆ ($T_c = 45.6$ °C, $P_c = 3.76$ MPa) have convenient critical points, zero ozone-depletion potential, well-characterised reference equations, and mature handling practice inherited from refrigeration. They also have global warming potentials of roughly 1430 and 24 300 respectively, which under current phase-down regulation rules them out of any new process where an alternative exists. Their density at matched reduced conditions is the highest of the set, so on transport grounds alone they would be attractive — a clean illustration that solvent selection is not a transport-properties problem.

All of Them at the Same Reduced State

The fair comparison is at matched reduced conditions, because that is what the principle of corresponding states says should collapse the differences.

Code Example 8: Transport Properties Across the Supercritical Solvent Set
"""Example 8: transport properties across the supercritical solvent set."""
import numpy as np
import CoolProp.CoolProp as CP

k_B = 1.380649e-23
N_A = 6.02214076e23
r_A = (3.0 * 147.6e-6 / (4.0 * np.pi * N_A)) ** (1.0 / 3.0)   # naphthalene

FLUIDS = ['CO2', 'Ethanol', 'Water', 'Propane', 'Nitrogen', 'Xenon',
          'R134a', 'SF6']

Tr_TARGET = 1.05
Pr_TARGET = 2.00


def matched_state(fluid, Tr=Tr_TARGET, Pr=Pr_TARGET):
    Tc = CP.PropsSI('Tcrit', fluid)
    Pc = CP.PropsSI('pcrit', fluid)
    return Tr * Tc, Pr * Pc, Tc, Pc


print(f"=== All fluids at the same reduced state: Tr = {Tr_TARGET}, "
      f"Pr = {Pr_TARGET} ===")
print(f"{'Fluid':10s} {'Tc':>8s} {'Pc':>7s} {'T':>8s} {'P':>7s} {'rho':>8s} "
      f"{'eta':>9s} {'lam':>9s} {'nu':>11s}")
print(f"{'':10s} {'(C)':>8s} {'(MPa)':>7s} {'(C)':>8s} {'(MPa)':>7s} "
      f"{'(kg/m3)':>8s} {'(uPa.s)':>9s} {'(mW/m/K)':>9s} {'(1e-8 m2/s)':>11s}")
print("-" * 82)

data = {}
for fluid in FLUIDS:
    T, P, Tc, Pc = matched_state(fluid)
    rho = CP.PropsSI('D', 'T', T, 'P', P, fluid)
    try:
        eta = CP.PropsSI('V', 'T', T, 'P', P, fluid)
        lam = CP.PropsSI('L', 'T', T, 'P', P, fluid)
    except ValueError:
        print(f"{fluid:10s} {Tc-273.15:8.2f} {Pc/1e6:7.3f} {T-273.15:8.2f} "
              f"{P/1e6:7.3f} {rho:8.1f} "
              f"{'no model':>9s} {'no model':>9s} {'--':>11s}")
        data[fluid] = None
        continue
    nu = eta / rho
    data[fluid] = (T, P, rho, eta, lam, nu)
    print(f"{fluid:10s} {Tc-273.15:8.2f} {Pc/1e6:7.3f} {T-273.15:8.2f} "
          f"{P/1e6:7.3f} {rho:8.1f} {eta*1e6:9.2f} {lam*1e3:9.2f} {nu*1e8:11.2f}")

print()
print("=== Derived transport groups at the same reduced state ===")
print(f"{'Fluid':10s} {'cp':>10s} {'a':>12s} {'Pr':>8s} {'D (S-E)':>12s} {'Sc':>8s}")
print(f"{'':10s} {'(J/kg/K)':>10s} {'(1e-8 m2/s)':>12s} {'(-)':>8s} "
      f"{'(1e-8 m2/s)':>12s} {'(-)':>8s}")
print("-" * 64)
for fluid in FLUIDS:
    if data[fluid] is None:
        print(f"{fluid:10s} {'--':>10s} {'--':>12s} {'--':>8s} {'--':>12s} {'--':>8s}")
        continue
    T, P, rho, eta, lam, nu = data[fluid]
    cp = CP.PropsSI('C', 'T', T, 'P', P, fluid)
    a = lam / (rho * cp)
    Pr = nu / a
    D12 = k_B * T / (6 * np.pi * eta * r_A)
    Sc = nu / D12
    print(f"{fluid:10s} {cp:10.1f} {a*1e8:12.3f} {Pr:8.3f} {D12*1e8:12.3f} "
          f"{Sc:8.2f}")

print()
print("=== Ranking (best first) at Tr = 1.05, Pr = 2.00 ===")
avail = [f for f in FLUIDS if data[f] is not None]
by_visc = sorted(avail, key=lambda f: data[f][3])
by_lam = sorted(avail, key=lambda f: -data[f][4])
by_rho = sorted(avail, key=lambda f: -data[f][2])
print("Lowest viscosity (cheapest to pump):      " + ", ".join(by_visc))
print("Highest thermal conductivity (heat duty): " + ", ".join(by_lam))
print("Highest density (most solvent power):     " + ", ".join(by_rho))

print()
print("=== The catch: reduced states are not equal operating states ===")
print(f"{'Fluid':10s} {'T (C)':>8s} {'P (MPa)':>9s} {'comment'}")
print("-" * 60)
comments = {
    'CO2': 'ambient-temperature vessel',
    'Ethanol': 'flammable, needs inerting',
    'Water': 'corrosion and alloy cost dominate',
    'Propane': 'flammable, lowest pressure',
    'Nitrogen': 'cryogenic plant required',
    'Xenon': 'no reference transport model, and the cost',
    'R134a': 'high-GWP refrigerant, phase-down',
    'SF6': 'GWP 24 300, restricted use',
}
for fluid in FLUIDS:
    T, P, Tc, Pc = matched_state(fluid)
    print(f"{fluid:10s} {T-273.15:8.1f} {P/1e6:9.3f} {comments[fluid]}")
=== All fluids at the same reduced state: Tr = 1.05, Pr = 2.0 === Fluid Tc Pc T P rho eta lam nu (C) (MPa) (C) (MPa) (kg/m3) (uPa.s) (mW/m/K) (1e-8 m2/s) ---------------------------------------------------------------------------------- CO2 30.98 7.377 46.18 14.755 727.2 60.43 78.66 8.31 Ethanol 241.56 6.268 267.29 12.536 422.6 53.92 130.84 12.76 Water 373.95 22.064 406.30 44.128 527.2 62.19 413.13 11.80 Propane 96.74 4.251 115.23 8.502 343.7 42.74 68.55 12.44 Nitrogen -146.96 3.396 -140.65 6.792 494.9 35.12 56.64 7.10 Xenon 16.58 5.842 31.07 11.684 1776.1 no model no model -- R134a 101.06 4.059 119.77 8.119 786.6 64.92 50.67 8.25 SF6 45.57 3.755 61.51 7.510 1140.9 82.30 48.71 7.21 === Derived transport groups at the same reduced state === Fluid cp a Pr D (S-E) Sc (J/kg/K) (1e-8 m2/s) (-) (1e-8 m2/s) (-) ---------------------------------------------------------------- CO2 2945.4 3.672 2.263 0.997 8.33 Ethanol 6266.1 4.941 2.582 1.891 6.75 Water 8177.0 9.584 1.231 2.061 5.72 Propane 3905.8 5.107 2.435 1.715 7.25 Nitrogen 3277.1 3.492 2.032 0.712 9.97 Xenon -- -- -- -- -- R134a 2064.8 3.120 2.645 1.142 7.23 SF6 1357.3 3.145 2.293 0.767 9.40 === Ranking (best first) at Tr = 1.05, Pr = 2.00 === Lowest viscosity (cheapest to pump): Nitrogen, Propane, Ethanol, CO2, Water, R134a, SF6 Highest thermal conductivity (heat duty): Water, Ethanol, CO2, Propane, Nitrogen, R134a, SF6 Highest density (most solvent power): SF6, R134a, CO2, Water, Nitrogen, Ethanol, Propane === The catch: reduced states are not equal operating states === Fluid T (C) P (MPa) comment ------------------------------------------------------------ CO2 46.2 14.755 ambient-temperature vessel Ethanol 267.3 12.536 flammable, needs inerting Water 406.3 44.128 corrosion and alloy cost dominate Propane 115.2 8.502 flammable, lowest pressure Nitrogen -140.6 6.792 cryogenic plant required Xenon 31.1 11.684 no reference transport model, and the cost R134a 119.8 8.119 high-GWP refrigerant, phase-down SF6 61.5 7.510 GWP 24 300, restricted use

Corresponding states works, and that is the point


8.8 Choosing a Supercritical Solvent

The Decision Tree

Solute polarity first, then thermal stability, then safety. Transport properties enter last, as a tie-breaker.

graph TD A[Separation or reaction target] --> B{Solute polarity} B -->|Non-polar| C{Thermal stability} B -->|Intermediate| D{Biomass-derived feed?} B -->|Polar or ionic| E{Water-soluble?} C -->|Low T required| F[scCO2] C -->|High T acceptable| G[sc-Propane] D -->|Yes| H[sc-Ethanol] D -->|No| I[scCO2 + co-solvent] E -->|Yes| J[sc-Water] E -->|No| K[sc-Ethanol] F --> L[Extraction, cleaning, particle formation] G --> M[Lipid and oil extraction] H --> N[Biodiesel, delignification] I --> O[Polar natural products] J --> P[Oxidation, hydrothermal synthesis] K --> Q[Purification, reaction medium] style A fill:#e0f7fa style F fill:#e8f5e9 style J fill:#fff3e0

The Comparison Table

Fluid $T_c$ (°C) $P_c$ (MPa) Polarity Usable density (kg/m³) Safety Cost Principal use
CO₂31.07.38Non- to weakly polar (tunable with a modifier) 200-900Excellent: non-toxic, non-flammable, GRASLow Extraction, cleaning, particle formation, power cycles
Water374.022.06Polar → non-polar above $T_c$ 50-600Good, but severe corrosion and burn hazardsVery low Oxidation (SCWO), hydrothermal synthesis, gasification
Ethanol241.66.27Intermediate 100-500Good, but flammable at operating temperatureMedium Biodiesel, polar natural products, delignification
Propane96.74.25Non-polar 150-500Flammable and explosiveLow Vegetable oil extraction, deasphalting
Nitrogen-147.03.40Non-polar 300-800Excellent (inert); asphyxiation riskLow Inert-atmosphere processing, not extraction
Xenon16.65.84Non-polar 1000-2000Excellent (inert)Very high Specialised pharmaceutical work; no reference transport model
R-134a101.14.06Weakly polar 300-900Good; non-flammableHigh Specialised cleaning; restricted by GWP 1430

Co-Solvents: Buying Polarity

Adding a few mole percent of a polar modifier is the standard way to move CO₂ into a polarity range it cannot reach alone. Chapter 2 covers the solubility side; the transport side matters too, because the modifier raises viscosity and therefore lowers diffusivity.

Modifier Typical loading Effect Target solutes
Ethanol1-10 vol% (5-15 mol%)Raises polarity and adds hydrogen bondingPolyphenols, alkaloids
Methanol1-5 vol%Stronger polarity increase than ethanolSugars, amino acids
Water1-10 vol%Large hydrophilicity increasePeptides, proteins
Acetic acid0.1-3 vol%Acidifies the mediumBasic compounds

The Hildebrand parameter of pure CO₂ at 40 °C and 20 MPa is around 12 MPa1/2, comparable to hexane; with 10 mol% ethanol it rises to roughly 15-16 MPa1/2, comparable to toluene. The costs are that the modifier must be separated from the product, that selectivity usually falls because unwanted compounds are co-extracted, and that reference mixture property data are thin — CoolProp has no binary interaction data for the CO₂ + ethanol pair, as Chapter 7 demonstrates by raising an exception on it.

Safety and Regulatory Constraints

Fluid Principal hazard Required controls Regulatory position
CO₂High pressure; asphyxiation in poorly ventilated spacesRelief valves, pressure interlocks, ventilation and CO₂ monitoringGRAS; widely approved for food and pharmaceutical use
WaterVery high pressure; thermal burns; corrosion and salt foulingNickel-alloy or lined vessels, insulation, corrosion monitoringApproved; effluent discharge regulated
EthanolFlammability at operating temperatureInert gas blanketing, explosion-proof electrical equipmentFood-grade grades available; flammable-liquid rules apply
PropaneFlammability and explosionHazardous-area classification, gas detection, automatic isolationRestricted; flammable-gas regulation
R-134a / SF₆Asphyxiation; environmental releaseLeak detection and recovery, no ventingRestricted or being phased down on GWP grounds

A Screening Procedure You Can Defend

The right structure separates the hard constraints, which are pass/fail and mostly non-computable, from the ranking, which should use computed properties only. Scoring schemes that add up arbitrary points for temperature, polarity, safety and cost produce a single number that looks objective and cannot be audited; a filter followed by a transparent transport ranking can be.

Code Example 9: Constraint-Then-Rank Solvent Screening
"""Example 9: transport-aware supercritical solvent screening."""
import numpy as np
import CoolProp.CoolProp as CP

k_B = 1.380649e-23
N_A = 6.02214076e23
r_A = (3.0 * 147.6e-6 / (4.0 * np.pi * N_A)) ** (1.0 / 3.0)

# Non-computable attributes have to be tabulated. Keep them separate from
# anything CoolProp can supply, so it stays obvious which numbers are
# judgement calls and which are reference-quality property data.
CANDIDATES = {
    'CO2':      dict(polarity='non-polar', flammable=False, cost='low',
                     cosolvent=True,
                     notes='GRAS, non-flammable, easy depressurisation'),
    'Ethanol':  dict(polarity='polar',     flammable=True,  cost='medium',
                     cosolvent=False,
                     notes='renewable, food-grade, needs inerting'),
    'Water':    dict(polarity='polar',     flammable=False, cost='very low',
                     cosolvent=False,
                     notes='corrosive above Tc, high-alloy vessels'),
    'Propane':  dict(polarity='non-polar', flammable=True,  cost='low',
                     cosolvent=False,
                     notes='better lipid solvent than CO2, explosion risk'),
    'Nitrogen': dict(polarity='non-polar', flammable=False, cost='low',
                     cosolvent=False,
                     notes='inert, but Tc = -147 C'),
    'R134a':    dict(polarity='weak',      flammable=False, cost='high',
                     cosolvent=False,
                     notes='GWP 1430, under phase-down'),
}

POLARITY_OK = {
    'non-polar': {'non-polar', 'weak'},
    'polar':     {'polar'},
}


def screen(max_T_C, required_polarity, allow_flammable,
           allow_cosolvent=True, Tr=1.05, Pr=2.0):
    """Hard-filter the candidates, then rank the survivors on transport.

    Constraints are pass/fail; the ranking uses computed properties only.
    """
    passed, rejected = [], []
    for fluid, meta in CANDIDATES.items():
        Tc = CP.PropsSI('Tcrit', fluid)
        Pc = CP.PropsSI('pcrit', fluid)
        T_op, P_op = Tr * Tc, Pr * Pc
        reasons, flags = [], []

        if Tc < 273.15:
            reasons.append('cryogenic critical temperature')
        if T_op - 273.15 > max_T_C:
            reasons.append(f'operating T {T_op-273.15:.0f} C > limit {max_T_C} C')
        if meta['flammable'] and not allow_flammable:
            reasons.append('flammable')
        if meta['polarity'] not in POLARITY_OK[required_polarity]:
            if required_polarity == 'polar' and meta['cosolvent'] and allow_cosolvent:
                flags.append('needs a polar co-solvent (5-10 mol% ethanol)')
            else:
                reasons.append(f"{meta['polarity']} solvent for a "
                               f"{required_polarity} solute")
        if reasons:
            rejected.append((fluid, reasons))
            continue
        try:
            rho = CP.PropsSI('D', 'T', T_op, 'P', P_op, fluid)
            eta = CP.PropsSI('V', 'T', T_op, 'P', P_op, fluid)
        except ValueError:
            rejected.append((fluid, ['no reference transport model available']))
            continue
        D12 = k_B * T_op / (6 * np.pi * eta * r_A)
        passed.append(dict(fluid=fluid, T=T_op - 273.15, P=P_op / 1e6, rho=rho,
                           eta=eta, D12=D12, Sc=(eta / rho) / D12,
                           cost=meta['cost'], flags=flags))

    passed.sort(key=lambda r: -r['D12'])

    print(f"  Constraints: T <= {max_T_C} C, {required_polarity} solute, "
          f"flammable {'allowed' if allow_flammable else 'not allowed'}, "
          f"co-solvent {'allowed' if allow_cosolvent else 'not allowed'}")
    print(f"  Screening state: Tr = {Tr}, Pr = {Pr}")
    if passed:
        print(f"  {'rank':>4s} {'fluid':10s} {'T (C)':>7s} {'P (MPa)':>8s} "
              f"{'rho':>8s} {'eta':>8s} {'D (1e-8)':>9s} {'Sc':>6s} {'cost':>9s}")
        for i, r in enumerate(passed, 1):
            print(f"  {i:4d} {r['fluid']:10s} {r['T']:7.1f} {r['P']:8.2f} "
                  f"{r['rho']:8.1f} {r['eta']*1e6:8.2f} {r['D12']*1e8:9.3f} "
                  f"{r['Sc']:6.2f} {r['cost']:>9s}")
            for f in r['flags']:
                print(f"       -> {f}")
    else:
        print("  No candidate satisfies the constraints.")
    for fluid, reasons in rejected:
        print(f"  rejected  {fluid:10s} -- {'; '.join(reasons)}")
    return passed, rejected


print("=== Case 1: heat-sensitive natural product, non-polar, no flammables ===")
screen(max_T_C=80, required_polarity='non-polar', allow_flammable=False)

print()
print("=== Case 2: polyphenol extraction, polar solute, no flammables ===")
screen(max_T_C=120, required_polarity='polar', allow_flammable=False)

print()
print("=== Case 3: lipid extraction, flammables acceptable ===")
screen(max_T_C=150, required_polarity='non-polar', allow_flammable=True)

print()
print("=== Case 4: hydrothermal oxidation, polar, high temperature allowed ===")
screen(max_T_C=500, required_polarity='polar', allow_flammable=False,
       allow_cosolvent=False)

print()
print("Case 1 leaves exactly one survivor, which is the honest answer: scCO2")
print("dominates industrial practice because the constraints that matter")
print("commercially eliminate everything else before any transport comparison")
print("is made. Transport properties decide the outcome only once two or more")
print("candidates clear the constraints, as in Case 3.")
=== Case 1: heat-sensitive natural product, non-polar, no flammables === Constraints: T <= 80 C, non-polar solute, flammable not allowed, co-solvent allowed Screening state: Tr = 1.05, Pr = 2.0 rank fluid T (C) P (MPa) rho eta D (1e-8) Sc cost 1 CO2 46.2 14.75 727.2 60.43 0.997 8.33 low rejected Ethanol -- operating T 267 C > limit 80 C; flammable; polar solvent for a non-polar solute rejected Water -- operating T 406 C > limit 80 C; polar solvent for a non-polar solute rejected Propane -- operating T 115 C > limit 80 C; flammable rejected Nitrogen -- cryogenic critical temperature rejected R134a -- operating T 120 C > limit 80 C === Case 2: polyphenol extraction, polar solute, no flammables === Constraints: T <= 120 C, polar solute, flammable not allowed, co-solvent allowed Screening state: Tr = 1.05, Pr = 2.0 rank fluid T (C) P (MPa) rho eta D (1e-8) Sc cost 1 CO2 46.2 14.75 727.2 60.43 0.997 8.33 low -> needs a polar co-solvent (5-10 mol% ethanol) rejected Ethanol -- operating T 267 C > limit 120 C; flammable rejected Water -- operating T 406 C > limit 120 C rejected Propane -- flammable; non-polar solvent for a polar solute rejected Nitrogen -- cryogenic critical temperature; non-polar solvent for a polar solute rejected R134a -- weak solvent for a polar solute === Case 3: lipid extraction, flammables acceptable === Constraints: T <= 150 C, non-polar solute, flammable allowed, co-solvent allowed Screening state: Tr = 1.05, Pr = 2.0 rank fluid T (C) P (MPa) rho eta D (1e-8) Sc cost 1 Propane 115.2 8.50 343.7 42.74 1.715 7.25 low 2 R134a 119.8 8.12 786.6 64.92 1.142 7.23 high 3 CO2 46.2 14.75 727.2 60.43 0.997 8.33 low rejected Ethanol -- operating T 267 C > limit 150 C; polar solvent for a non-polar solute rejected Water -- operating T 406 C > limit 150 C; polar solvent for a non-polar solute rejected Nitrogen -- cryogenic critical temperature === Case 4: hydrothermal oxidation, polar, high temperature allowed === Constraints: T <= 500 C, polar solute, flammable not allowed, co-solvent not allowed Screening state: Tr = 1.05, Pr = 2.0 rank fluid T (C) P (MPa) rho eta D (1e-8) Sc cost 1 Water 406.3 44.13 527.2 62.19 2.061 5.72 very low rejected CO2 -- non-polar solvent for a polar solute rejected Ethanol -- flammable rejected Propane -- flammable; non-polar solvent for a polar solute rejected Nitrogen -- cryogenic critical temperature; non-polar solvent for a polar solute rejected R134a -- weak solvent for a polar solute Case 1 leaves exactly one survivor, which is the honest answer: scCO2 dominates industrial practice because the constraints that matter commercially eliminate everything else before any transport comparison is made. Transport properties decide the outcome only once two or more candidates clear the constraints, as in Case 3.

What the screening procedure actually shows


Summary

Key Takeaways

1. Structure of a transport coefficient

2. The critical anomalies are not alike

3. Diffusivity and mass transfer

4. In a real bed

5. Solvent selection

With Chapter 6 supplying the equilibrium state, Chapter 7 the computational tools, and this chapter the rates, the quantitative half of the series is complete: you can now compute where a supercritical process sits, how fast it will run, and which fluid to run it in.


Exercises

Exercise 1: Extend the Lucas Correlation to High Pressure

The Lucas method has a high-pressure extension that multiplies the dilute-gas result by a correction factor built from $T_r$ and $P_r$. Look it up in Poling et al., implement it, and compare against CoolProp for CO₂ at 40 °C over 8-30 MPa. Where does it break down, and does the failure correlate with $\rho/\rho_c$ or with $P_r$?

Exercise 2: Residual Viscosity for a Polar Fluid

Repeat Example 2 for water and for ethanol. Does the residual viscosity collapse onto a density curve as cleanly as it does for CO₂? Quantify the spread at $\rho = \rho_c$ and $\rho = 2\rho_c$, and explain any difference in terms of hydrogen bonding.

Exercise 3: The Cost of a Co-Solvent

Adding 10 mol% ethanol to CO₂ raises the mixture viscosity. Using the CoolProp HEOS backend where it works (and documenting where it does not — see Chapter 7), estimate the viscosity penalty and the resulting reduction in Stokes-Einstein diffusivity. Express the result as the percentage increase in extraction time needed to offset it, assuming intraparticle control.

Exercise 4: Locate the Enhancement Boundary

Example 4 finds a local thermal-conductivity peak up to 35 °C and none at 40 °C. Bisect on temperature to find, to 0.1 °C, the isotherm at which the local maximum disappears. Convert that temperature to $T_r$ and compare it with the $T_r$ at which the enhancement factor at $\rho_c$ falls below 4.

Exercise 5: Deteriorated Heat Transfer

Compute $Nu$ from the Dittus-Boelter correlation $Nu = 0.023 Re^{0.8} Pr^{0.4}$ for CO₂ in a 2 mm tube at 8 MPa, sweeping the bulk temperature through the pseudo-critical point at a fixed mass flux of 500 kg/(m²·s). Plot the heat-transfer coefficient against temperature. Where does the correlation predict a maximum, and why should you not trust it there?

Exercise 6: Optimal Particle Size

Combine Examples 6 and 7 into a single optimisation: minimise total extraction time subject to a pumping-power limit, with particle diameter as the decision variable. Use the intraparticle time constant as the objective and the Ergun pressure drop as the constraint. How sensitive is the optimum to the assumed tortuosity?

Exercise 7: Add a Fluid to the Survey

Extend Example 8 with ethane, ammonia and methanol. Do they fall inside the factor-of-2.3 viscosity band found for the original seven? Ammonia is strongly hydrogen-bonding — does it join water as a thermal-conductivity outlier, and by how much?


References and Further Reading

Transport Property Correlations

Supercritical Transport Properties Specifically

Tools and Data


Disclaimer