Skip to content
cfd-lab:~/es/posts/2026-06-06-weston-block-…online
NOTE #066DAY SAT 논문리뷰DATE 2026.06.06READ 7 min readWORDS 1,204#Block-Preconditioner#Newton-Krylov#All-Speed#Schur-Complement#JFNK#논문리뷰

[Reseña de artículo] GMRES corrió 1000 veces y no se detenía — precondicionamiento por bloques físico para un melt pool a toda velocidad

Un precondicionador de complemento de Schur velocidad–presión mantiene plano el número de iteraciones cuando el Mach tiende a cero

Corrí una simulación implícita de un melt pool (charco fundido) por láser. En el primer paso de tiempo, GMRES dio 1000 vueltas y el residuo se quedó clavado en 1e-2. Al bajar el número de Mach a 1e-3, el síntoma empeoró — la velocidad del sonido era 1000 veces la del flujo, y el jacobiano estaba enfermo. Weston, Nourgaliev, Delplanque y Barker (2019) resolvieron justo esto. El truco es usar el complemento de Schur velocidad–presión como precondicionador. Hoy reproduzco su núcleo en un toy de 2 campos y observo cómo un solo precondicionador devuelve la convergencia independiente del Mach.

Dónde se sitúa este artículo#

  • Autores: B. Weston, R. Nourgaliev, J.-P. Delplanque, A. T. Barker (LLNL / UC Davis)
  • Revista: Journal of Computational Physics, 397, 108847 (2019)
  • Título: Preconditioning a Newton-Krylov solver for all-speed melt pool flow physics
  • En una línea: resolver las Navier–Stokes compresibles a toda velocidad (all-speed) con cambio de fase mediante JFNK totalmente implícito, y domar la rigidez de bajo Mach con un precondicionador de complemento de Schur por bloques en variables primitivas.

Los precondicionadores habituales — SOR por bloque de elemento, AMG monolítico, Gauss–Seidel por bloques — disparan todos el número de iteraciones cuando el paso de tiempo o el Mach llegan a un extremo. El aporte del artículo es el complemento de Schur vP-vT, un precondicionador que copia directamente la estructura del acoplamiento físico.

Por qué el jacobiano enferma cuando M tiende a cero#

"A toda velocidad" significa que un mismo código resuelve desde lo incompresible (M0M\to 0) hasta lo supersónico. El problema es el límite de bajo Mach. La razón entre la velocidad acústica cc y la del flujo uu se abre como c/u=1/Mc/u = 1/M. En M=103M=10^{-3}, el sonido va 1000 veces más rápido que el fluido.

Al ir totalmente implícito, esas dos escalas se mezclan dentro de un único jacobiano. El bloque de acoplamiento presión–velocidad crece como 1/M21/M^2 y el número de condición de la matriz se dispara. Un solver de Krylov se arrastra sobre una matriz cuyos autovalores están muy esparcidos. Por eso GMRES no se detenía.

JFNK (Jacobian-Free Newton–Krylov) nunca forma el jacobiano de forma explícita; solo aproxima el producto matriz–vector con una diferencia de Fréchet.

JκF(x+hκ)F(x)hJ\boldsymbol{\kappa} \approx \frac{F(x + h\boldsymbol{\kappa}) - F(x)}{h}

FF es el residuo no lineal, κ\boldsymbol{\kappa} un vector de Krylov y hh un número finito pequeño. No almacenar la matriz ahorra memoria, pero sin precondicionador la convergencia muere a bajo Mach. Precondicionar no es opcional aquí: es todo el juego.

Soltar las variables conservativas por las primitivas (P,v,T)(P,v,T)#

El primer movimiento es un cambio de variables. En vez del conjunto conservativo U=(ρ,ρv,E)U=(\rho, \rho v, E), se resuelve por las primitivas W=(P,v,T)W=(P, v, T), porque están mucho mejor condicionadas a bajo Mach.

F(x)UUWδW=F(x)\frac{\partial F(x)}{\partial U}\frac{\partial U}{\partial W}\,\delta W = -F(x)

