Skip to content

evaluate() silently returns wrong values in parallel for points the rank does not own — including mesh nodes #606

Description

@lmoresi

In an MPI run, uw.function.evaluate silently returns wrong values for any
query point the calling rank does not own — including points that ARE mesh
points. Serial is exact, global_evaluate is exact, and nothing in evaluate's
signature, docstring or return value says the answer is rank-local.

Measured

UnstructuredSimplexBox, cellSize=0.2, a P2 MeshVariable holding
x^2 + 2 y^2 — exact in P2, so any error is location, not approximation. The
query set is every rank's own P2 DOF coordinates, allgathered, so every
query point is a node of the mesh. True values span [0, 3].

np sampler query set extrapolated max abs error
1 evaluate all coords 0 / 153 8.9e-16
2 evaluate all coords 69 / 166, 71 / 166 1.48, 0.54
4 evaluate all coords 117-126 / 180 up to 2.59
2 evaluate own coords 0 / 84 6.7e-16
4 evaluate own coords 0 / 46 8.9e-16
2 global_evaluate all coords 0 / 166 8.9e-16
4 global_evaluate all coords 0 / 180 8.9e-16

Two thirds of the points are wrong at np=4, by up to 2.59 on a field whose whole
range is 3. The ranks also disagree with each other — 1.48 on rank 0 against
0.54 on rank 1 at np=2, for the identical query set — so the answer depends on
who asked.

Why

evaluate classifies points against the rank-local mesh. A point owned by
another rank is not in any local cell, so it is extrapolated from the nearest
local cell, which may be most of a domain away. The extrapolation flag is set
correctly — check_extrapolated=True reports every one of them — but the flag is
off by default and evaluate returns bare values.

This is not the curved-boundary case in #605, where the point is genuinely
outside the meshed domain and extrapolation is the only option. Here the point is
inside the domain, is a node of the mesh, and another rank holds the exact value.

Why it matters more than it looks

The natural thing to write is the thing that breaks:

line = np.column_stack([np.linspace(0, 1, 200), np.full(200, 0.5)])
profile = uw.function.evaluate(T.sym[0], line)      # every rank asks for all 200

In serial that is correct. On four ranks each rank gets a different, mostly
wrong profile, with no error, no warning and no NaN. Anything that samples a
fixed set of points — a profile, a probe location, a set of observation stations,
a comparison against an analytic solution — has this shape.

Options

  1. Delegate. When uw.mpi.size > 1 and any point falls outside the local
    domain, route the whole call through global_evaluate. Correct by default,
    at the cost of making a fast local call sometimes collective — and a
    collective call from a conditional is its own hazard, so the condition would
    have to be reduced across ranks.
  2. Refuse. Raise when a point is foreign in an MPI run, naming
    global_evaluate. Loud, cheap, and it breaks working rank-local code only if
    that code was already asking for points it does not own.
  3. Warn. Emit once per call with a count of foreign points. Least
    disruptive, keeps the current contract, and makes the failure visible.
  4. Document it. Necessary regardless: the docstring currently does not
    mention parallelism at all, and has no See Also pointing at
    global_evaluate.

Rank-locality is what makes evaluate fast for the common case where each rank
asks about its own points, and that is worth keeping. The defect is answering
confidently for points it cannot see.

Related: #551 measures this same path and records the rank-local classification
in one clause, but frames it as anti-scaling; the correctness consequence is not
stated there. #605 is the curved-boundary case. #604 is a located point
returning NaN.

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