Fix thermal convection units tutorial#263
Conversation
Holding this one — it exposes a real units + semi-Lagrangian bugThanks @ss2098 — the setup half of the tutorial is correctly updated (it gets all the way through the Rayleigh-number / scaling section). But I ran it end-to-end on current Root cause (core, not tutorial)The SL trace-back midpoint is if has_units:
if hasattr(dt, "to"):
dt_for_calc = dt.to("second")
else:
dt_for_calc = dt # comment says "treat as seconds" but leaves it unitlessso So the tutorial can't run until the units-active SL trace-back consistently reconciles dt with the dimensional coords/velocity. This is a genuine UW3 bug the tutorial usefully surfaced, not something to patch in the example. I'll file it separately and link it here; let's hold this PR until that lands (then this tutorial should be the natural regression test for it). Everything else in the PR (the setup/units fixes) looks good and can ride along once the core fix is in. |
|
Filed the underlying bug: #267 — holding this PR until that lands. |
|
Thanks Professor @lmoresi , this makes sense. I will leave PR #263 open and on hold until #267 is fixed. I agree that this should be handled in the semi-Lagrangian units path rather than patched only in the tutorial. Once the core fix lands, I can rebase the tutorial branch on current development, rerun Thanks for identifying and filing the underlying issue. |
SemiLagrangian.update_pre_solve crashed for any unit-bearing model in the time
loop:
ValueError: Cannot subtract arrays with incompatible units:
'meter' and 'meter / second'
Root cause: the trace-back is performed in the mesh's NON-DIMENSIONAL (DM)
coordinate space (evaluate()/global_evaluate treat plain arrays as DM coords,
and DM point-location uses DM values 0..L_model, NOT dimensional metres). But
the has_units branch kept dimensional coords (mesh.X.coords ~ metres) and
velocity (m/s) and left dt a unitless float, so:
- coords[m] - v[m/s]*dt[dimensionless] -> unit subtraction crash, and
- even patched, dimensional coords (~1e9) fed to a DM that locates in 0..1000
space would mislocate.
Fix: route the whole trace-back through ND/DM space regardless of units —
departure point from psi_star[i].coords_nd (== .coords for a non-units model;
the ND reduction of the dimensional coords when units are active), velocity
non-dimensionalised, dt non-dimensional. Both the node-velocity and
midpoint-velocity legs are corrected. The non-units path is unchanged (it
already used exactly this ND logic).
Verified: units-active AdvDiffusionSLCN now runs and tracks the equivalent
non-dimensional run to ~1e-3 (the small residual is the constitutive diffusivity
scaling under units, a separate concern, not the trace-back). Non-units SL
regression unchanged (test_0855/0610/1054/1100 pass; test_1110 fails identically
with/without this change — a pre-existing MG DMCreateInjection issue). New
regression: tests/test_1056_units_slcn_traceback.py.
Unblocks the time-loop half of #263 (Tutorial_Thermal_Convection_Units).
Underworld development team with AI support from Claude Code
|
Unblocked: the underlying SL trace-back bug is fixed on |
374d1e6 to
5fbbc0f
Compare
|
Thanks Professor @lmoresi. I rebased this branch on current development after #277 landed and re-ran the tutorial end-to-end. Tested with: pixi run python docs/examples/Tutorial_Thermal_Convection_Units.py The time-stepping section now completes successfully. Result: ✅ Time-stepping completed! I have pushed the rebased branch. |
|
Re-ran end-to-end on current So this PR is unblocked on #267 but blocked on #282. Two options:
Happy to push option (2) so the tutorial merges now, or hold for #282 — your call / @ss2098's. |
|
Thanks Professor @lmoresi. I think option (2) makes sense here so the tutorial can merge now. Since the tutorial is already using physical quantities for setup and then explicit model-unit numeric values in solver expressions, using Please feel free to push that workaround, or I can update the branch on my side if you prefer. |
A MeshVariable's .sym lives in non-dimensional (model-unit) space, so the MathematicalMixin arithmetic operators could not compose with a unit-bearing scalar operand: T - uw.quantity(500, 'K') raised TypeError: Cannot subtract UWQuantity from EnhancedMeshVariable even for compatible units (the Pint quantity reached sympy and failed to sympify). This blocks the fundamental buoyancy form T - T_ref. Add a coercion branch to the operand handling in __add__/__sub__/__rsub__/ __mul__/__rmul__/__truediv__/__rtruediv__: a UWQuantity (anything with .magnitude and .units) is reduced to its non-dimensional value via uw.non_dimensionalise before composing with the variable's .sym. Compatible units (incl. prefixes like kK) reduce consistently via the model scaling; the change is behaviour-preserving for all non-quantity operands (sympy / variable / plain scalar hit the same branches as before — verified the sympy-scalar matrix quirk is pre-existing, not introduced here). Scope: variable on the LEFT (meshvar ± / * quantity). 'quantity - meshvar' (quantity on the left) is handled by the UWQuantity class and is out of scope. Verified: T ±/* quantity compose with exact ND values (T - 500K == T.sym - 0.5), kK prefix consistent, no regression (test_0700/0750/0754 unchanged). Regression: tests/test_0756_meshvar_quantity_arithmetic.py. Helps unblock the buoyancy expression in PR #263. Closes #282. Underworld development team with AI support from Claude Code
The Stokes bodyforce setter tried to handle UWQuantity components via item._sympify_() — a method that does not exist on UWQuantity (_sympy_ also raises on a dimensional quantity). So any units-active buoyancy assignment (stokes.bodyforce = [0, ρ₀ α g (T − T_ref)]) crashed with AttributeError: 'UWQuantity' object has no attribute '_sympify_'. The body force feeds the non-dimensional solver, so non_dimensionalise the UWQuantity component (consistent with the ND<->units boundary contract and the #282 fix). The component may be symbolic (a buoyancy carries the T field), so non_dimensionalise yields a 1x1 array/Matrix whose element is extracted (the existing shape-handling is preserved). Plain non-quantity components are unchanged. Verified: a symbolic UWQuantity buoyancy assigns to bodyforce as the correct ND expression; test_1010 (Stokes) unchanged. Regression: tests/test_1011_bodyforce_units.py. The second blocker (after #267/#282) for the thermal-convection units tutorial (#263). Underworld development team with AI support from Claude Code
Units-active convection now runs — the system blockers are fixedRe-ran on current
So the units-active convection path works now. What remains is tutorial-side:
@ss2098 — once the °C prints use K (or manual −273.15) and the timestep/scaling is tuned so the run stays stable, this should be ready. The hard part (the three system bugs) is done. Happy to help with the stability tuning if useful. |
|
Professor @lmoresi, I also tested docs/examples/porous_flow/advanced/Ex_viscousFingering.py on current development and found that the migrated script currently fails in the swarm path. First swarm.populate(fill_param=4) hits a NameError for all_local_cells, and after bypassing populate the later swarm.advection path hits additional missing internal bookkeeping such as particle_X_orig and cellid. I made a local runnable version by removing the swarm-material route and using the existing AdvDiffusionSLCN mesh-field material advection instead. That version runs end-to-end and finishes with: Viscous fingering example complete. However, this changes the example from swarm-tracked material advection to mesh-field advection-diffusion, so I do not want to open it as a simple bug-fix PR without checking. Would you prefer that I open this as a script-safe migration PR, or should the swarm/populate/advection issues be treated as a separate core bug first? |
5fbbc0f to
bf7921c
Compare
|
Thanks Professor @lmoresi. I updated the tutorial-side issues after rebasing on current development. Changes made:
Tested with: pixi run python docs/examples/Tutorial_Thermal_Convection_Units.py Result: ✅ Time-stepping completed! I pushed the updated branch. |
This updates docs/examples/Tutorial_Thermal_Convection_Units.py so that it runs with the current unit-aware array and strict-units behavior.
The previous tutorial failed in several places under the current API:
The updated tutorial keeps the physical inputs in units, but uses explicit model-unit numeric values in solver expressions. It also converts unit-aware arrays before assignment, constructs the initial temperature field from normalized coordinates, and reports original physical parameters for interpretation.
No solver behavior is changed.
Tested with:
pixi run python docs/examples/Tutorial_Thermal_Convection_Units.py
Result: script completes successfully.