Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions src/underworld3/systems/ddt.py
Original file line number Diff line number Diff line change
Expand Up @@ -1765,11 +1765,28 @@ def update_pre_solve(

except Exception:
# Fallback to projection solver for expressions that can't be directly evaluated
# (e.g., containing derivatives)
self._psi_star_projection_solver.uw_function = self.psi_fn
# (e.g., containing derivatives — true for the NS viscous flux every step).
# Route via _build_projection_source so the (1, Nc) row-matrix flattening
# required by SNES_MultiComponent_Projection is applied for tensor vtypes.
# Without this, a (dim, dim) tensor function meets a (1, Nc) solver field
# and SymPy raises "Matrix size mismatch: (1, Nc) + (dim, dim)" (issue #180).
self._psi_star_projection_solver.uw_function = self._build_projection_source(
self.psi_fn
)
self._psi_star_projection_solver.smoothing = 0.0
self._psi_star_projection_solver.solve(verbose=verbose)

# For tensor vtypes the projection writes into the flat (1, Nc) variable,
# so we must fan it back out to psi_star[0] — otherwise subsequent
# history operations read a stale tensor. Mirrors the same fan-out in
# initialise_history() (~line 1540).
if getattr(self, '_psi_star_use_multicomponent', False):
for k, (i, j) in enumerate(self._psi_star_indep_indices):
vals = self._psi_star_flat_var.array[:, 0, k]
self.psi_star[0].array[:, i, j] = vals
if i != j:
self.psi_star[0].array[:, j, i] = vals

# 3. Compute the upstream values from the psi_fn

# We use the u_star variable as a working value here so we have to work backwards
Expand Down
54 changes: 54 additions & 0 deletions tests/test_0610_navier_stokes_slcn_projection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""
Regression test for the NavierStokesSLCN DFDt projection fallback.

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 a ``(1, Nc)`` row matrix via
``_build_projection_source``. The fallback path inside ``update_pre_solve``
(taken when ``uw.function.evaluate`` raises on expressions containing
derivatives — true for the NavierStokes 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).

See: https://github.com/underworldcode/underworld3/issues/180
"""
import pytest
import sympy
import underworld3 as uw


@pytest.mark.level_2
@pytest.mark.tier_a
def test_navier_stokes_slcn_solve_does_not_raise_shape_error():
"""First solve completes without ShapeError from the DDt projection fallback."""
mesh = uw.meshing.UnstructuredSimplexBox(
minCoords=(0.0, 0.0),
maxCoords=(1.0, 1.0),
cellSize=1 / 8,
regular=False,
)

v = uw.discretisation.MeshVariable("U", mesh, mesh.dim, degree=2)
p = uw.discretisation.MeshVariable("P", mesh, 1, degree=1, continuous=True)

ns = uw.systems.NavierStokes(
mesh,
velocityField=v,
pressureField=p,
rho=1.0,
order=2,
)
ns.constitutive_model = uw.constitutive_models.ViscousFlowModel
ns.constitutive_model.Parameters.shear_viscosity_0 = 1.0

ns.bodyforce = sympy.Matrix([0, 0]).T
ns.add_dirichlet_bc((1.0, 0.0), "Top")
ns.add_dirichlet_bc((0.0, 0.0), "Bottom")
ns.add_dirichlet_bc((0.0, 0.0), "Left")
ns.add_dirichlet_bc((0.0, 0.0), "Right")

# Take a single step. Pre-fix this raised ShapeError("(1, 3) + (2, 2)")
# from the DDt projection fallback. Post-fix it just converges.
ns.solve(timestep=0.01, zero_init_guess=True)
Loading