Skip to content

Fix NavierStokesSLCN DDt projection source shape mismatch (#180)#183

Open
lmoresi wants to merge 2 commits into
developmentfrom
bugfix/ns-ddt-projection-source
Open

Fix NavierStokesSLCN DDt projection source shape mismatch (#180)#183
lmoresi wants to merge 2 commits into
developmentfrom
bugfix/ns-ddt-projection-source

Conversation

@lmoresi
Copy link
Copy Markdown
Member

@lmoresi lmoresi commented May 11, 2026

Summary

One-line outdated-API fix. When SNES_MultiComponent_Projection was wired into the SemiLagrangian DDt path, the psi_fn setter and _setup_projections were updated to flatten the source tensor to (1, Nc) via _build_projection_source — but the fallback inside update_pre_solve (taken whenever uw.function.evaluate() raises on expressions containing derivatives) was missed and kept assigning self.psi_fn directly.

Stokes never hits this fallback because its projection source has no derivatives. NavierStokes hits it every solve, producing:

sympy.matrices.exceptions.ShapeError: Matrix size mismatch: (1, 3) + (2, 2).

(1, 3) is the projector's Nc=3 symmetric-tensor solution variable in 2D; (2, 2) is the raw stress flux tensor that should have been flattened.

Files

  • src/underworld3/systems/ddt.py — route the fallback through _build_projection_source.
  • tests/test_0610_navier_stokes_slcn_projection.py — regression test: one ns.solve() on a tiny mesh. Reliably raises ShapeError without the fix, passes with it.

Test plan

  • pytest tests/test_0610_navier_stokes_slcn_projection.py — passes
  • Without the fix the same test fails with the user's exact error (1, 3) + (2, 2)
  • Reporter @Chawgai to confirm on their nstt.py reproducer

Reported by @Chawgai. Diagnosed with @ss2098's investigation pointing at the SLCN projection/history-field path as the root cause.

Underworld development team with AI support from Claude Code

When SNES_MultiComponent_Projection was wired into the SemiLagrangian DDt
path, the psi_fn setter (line 1402) and _setup_projections (lines 1373,
863) were updated to flatten the source tensor to a (1, Nc) row matrix
via _build_projection_source. The fallback inside update_pre_solve at
line 1769 — taken when uw.function.evaluate() raises on expressions
containing derivatives (the NS viscous flux every step) — missed the
migration and assigned self.psi_fn directly, producing:

  sympy.matrices.exceptions.ShapeError:
    Matrix size mismatch: (1, 3) + (2, 2).

Stokes never hits this because its projection source has no derivatives.
NavierStokes hits it every solve.

Fix: route the fallback through _build_projection_source like the setter
already does. Adds tests/test_0610_navier_stokes_slcn_projection.py which
reproduces the user's failure (one ns.solve() on a tiny mesh) — reliably
raises ShapeError without the fix, passes with it.

Reported by @Chawgai. Diagnosed with @ss2098's investigation pointing at
the SLCN projection/history-field path.

Underworld development team with AI support from Claude Code
Copy link
Copy Markdown
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 NavierStokes SemiLagrangian (SLCN) DDt projection fallback path where tensor-valued projection sources were not flattened to the (1, Nc) row-matrix shape required by SNES_MultiComponent_Projection, which previously caused SymPy ShapeError during solves.

Changes:

  • Route the update_pre_solve() projection fallback through _build_projection_source() so tensor sources are flattened consistently.
  • Add a regression test that runs a minimal NavierStokes.solve() and asserts it no longer raises the (1, 3) + (2, 2) shape mismatch error.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/underworld3/systems/ddt.py Updates the SemiLagrangian DDt fallback projection source wiring to apply tensor flattening via _build_projection_source().
tests/test_0610_navier_stokes_slcn_projection.py Adds a regression test covering the NavierStokes SLCN DDt projection fallback shape mismatch.

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

)
self._psi_star_projection_solver.smoothing = 0.0
self._psi_star_projection_solver.solve(verbose=verbose)

Copilot flagged that after the shape fix, the multicomponent projector
writes into _psi_star_flat_var but update_pre_solve() never unpacks the
solved flat (1, Nc) field back into psi_star[0]. Mirrors the fan-out
already in initialise_history() (~ddt.py:1540): walk
_psi_star_indep_indices, copy flat var k-th component into
psi_star[0][:, i, j], and symmetric-fill [:, j, i] for SYM_TENSOR.

In practice the SemiLagrangian advection step at line 1016 then
modifies psi_star[0] in place, so the missing fan-out wasn't producing
an immediate symptom in this short test — but the projected flux value
was being silently discarded. Fix restores the intended semantics:
psi_star[0] reflects the current psi_fn (projected flux) before SL
advection updates it for the upstream sample.

Reverted the over-eager second test (asserting psi_star[0] non-zero
after one solve) since the SL advection masks the missing fan-out in
that observation window. Kept the ShapeError regression test which
remains a definitive guard for the original visible failure.

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

lmoresi commented May 14, 2026

Review pass — ready to merge

Scope: the DDt-fallback path was assigning self.psi_fn directly to _psi_star_projection_solver.uw_function, which collided with SNES_MultiComponent_Projection's (1, Nc) row-matrix contract — producing the ShapeError("(1, 3) + (2, 2)") reported in #180. The fix routes through _build_projection_source(self.psi_fn) so the same flattening used at setup time applies to the runtime fallback, and adds the matching flat→tensor fan-out so subsequent advection sees the freshly-projected values.

Verification:

  • Confirmed _build_projection_source already exists and is what _setup_projections and the psi_fn setter use — the fallback was the only place that had been missed in the multicomponent migration.
  • Fan-out mirrors initialise_history() (~line 1540) — same indexing, same symmetric fill.
  • The new regression test (tests/test_0610_navier_stokes_slcn_projection.py) reproduces the exact ShapeError("(1, 3) + (2, 2)") from the issue, and is correctly tagged level_2 / tier_a so it runs in standard CI.
  • @Chawgai already reported on [BUG] - Matrix Mismatch Regarding NavierStokesSLCN Solver #180 that this branch resolves the crash on his nstt.py reproducer.

Copilot note on stale state: Copilot's earlier comment about the missing fan-out is now stale — the current diff includes exactly the fan-out it asked for (lines 1779–1785). Worth marking resolved.

Suggested follow-up: the fallback exists at all because uw.function.evaluate couldn't handle tensor expressions with derivatives. PR #185 removes that limitation, which means in steady-state operation this fallback path should become rare. Both PRs together close out #180.

LGTM modulo standard maintainer eyeball.

Underworld development team with AI support from Claude Code

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.

2 participants