Skip to content

diffuse() does not validate user-supplied dt against CFL stability bound #1281

@brendancol

Description

@brendancol

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions