Source code for siim._core.diffusion
"""Explicit H diffusion for the siim 2D model (CFL-substepped 5-point FD).
Consolidated into the numerical core in the pre-v1.0 rewrite. numpy/numba
only -- no model/fastscape imports.
"""
import math
import numpy as np
import numba
[docs]
@numba.njit(cache=True)
def _diffuse_H_2d(H_flat, ny, nx, dx, dy, D, dt, wrap_y=False, wrap_x=False):
"""Explicit 5-point FD diffusion on H, sub-stepped for CFL: per sub-step
ax + ay <= 0.4 with ax = D*dt_sub/dx², ay = D*dt_sub/dy² (the 2D FTCS
stability limit is ax + ay <= 0.5). Reduces to the previous square-grid
behaviour exactly when dx == dy.
A looped axis (``wrap_y`` / ``wrap_x``) wraps the stencil across the seam so
the seam cells diffuse as the interior cells they physically are (matching
the fill / facet-scan / carve seam-awareness; audit m16); a non-looped axis
holds its outer ring fixed. Bit-for-bit with the old kernel when both are
False. No-op when D <= 0. Clamps H >= 0 after each sub-step.
"""
if D <= 0.0 or dt <= 0.0:
return
ax = D * dt / (dx * dx)
ay = D * dt / (dy * dy)
n_sub = max(1, int(math.ceil((ax + ay) / 0.4)))
ax_sub = ax / n_sub
ay_sub = ay / n_sub
j0 = 0 if wrap_y else 1
j1 = ny if wrap_y else ny - 1
i0 = 0 if wrap_x else 1
i1 = nx if wrap_x else nx - 1
work = np.empty(ny * nx)
for _ in range(n_sub):
for k in range(ny * nx):
work[k] = H_flat[k]
for j in range(j0, j1):
jm = (j - 1) % ny if wrap_y else j - 1
jp = (j + 1) % ny if wrap_y else j + 1
for i in range(i0, i1):
im = (i - 1) % nx if wrap_x else i - 1
ip = (i + 1) % nx if wrap_x else i + 1
k = j * nx + i
H_new = work[k] + (
ay_sub * (work[jm * nx + i] + work[jp * nx + i] - 2.0 * work[k])
+ ax_sub * (work[j * nx + im] + work[j * nx + ip] - 2.0 * work[k])
)
if H_new < 0.0:
H_new = 0.0
H_flat[k] = H_new