Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1ed2c1f
Forward semi-Lagrangian history launched from the field's nodes and e…
lmoresi Sep 26, 2026
c434d8a
Semi-Lagrangian histories named by trace and launch; one entry point;…
lmoresi Sep 27, 2026
b20c992
Every stress and advection history gives the serial answer in paralle…
lmoresi Sep 27, 2026
98c4193
Review (b20c992e): inflow across seams, NaN fallback, reproducible hi…
lmoresi Sep 27, 2026
28314aa
The monotone clamp is applied where a point is evaluated; the Navier-…
lmoresi Sep 27, 2026
2adf50d
SUPG sees the carried elastic stress; the semi-Lagrangian Navier-Stok…
lmoresi Sep 28, 2026
c0704a8
Review (2adf50d0): momentum weights for the theta rule, parameter gra…
lmoresi Sep 29, 2026
78e62a9
One NavierStokes and one AdvDiffusion, the transport chosen by argument
lmoresi Sep 30, 2026
bf4e663
Store smoothing on every semi-Lagrangian stress history; a slope limi…
lmoresi Oct 2, 2026
a62aa9d
Forward integration-point history: a global L2 projection of the arri…
lmoresi Oct 3, 2026
ea5b202
Forward nodal history: the global projection as an alternative to the…
lmoresi Oct 3, 2026
42d0aab
ParticleL2Projector at degree 2, so the forward nodal velocity histor…
lmoresi Oct 3, 2026
e37ab35
ParticleL2Projector: fill a cell's uncovered share from the previous …
lmoresi Oct 4, 2026
2fa180a
Projection smoothing setter: request a rewire only when the value cha…
lmoresi Oct 4, 2026
904048d
FENE-P relaxation law for the viscoelastic model; element and relaxat…
lmoresi Oct 4, 2026
59366e9
Review (78e62a9a): the composed solvers off the Eulerian path, and th…
lmoresi Oct 4, 2026
fda1286
Review (bf4e663e..59366e96): rank-symmetric FENE-P variables, the pro…
lmoresi Oct 4, 2026
d4e6fa5
Global projection: the deficit fill only for cells that have emptied;…
lmoresi Oct 4, 2026
3170929
FENE-P: store log(f c), decode the conformation through the record's …
lmoresi Oct 5, 2026
020b02f
FENE-P review fixes: L^2 > d guard, field-route test, dead Peterlin path
lmoresi Oct 5, 2026
c42e4fa
Merge development: two mechanical conflicts and one rename
lmoresi Oct 6, 2026
2a4f892
Merge remote-tracking branch 'origin/feature/log-conformation' into w…
lmoresi Oct 6, 2026
9328620
Merge remote-tracking branch 'origin/development' into wip/fix-800
lmoresi Oct 8, 2026
dcd363c
Merge development: the renamed schemes reach the contracts and the docs
lmoresi Oct 8, 2026
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
38 changes: 28 additions & 10 deletions docs/advanced/eulerian-advection-diffusion.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,18 @@ is resolved in time (a fraction of a feature width per step), the Eulerian solve
is cheaper and more accurate; if the step is deliberately long relative to the
transported features, the semi-Lagrangian solver is the one that survives it.

## The three transport managers
## The transport managers

`DuDt` selects the transport, and three managers are worth considering for a
scalar field. The choice turns on the Courant number the model runs at and on
whether the model is carrying particles for another reason.
`DuDt` selects the transport. The choice turns on the Courant number the model
runs at and on whether the model is carrying particles for another reason.

The semi-Lagrangian schemes are named by two choices, and
`uw.systems.ddt.SemiLagrangian(..., trace=, launch=)` builds any of them:
`trace` is `"backward"` (follow the characteristic back from each storage point
and sample the old field at the departure point) or `"forward"` (launch the old
field from where it is known, carry it one step, and fit the arrivals in each
cell); `launch` is `"nodes"` or `"integration_points"`. Called with neither, it
builds `BackwardNodesSemiLagrangian`, the historical default.

**Eulerian SUPG** (`uw.systems.ddt.EulerianSUPG`, the default) is the general
choice. Its error falls as $\Delta t^2$, it puts no lower limit on the Courant
Expand All @@ -95,7 +102,9 @@ enclosed volume to 4e-5 against that scheme's 5e-3. Use it unless something
below applies.

**Semi-Lagrangian on the integration points**
(`uw.systems.ddt.IntegrationPointSemiLagrangian`) is the accurate choice at
(`uw.systems.ddt.BackwardIntegrationPointsSemiLagrangian`, or
`SemiLagrangian(trace="backward", launch="integration_points")`) is the
accurate choice at
larger Courant numbers. Its error is flat between Courant 0.5 and 2, so a model
that takes long steps keeps its accuracy where the Eulerian scheme loses it, and
it loses 45 times less of the second moment than the nodal scheme does. It
Expand All @@ -118,11 +127,20 @@ aimed at models that already carry a swarm for material tracking, where the
transport rides on particles the model is advecting anyway. We would not expect
to introduce particles in order to use it.

