Skip to content

SemiLagrangian: swarm_degree / swarm_continuous are accepted but never used #704

Description

@lmoresi

Severity: minor. No wrong answers — the scheme does the sensible thing. The
cost is a misleading API, a false docstring claim, and one unused
MeshVariable per SemiLagrangian instance.

What happens

Both parameters are accepted, stored, and used to size self._workVar — which
is then never read. The advection always samples at psi_star's own nodes, i.e.
the tracked field's degree/continuous.

Evidence

Sweeping them on advection–diffusion of a Gaussian blob (P2 field, 24 cells,
Courant 0.5, carried 0.5 box widths; relative L2 against the exact solution):

swarm_degree swarm_continuous _workVar nodes L2 error
1 True 98 6.745953388114e-04
1 False 6.745953388114e-04
2 True 357 6.745953388114e-04
2 False 972 6.745953388114e-04
3 True 778 6.745953388114e-04
3 False 6.745953388114e-04

Bit-identical to 12 significant figures while the work variable spans 98 → 972
nodes. The result cannot depend on it.

Why

_workVar is wired into the projection at construction (ddt.py:2244):

self._psi_star_projection_solver.uw_function = self._workVar.sym

but every actual use overwrites that source before solving — ddt.py:2393,
:2506, :2883:

self._psi_star_projection_solver.uw_function = self._build_projection_source(self.psi_fn)

and _build_projection_source returns source_fn unchanged on the
scalar/vector path. Meanwhile the trace-back takes its sample points straight
from the history variable (ddt.py:2959):

coords = np.asarray(self.psi_star[i].coords_nd)

This looks like the tail of a removal that was completed elsewhere.
ddt.py:2160 records the other half:

Historically this allocated a NodalPointSwarm cache here, but the actual
trace-back path uses uw.function.global_evaluate on the upstream coords
directly — the swarm was vestigial

The part that actually misleads

  • docstring: "swarm_degree : Polynomial degree for swarm interpolation."
  • comment at ddt.py:2133: "it defines the advection sample points"

Both false. Someone tuning semi-Lagrangian accuracy will reach for these first.
The answer to "is SLCN accuracy the same for the nodal and the
integration-point version?" is currently "there is only the nodal one", and
nothing in the API says so.

Suggested fix

Either wire _workVar back in — project the field onto it, trace from its
nodes, project back — or delete the pair and the allocation, and say in the
docstring that departure points are the field's own nodes. The second is the
smaller change and matches what the scheme does today; the first is only worth
it if denser departure-point sampling is wanted as a real option.

Interaction with other work

SemiLagrangian only — does not touch EulerianSUPG (#688) or the
predictor-corrector manager in #689. Worth flagging to whoever is in ddt.py
now, since #689's own summary line "allocate no unused implicit history" is
the same concern applied to the other managers.

Found while updating the technical note on symbolic time derivatives for the
composing solvers.

Underworld development team with AI support from Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions