Skip to content

Fix SemiLagrangian DDt blowup: clamp departure points to domain#85

Merged
lmoresi merged 2 commits intodevelopmentfrom
bugfix/sl-departure-clamping
Mar 21, 2026
Merged

Fix SemiLagrangian DDt blowup: clamp departure points to domain#85
lmoresi merged 2 commits intodevelopmentfrom
bugfix/sl-departure-clamping

Conversation

@lmoresi
Copy link
Member

@lmoresi lmoresi commented Mar 19, 2026

Summary

Root cause

The SemiLagrangian update_pre_solve computed upstream departure points (mid_pt_coords and end_pt_coords) without clamping them to the domain boundary. Near corners -- especially velocity discontinuities in the lid-driven cavity -- departure points landed outside the mesh. global_evaluate then extrapolated using the P2 basis, producing values ~1000x the actual field maximum. This injected a spurious acceleration that blew up the solver on the second timestep.

Fix

Clamp both mid_pt_coords and end_pt_coords using mesh.return_coords_to_bounds() in SemiLagrangian.update_pre_solve(). The Lagrangian DDt already did this for swarm advection; the SemiLagrangian (nodal) path was missing it. Two lines of code.

Validation

Stability: All combinations stable (Re = 10, 100, 400, 1000; cellSize = 0.1, 0.05, 0.025).

Ghia et al. (1982) benchmark at Re=100 (cellSize=0.04, 200 steps, t=10): RMS error = 0.004 against published centreline velocity data.

Benchmark images (Re=100 and Re=400 streamlines) to follow in comments.

Test plan

  • Stability across Re and cellSize combinations
  • Ghia et al. (1982) centreline velocity comparison (Re=100)
  • Re=400 streamline visualisation (corner vortices visible)
  • @bknight1 to validate with original reproducer script
  • Test with order=2 (second-order DuDt)
  • Check that the Eulerian DDt callback reshape error is a separate issue

Underworld development team with AI support from Claude Code

lmoresi added 2 commits March 19, 2026 22:09
…#82)

The SemiLagrangian update_pre_solve computed upstream departure points
(mid_pt_coords, end_pt_coords) without clamping them to the domain.
Near boundaries — especially corner singularities in lid-driven cavity
flow — departure points landed outside the mesh, causing global_evaluate
to extrapolate wildly (values of order 1000x the actual field maximum).
This injected a massive spurious acceleration that blew up the solver
on the second timestep.

Fix: clamp both mid_pt_coords and end_pt_coords using the mesh's
existing return_coords_to_bounds function. This mirrors the treatment
already present in the Lagrangian DDt's swarm advection path.

Validated against Ghia et al. (1982) lid-driven cavity benchmark at
Re=100 (RMS error 0.004) and Re=400. Stable across all tested
combinations of Re (10, 100, 400, 1000) and cellSize (0.1, 0.05, 0.025).

Underworld development team with AI support from Claude Code
Clean rewrite of Ex_NS_PipeFlow.py: Poiseuille flow between parallel
plates with analytic steady-state comparison. Replaces the unmigrated
HPC batch script with a self-contained notebook following style guide.

Underworld development team with AI support from Claude Code
Copilot AI review requested due to automatic review settings March 19, 2026 11:10
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a SemiLagrangian DDt stability issue by preventing upstream departure points from leaving the mesh domain, and modernizes the Navier–Stokes pipe/Poiseuille flow example into a simpler standalone notebook-style script.

Changes:

  • Clamp SemiLagrangian midpoint and end-point departure coordinates to mesh bounds before evaluation.
  • Rewrite Ex_NS_PipeFlow into a streamlined transient Poiseuille-flow example with analytic comparison.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/underworld3/systems/ddt.py Adds domain clamping for semi-Lagrangian departure points to avoid out-of-mesh extrapolation blowups.
