Skip to content
cfd-lab:~/es/posts/2026-08-14-lbm-convectio…● online

Traducción del original en coreano escrito por el autor. Leer el original (한국어)

NOTE #130DAY FRI CFD기법DATE 2026.08.14UPDATED 2026.10.19READ 7 min read#LBM#Chapman-Enskog#Convection-Diffusion#Advection#Diffuse-Interface

Subir la velocidad cuesta el 27% de la difusión — el flujo sobrante del modelo de convección-difusión en LBM

La difusividad ajustada mediante tau solo es correcta en u = 0. En cuanto aparece el flujo, u²/cs² de ella desaparece en silencio.

En lattice Boltzmann un solo tiempo de relajación fija la difusividad. D=cs2(τ−1/2)δtD = c_s^2(\tau - 1/2)\delta t. Línea corta. Sin velocidad. ¿Sale el mismo DD con el flujo encendido? Bajo una advección uniforme uu cae a D(1−u2/cs2)D(1 - u^2/c_s^2). Con u=0.3u = 0.3 se pierde un 27%. La expansión de Chapman–Enskog es donde se escapa el término, y un único término fuente es lo que lo devuelve.

Donde duele es el flujo multifásico con phase-field. Cahn–Hilliard o Allen–Cahn sobre un núcleo de lattice Boltzmann atan el espesor de interfaz a la movilidad. Una movilidad con 27% de error arrastra el espesor, y el espesor arrastra el coeficiente de tensión superficial.

La difusividad está ajustada y aun así la interfaz adelgaza#

Conviene mirarlo primero. Abajo está la ecuación de convección-difusión unidimensional

∂tϕ+∂x(uϕ)=D ∂xxϕ\partial_t \phi + \partial_x(u\phi) = D\,\partial_{xx}\phi

resuelta con un esquema lattice Boltzmann D1Q3. La condición inicial es una única gaussiana; la solución exacta también es una gaussiana y su anchura crece como σ2(t)=σ02+2Dt\sigma^2(t) = \sigma_0^2 + 2Dt. Al empujar u de 0 a 0.30 el pico se estrecha frente a la curva punteada, y tau no mueve esa razón.

D = cs²(tau−½) =0.1667
sigma² sim 0 / exact 0
Push u to 0.30 with the source term off: the amber packet climbs above the dashed exact curve and its sigma² line falls below the dashed target — it is diffusing at 0.73 D, exactly 1 − u²/cs². Now drag tau. The deficit ratio does not move, because the missing flux scales with D itself. Switch the source term on and the two curves merge at every u.

Con u = 0.30 la curva sólida (calculada) queda más aguda que la punteada (exacta). La traza de σ2\sigma^2 no alcanza la pendiente punteada 2D2D. Con tau en 0.6 o en 1.5 la razón de déficit se queda en la misma casilla.

D1Q3 solo tiene tres momentos que respetar#

A diferencia de un LBM que resuelve Navier–Stokes, una distribución gig_i construida para transporte escalar tiene menos momentos que satisfacer. El equilibrio que usó Guo en su modelo de 2009 para ecuaciones de convección-difusión no lineales es este:

gieq=wi ϕ(1+ci⋅ucs2)g_i^{eq} = w_i\,\phi\left(1 + \frac{\mathbf{c}_i\cdot\mathbf{u}}{c_s^2}\right)

donde wiw_i son los pesos de la red, ci\mathbf{c}_i las velocidades de la red y cs2=1/3c_s^2 = 1/3 el cuadrado de la velocidad del sonido reticular. Esta distribución cumple tres condiciones de momento.

∑igieq=ϕ,∑icigieq=ϕu,∑icicigieq=cs2ϕ I\sum_i g_i^{eq} = \phi, \qquad \sum_i \mathbf{c}_i g_i^{eq} = \phi\mathbf{u}, \qquad \sum_i \mathbf{c}_i\mathbf{c}_i g_i^{eq} = c_s^2\phi\,\mathbf{I}

El tercero es donde esto se separa de un equilibrio de flujo: no hay término ϕuu\phi\mathbf{u}\mathbf{u}. El término de segundo orden en la velocidad nunca se puso. Una ecuación escalar no tiene tensor de esfuerzos, así que basta la parte isótropa del segundo momento, y D2Q5 puede sustituir a D2Q9. El medio paso dentro de τ−1/2\tau - 1/2, delante de la difusividad, es el mismo resto que el desarrollo de segundo orden de la LBE discreta deja en la viscosidad.

