Fix swarm proxy refresh under an active units model (#426) - #434
Conversation
The swarm kd-tree is built from Swarm._particle_coordinates.data, which is
always non-dimensional. The proxy refresh queried it with
MeshVariable.coords, which dimensionalises as soon as the model has
reference quantities set, so KDTree._convert_coords_to_tree_units raised:
ValueError: KD-tree was built with dimensionless coordinates, but
query coordinates have units 'meter'.
Proxied swarm variables therefore did not work at all under units --
including IndexSwarmVariable material level sets, which have their own
copy of the same pattern. Three live sites, all fixed to .coords_nd:
SwarmVariable._rbf_to_meshVar
IndexSwarmVariable._update_proxy_variables (both update_type branches)
It failed loudly rather than silently corrupting, which is how it
survived: no test set reference quantities and then touched a proxy.
tests/test_0116_swarm_proxy_units.py now does, and covers the plain proxy
path, the level-set path (asserting partition of unity and [0,1]), and a
dimensionless control so the fix cannot regress the untouched case.
A fourth instance of the same pattern remains in Swarm._get_map, reached
only from the dead _rbf_reduce_to_meshVar; both are scheduled for deletion
under #428 and are deliberately left alone here.
Not addressed, and split off deliberately: SwarmVariable._create_proxy_variable
does not forward units= to the proxy MeshVariable, so var.units is set
while var._meshVar.units is None. Attaching units there would make var.sym
unit-aware and change symbolic behaviour wherever proxies are composed, so
it needs its own design rather than riding along with a coordinate fix.
Underworld development team with AI support from Claude Code
Adversarial review (retrospective — this PR was merged without one)The house rule is an adversarial review posted publicly before a PR is marked ready. That did not happen here: #434 went straight to ready and was merged. Running it now against the merged state, What the fix does not coverOnly three of four instances. Half of #426 did not ship. The What the test does not prove
The units fixture sets What I did not check
What holds up
|
`SwarmVariable._create_proxy_variable` did not forward `units=` to the
proxy MeshVariable. Since `var.sym` resolves to the proxy, reading a
proxied variable through the symbolic path returned the NON-DIMENSIONAL
number with no units attached, presented as though it were the answer.
Measured before, with density=3300 kg/m^3 as the reference quantity and a
stored non-dimensional value of 1.0:
var.units kilogram / meter ** 3
var._meshVar.units None
var.array 3300.0
var._meshVar.array 1.0 <- disagree
evaluate(var.sym) ndarray 1.0, no units <- silently wrong
after:
var._meshVar.units kilogram / meter ** 3
var.array 3300.0
var._meshVar.array 3300.0
evaluate(var.sym) UnitAwareArray 3300.0 kg/m^3
Stored data stays non-dimensional on both sides; only what the proxy
advertises changes, so the transfer itself is untouched.
This is the half of #426 that PR #434 deliberately left out, split off at
the time as #439 because it needed a units-boundary decision rather than a
coordinate fix. Maintainer decision was to forward the units and measure
the blast radius.
Blast radius: none. level_1 571 passed (567 + the 4 new tests), level_2
319 passed, np=2 parallel 4 passed, zero failures. No existing test both
declares units on a swarm variable and reads it symbolically -- which is
also why the defect survived, and is itself worth recording.
Variables that declare no units keep a dimensionless proxy, asserted by
test.
Underworld development team with AI support from Claude Code
Fixes #426.
The defect
The swarm kd-tree is built from
Swarm._particle_coordinates.data, which is always non-dimensional. The proxy refresh queried it withMeshVariable.coords, which dimensionalises as soon as the model has reference quantities set:So proxied swarm variables did not work at all under an active units model — including
IndexSwarmVariablematerial level sets, which carry their own copy of the same pattern.Reproduced before the fix:
The fix
.coords→.coords_ndat the three live sites:SwarmVariable._rbf_to_meshVarIndexSwarmVariable._update_proxy_variables, bothupdate_typebranchesThis is the pattern the rest of the codebase already uses —
MeshVariable._get_kdtreebuilds fromcoords_nd,SemiLagrangianusespsi_star[i].coords_nd,_apply_monotone_limitnon-dimensionalises explicitly.swarm.pywas the outlier:grep coords_nd src/underworld3/swarm.pypreviously returned nothing.Why it survived
It failed loudly rather than silently corrupting, and no test set reference quantities and then touched a proxy.
tests/test_0116_swarm_proxy_units.pynow does:SwarmVariableproxy path;IndexSwarmVariablelevel-set path, asserting partition of unity and that every level set stays in[0, 1]— the propertiesconstitutive_models.pyrelies on when it divides by the level-set sum;Written first and shown to fail (2 failed, 1 passed — the control), then fixed (3 passed).
Regression
tier_a/tier_b: 469 passed, 0 failed (466 before, plus the 3 new).Deliberately not in this PR
A fourth instance of the same pattern is in
Swarm._get_map, reachable only from the dead_rbf_reduce_to_meshVar. Both are scheduled for deletion under #428, so touching them here would be churn._create_proxy_variabledoes not forwardunits=to the proxyMeshVariable, sovar.unitsiskg/m³whilevar._meshVar.unitsisNone. That is filed in #426 alongside this, but attaching units there would makevar.symunit-aware and change symbolic behaviour everywhere proxies are composed into expressions. It needs its own design rather than riding along with a coordinate fix, so #426 stays open for that half.Underworld development team with AI support from Claude Code