Particles and Beds#

difflow.particles holds pure JAX functions for flow past particles and through particle beds: drag and terminal velocity, the full Ergun pressure drop, minimum fluidization and bed expansion. They are building blocks (not unit operations) for adsorbers, catalytic beds, settlers and filters. Correlations: Haider & Levenspiel (1989), Turton & Levenspiel (1986), Ergun (1952), Wen & Yu (1966), Richardson & Zaki (1954). See Particles and Beds in docs/unit-operations-chemical.md for what is verified and what is not.

1. Drag curve and terminal velocity#

import jax, jax.numpy as jnp
import matplotlib.pyplot as plt
import numpy as np
from difflow.particles import *

Re = np.logspace(-1, np.log10(2e5), 200)
for m in DRAG_METHODS:
    plt.loglog(Re, [float(drag_coefficient(r, 1.0, m)) for r in Re], label=m)
plt.loglog(Re, 24 / Re, 'k:', label='24/Re (Stokes)')
plt.xlabel('Re_p'); plt.ylabel('C_D'); plt.ylim(0.2, 300); plt.legend(); plt.show()
../_images/1292da4f2a990bc724ea6bd612b2a6c28fe490c432b93d7691d01701231c2906.png
# Terminal velocity of quartz-density spheres in water, Stokes -> Newton regime,
# and its exact derivative through the implicit root find.
for d in [10e-6, 100e-6, 1e-3, 1e-2]:
    v = terminal_velocity(d, 2650.0, 998.0, 1e-3)
    dv = jax.grad(terminal_velocity)(d, 2650.0, 998.0, 1e-3)
    print(f"d = {d*1e6:8.0f} um   v_t = {float(v):.5f} m/s   dv/dd = {float(dv):10.2f} 1/s")
d =       10 um   v_t = 0.00009 m/s   dv/dd =      17.93 1/s
d =      100 um   v_t = 0.00777 m/s   dv/dd =     136.42 1/s
d =     1000 um   v_t = 0.15070 m/s   dv/dd =     139.85 1/s
d =    10000 um   v_t = 0.71229 m/s   dv/dd =      31.46 1/s

2. Packed-bed pressure drop (Ergun)#

dpdl, info = ergun_pressure_gradient(1.0, 3e-3, 0.4, 1.2, 1.8e-5)   # 3 mm spheres in air
print("dP/L =", float(dpdl), "Pa/m  (hand calculation: 1687.5 viscous + 6562.5 inertial = 8250)")
print({k: float(v) for k, v in info.items()})
g = jax.grad(lambda d, e: ergun_pressure_gradient(1.0, d, e, 1.2, 1.8e-5)[0], argnums=(0, 1))(3e-3, 0.4)
print("d(dP/L)/d(d_p), d(dP/L)/d(eps) =", [float(x) for x in g])
dP/L = 8249.999999999998 Pa/m  (hand calculation: 1687.5 viscous + 6562.5 inertial = 8250)
{'viscous': 1687.4999999999995, 'inertial': 6562.499999999999, 'Re_bed': 333.3333333333333}
d(dP/L)/d(d_p), d(dP/L)/d(eps) = [-3312499.999999999, -78437.49999999997]

3. Fluidization window and bed expansion#

u_mf, u_t = fluidization_window(300e-6, 2600.0, 1.2, 1.8e-5, 0.45, 0.8)
print(f"u_mf = {float(u_mf):.4f} m/s (Ergun), Wen-Yu: "
      f"{float(minimum_fluidization_velocity(300e-6, 2600.0, 1.2, 1.8e-5, method='wen_yu')):.4f} m/s, u_t = {float(u_t):.2f} m/s")
print("Geldart group:", geldart_group(300e-6, 2600.0, 1.2), "(not differentiable)")

# Richardson-Zaki expansion of a liquid-fluidized bed: 500 um glass beads in water
d, rp, rf, mu = 500e-6, 2500.0, 998.0, 1e-3
u_t = terminal_velocity(d, rp, rf, mu)
Re_t = terminal_reynolds(d, rp, rf, mu)
print(f"liquid bed: u_t = {float(u_t):.3f} m/s, Re_t = {float(Re_t):.0f}, n = {float(richardson_zaki_exponent(Re_t)):.2f}")
u = jnp.linspace(0.2, 0.8, 4) * u_t
eps, H = bed_expansion(u, u_t, Re_t=Re_t, voidage_mf=0.45, H_mf=1.0)
for ui, e, h in zip(u, eps, H):
    print(f"u = {float(ui):.3f} m/s  voidage = {float(e):.3f}  H/H_mf = {float(h):.3f}")
u_mf = 0.0875 m/s (Ergun), Wen-Yu: 0.0754 m/s, u_t = 2.12 m/s
Geldart group: B (not differentiable)
liquid bed: u_t = 0.070 m/s, Re_t = 35, n = 3.12
u = 0.014 m/s  voidage = 0.597  H/H_mf = 1.364
u = 0.028 m/s  voidage = 0.745  H/H_mf = 2.160
u = 0.042 m/s  voidage = 0.849  H/H_mf = 3.639
u = 0.056 m/s  voidage = 0.931  H/H_mf = 7.962

4. GasPFR with a physical bed#

Instead of a lumped alpha, give the bed and let Ergun compute it.

from difflow import make_stream
from difflow.units.pfr import GasPFR, GasPFRParams

bed = dict(d_p=3e-3, voidage=0.4, mu=1.8e-5, rho_gas=1.2, u_s0=1.0, bed_area=0.5)
def make(**kw):
    return GasPFRParams(V=1.0, rate_fn=lambda C, T, p: jnp.zeros(1), stoich=jnp.array([[-1.0], [1.0]]),
                        rate_params={}, species_order=['A', 'B'], **kw)
inlet = make_stream({'A': 1.0, 'B': 0.0}, 400.0, 2.0e5)
_, ia = GasPFR(make(alpha=16500.0))(inlet)
_, ib = GasPFR(make(**bed))(inlet)
print("alpha from Ergun:", float(make(**bed).effective_alpha), "Pa/m^3")
print("pressure drop, explicit alpha vs physical bed:", float(ia['pressure_drop']), float(ib['pressure_drop']))
alpha from Ergun: 16499.999999999996 Pa/m^3
pressure drop, explicit alpha vs physical bed: 17243.331159257155 17243.331159257155