**Semi-Lagrangian at the nodes** (`uw.systems.ddt.SemiLagrangian`) remains the
historical default of `AdvDiffusionSLCN`. It re-interpolates once per step, which
costs it accuracy at small Courant numbers, and on a deforming flow with a sharp
interface it diverges below a Courant number that depends on the problem. Prefer
one of the three above.
**Semi-Lagrangian at the nodes**
(`uw.systems.ddt.BackwardNodesSemiLagrangian`) remains the historical default of
`AdvDiffusionSLCN`. It re-interpolates once per step, which costs it accuracy at
small Courant numbers, and on a deforming flow with a sharp interface it
diverges below a Courant number that depends on the problem. Prefer one of the
three above.

**Forward traces** (`ForwardIntegrationPointsSemiLagrangian`,
`ForwardNodesSemiLagrangian`) carry the field from where it is known rather
than sampling where it is wanted, which is what a stress history needs when the
departure point falls outside the domain. They are newer than the measurements
above and are documented with the stress transport, in
`docs/developer/subsystems/stress-transport.md`. The per-cell fit a forward
trace performs is unstable where the flow empties a cell (#811).

## Choosing the time scheme

Expand Down
6 changes: 4 additions & 2 deletions docs/api/solvers.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,10 @@ default `EulerianSUPG` manager it is the implicit Eulerian SUPG scheme.

### SNES_NavierStokes_Composed (`uw.systems.NavierStokes`)

Navier-Stokes composed from a DDt transport manager (Eulerian SUPG momentum
transport by default); the semi-Lagrangian class above is `uw.systems.NavierStokesSLCN`.
Navier-Stokes composed from a DDt transport manager: `velocity_transport="eulerian"`
(SUPG on the mesh, the default) or one of the semi-Lagrangian schemes, and for a
viscoelastic model `stress_transport` chooses the stress history the same way. The
former `uw.systems.NavierStokesSLCN` is deprecated and still returns the class above.

```{eval-rst}
.. autoclass:: underworld3.systems.navier_stokes_eulerian.SNES_NavierStokes_Composed
Expand Down
34 changes: 32 additions & 2 deletions docs/api/systems_ddt.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,37 @@ bypasses the order ramp, so the first solve runs at full BDF order.
### SemiLagrangian

```{eval-rst}
.. autoclass:: underworld3.systems.ddt.SemiLagrangian
.. autofunction:: underworld3.systems.ddt.SemiLagrangian
```

### BackwardNodesSemiLagrangian

```{eval-rst}
.. autoclass:: underworld3.systems.ddt.BackwardNodesSemiLagrangian
:members:
:show-inheritance:
```

### BackwardIntegrationPointsSemiLagrangian

```{eval-rst}
.. autoclass:: underworld3.systems.ddt.BackwardIntegrationPointsSemiLagrangian
:members:
:show-inheritance:
```

### ForwardIntegrationPointsSemiLagrangian

```{eval-rst}
.. autoclass:: underworld3.systems.ddt.ForwardIntegrationPointsSemiLagrangian
:members:
:show-inheritance:
```

### ForwardNodesSemiLagrangian

```{eval-rst}
.. autoclass:: underworld3.systems.ddt.ForwardNodesSemiLagrangian
:members:
:show-inheritance:
```
Expand All @@ -63,6 +93,6 @@ bypasses the order ramp, so the first solve runs at full BDF order.
The following aliases are available via ``underworld3.systems``:

- ``Lagrangian_DDt`` → {class}`~underworld3.systems.ddt.Lagrangian`
- ``SemiLagragian_DDt`` → {class}`~underworld3.systems.ddt.SemiLagrangian`
- ``SemiLagragian_DDt`` → {class}`~underworld3.systems.ddt.BackwardNodesSemiLagrangian`
- ``Lagrangian_Swarm_DDt`` → {class}`~underworld3.systems.ddt.Lagrangian_Swarm`
- ``Eulerian_DDt`` → {class}`~underworld3.systems.ddt.Eulerian`
7 changes: 7 additions & 0 deletions docs/api/utilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,10 @@ Array wrapper that triggers callbacks on modification.
:members:
:show-inheritance:
```

## Particle projection

```{eval-rst}
.. automodule:: underworld3.utilities.particle_projection
:members:
```
4 changes: 2 additions & 2 deletions docs/developer/design/REMESH_FIELD_TRANSFER_DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ REMAP.

## 7. The band-aid (already landed) and its relationship to the true fix

`ddt.py` `SemiLagrangian._record_psi_star_from_field_data()` + a guarded call:
`ddt.py` `BackwardNodesSemiLagrangian._copy_tracked_field()` (formerly the parallel-only `_record_psi_star_from_field_data`; since 2026-09-27 used in serial too) + a guarded call:
under MPI, when `psi_fn` is a single mesh-variable component on this mesh, the
per-step "record current field into `psi_star[0]`" copies the field's nodal
data **directly** instead of evaluating it at its own (on-vertex) coords.
Expand Down Expand Up @@ -255,6 +255,6 @@ Relationship to the true fix:
|---|---|---|
| `discretisation/discretisation_mesh.py` | `_deform_mesh` @2001, `nuke_coords_and_rebuild` @1757 (per-var refill @1890), `vars` registry @3082 | coords/cache rebuild; scoped-refill change (§5) |
| `meshing/smoothing.py` | movers @1570/1861/2639 (per-outer `_deform_mesh`); `smooth_mesh_interior` @2683; `OT_adapt`, `follow_metric` | adapt op owns transfer; mover sweep refill scope |
| `systems/ddt.py` | `SemiLagrangian` (history @119, shift @2035, re-record @2085, band-aid `_record_psi_star_from_field_data`); flavors @98/108/119/146 | history policy = REMAP/ALE; `on_remesh` hook |
| `systems/ddt.py` | `SemiLagrangian` (history @119, shift @2035, re-record @2085, nodal copy `_copy_tracked_field`, formerly `_record_psi_star_from_field_data`); flavors @98/108/119/146 | history policy = REMAP/ALE; `on_remesh` hook |
| `systems/solvers.py` | `AdvDiffusionSLCN`, NS, VE (`DuDt`/`DFDt` @997/1354) | use DDt; nothing solver-specific for ALE |
| `swarm.py` | `_proxy_stale` @309, `_update` @1019/2129 | proxy REINIT + adapt-triggered staleness (§8) |
2 changes: 1 addition & 1 deletion docs/developer/design/lagged-clone-sl-history.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ despite old-frame being active, because the standard `store_result` path
**re-records** `psi_star[0]` by evaluating `psi_fn` on the *deformed* mesh at
centroid-shifted nodes — injecting boundary-layer interpolation error that grows
with `h_max` and then rides the old-geometry sample. Recording the history by a
**direct nodal carry** (reusing the parallel `_record_psi_star_from_field_data`
**direct nodal carry** (reusing the nodal copy `_copy_tracked_field`, formerly `_record_psi_star_from_field_data`
path) restores the prototype's exact behaviour. This is the "store primitives,
not re-derived values" principle of invariant 3, in miniature.

Expand Down
4 changes: 3 additions & 1 deletion docs/developer/guides/transport-schemes.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
---
name: transport-schemes
description: Which transport scheme and which time history to use in Underworld3, and why — nodal, integration-point or grid histories; semi-Lagrangian, Eulerian SUPG or Lagrangian swarm transport; the Courant number to run at; what each choice does to a settled state and to a peak. The evidence is the tests and notes named beside each ruling.
families: [AdvDiffusion, NavierStokes, Eulerian, EulerianSUPG, SemiLagrangian, Lagrangian, Lagrangian_Swarm, IntegrationPointSemiLagrangian]
families: [AdvDiffusion, NavierStokes, Eulerian, EulerianSUPG, Lagrangian, Lagrangian_Swarm,
BackwardNodesSemiLagrangian, BackwardIntegrationPointsSemiLagrangian,
ForwardIntegrationPointsSemiLagrangian, ForwardNodesSemiLagrangian]
kind: guide
status: draft, rulings to be confirmed
---
Expand Down
8 changes: 4 additions & 4 deletions docs/developer/subsystems/integration-point-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ reading it, `evaluate`, the guards).

## Semi-Lagrangian history on the integration points

`uw.systems.ddt.IntegrationPointSemiLagrangian` is the SLCN history built on
`uw.systems.ddt.BackwardIntegrationPointsSemiLagrangian` is the SLCN history built on
this variable. Its slots `psi_star[k]` are integration-point variables, so
the value the weak form sees at each integration point is the solution from
`k+1` steps ago evaluated exactly at the departure point of that
Expand All @@ -158,7 +158,7 @@ evaluating the snapshot from time `n-k` at the foot. Every slot carries one
evaluation error rather than one per generation.

```python
DuDt = uw.systems.ddt.IntegrationPointSemiLagrangian(mesh, T, V_fn, degree=2, order=1)
DuDt = uw.systems.ddt.BackwardIntegrationPointsSemiLagrangian(mesh, T, V_fn, degree=2, order=1)
adv = uw.systems.AdvDiffusionSLCN(mesh, u_Field=T, V_fn=V_fn, DuDt=DuDt, order=1)
```

Expand All @@ -182,11 +182,11 @@ component per integration point:

```python
# a momentum history for Navier-Stokes
DuDt = uw.systems.ddt.IntegrationPointSemiLagrangian(
DuDt = uw.systems.ddt.BackwardIntegrationPointsSemiLagrangian(
mesh, v, v.sym, vtype=uw.VarType.VECTOR, degree=2, order=2)

# a viscoelastic stress history
DFDt = uw.systems.ddt.IntegrationPointSemiLagrangian(
DFDt = uw.systems.ddt.BackwardIntegrationPointsSemiLagrangian(
mesh, stress, v.sym, vtype=uw.VarType.SYM_TENSOR, degree=2, order=1)

DFDt.psi_star[0].sym # a 2x2 symbolic matrix
Expand Down
Loading
Loading