Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/underworld3/systems/solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2496,6 +2496,28 @@ class SNES_AdvectionDiffusion(SNES_Scalar):
Time derivative operator for the unknown.
DFDt : SemiLagrangian_DDt or Lagrangian_DDt, optional
Time derivative operator for the flux.
monotone_mode : str or None, optional
Monotonicity limiter for the semi-Lagrangian trace-back.
Forwarded to the internally-constructed ``SemiLagrangian_DDt``
instances for ``DuDt`` and ``DFDt``.

- ``None`` (default): pure FE trace-back. Can overshoot at
non-nodal upstream points in cells with sharp gradients
(e.g. thin boundary layers in deformed cells); legacy
behaviour.
- ``"clamp"``: clip the FE trace-back result to
``[nbr_min, nbr_max]`` of the ``k = dim + 1`` nearest
``psi_star`` DOFs. Bit-identical to pure FE in smooth
regions; bounds overshoots where the FE interpolant would
otherwise leave the local data range.
- ``"pick"``: keep the FE result where in-bounds; for
out-of-bounds DOFs only, re-evaluate via Shepard's-method
RBF. More conservative than clamp at the catastrophe edge.

When a user supplies a pre-built ``DuDt``, this kwarg is
applied to the internally-constructed ``DFDt`` only — the
user's ``DuDt`` already encodes whatever ``monotone_mode``
it was constructed with.

Notes
-----
Expand Down Expand Up @@ -2536,6 +2558,7 @@ def __init__(
verbose=False,
DuDt: Union[SemiLagrangian_DDt, Lagrangian_DDt] = None,
DFDt: Union[SemiLagrangian_DDt, Lagrangian_DDt] = None,
monotone_mode: Optional[str] = None,
):
## Parent class will set up default values etc
super().__init__(
Expand Down Expand Up @@ -2581,6 +2604,7 @@ def __init__(
bcs=self.essential_bcs,
order=1,
smoothing=0.0,
monotone_mode=monotone_mode,
)

else:
Expand Down Expand Up @@ -2612,6 +2636,7 @@ def __init__(
bcs=None,
order=order,
smoothing=0.0,
monotone_mode=monotone_mode,
)

return
Expand Down
78 changes: 78 additions & 0 deletions tests/test_1054_advdiff_monotone_mode_kwarg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""Regression tests for the ``monotone_mode`` kwarg on
``SNES_AdvectionDiffusion`` / ``AdvDiffusionSLCN``.

The underlying ``SemiLagrangian_DDt`` already accepts ``monotone_mode``
(landed in PR #186). This kwarg forwards it through the solver
constructor so users can write the one-line idiom
``adv_diff = AdvDiffusionSLCN(..., monotone_mode="clamp")`` instead of
the two-step ``adv_diff.DuDt.monotone_mode = "clamp";
adv_diff.DFDt.monotone_mode = "clamp"`` dance.
"""

import pytest

import underworld3 as uw


pytestmark = [pytest.mark.level_1, pytest.mark.tier_a]


def _make_mesh_and_field():
mesh = uw.meshing.StructuredQuadBox(
elementRes=(4, 4),
minCoords=(0.0, 0.0),
maxCoords=(1.0, 1.0),
)
v = uw.discretisation.MeshVariable(
"V_advtest", mesh, mesh.dim, degree=1)
T = uw.discretisation.MeshVariable(
"T_advtest", mesh, 1, degree=2)
return mesh, v, T


class TestMonotoneModeKwarg:

def test_default_is_none(self):
mesh, v, T = _make_mesh_and_field()
adv = uw.systems.AdvDiffusionSLCN(
mesh, u_Field=T, V_fn=v.sym)
assert adv.DuDt.monotone_mode is None
assert adv.DFDt.monotone_mode is None

def test_clamp_forwarded_to_both(self):
mesh, v, T = _make_mesh_and_field()
adv = uw.systems.AdvDiffusionSLCN(
mesh, u_Field=T, V_fn=v.sym,
monotone_mode="clamp")
assert adv.DuDt.monotone_mode == "clamp"
assert adv.DFDt.monotone_mode == "clamp"

def test_pick_forwarded_to_both(self):
mesh, v, T = _make_mesh_and_field()
adv = uw.systems.AdvDiffusionSLCN(
mesh, u_Field=T, V_fn=v.sym,
monotone_mode="pick")
assert adv.DuDt.monotone_mode == "pick"
assert adv.DFDt.monotone_mode == "pick"

def test_explicit_DuDt_overrides_kwarg(self):
"""If the caller supplies a pre-built ``DuDt``, the
constructor must not silently rewrite its ``monotone_mode``
— the caller-supplied DDt is the source of truth."""
from underworld3.systems.ddt import SemiLagrangian as SL_DDt
mesh, v, T = _make_mesh_and_field()
# Build a DuDt with mode 'pick' explicitly
custom = SL_DDt(
mesh, psi_fn=T.sym, V_fn=v.sym,
vtype=uw.VarType.SCALAR, degree=T.degree,
continuous=T.continuous, order=1,
monotone_mode="pick",
)
adv = uw.systems.AdvDiffusionSLCN(
mesh, u_Field=T, V_fn=v.sym,
DuDt=custom,
monotone_mode="clamp", # ignored for the supplied DuDt
)
assert adv.DuDt.monotone_mode == "pick" # preserved
# DFDt is constructed internally → uses the kwarg
assert adv.DFDt.monotone_mode == "clamp"
Loading