docs/examples/fluid_mechanics/intermediate/Ex_NS_PipeFlow.py Replaces migrated legacy example with a simpler, self-contained Poiseuille-flow notebook example and plotting/comparison workflow.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1423 to +1425
# Clamp midpoint coordinates to the domain boundary
if self.mesh.return_coords_to_bounds is not None:
mid_pt_coords = self.mesh.return_coords_to_bounds(mid_pt_coords)
Comment on lines +122 to +125
if step % 5 == 0 or step == NSTEPS - 1:
# Sample centreline velocity at the outlet (x = max, y = 0)
vx_max = np.abs(v_soln.data[:, 0]).max()
print(f"step {step:3d} | max |vx| = {vx_max:.4e}")
sample_coords = np.column_stack([np.full(n_sample, x_sample), y_sample])

delta_t = Cmax*delta_x/max_vel
vx_numerical = uw.function.evaluate(v_soln.sym[0], sample_coords)
delta_x = meshbox.get_min_radius()
max_vel = vel
# Sample the velocity along a vertical line near the outlet
x_sample = 0.4 * ASPECT_RATIO * HEIGHT * 0.5 # near the outlet
@lmoresi
Copy link
Member Author

lmoresi commented Mar 19, 2026

Here are two quick "benchmark" runs at Re=100, Re=400 for the Ghia et al benchmark. Qualitatively, this looks promising with the counterflow vortices developing at approximately the correct size ratios. @bknight1 - needs more testing, but merge if you think the main bugs are killed.

NS_Re100_streamlines NS_Re400_streamlines

@bknight1
Copy link
Member

Looks good to me! Thanks @lmoresi.

A quick suggestion would be allowing the user to pass a DFDt history manager, as we currently enforce the SL version. We currently do this for all solvers that require the flux history.

@lmoresi
Copy link
Member Author

lmoresi commented Mar 20, 2026

Order-1 and Order-2 validation: Lid-driven cavity Re=100

Tested both order=1 and order=2 SemiLagrangian on the lid-driven cavity benchmark (cellSize=0.04, 200 steps, dt=0.05, t_final=10) with the departure-point clamping fix applied.

Results

Order RMS error vs Ghia et al. (1982)
1 0.0038
2 0.0415

Order 1

sl_cavity_order1

Order 2

sl_cavity_order2

Note on order-2: This does not really look stable, so we will keep this open for now and keep digging

Underworld development team with AI support from Claude Code

lmoresi added a commit that referenced this pull request Mar 21, 2026
Clamp mid_pt_coords and end_pt_coords to domain bounds in
SemiLagrangian.update_pre_solve() to prevent extrapolation blowup
near boundary discontinuities.

Underworld development team with AI support from Claude Code
@lmoresi
Copy link
Member Author

lmoresi commented Mar 21, 2026

Update: Order-2 results with working BDF/AM coefficients

The earlier order-2 results were invalid. We discovered that BDF/AM coefficients were frozen at order 1 from the first solve — effective_order ramping had no effect on the compiled pointwise functions. All three order-2 BDF/AM combinations produced identical RMS (0.041469) because they were all running BDF1+AM1 with extra history noise.

This has been fixed in PR #88 (BDF/AM coefficients routed through PetscDSSetConstants()). With the fix applied, order-2 (BDF2+AM2) produces a
clean solution:

sl_cavity_order2_streamlines
Order RMS vs Ghia et al. (1982)
1 (BDF1+AM1) 0.0038
2 (BDF2+AM2) 0.0065

Both orders are stable with the departure-point clamping fix from this PR. The order-2 instabilities seen previously were entirely the frozen-coefficients bug, not a problem with the clamping.

  • Test with order=2 (second-order DuDt) — validated, clean solution

@lmoresi
Copy link
Member Author

lmoresi commented Mar 21, 2026

I think we can merge this fix. Test passing, comments from @bknight1 approving 1st order variant of the PR

@lmoresi lmoresi merged commit fa819e1 into development Mar 21, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants