Skip to content

feat(free-surface): uw.systems.FreeSurface manager#375

Open
lmoresi wants to merge 2 commits into
developmentfrom
feature/free-surface-api
Open

feat(free-surface): uw.systems.FreeSurface manager#375
lmoresi wants to merge 2 commits into
developmentfrom
feature/free-surface-api

Conversation

@lmoresi

@lmoresi lmoresi commented Jul 21, 2026

Copy link
Copy Markdown
Member

What

A user-facing manager, uw.systems.FreeSurface, that packages the validated free-slip + prognostic dynamic-topography free-surface recipe (design note: FREESLIP_DYNAMIC_TOPOGRAPHY_FREESURFACE.md) as one object.

The user configures a single stress-free-top Stokes solve; FreeSurface reads it and owns the rest:

  • derives the held rotated free-slip lid (constraint reaction σ_nn → h∞ via dynamic_topography) and the consistent solve (prescribed surface-normal velocity → material-boundary advection);
  • runs the three-number L-stable exponential surface step;
  • transports a composition (mesh level set / temperature) with enclosed-area conservation;
  • carries the surface increment inward (Laplacian diffuser) and mesh.deforms.
stokes = uw.systems.Stokes(mesh)
stokes.constitutive_model = uw.constitutive_models.ViscousFlowModel
stokes.constitutive_model.Parameters.shear_viscosity_0 = eta
stokes.bodyforce = (drho*buoy - rho_g)*yhat
stokes.add_essential_bc((0,0), "Bottom")
stokes.add_rotated_freeslip_bc(0.0, "Left"); stokes.add_rotated_freeslip_bc(0.0, "Right")

fs = uw.systems.FreeSurface(stokes, "Top", buoyancy_scale=rho_g, composition=phi, conserve=buoy)
fs.solve(); dt = fs.estimate_dt(); fs.advance(dt)

Hardening (the three gaps)

  • Surface ring from the DMPlex boundary stratum (label-based, parallel-correct) — not a |y-H| radial heuristic.
  • Surface-motion dt cap in estimate_dt on top of the advective CFL.
  • Continuous-pressure check (CBF recovery requirement).

Nonlinear (strain-rate-dependent) viscosity

The derived held/consistent solves mirror the free solve's nonlinear configuration (snes_type + consistent_jacobian) instead of a hardcoded ksponly. The constitutive-model copy is a solver-retarget: a strain-rate viscosity is tagged with its source solver's velocity identity, so _copy_constitutive_model unwraps and rebinds the free solve's u/Unknowns.L atoms onto each derived solve's own (_velocity_rebind_map). The derived held solve is then bit-identical to an independently built self-consistent solve. The retarget pitfall (and its adjoint-operator relevance) is documented in docs/developer/subsystems/constitutive-models.md.

Validation

  • Reproduces the reference driver (crameri_study Case-2, res 48) to < 0.2% on surface amplitude over two steps.
  • tests/test_1070_free_surface_plume.py: linear rise+relaxation+conservation, nonlinear self-consistency (power-law held solve == hand-built), and np2 parallel — all green.

Remaining scope (next work items, documented in code)

  • Cartesian vertical topography only (spherical/annulus radial deform to follow).
  • Tangent-advection deferred (zero on a flat surface; ~0.2% at step 2).

Additive only: a new class behind a new export, plus a FreeSurface-local solver-copy fix; the nonlinear solver subsystem is unchanged.

🤖 Generated with Claude Code

Underworld development team with AI support from Claude Code

…ace integrator

Package the validated free-slip + prognostic dynamic-topography recipe as a
single user-facing object, uw.systems.FreeSurface. The user configures one
stress-free-top Stokes solve (constitutive model, driving body force, wall BCs,
options); FreeSurface reads it and owns the rest:

  - derives the held rotated free-slip lid (constraint reaction sigma_nn -> h_inf
    via dynamic_topography) and the consistent solve (prescribed surface-normal
    velocity, so the surface stays a material boundary);
  - runs the three-number L-stable exponential surface step;
  - transports a composition (mesh level set / temperature) with enclosed-area
    conservation;
  - carries the surface increment inward with a Laplacian diffuser and deforms.

Surface nodes come from the DMPlex boundary stratum (label-based, parallel-correct)
rather than a |y-H| radial heuristic; estimate_dt adds a surface-motion cap on top
of the advective CFL; continuous pressure is checked (CBF recovery requirement).

Reproduces the reference driver (crameri_study Case-2, res48) to < 0.2% on surface
amplitude over two steps. Serial and np2 regression tests included.

v1 scope (documented in code): Cartesian vertical topography, and linear /
field-dependent viscosity (a strain-rate-dependent law is not yet supported on the
derived solves). Tangent-advection is deferred. These are the next work items.

Underworld development team with AI support from Claude Code
Copilot AI review requested due to automatic review settings July 21, 2026 21:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

The derived held/consistent solves inherited two linear-only assumptions from the
Crameri driver; both are FreeSurface-local (the nonlinear solver subsystem is
unchanged and correct):

1. snes_type was hardcoded to "ksponly" on the derived solves — a single linear
   solve. Now mirror the free solve's nonlinear configuration (snes_type +
   consistent_jacobian): a nonlinear free solve gives nonlinear derived solves, a
   linear one keeps its ksponly one-shot. (The rotated held solve additionally
   auto-detects nonlinearity and dispatches its own linear/Newton path.)

2. The constitutive-model copy carried the free solve's velocity and velocity-
   gradient symbols into the derived solves, so a strain-rate-dependent viscosity
   computed itself from the free solution. This is a solver-copy / model-retarget
   problem: a constitutive expression is tagged with its source solver's variable
   identity. _copy_constitutive_model now unwraps the copied viscosity and rebinds
   the source u and Unknowns.L atoms onto each derived solve's own (extracted as
   _velocity_rebind_map; a no-op for linear / field-dependent viscosity). The derived
   held solve is now bit-identical to an independently built self-consistent solve.

The retarget pitfall (and its adjoint-operator relevance) is documented in
docs/developer/subsystems/constitutive-models.md ("Retargeting a model to another
solver") so it is not re-hit.

Regression test (test_1070): a power-law free surface whose FreeSurface held solve
must equal a hand-built held solve using its own velocity — fails without the rebind,
passes with it. Linear Crameri result and np2 parallel test unchanged.

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