U/W\partial U/\partial W es el jacobiano del cambio de variables. El residuo FF se sigue escribiendo para satisfacer las leyes de conservación, así que cambiar de variables no rompe la conservación de masa, momento ni energía. Ahora se ordenan los grados de libertad por campo en una matriz por bloques 3×33\times3.

M=[MvvMvPMvTMPvMPPMPTMTvMTPMTT]M = \begin{bmatrix} M_{vv} & M_{vP} & M_{vT} \\ M_{Pv} & M_{PP} & M_{PT} \\ M_{Tv} & M_{TP} & M_{TT} \end{bmatrix}

Cada bloque corresponde a un campo primitivo (velocidad, presión, temperatura). Esta estructura es el trampolín del siguiente paso.

LU por bloques y el complemento de Schur velocidad–presión#

El acoplamiento presión–temperatura (MPT,MTPM_{PT}, M_{TP}) es débil. Al descartar ambos, la factorización LU por bloques sale limpia y el núcleo se condensa en el complemento de Schur velocidad–presión.

SvP=MPPMPvMvv1MvPS_{vP} = M_{PP} - M_{Pv}\,M_{vv}^{-1}\,M_{vP}

SvPS_{vP} es la ecuación efectiva para la presión tras eliminar la velocidad — el mismo esqueleto que la ecuación de corrección de presión de la familia SIMPLE. Toma el precondicionador PP con esta forma triangular superior por bloques y los autovalores de P1AP^{-1}A se agrupan cerca de 1. Con el SS exacto, el factor UU de A=LUA=LU es literalmente PP, de modo que P1AP^{-1}A es semejante a una triangular inferior unipotente: todos los autovalores valen exactamente 1. GMRES termina en dos o tres pasos.

Python — contar iteraciones de GMRES al bajar el Mach#

Deja fuera la temperatura y reproduce el corazón en un toy de 2 campos (v,P)(v,P). El sistema por bloques es A=[IβGDvI]A=\begin{bmatrix} I & \beta G \\ D_v & I\end{bmatrix} con β=1/M2\beta=1/M^2. El precondicionador es el complemento de Schur exacto S=IβDvGS=I-\beta D_v G.

import numpy as np
from scipy.sparse import diags, eye, bmat
from scipy.sparse.linalg import gmres, LinearOperator, splu
 
n = 64
dx = 1.0 / n
 
def fwd_diff():
    # diferencia hacia adelante periodica (operador de gradiente de presion)
    D = diags([-1.0, 1.0], [0, 1], shape=(n, n)).tolil()
    D[-1, 0] = 1.0
    return (D / dx).tocsr()
 
G = fwd_diff()
Dv = (-G.T).tocsr()          # divergencia = -G^T  ->  Dv*G ~ laplaciano
 
def all_speed_system(M):
    beta = 1.0 / M**2        # factor de rigidez de bajo Mach
    A = bmat([[eye(n), beta * G], [Dv, eye(n)]]).tocsr()
    return A, beta
 
def schur_preconditioner(beta):
    # complemento de Schur velocidad-presion S = M_PP - M_Pv M_vv^{-1} M_vP = I - beta Dv G
    S = (eye(n) - beta * (Dv @ G)).tocsc()
    luS = splu(S)
    def apply(r):
        ru, rp = r[:n], r[n:]
        zp = luS.solve(rp)               # S z_p = r_p
        zu = ru - beta * (G @ zp)        # z_u = r_u - beta G z_p
        return np.concatenate([zu, zp])
    return LinearOperator((2 * n, 2 * n), matvec=apply)
 
def count_iters(M, use_prec):
    A, beta = all_speed_system(M)
    b = np.random.default_rng(0).standard_normal(2 * n)
    P = schur_preconditioner(beta) if use_prec else None
    it = {"k": 0}
    x, info = gmres(A, b, M=P, rtol=1e-8, maxiter=500, restart=2 * n,
                    callback=lambda xk: it.__setitem__("k", it["k"] + 1))
    return it["k"]
 
