Skip to content

A located point can interpolate to NaN, and #602 makes that invisible #604

Description

@lmoresi

global_evaluate_nd can return a non-finite value for a point its own
return_mask reports as located. PR #602 recovers from that condition; this
issue is for the condition itself, which is not diagnosed anywhere.

Why it needs its own issue

#602 widens the parallel best-claim fallback to include any point with a
non-finite component, so after it lands such a point receives a finite value
instead of a NaN. That is the right immediate response — one silent NaN in SLCN
midpoint tracing makes the departure coordinate and then the transported history
non-finite — but it removes the only external evidence that anything went wrong.
The recovery is also silent: global_evaluate returns return_value alone by
default, and only a caller passing check_extrapolated=True sees return_mask
flip for the affected point.

So from the moment #602 merges, this defect produces a plausible number and no
signal.

The clue in the fix

The value that replaces the NaN comes from a fresh evaluation on a
different interpolation path:

ext_vals, ext_flag = evaluate_nd(expr, all_ext, rbf=True, evalf=False, ...)

For a point flagged located, this rank's nearest-centroid distance is
effectively zero, so it wins the best-claim and supplies its own value — an RBF
extrapolation where the caller asked for the interpolant. That the RBF path
succeeds at a point where the located path returned NaN is itself the strongest
available hint about where the fault is: it is not that the point is
unreachable, it is that one of the two routes to it fails.

What we would want to know

  • Which expression. Does it contain a derivative, a UWexpression, a swarm
    proxy? (uw.function.evaluate returns negative values for a squared DERIVATIVE expression #491 shows the derivative-composite path already takes a substitution.)
  • Which mesh and dimension, and whether the point sits on a facet, a cell
    boundary, or a partition boundary.
  • Whether the same coordinates evaluate to NaN in serial evaluate(). If
    serial is clean, this is in the migrate/round-trip; if it is not, the located
    path itself is producing the NaN and the parallel wrapper is incidental.
  • Whether it is genuinely intermittent. The PR says "occasionally", which if
    taken literally points at uninitialised storage or an ordering dependence
    rather than a geometric predicate.

Suggested first step

Have the recovery count what it repaired and say so once per call — the number
of points, and whether each was extrapolated or non-finite-but-located. That
costs nothing, keeps #602's behaviour, and turns a silent substitution into an
observation somebody can act on. Without it the next report of this will be a
model that runs and is quietly wrong somewhere else.

Family: #551 (the locator umbrella — anti-scaling, the #491 smoothed
substitution, empty-rank behaviour, the ND↔units boundary). This is a distinct
mechanism from all four, but the same subsystem, and #551's conclusion that the
root cause is the locator's lack of a clean contract applies here too.

Underworld development team with AI support from Claude Code

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