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
Severity: minor. No wrong answers — the scheme does the sensible thing. The
cost is a misleading API, a false docstring claim, and one unused
MeshVariableperSemiLagrangianinstance.What happens
Both parameters are accepted, stored, and used to size
self._workVar— whichis 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_degreeswarm_continuous_workVarnodesBit-identical to 12 significant figures while the work variable spans 98 → 972
nodes. The result cannot depend on it.
Why
_workVaris wired into the projection at construction (ddt.py:2244):but every actual use overwrites that source before solving —
ddt.py:2393,:2506,:2883:and
_build_projection_sourcereturnssource_fnunchanged on thescalar/vector path. Meanwhile the trace-back takes its sample points straight
from the history variable (
ddt.py:2959):This looks like the tail of a removal that was completed elsewhere.
ddt.py:2160records the other half:The part that actually misleads
swarm_degree: Polynomial degree for swarm interpolation."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
_workVarback in — project the field onto it, trace from itsnodes, 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
SemiLagrangianonly — does not touchEulerianSUPG(#688) or thepredictor-corrector manager in #689. Worth flagging to whoever is in
ddt.pynow, 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