Skip to content

Commit 04509cc

Browse files
committed
Merge development: add GitHub Release workflow and release notes
Underworld development team with AI support from Claude Code # Conflicts: # docs/conf.py
2 parents e3ef3b3 + b903967 commit 04509cc

53 files changed

Lines changed: 2065 additions & 1978 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/binder-image.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ name: "GHCR: Binder Base Image"
88

99
on:
1010
push:
11-
branches: [main, development, 'release/*']
11+
branches: [main, development]
1212
tags: ['v*'] # Build on release tags (v3.0.0, v3.1.0, etc.)
1313
paths:
1414
# Only rebuild when these files change (Cython/dependencies require rebuild)

.github/workflows/release.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
name: Create GitHub Release
2+
3+
# Creates a GitHub Release when a version tag is pushed.
4+
#
5+
# If a release notes file exists at docs/release-notes/vX.Y.Z.md,
6+
# it is used as the release body (published immediately).
7+
# If no notes file exists, a draft release is created instead —
8+
# someone must write the notes and publish manually.
9+
10+
on:
11+
push:
12+
tags: ['v*']
13+
14+
permissions:
15+
contents: write
16+
17+
jobs:
18+
create-release:
19+
runs-on: ubuntu-latest
20+
21+
steps:
22+
- name: Checkout repository
23+
uses: actions/checkout@v4
24+
25+
- name: Extract version from tag
26+
run: |
27+
TAG_NAME=${GITHUB_REF#refs/tags/}
28+
echo "TAG_NAME=${TAG_NAME}" >> $GITHUB_ENV
29+
30+
# Detect pre-releases (alpha, beta, rc)
31+
if [[ "$TAG_NAME" =~ (a|b|rc)[0-9]+ ]]; then
32+
echo "IS_PRERELEASE=true" >> $GITHUB_ENV
33+
else
34+
echo "IS_PRERELEASE=false" >> $GITHUB_ENV
35+
fi
36+
37+
- name: Check for release notes file
38+
id: notes
39+
run: |
40+
NOTES_FILE="docs/release-notes/${TAG_NAME}.md"
41+
if [ -f "$NOTES_FILE" ]; then
42+
echo "found=true" >> $GITHUB_OUTPUT
43+
echo "path=${NOTES_FILE}" >> $GITHUB_OUTPUT
44+
else
45+
echo "found=false" >> $GITHUB_OUTPUT
46+
fi
47+
48+
- name: Create release (with notes)
49+
if: steps.notes.outputs.found == 'true'
50+
run: |
51+
gh release create "$TAG_NAME" \
52+
--title "Underworld3 ${TAG_NAME}" \
53+
--notes-file "${{ steps.notes.outputs.path }}" \
54+
${{ env.IS_PRERELEASE == 'true' && '--prerelease' || '' }}
55+
env:
56+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
57+
58+
- name: Create draft release (no notes file)
59+
if: steps.notes.outputs.found == 'false'
60+
run: |
61+
gh release create "$TAG_NAME" \
62+
--title "Underworld3 ${TAG_NAME}" \
63+
--draft \
64+
--generate-notes \
65+
--notes "$(printf '> **Release notes not yet written.**\n>\n> No file found at `docs/release-notes/%s.md`.\n> Please add release notes and publish this draft.\n\n---\n\n## Auto-generated commit log\n\n' "$TAG_NAME")$(gh api repos/${{ github.repository }}/releases/generate-notes -f tag_name="$TAG_NAME" --jq '.body' 2>/dev/null || echo 'See commit history for changes.')" \
66+
${{ env.IS_PRERELEASE == 'true' && '--prerelease' || '' }}
67+
env:
68+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

CHANGES.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
# CHANGES: Underworld3
22

3+
## 2026-03-14
4+
5+
- **Release v3.0.0**: Merged development (398 commits) to main, tagged v3.0.0
6+
- **Release v0.99**: Tagged pixi-compatible snapshot of previous main for JOSS compatibility
7+
- Binder infrastructure overhaul:
8+
- Four versioned binder links: v0.99, v3.0.0, main, development
9+
- CI workflow handles nested branch names and `v*` tag builds
10+
- Manual dispatch overrides (`uw3_branch`, `image_tag`) for building old tags
11+
- Launcher dispatch payload fixed (field name mismatch prevented branch creation)
12+
- Dockerfile made resilient to different dependency versions (no hardcoded paths)
13+
- Deleted obsolete `uw3-release-candidate` branch
14+
- README updated: versioned binder badges, fixed CI badge filenames (.yml → .yaml)
15+
- `petsc_save_checkpoint()` now delegates to `write_timestep()` (fixes #80):
16+
- Gains vertex/cell compat groups, field projection, tensor repacking
17+
- Single checkpoint code path for easier maintenance
18+
- Cleaned up 10 merged feature/bugfix branches
19+
320
## 2026-03-13
421

522
- New `uw.maths.BdIntegral` for boundary and surface integrals (closes #47):

CLAUDE.md

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -229,14 +229,9 @@ Underworld development team with AI support from [Claude Code](https://claude.co
229229
- Changes go to `.pixi/envs/default/lib/python3.12/site-packages/underworld3/`
230230
- Verify with `uw.model.__file__`
231231

232-
**⚠️ STALE BUILD CACHE**: If `./uw build` succeeds but Python still uses old code
233-
(e.g. a new parameter is "unknown"), pip's wheel cache is stale. Fix with:
234-
```bash
235-
rm -rf build/lib.* build/bdist.*
236-
pixi run -e default pip install --no-build-isolation --force-reinstall --no-deps .
237-
```
238-
This is the most common build issue — `./uw build` reuses cached wheels when the
239-
version number hasn't changed. Always verify changes are installed before debugging.
232+
**Note**: `./uw build` uses `--no-cache-dir` to prevent pip from reusing stale
233+
wheels (UW3 is always version `0.0.0`). If you still suspect stale code, clean
234+
the build directory: `rm -rf build/lib.* build/bdist.*` then rebuild.
240235

241236
### Test Quality Principles
242237
**New tests must be validated before making code changes to fix them!**
@@ -445,6 +440,22 @@ velocity.norm() # Magnitude
445440

446441
## Coding Conventions
447442

443+
### Prefer Glob and Grep Over find
444+
**Use the Glob and Grep tools instead of `find` in Bash.**
445+
- `Glob` handles file pattern matching (e.g., `**/*.py`, `src/**/*.pyx`)
446+
- `Grep` handles content search (e.g., searching for class definitions, imports)
447+
- Both are faster, safer, and give the user better visibility than shell `find`
448+
- `find` with `-exec`, `-execdir`, or `-delete` can execute arbitrary commands — avoid it
449+
- Only fall back to `find` via Bash if Glob/Grep genuinely cannot express the query
450+
451+
### Desktop Notifications for Background Monitoring
452+
When using CronCreate for background monitoring (CI status, issues, etc.), use
453+
platform-appropriate notification commands. Both are in the allowed tools list:
454+
- **macOS**: `osascript -e 'display notification "message" with title "title" sound name "Glass"'`
455+
- **Linux**: `notify-send "title" "message"`
456+
457+
Be quiet when everything is fine — only notify when something needs attention.
458+
448459
### Plan File Naming Policy
449460
**Plan files must have descriptive names that indicate their content.**
450461

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/beginner/tutorials/1-Meshes.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
},
1818
"tags": []
1919
},
20-
"source": "# Notebook 1: Meshes\n\n[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/underworldcode/uw3-binder-launcher/development?labpath=underworld3%2Fdocs%2Fbeginner%2Ftutorials%2F1-Meshes.ipynb)\n\n<div style=\"float: right; width: 40%\">\n \n![](media/SampleSphericalMesh.png)\n\n</div>\n\n\n\nThis notebook introduces the mesh discretisation that we use in `Underworld3` and how you can build one of the pre-defined meshes. This notebook also show you how to use the `pyvista` visualisation tools for `Underworld3` objects. The mesh holds information on the mesh geometry, boundaries and coordinate systems and you can attach data to the mesh (see Notebook 2: [Variables](2-Variables.ipynb)). "
20+
"source": "# Notebook 1: Meshes\n\n[![Run this notebook on Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/underworldcode/uw3-binder-launcher/development?labpath=docs%2Fbeginner%2Ftutorials%2F1-Meshes.ipynb)\n\n<div style=\"float: right; width: 40%\">\n \n![](media/SampleSphericalMesh.png)\n\n</div>\n\n\n\nThis notebook introduces the mesh discretisation that we use in `Underworld3` and how you can build one of the pre-defined meshes. This notebook also show you how to use the `pyvista` visualisation tools for `Underworld3` objects. The mesh holds information on the mesh geometry, boundaries and coordinate systems and you can attach data to the mesh (see Notebook 2: [Variables](2-Variables.ipynb)). "
2121
},
2222
{
2323
"cell_type": "code",

docs/beginner/tutorials/10-Particle_Swarms.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"cell_type": "markdown",
55
"id": "26d2a9ca-c094-40f2-b978-333c14142874",
66
"metadata": {},
7-
"source": "# Notebook 8: Particle Swarms\n\n[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/underworldcode/uw3-binder-launcher/development?labpath=underworld3%2Fdocs%2Fbeginner%2Ftutorials%2F10-Particle_Swarms.ipynb)\n\n\n<!--\n<div style=\"float: right; width: 50%; padding-left:10px;\">\n<img src=\"media/CompositeImage.png\" width=100%>\n<caption>\n<i>\n Flow in a pipe with inflow at the left boundary\n after 50, 100, 150 timesteps (top to bottom) showing the\n progression of the impulsive initial condition. For details,\n see the notebook code.\n</i>\n</caption>\n</div>\n-->\n\nWe used a particle swarm to track the flow in Example 7. We called this a \"passive\" swarm because the points did not influence the flow in any way but were simply carried along with the fluid. \n\nParticle swarms are unstructured data objects that live within the computational domain. Their points can be moved arbitrarily through the domain and points may migrate from one process to another when the coordinates are changed. By default they carry only the particle location, but we can add scalar, vector and tensor variables to the swarm and they will be transported with the particles.\n\nParticle transport is usually through a velocity or displacement field that incrementally changes the locations. This is a common use, but particles can be used to represent any unstructured field. For example, during mesh adaptation, the nodal points from the previous mesh are equivalent to a disconnected swarm from the point of view of the new mesh. The same is true when reading data save from one mesh to the `MeshVariables` on another.\n"
7+
"source": "# Notebook 8: Particle Swarms\n\n[![Run this notebook on Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/underworldcode/uw3-binder-launcher/development?labpath=docs%2Fbeginner%2Ftutorials%2F10-Particle_Swarms.ipynb)\n\n\n<!--\n<div style=\"float: right; width: 50%; padding-left:10px;\">\n<img src=\"media/CompositeImage.png\" width=100%>\n<caption>\n<i>\n Flow in a pipe with inflow at the left boundary\n after 50, 100, 150 timesteps (top to bottom) showing the\n progression of the impulsive initial condition. For details,\n see the notebook code.\n</i>\n</caption>\n</div>\n-->\n\nWe used a particle swarm to track the flow in Example 7. We called this a \"passive\" swarm because the points did not influence the flow in any way but were simply carried along with the fluid. \n\nParticle swarms are unstructured data objects that live within the computational domain. Their points can be moved arbitrarily through the domain and points may migrate from one process to another when the coordinates are changed. By default they carry only the particle location, but we can add scalar, vector and tensor variables to the swarm and they will be transported with the particles.\n\nParticle transport is usually through a velocity or displacement field that incrementally changes the locations. This is a common use, but particles can be used to represent any unstructured field. For example, during mesh adaptation, the nodal points from the previous mesh are equivalent to a disconnected swarm from the point of view of the new mesh. The same is true when reading data save from one mesh to the `MeshVariables` on another.\n"
88
},
99
{
1010
"cell_type": "code",

docs/beginner/tutorials/11-Multi-Material_SolCx.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"cell_type": "markdown",
55
"id": "cell-intro",
66
"metadata": {},
7-
"source": "# Notebook 9: Multi-Material Constitutive Models\n\n[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/underworldcode/uw3-binder-launcher/development?labpath=underworld3%2Fdocs%2Fbeginner%2Ftutorials%2F11-Multi-Material_SolCx.ipynb)\n\n\n**PHYSICS:** fluid_mechanics \n**DIFFICULTY:** intermediate \n**PURPOSE:** demonstration\n\n## Description\n\nThis notebook demonstrates the new multi-material constitutive model by recreating the classic SolCx benchmark using two different materials instead of a piecewise viscosity function.\n\n**Key Features:**\n- Multi-material constitutive model with level-set averaging\n- Index swarm variable for material tracking\n- Comparison with analytical SolCx solution\n- Simple setup suitable for quickstart examples\n\n**Physical Setup:**\n- Two materials: low viscosity ($\\eta=1$) and high viscosity ($\\eta=10^6$)\n- Material boundary at x = 0.5\n- SolCx harmonic forcing: $f_y = -\\cos(\\pi x) \\sin(2\\pi y)$\n- Free slip boundary conditions\n\n## Mathematical Foundation\n\nThe multi-material model uses level-set weighted flux averaging:\n$$\\mathbf{f}_{\\text{composite}}(\\mathbf{x}) = \\sum_{i=0}^{N-1} \\phi_i(\\mathbf{x}) \\cdot \\mathbf{f}_i(\\mathbf{x})$$\n\nwhere $\\phi_i(\\mathbf{x})$ are level-set functions from IndexSwarmVariable."
7+
"source": "# Notebook 9: Multi-Material Constitutive Models\n\n[![Run this notebook on Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/underworldcode/uw3-binder-launcher/development?labpath=docs%2Fbeginner%2Ftutorials%2F11-Multi-Material_SolCx.ipynb)\n\n\n**PHYSICS:** fluid_mechanics \n**DIFFICULTY:** intermediate \n**PURPOSE:** demonstration\n\n## Description\n\nThis notebook demonstrates the new multi-material constitutive model by recreating the classic SolCx benchmark using two different materials instead of a piecewise viscosity function.\n\n**Key Features:**\n- Multi-material constitutive model with level-set averaging\n- Index swarm variable for material tracking\n- Comparison with analytical SolCx solution\n- Simple setup suitable for quickstart examples\n\n**Physical Setup:**\n- Two materials: low viscosity ($\\eta=1$) and high viscosity ($\\eta=10^6$)\n- Material boundary at x = 0.5\n- SolCx harmonic forcing: $f_y = -\\cos(\\pi x) \\sin(2\\pi y)$\n- Free slip boundary conditions\n\n## Mathematical Foundation\n\nThe multi-material model uses level-set weighted flux averaging:\n$$\\mathbf{f}_{\\text{composite}}(\\mathbf{x}) = \\sum_{i=0}^{N-1} \\phi_i(\\mathbf{x}) \\cdot \\mathbf{f}_i(\\mathbf{x})$$\n\nwhere $\\phi_i(\\mathbf{x})$ are level-set functions from IndexSwarmVariable."
88
},
99
{
1010
"cell_type": "markdown",

docs/beginner/tutorials/12-Units_System.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
{
44
"cell_type": "markdown",
55
"metadata": {},
6-
"source": "# Notebook 12: Working with Physical Units\n\n[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/underworldcode/uw3-binder-launcher/development?labpath=underworld3%2Fdocs%2Fbeginner%2Ftutorials%2F12-Units_System.ipynb)\n\n\nUnderworld3 has built-in support for physical units throughout the modeling workflow. This makes your models easier to understand and helps catch dimensional errors early.\n\nIn this notebook you'll learn:\n- Creating physical quantities (temperatures, velocities, viscosities)\n- Converting between units\n- Working with unit-aware arrays and coordinates\n- Automatic unit tracking through derivatives"
6+
"source": "# Notebook 12: Working with Physical Units\n\n[![Run this notebook on Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/underworldcode/uw3-binder-launcher/development?labpath=docs%2Fbeginner%2Ftutorials%2F12-Units_System.ipynb)\n\n\nUnderworld3 has built-in support for physical units throughout the modeling workflow. This makes your models easier to understand and helps catch dimensional errors early.\n\nIn this notebook you'll learn:\n- Creating physical quantities (temperatures, velocities, viscosities)\n- Converting between units\n- Working with unit-aware arrays and coordinates\n- Automatic unit tracking through derivatives"
77
},
88
{
99
"cell_type": "code",

docs/beginner/tutorials/13-Scaling-problems-with-physical-units.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"cell_type": "markdown",
55
"id": "cell-0",
66
"metadata": {},
7-
"source": "# Notebook 13: Non-Dimensional Scaling\n\n[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/underworldcode/uw3-binder-launcher/development?labpath=underworld3%2Fdocs%2Fbeginner%2Ftutorials%2F13-Scaling-problems-with-physical-units.ipynb)\n\n\nWhen working with physical problems, the range of scales can cause numerical difficulties. For example, mantle convection involves:\n- Velocities of ~10\u207b\u2079 m/s (tiny)\n- Viscosities of ~10\u00b2\u00b9 Pa\u00b7s (huge)\n- Pressures of ~10\u2079 Pa (large)\n\nNon-dimensional (ND) scaling transforms the problem so all quantities are order-one, improving numerical conditioning and stability.\n\nIn this notebook you'll learn:\n- Setting reference quantities for automatic scaling\n- Solving Poisson and Stokes equations with ND scaling\n- Understanding what happens under the hood\n- Validating that ND and dimensional solutions match"
7+
"source": "# Notebook 13: Non-Dimensional Scaling\n\n[![Run this notebook on Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/underworldcode/uw3-binder-launcher/development?labpath=docs%2Fbeginner%2Ftutorials%2F13-Scaling-problems-with-physical-units.ipynb)\n\n\nWhen working with physical problems, the range of scales can cause numerical difficulties. For example, mantle convection involves:\n- Velocities of ~10\u207b\u2079 m/s (tiny)\n- Viscosities of ~10\u00b2\u00b9 Pa\u00b7s (huge)\n- Pressures of ~10\u2079 Pa (large)\n\nNon-dimensional (ND) scaling transforms the problem so all quantities are order-one, improving numerical conditioning and stability.\n\nIn this notebook you'll learn:\n- Setting reference quantities for automatic scaling\n- Solving Poisson and Stokes equations with ND scaling\n- Understanding what happens under the hood\n- Validating that ND and dimensional solutions match"
88
},
99
{
1010
"cell_type": "code",

0 commit comments

Comments
 (0)