Parece suficiente, y con u=0u = 0 lo es exactamente. El problema es que el truncamiento no sale gratis.

El cuarto término que Chapman-Enskog deja atrás#

Escribamos la ecuación de lattice Boltzmann BGK con una fuente SiS_i añadida:

gi(x+ciδt, t+δt)−gi(x,t)=−1τ(gi−gieq)+δt Sig_i(\mathbf{x}+\mathbf{c}_i\delta t,\, t+\delta t) - g_i(\mathbf{x},t) = -\frac{1}{\tau}\left(g_i - g_i^{eq}\right) + \delta t\, S_i

Se expande gi=gi(0)+εgi(1)+ε2gi(2)g_i = g_i^{(0)} + \varepsilon g_i^{(1)} + \varepsilon^2 g_i^{(2)} y se separa la derivada temporal como ∂t=ε∂t1+ε2∂t2\partial_t = \varepsilon\partial_{t_1} + \varepsilon^2\partial_{t_2}. El momento de orden cero a orden ε\varepsilon devuelve intacta la parte advectiva de la ecuación objetivo.

∂t1ϕ+∇⋅(ϕu)=0\partial_{t_1}\phi + \nabla\cdot(\phi\mathbf{u}) = 0

El primer momento de esa misma ecuación de orden ε\varepsilon es donde se decide todo, porque fija el flujo que transporta g(1)g^{(1)}.

∑icigi(1)=−τδt[∂t1(ϕu)+cs2∇ϕ−∑iciSi]\sum_i \mathbf{c}_i g_i^{(1)} = -\tau\delta t\left[\partial_{t_1}(\phi\mathbf{u}) + c_s^2\nabla\phi - \sum_i \mathbf{c}_i S_i\right]

El segundo término del corchete, cs2∇ϕc_s^2\nabla\phi, es el flujo difusivo que se pedía. El primero viaja con él de polizón. En un esquema de flujo, el término ρuu\rho\mathbf{u}\mathbf{u} del segundo momento del equilibrio lo cancela casi por completo; el modelo escalar no tiene ese término. Así que sobrevive.

Reuniendo el momento de orden cero a orden ε2\varepsilon^2 se obtiene la forma final.

∂tϕ+∇⋅(ϕu)=∇⋅(D∇ϕ)+∇⋅[(τ−12)δt ∂t(ϕu)]−∇⋅[τδt∑iciSi]\partial_t \phi + \nabla\cdot(\phi\mathbf{u}) = \nabla\cdot(D\nabla\phi) + \nabla\cdot\left[\left(\tau-\tfrac{1}{2}\right)\delta t\,\partial_t(\phi\mathbf{u})\right] - \nabla\cdot\left[\tau\delta t\sum_i \mathbf{c}_i S_i\right]

D=cs2(τ−1/2)δtD = c_s^2(\tau - 1/2)\delta t aparece como se esperaba. El segundo término de la derecha es un flujo que nadie encargó. Se anula en un estado estacionario genuino, con uu constante en el tiempo y ϕ\phi ya sin cambiar. Pero mientras ϕ\phi siga moviéndose —mientras el cálculo siga corriendo— ∂t(ϕu)\partial_t(\phi\mathbf{u}) no es cero.

Con uu uniforme ese término es difusión negativa#

Tomemos el caso más simple: uu constante en espacio y tiempo. Entonces ∂t(ϕu)=u ∂tϕ\partial_t(\phi u) = u\,\partial_t\phi y, a orden dominante, ∂tϕ≃−u ∂xϕ\partial_t\phi \simeq -u\,\partial_x\phi. Sustituyendo,

∂x[(τ−12)δt ∂t(ϕu)]=−(τ−12)δt u2 ∂xxϕ\partial_x\left[\left(\tau-\tfrac{1}{2}\right)\delta t\,\partial_t(\phi u)\right] = -\left(\tau-\tfrac{1}{2}\right)\delta t\, u^2\,\partial_{xx}\phi

Un término de difusión con el signo invertido. Sumado al físico,

