Summary
xrspatial.diffusion.diffuse() runs explicit forward-Euler iterations of the 2-D diffusion equation. For the 5-point Laplacian, the explicit Euler scheme is stable only when:
dt <= 0.25 * dx**2 / max(alpha)
When the caller passes dt=None, diffusion.py:444-445 already picks this bound:
dt = 0.25 * dx * dx / alpha_max
When the caller passes their own dt, diffusion.py:447-448 only checks dt > 0. Any dt above the stable bound makes the scheme diverge. steps is accepted up to 100,000, so the field blows up to Inf/NaN with no error and no warning.
Reproduction
import numpy as np, xarray as xr
from xrspatial.diffusion import diffuse
agg = xr.DataArray(np.ones((50, 50)) + 0.01*np.random.randn(50, 50))
out = diffuse(agg, diffusivity=1.0, steps=200, dt=10.0) # 40x the CFL bound
print(np.nanmax(out)) # inf
No exception is raised. The output is garbage.
Severity
HIGH. Each unstable step amplifies the previous one, so by step 200 the field is full of Inf/NaN with no diagnostic.
Fix
Add a CFL check in the user-dt branch:
cfl_max = 0.25 * dx * dx / alpha_max
if dt > cfl_max:
raise ValueError(
f"diffuse(): dt={dt} exceeds CFL stability bound "
f"{cfl_max} (= 0.25 * dx**2 / max(alpha)). "
f"Pass dt=None for an automatic stable step."
)
Test plan
- Test that
ValueError is raised when dt is above the CFL bound.
- Test that
dt=None still works on the same input.
- Existing tests continue to pass.
Module
diffusion (audited 2026-04-27 under sweep-security)
Summary
xrspatial.diffusion.diffuse()runs explicit forward-Euler iterations of the 2-D diffusion equation. For the 5-point Laplacian, the explicit Euler scheme is stable only when:When the caller passes
dt=None,diffusion.py:444-445already picks this bound:When the caller passes their own
dt,diffusion.py:447-448only checksdt > 0. Anydtabove the stable bound makes the scheme diverge.stepsis accepted up to 100,000, so the field blows up to Inf/NaN with no error and no warning.Reproduction
No exception is raised. The output is garbage.
Severity
HIGH. Each unstable step amplifies the previous one, so by step 200 the field is full of Inf/NaN with no diagnostic.
Fix
Add a CFL check in the user-dt branch:
Test plan
ValueErroris raised whendtis above the CFL bound.dt=Nonestill works on the same input.Module
diffusion (audited 2026-04-27 under sweep-security)