Skip to content

Commit 05dfa92

Browse files
authored
Merge pull request #87 from underworldcode/feature/petscds-constants
Route constant solver parameters through PetscDS constants[] array
2 parents 11f8733 + 6f6e704 commit 05dfa92

10 files changed

Lines changed: 919 additions & 96 deletions

docs/beginner/parameters.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,47 @@ mpirun -np 256 python convection.py \
251251
-uw_max_iterations 100
252252
```
253253

254+
## Using Parameters with Solvers: Expressions
255+
256+
When passing parameters to solver constitutive models, wrap them in
257+
`uw.expression()`. This creates a named symbolic container that the JIT
258+
compiler can update efficiently — changing the value between time steps
259+
does **not** trigger recompilation of the C extension.
260+
261+
```python
262+
import underworld3 as uw
263+
264+
# Define parameters
265+
VISCOSITY = 1e21 # Named constant
266+
MODULUS = 1e10 # Named constant
267+
DT = 0.01 # Timestep
268+
269+
params = uw.Params(
270+
uw_viscosity = VISCOSITY,
271+
uw_modulus = MODULUS,
272+
)
273+
274+
# Wrap in expressions for solver use
275+
eta = uw.expression("eta", params.uw_viscosity)
276+
mu = uw.expression("mu", params.uw_modulus)
277+
dt_e = uw.expression("dt_e", DT)
278+
279+
# Pass expressions to constitutive model
280+
stokes.constitutive_model.Parameters.shear_viscosity_0 = eta
281+
stokes.constitutive_model.Parameters.shear_modulus = mu
282+
stokes.constitutive_model.Parameters.dt_elastic = dt_e
283+
284+
# Time-stepping: change dt without recompilation
285+
for step in range(100):
286+
dt_e.sym = compute_new_timestep() # Updates value, ~0ms
287+
stokes.solve() # No JIT rebuild needed
288+
```
289+
290+
Without expressions, changing a solver parameter between steps requires
291+
setting `_force_setup=True` on the solve call, which triggers a full JIT
292+
recompilation (~5–15 seconds). With expressions, parameter updates go
293+
through PETSc's `constants[]` array and cost essentially nothing.
294+
254295
## Angle Units
255296

256297
Angles work naturally - you can define in degrees and provide radians (or vice versa):

docs/developer/UW3_Developers_MathematicalObjects.md

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -428,13 +428,13 @@ The JIT compilation system needs to:
428428
1. Identify SymPy Function atoms in expressions
429429
2. Map them to PETSc vector components
430430
3. Generate C code with appropriate substitutions
431+
4. Allow constant parameters to change without recompilation
431432

432433
## The Solution: Transparent SymPy Objects
433434

434435
The mathematical object system preserves JIT compatibility by ensuring all operations return pure SymPy objects:
435436

436437
```python
437-
438438
# User writes natural syntax
439439
momentum = density * velocity
440440

@@ -447,23 +447,50 @@ atoms = momentum.atoms(sympy.Function) # Finds V_0, V_1
447447
# Maps to velocity.fn for PETSc substitution
448448
```
449449

450-
## Expression Unwrapping
450+
## Expression Unwrapping and Constants
451451

452-
The `unwrap()` function resolves nested expressions before compilation:
452+
The JIT compiler performs a **two-phase unwrap** on expressions:
453453

454-
```python
454+
1. **Phase 1 — Constants extraction**: `UWexpression` atoms that resolve to
455+
pure numbers (no spatial/field dependencies) are replaced with
456+
`_JITConstant` symbols that render as `constants[i]` in C code.
457+
458+
2. **Phase 2 — Full unwrap**: Remaining `UWexpression` atoms are expanded
459+
to their numerical values and baked into the C code.
455460

461+
```python
456462
# Expression with nested UWexpressions
457463
complex_expr = alpha * (temperature - T0) * velocity
458464

459-
# unwrap() substitutes all UWexpression.sym values
460-
unwrapped = unwrap(complex_expr)
461-
# Result: 2e-5 * (T(x,y,z) - 293) * Matrix([[V_0(x,y,z)], [V_1(x,y,z)]])
465+
# Phase 1: alpha and T0 are constants → constants[0], constants[1]
466+
# Phase 2: temperature and velocity are field variables → petsc_a[], petsc_u[]
467+
468+
# Generated C code:
469+
# result = constants[0] * (petsc_a[0] - constants[1]) * petsc_u[0];
470+
```
471+
472+
This means **changing `alpha` or `T0` between solves does not require
473+
recompilation** — only `PetscDSSetConstants()` is called (~0ms vs ~10s).
474+
475+
## Why Use UWexpressions for Solver Parameters
476+
477+
**UWexpressions are the preferred way to define solver parameters.**
478+
Using raw numbers forces recompilation when values change:
462479

463-
# JIT compilation proceeds normally
464-
compiled = uw.systems.compile(unwrapped)
480+
```python
481+
# PREFERRED — expression parameter, efficient for time-stepping
482+
eta = uw.expression("eta", 1e21)
483+
stokes.constitutive_model.Parameters.shear_viscosity_0 = eta
484+
# Changing eta.sym later → no recompilation
485+
486+
# AVOID for time-varying parameters — raw number, requires rebuild
487+
stokes.constitutive_model.Parameters.shear_viscosity_0 = 1e21
488+
# Changing this later → full JIT rebuild (~10s)
465489
```
466490

491+
This is particularly important for viscoelastic and Navier-Stokes
492+
solvers where parameters like `dt_elastic` change every time step.
493+
467494
# Migration from Legacy Patterns
468495

469496
## Automatic Migration Patterns

docs/developer/guides/HOW-TO-WRITE-UW3-SCRIPTS.md

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -610,16 +610,34 @@ def test_swarm_functionality():
610610

611611
### JIT Compilation Issues
612612

613-
If you see generated C code with symbolic expressions instead of numbers:
613+
**Slow time-stepping loops**: If each `solver.solve()` call takes 10+ seconds
614+
in a time-stepping loop, the solver is probably recompiling the JIT extension
615+
every step. Use `UWexpression` objects for any parameter that changes between
616+
steps:
617+
618+
```python
619+
# FAST — expression parameter, no recompilation on change
620+
dt_e = uw.expression("dt_e", 0.01)
621+
model.Parameters.dt_elastic = dt_e
622+
623+
for step in range(100):
624+
dt_e.sym = compute_timestep() # Updates constants[], ~0ms
625+
solver.solve() # No JIT rebuild
626+
```
627+
628+
**Symbolic names in generated C code**: If you see LaTeX-like names in the
629+
generated C code (e.g., `\eta` instead of a number or `constants[i]`):
614630

615631
```text
616-
// ERROR symptom in generated code:
617-
out[0] = 1.0/{ \eta \hspace{ 0.0006pt } }; // Should be numeric!
632+
// ERROR symptom — expression not unwrapped:
633+
out[0] = 1.0/{ \eta \hspace{ 0.0006pt } };
618634
```
619635

620-
**Cause**: `unwrap(fn, keep_constants=False)` not properly unwrapping constants.
636+
**Cause**: A `UWexpression` was not properly detected as constant or unwrapped.
621637

622-
**Solution**: Check that constants (like UWQuantity) are being unwrapped to numeric values.
638+
**Solution**: Check that the expression resolves to a pure number when fully
639+
unwrapped. Composite expressions containing mesh variables or coordinates
640+
cannot be routed through `constants[]`.
623641

624642
### PETSc DM Errors with Swarms
625643

0 commit comments

Comments
 (0)