(τ−12)δt(cs2−u2)∂xxϕ=D(1−u2cs2)∂xxϕ\left(\tau-\tfrac{1}{2}\right)\delta t\left(c_s^2 - u^2\right)\partial_{xx}\phi = D\left(1 - \frac{u^2}{c_s^2}\right)\partial_{xx}\phi Deff=D(1−u2cs2)D_{\text{eff}} = D\left(1 - \frac{u^2}{c_s^2}\right)

El déficit es un solo cuadrado. El cuadrado de la velocidad del sonido reticular es un tercio, así que la velocidad 0.3 al cuadrado da 0.09 y el triple da 0.27. Con tiempo de relajación 1, la difusividad marcada es un tercio por un medio, 0.166667. Lo que queda es 0.166667 por 0.73, es decir 0.121667. La brecha de 0.045 es la difusividad marcada por 0.27. La fila u=0.30u = 0.30 de la salida es esa resta. A baja velocidad el cuadrado se encoge. La velocidad 0.05 al cuadrado da 0.0025, el triple da 0.0075 y el déficit se queda en 0.75%. El pico se pega al lado de la curva exacta. El ojo lo pasa por alto.

La razón no sigue al tiempo de relajación por el mismo renglón. El factor de tiempo de relajación menos un medio multiplica a la vez la difusión física y el término sobrante. Al dividir ambos, el factor se va. La razón no depende de τ\tau. Subir τ\tau de 0.6 a 1.5 lleva DD de 0.033333 a 0.333333, un factor de diez. A velocidad 0.25, tres veces 0.0625 es 0.1875, y uno menos eso es 0.8125, fijo en las cuatro filas. Solo crece la brecha absoluta, de cerca de 0.006 a cerca de 0.062. No hay sitio donde enterrar el error bajo una difusividad mayor.

Cuando la velocidad alcanza la del sonido reticular, el inverso de la raíz cuadrada de un tercio, cerca de 0.577, el paréntesis llega a cero. Más allá, DeffD_{\text{eff}} es negativo. El pico se aguza solo en lugar de extenderse, y la distribución diverge.

1−1/(2τ)1 - 1/(2\tau) vuelve a aparecer#

Toca fijar el coeficiente de SiS_i. Para que el término espurio y el término fuente se cancelen en la ecuación anterior,

τδt∑iciSi=(τ−12)δt ∂t(ϕu)\tau\delta t\sum_i \mathbf{c}_i S_i = \left(\tau-\tfrac{1}{2}\right)\delta t\,\partial_t(\phi\mathbf{u}) ∑iciSi=(1−12τ)∂t(ϕu)\sum_i \mathbf{c}_i S_i = \left(1 - \frac{1}{2\tau}\right)\partial_t(\phi\mathbf{u})

Solo hay una forma simple que cumpla esto manteniendo además ∑iSi=0\sum_i S_i = 0.

Si=wi(1−12τ)ci⋅∂t(ϕu)cs2S_i = w_i\left(1 - \frac{1}{2\tau}\right)\frac{\mathbf{c}_i\cdot\partial_t(\phi\mathbf{u})}{c_s^2}

Ahí está otra vez 1−1/(2τ)1 - 1/(2\tau). Este factor ocupa el mismo sitio que el límite de momento tercero que el término de fuerza tiene que respetar. En una red de tiempo discreto una fuente actúa dos veces —una a través de g(1)g^{(1)} y otra a través del término de segundo orden de la expansión de Taylor— y el factor 1/21/2 es el residuo de esa doble contabilidad.

¿Qué pasa si se omite el coeficiente y se pone ∑iciSi=∂t(ϕu)\sum_i \mathbf{c}_i S_i = \partial_t(\phi\mathbf{u})? Con τ=1\tau = 1 se aplica exactamente el doble de corrección, y un déficit del 27% se convierte en un exceso del 27%. La magnitud del error no cambia, así que una gráfica log-log de convergencia no lo delata.

Calcular ∂t(ϕu)\partial_t(\phi\mathbf{u}) en código consiste en guardar ϕu\phi u del paso anterior y hacer una diferencia hacia atrás. Un arreglo extra es todo el coste.

Midiendo DeffD_{\text{eff}} en 60 líneas de Python#