print(f"{'Mach':>8}  {'no-prec':>8}  {'Schur':>6}")
for M in [1.0, 1e-1, 1e-2, 1e-3]:
    print(f"{M:>8.0e}  {count_iters(M, False):>8d}  {count_iters(M, True):>6d}")

El resultado es contundente. Sin precondicionador, el número de iteraciones trepa a decenas y cientos a medida que MM encoge, y en M=103M=10^{-3} ni siquiera converge dentro del tope. El precondicionador de Schur termina en dos o tres pasos sin importar MM. La "convergencia independiente del Mach" que reporta el artículo nace de este precondicionador de una sola línea.

Interactivo — acústica y convección, dos velocidades que se separan#

Primero, mira de dónde sale la rigidez. Baja el número de Mach en la simulación de abajo. La onda acústica de la pista superior corre a c=1/Mc=1/M, y el trazador convectivo de abajo a u=1u=1.

explicit acoustic CFL forces Δt ∝ M·Δx — the smaller M, the stiffer the coupled system.

Bájalo a M=102M=10^{-2} y la onda acústica da 100 vueltas a la pista mientras el trazador convectivo avanza una sola muesca. Esa brecha de velocidad 100:1 se traslada directamente al número de condición del jacobiano. De forma explícita, el CFL acústico aplastaría el paso de tiempo hasta ΔtMΔx\Delta t \propto M\Delta x.

Interactivo — dónde el precondicionador derrumba el residuo#

Ahora observa el residuo real de GMRES. Mueve el deslizador de Mach abajo y las dos curvas — con y sin precondicionamiento — se redibujan.

unpreconditioned: 0 iters to 1.0e+0 · Schur: 0 iters to 1.0e+0

La curva naranja (sin precondicionador) se aplana y se estanca al bajar MM. La curva cian (precondicionada con Schur) se desploma a 1e-11 en dos o tres pasos, esté donde esté MM. La brecha entre ambas es más dramática en M=103M=10^{-3} — es el precondicionador, construido a partir del acoplamiento físico, absorbiendo la rigidez por completo.

Tres cosas que me hicieron dudar al programarlo#

Primero, un SS exacto solo es gratis en un toy. Aquí resolví S=IβDvGS=I-\beta D_v G de forma exacta por LU y convergí en dos pasos. En la práctica, el Mvv1M_{vv}^{-1} dentro de SvP=MPPMPvMvv1MvPS_{vP}=M_{PP}-M_{Pv}M_{vv}^{-1}M_{vP} es una inversa densa que no se puede formar. El artículo resuelve SS de forma aproximada (un V-cycle de AMG, por ejemplo), y la calidad de esa aproximación gobierna el número de iteraciones externas. El costo real de la "independencia del Mach" se esconde justo aquí.

Segundo, hasta dónde descartar el acoplamiento. El artículo dice que descartar MPT,MTPM_{PT}, M_{TP} no cuesta robustez. Es un supuesto ajustado a la física del melt pool. Con un acoplamiento térmico compresible fuerte — deflagración, combustión — el mismo truncamiento puede matar la convergencia. Simplificar el precondicionador depende de la física.

Tercero, la diferencia de Fréchet hh de JFNK. Un hh demasiado pequeño deja entrar el redondeo; demasiado grande deja que el error de truncamiento contamine el producto matriz–vector. Aun con un buen precondicionador, un hh mal ajustado estanca a Newton. Es la primera trampa al pasar del enfoque segregado (SIMPLE) de OpenFOAM a uno totalmente acoplado e implícito.

Cierre — un precondicionador no es gratis#

La lección de esa noche en que GMRES no se detenía fue simple. La rigidez de bajo Mach no se resuelve apretando más el jacobiano. Hace falta un precondicionador que copie la propia estructura del acoplamiento. El complemento de Schur es la ecuación efectiva para la presión, y resolverlo de forma exacta agrupa los autovalores en 1. Pero el SS del mundo real es una aproximación, y cuán barata y exactamente lo resuelves es la próxima batalla. La próxima vez agregaré una capa más a este mismo toy y observaré qué pasa con el número de iteraciones externas cuando SS se aproxima con AMG.

Comparte si te resultó útil.