En lugar de argumentar, conviene medir. Se deja advectar una gaussiana, se ajusta por mínimos cuadrados la pendiente de crecimiento de su segundo momento σ2\sigma^2, y esa pendiente es 2Deff2D_{\text{eff}}.

import numpy as np
 
CS2 = 1.0 / 3.0
C = np.array([0, 1, -1])
W = np.array([2 / 3, 1 / 6, 1 / 6])
 
 
def d1q3_equilibrium(phi, u):
    """g_i^eq = w_i phi (1 + c_i u / cs^2) — equilibrio que solo fija tres momentos"""
    return np.stack([W[i] * phi * (1.0 + C[i] * u / CS2) for i in range(3)])
 
 
def gaussian_moments(x, phi):
    m0 = phi.sum()
    mean = (x * phi).sum() / m0
    return mean, (((x - mean) ** 2) * phi).sum() / m0
 
 
def run_cde_lbm(L, steps, tau, u, sigma0, x0, corrected):
    x = np.arange(L, dtype=float)
    phi = np.exp(-((x - x0) ** 2) / (2 * sigma0**2))
    g = d1q3_equilibrium(phi, u)
    phi_old = phi.copy()
    hist = []
    for n in range(steps + 1):
        if n % 100 == 0:
            hist.append((n, gaussian_moments(x, phi)[1]))
        src = np.zeros_like(g)
        if corrected and n > 0:
            # S_i = w_i (1 - 1/(2 tau)) c_i d_t(phi u) / cs^2,  dt = 1
            dt_phiu = (1.0 - 1.0 / (2 * tau)) * u * (phi - phi_old)
            for i in range(3):
                src[i] = W[i] * C[i] * dt_phiu / CS2
        geq = d1q3_equilibrium(phi, u)
        g = g - (g - geq) / tau + src          # colisión
        for i in range(3):
            g[i] = np.roll(g[i], C[i])         # propagación
        phi_old = phi
        phi = g.sum(axis=0)
    return np.array(hist)
 
 
def fit_diffusivity(hist):
    """lee D_eff en la pendiente de sigma^2 = sigma0^2 + 2 D_eff t"""
    return np.polyfit(hist[:, 0], hist[:, 1], 1)[0] / 2.0
 
 
L, STEPS, SIG0, X0 = 800, 1600, 10.0, 80.0
 
tau = 1.0
D = CS2 * (tau - 0.5)
print(f"tau = {tau},  D = cs^2 (tau-1/2) = {D:.6f},  cs^2 = {CS2:.6f}")
print(f"{'u':>6} {'u^2/cs^2':>9} | {'D_eff (no src)':>14} {'ratio':>7} {'1-u^2/cs^2':>11} |"
      f" {'D_eff (src)':>12} {'ratio':>7}")
for u in [0.05, 0.10, 0.20, 0.30]:
    d_raw = fit_diffusivity(run_cde_lbm(L, STEPS, tau, u, SIG0, X0, False))
    d_fix = fit_diffusivity(run_cde_lbm(L, STEPS, tau, u, SIG0, X0, True))
    print(f"{u:>6.2f} {u * u / CS2:>9.4f} | {d_raw:>14.6f} {d_raw / D:>7.4f} {1 - u * u / CS2:>11.4f} |"
          f" {d_fix:>12.6f} {d_fix / D:>7.4f}")
 
u = 0.25
print(f"\nu = {u} fixed, tau sweep   (theory: ratio = 1 - u^2/cs^2 = {1 - u * u / CS2:.4f}, tau-independent)")
print(f"{'tau':>6} {'D':>10} | {'D_eff (no src)':>14} {'ratio':>7} | {'D_eff (src)':>12} {'ratio':>7}")
for tau in [0.6, 0.8, 1.0, 1.5]:
    D = CS2 * (tau - 0.5)
    d_raw = fit_diffusivity(run_cde_lbm(L, STEPS, tau, u, SIG0, X0, False))
    d_fix = fit_diffusivity(run_cde_lbm(L, STEPS, tau, u, SIG0, X0, True))
    print(f"{tau:>6.1f} {D:>10.6f} | {d_raw:>14.6f} {d_raw / D:>7.4f} | {d_fix:>12.6f} {d_fix / D:>7.4f}")

La salida:

tau = 1.0,  D = cs^2 (tau-1/2) = 0.166667,  cs^2 = 0.333333
     u  u^2/cs^2 | D_eff (no src)   ratio  1-u^2/cs^2 |  D_eff (src)   ratio
  0.05    0.0075 |       0.165417  0.9925      0.9925 |     0.166666  1.0000
  0.10    0.0300 |       0.161667  0.9700      0.9700 |     0.166666  1.0000
  0.20    0.1200 |       0.146667  0.8800      0.8800 |     0.166663  1.0000
  0.30    0.2700 |       0.121667  0.7300      0.7300 |     0.166658  0.9999
 
u = 0.25 fixed, tau sweep   (theory: ratio = 1 - u^2/cs^2 = 0.8125, tau-independent)
   tau          D | D_eff (no src)   ratio |  D_eff (src)   ratio
   0.6   0.033333 |       0.027096  0.8129 |     0.033345  1.0004
   0.8   0.100000 |       0.081258  0.8126 |     0.100006  1.0001
   1.0   0.166667 |       0.135417  0.8125 |     0.166661  1.0000
   1.5   0.333333 |       0.270794  0.8124 |     0.333275  0.9998

Al subir u solo crece el flujo fantasma rosa, como u2u^2. Al llevar alpha de 0 a 1 el término fuente borra ese ancho.

D_eff / D =0.693
At alpha = 0 the rose lobe sits mirror-imaged under the blue one: the ghost flux points the wrong way everywhere, so the amber net curve is visibly shorter than the blue physical flux. Raise u and the rose lobe grows as u² while blue stays put. Then drag alpha to 1 — green fills in exactly on top of rose, and amber lands back on blue at every x.

Con alpha = 0 el lóbulo rosa pone el flujo físico azul cabeza abajo. Con alpha = 1 el verde cae sobre el rosa y la suma ámbar vuelve a la curva azul. Es la columna con la fuente activa, donde ratio se pega a 1.0000.

En la primera tabla la columna ratio coincide con 1-u^2/cs^2 hasta la cuarta cifra decimal. La predicción no es una estimación: es el orden dominante exacto. Al activar el término fuente, las cuatro velocidades vuelven a 1.0000.

La segunda tabla muerde más fuerte. Barrer τ\tau de 0.6 a 1.5 cambia DD en un factor de diez, y la razón de déficit apenas se mueve de 0.8129 a 0.8124. Intentar enterrar el error bajo una difusividad mayor no funciona: si DD se multiplica por diez, la cantidad que desaparece también.

El presupuesto de velocidad reticular ya estaba gastado#

u2/cs2u^2/c_s^2 es el cuadrado del número de Mach reticular. Con u=0.1u = 0.1, 0.01×3=0.030.01 \times 3 = 0.03: se va un 3% de la difusión. Con u=0.2u = 0.2, 0.04×3=0.120.04 \times 3 = 0.12, un 12%. En 0.05 o por debajo la pérdida es 0.75% y se esconde en el error de malla. La costumbre de atar uu cerca de 0.1 por la compresibilidad todavía no ha visto la factura del escalar. Si un espesor de interfaz o un número de Sherwood sale como cifra, el término fuente se enciende en cuanto uu pasa de 0.1. Por debajo, ahorrar un arreglo deja en pie el 0.9925 de la tabla.

En phase-field no alcanza con fiarse de u2/cs2u^2/c_s^2. El término sobrante es el ∂t(ϕu)\partial_t(\phi\mathbf{u}) completo, y el cuadrado vale solo cuando uu es uniforme y estacionaria. Cerca de la interfaz uu salta y ∂tu\partial_t u no es cero, así que también sobrevive la pieza ϕ ∂tu\phi\,\partial_t u. ∂t(ϕu)\partial_t(\phi\mathbf{u}) entra como diferencia hacia atrás de ϕu\phi u del paso anterior. El τ\tau dentro de 1−1/(2τ)1 - 1/(2\tau) es el tiempo de relajación del primer momento. Cómo cae la viscosidad al desplazar el marco de velocidad es la misma clase de factura que el momento tercero que le falta al equilibrio. Si la aritmética de la movilidad cuadra y la interfaz igual adelgaza, τ\tau no era el fallo. El flujo se llevó primero el presupuesto de difusión.

Comparte si te resultó útil.