Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
fe0cfe6
Post-release updates for master (#4265)
connorjward Apr 29, 2025
1d154ca
Make linkcheck not prevent later steps in workflows (#4267)
connorjward Apr 29, 2025
5066a62
BDDC: Fix block sizes (#4253)
pbrubeck Apr 30, 2025
8a14150
Merge remote-tracking branch 'origin/release' into connorjward/master…
connorjward May 2, 2025
0d5ba26
unundo some bits needed in master
connorjward May 2, 2025
96ce83a
Fix sdist usage for master
connorjward May 2, 2025
659ad4d
Merge pull request #4282 from firedrakeproject/connorjward/master-can…
connorjward May 6, 2025
6238f05
Merge remote-tracking branch 'origin/release'
connorjward May 6, 2025
9b4f147
Dual interpolation from/into MixedFunctionSpace (#4197)
pbrubeck May 8, 2025
7008cd7
Remove firedrake-install and friends (#4270)
connorjward May 8, 2025
5b670ef
Merge remote-tracking branch 'origin/release' into connorjward/merge-…
connorjward May 13, 2025
54a9ddc
Add TODO RELEASE to check-config
connorjward May 13, 2025
982437f
Merge pull request #4308 from firedrakeproject/connorjward/merge-release
connorjward May 13, 2025
bb26a0a
Rayleigh-Benard demo using Irksome and patch smoothing
rckirby May 14, 2025
944aa16
allow general number of stages in vanka patches
rckirby May 14, 2025
4503ca6
Add demo to advanced tutorial list
rckirby May 14, 2025
dcdbe43
switch to tinyasm
rckirby May 14, 2025
fd30db6
Use SPDX string for pyproject license (#4324)
connorjward May 14, 2025
bab5c96
fix typos, make my code examples flake8 compliant (#4321)
indiamai May 14, 2025
e3b7c29
add meson-python requirement to requirements-build.txt (#4327)
leo-collins May 15, 2025
155d48b
demo for steady Boussinesq problem with integral constraints (#4318)
KarsKnook May 15, 2025
426e696
Rayleigh-Benard demo using Irksome and patch smoothing
rckirby May 14, 2025
2311f10
allow general number of stages in vanka patches
rckirby May 14, 2025
c85ecdf
Add demo to advanced tutorial list
rckirby May 14, 2025
8e50f9e
switch to tinyasm
rckirby May 14, 2025
f29173d
add demo to running test
rckirby May 15, 2025
8283c02
Merge branch 'rckirby/brandy' of github.com:firedrakeproject/firedrak…
rckirby May 15, 2025
7e041b1
Oops.py
rckirby May 15, 2025
3856d9d
Rayleigh-Benard demo using Irksome and patch smoothing
rckirby May 14, 2025
aee4060
updates
rckirby May 16, 2025
787993c
Oops.py
rckirby May 15, 2025
6569df2
Merge branch 'rckirby/brandy' of github.com:firedrakeproject/firedrak…
rckirby May 16, 2025
91257b9
Update demos/time_dependent_rayleigh_benard/timedep-rayleigh-benard.p…
rckirby May 16, 2025
9bf34f1
update CI to install irksome, test for its presence
rckirby May 16, 2025
9aadc16
Merge branch 'rckirby/brandy' of github.com:firedrakeproject/firedrak…
rckirby May 16, 2025
ab3409b
Cleanup Interpolate assembly (#4288)
pbrubeck May 16, 2025
57fd4f3
try again on CI
rckirby May 17, 2025
2e73b0c
lint
rckirby May 20, 2025
90cc320
fix pyproject
rckirby May 20, 2025
c579a94
checkpointing bcs (#4284)
Ig-dolci May 21, 2025
4b1cfc3
switch to MC
rckirby May 22, 2025
7c154b0
Merge remote-tracking branch 'origin/master' into rckirby/brandy
rckirby May 22, 2025
28fa2d0
some review comments addressed
rckirby May 23, 2025
9c5d76e
Update demos/time_dependent_rayleigh_benard/timedep-rayleigh-benard.p…
rckirby May 23, 2025
7241a12
add some maths, review comments
rckirby May 23, 2025
62a50a4
Merge branch 'rckirby/brandy' of github.com:firedrakeproject/firedrak…
rckirby May 23, 2025
f39c413
Update demos/time_dependent_rayleigh_benard/timedep-rayleigh-benard.p…
rckirby May 23, 2025
62395dc
respond to reviews
rckirby May 24, 2025
f40eb35
mreging
rckirby May 24, 2025
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
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Please do not add screenshots unless the bug is purely graphical.
**Environment:**
- OS: [eg: Linux, MacOS, WSL (Windows Subsystem for Linux)] add this as a label too!
- Python version: [eg: 3.9.7]
- Output of `firedrake-status`
- Output of `pip list`
- Any relevant environment variables or modifications [eg: PYOP2_DEBUG=1]

**Additional Info**
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ jobs:
pip cache remove slepc4py

if [ ${{ inputs.target_branch }} = 'release' ]; then
EXTRA_BUILD_ARGS=''
EXTRA_PIP_FLAGS=''
else
: # Install build dependencies
Expand All @@ -192,12 +193,13 @@ jobs:
pip install --no-deps ngsPETSc netgen-mesher netgen-occt

: # We have to pass '--no-build-isolation' to use a custom petsc4py
EXTRA_BUILD_ARGS='--no-isolation'
EXTRA_PIP_FLAGS='--no-build-isolation'
fi

: # Install from an sdist so we can make sure that it is not ill-formed
pip install build
python -m build ./firedrake-repo --sdist
python -m build ./firedrake-repo --sdist "$EXTRA_BUILD_ARGS"

pip install --verbose $EXTRA_PIP_FLAGS \
--no-binary h5py \
Expand Down
Binary file added demos/boussinesq/T0_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demos/boussinesq/T0_10000.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
269 changes: 269 additions & 0 deletions demos/boussinesq/boussinesq.py.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
Steady Boussinesq problem with integral constraints
Copy link
Member

Choose a reason for hiding this comment

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

Please don't also attempt to re-merge the Boussinesq demo.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have absolutely no idea how this got here, or why I now have so many changed files after merging.

===================================================

.. rst-class:: emphasis

This demo demonstrates how integral constraints can be imposed on a field
that appears nonlinearly in the governing equations.

The demo was contributed by `Aaron Baier-Reinio
<mailto:[email protected]>`__ and `Kars Knook
<mailto:[email protected]>`__.

We consider a nondimensionalised steady Boussinesq problem.
The domain is :math:`\Omega \subset \mathbb{R}^2`
with boundary :math:`\Gamma`. The boundary value problem is:

.. math::

-\nabla \cdot (\mu(T) \epsilon (u)) + \nabla p - f T &= 0 \quad \textrm{in}\ \Omega,

\nabla \cdot u &= 0 \quad \textrm{in}\ \Omega,

u \cdot \nabla T - \nabla^2 T &= 0 \quad \textrm{in}\ \Omega,

u &= 0 \quad \textrm{on}\ \Gamma,

\nabla T \cdot n &= g \quad \textrm{on}\ \Gamma.

The unknowns are the :math:`\mathbb{R}^2`-valued velocity :math:`u`,
scalar pressure :math:`p` and temperature :math:`T`.
The viscosity :math:`\mu(T)` is assumed to be a known function of :math:`T`.
Moreover :math:`\epsilon (u)` denotes the symmetric gradient of :math:`u`
and :math:`f = (0, -1)^T` the acceleration due to gravity.
Inhomogeneous Neumann boundary conditions are imposed on :math:`\nabla T \cdot n` through
given data :math:`g` which must satisfy a compatibility condition

.. math::

\int_{\Gamma} g \ {\rm d} s = 0.

Evidently the pressure :math:`p` is only determined up to a constant, since it only appears in
the problem through its gradient. This choice of constant is arbitrary and does not affect the model.
The situation regarding the temperature :math:`T` is, however, more subtle.
For the sake of discussion let us first assume that :math:`\mu(T) = \mu_0` is a constant that does
not depend on :math:`T`. It is then clear that, just like the pressure, the temperature :math:`T`
is undetermined up to a constant. We shall pin this down by enforcing that the mean of :math:`T`
is a user-supplied constant :math:`T_0`,

.. math::

\int_{\Omega} (T - T_0) \ {\rm d} x = 0.

The Boussinesq approximation assumes that density varies linearly with temperature.
Hence, this constraint can be viewed as an indirect imposition on the total mass of fluid in :math:`\Omega`.

Now suppose that :math:`\mu(T)` does depend on :math:`T`.
For simplicity we use a basic power law :math:`\mu(T) = 10^{-4} \cdot T^{1/2}`
but emphasise that the precise functional form of :math:`\mu(T)` is unimportant to this demo.
We must still impose the integral constraint on :math:`T` to obtain a unique solution,
but the value of :math:`T_0` now affects the model in a non-trivial way since :math:`\mu(T)` and
:math:`T` are coupled (c.f. the figures at the bottom of the demo).
**In particular, this is not a "trivial" situation like the incompressible
Stokes problem where the discretized Jacobians have a nullspace corresponding to the constant pressures.
Instead, we have an integral constraint on** :math:`T` **even though
the discretized Jacobians do not have a nullspace corresponding to the constant temperatures.**

We build the mesh using :doc:`netgen <netgen_mesh.py>`, choosing a trapezoidal geometry
to prevent hydrostatic equilibrium and allow for a non-trivial velocity solution.::

from firedrake import *
import netgen.occ as ngocc

wp = ngocc.WorkPlane()
wp.MoveTo(0, 0)
wp.LineTo(2, 0)
wp.LineTo(2, 1)
wp.LineTo(0, 2)
wp.LineTo(0, 0)

shape = wp.Face()
shape.edges.Min(ngocc.X).name = "left"
shape.edges.Max(ngocc.X).name = "right"

ngmesh = ngocc.OCCGeometry(shape, dim=2).GenerateMesh(maxh=0.1)

left_id = [i+1 for i, name in enumerate(ngmesh.GetRegionNames(dim=1)) if name == "left"]
right_id = [i+1 for i, name in enumerate(ngmesh.GetRegionNames(dim=1)) if name == "right"]

mesh = Mesh(ngmesh)
x, y = SpatialCoordinate(mesh)

Next we set up the discrete function spaces.
We use lowest-order Taylor--Hood elements for the velocity and pressure,
and continuous piecewise-linear elements for the temperature.
We introduce a Lagrange multiplier to enforce the integral constraint on :math:`T`::

U = VectorFunctionSpace(mesh, "CG", degree=2)
V = FunctionSpace(mesh, "CG", degree=1)
W = FunctionSpace(mesh, "CG", degree=1)
R = FunctionSpace(mesh, "R", 0)

Z = U * V * W * R

The trial and test functions are:::

z = Function(Z)
(u, p, T_aux, l) = split(z)
(v, q, w, s) = split(TestFunction(Z))

T = T_aux + l

The test Lagrange multiplier :code:`s` will allow us to impose the integral constraint on the temperature.
We use the trial Lagrange multiplier :code:`l` by decomposing the discretized temperature field :code:`T`
as :code:`T = T_aux + l` where :code:`T_aux` is the trial function from :code:`W`.
The value of :code:`l` will then be determined by the integral constraint on :code:`T`.

The remaining problem data to be specified is the Neumann data,
viscosity, acceleration due to gravity and :math:`T_0`.
For the Neumann data we choose a parabolic profile on the left and right edges,
and zero data on the top and bottom.::

g_left = y*(y-2) # Neumann data on the left
g_right = -8*y*(y-1) # Neumann data on the right
mu = (1e-4) * T ** (1/2) # Viscosity
f = as_vector([0, -1]) # Acceleration due to gravity
T0 = Constant(1) # Mean of the temperature

The nonlinear form for the problem is:::

F = (mu * inner(sym(grad(u)), sym(grad(v))) * dx # Viscous terms
- inner(p, div(v)) * dx # Pressure gradient
- inner(f, T*v) * dx # Buoyancy term
- inner(div(u), q) * dx # Incompressibility constraint
+ inner(dot(u, grad(T)), w) * dx # Temperature advection term
+ inner(grad(T), grad(w)) * dx # Temperature diffusion term
- inner(g_left, w) * ds(tuple(left_id)) # Weakly imposed Neumann BC terms
- inner(g_right, w) * ds(tuple(right_id)) # Weakly imposed Neumann BC terms
+ inner(T - T0, s) * dx # Integral constraint on T
)

and the (strongly enforced) Dirichlet boundary conditions on :math:`u` are enforced by::

bc_u = DirichletBC(Z.sub(0), 0, "on_boundary")

At this point we could form and solve a :class:`~.NonlinearVariationalProblem`
using :code:`F` and :code:`bc_u`. However, the resultant problem has a nullspace of
dimension 2, corresponding to (i) shifting :math:`p` by a constant :math:`C_1`
and (ii) shifting :math:`l` by a constant :math:`C_2` while simultaneuosly shifting
:math:`T_{\textrm{aux}}` by :math:`-C_2`.

One way of dealing with nullspaces in Firedrake is to pass a :code:`nullspace` and
:code:`transpose_nullspace` to :class:`~.NonlinearVariationalSolver`. However, sometimes
this approach may not be practical. First, for nonlinear problems with Jacobians that
are not symmetric, it may not obvious what the :code:`transpose_nullspace` is. A second
reason is that, when using customised PETSc linear solvers, it may be desirable
to directly eliminate the nullspace from the assembled Jacobian matrix, since one
cannot always be sure that the linear solver at hand is correctly utilising the provided
:code:`nullspace` and :code:`transpose_nullspace`.

To directly eliminate the nullspace we introduce a class :code:`FixAtPointBC` which
implements a boundary condition that fixes a field at a single point.::

import firedrake.utils as firedrake_utils

class FixAtPointBC(firedrake.DirichletBC):
r'''A special BC object for pinning a function at a point.

:arg V: the :class:`.FunctionSpace` on which the boundary condition should be applied.
:arg g: the boundary condition value.
:arg bc_point: the point at which to pin the function.
The location of the finite element DOF nearest to bc_point is actually used.
'''
def __init__(self, V, g, bc_point):
super(FixAtPointBC, self).__init__(V, g, bc_point)
if isinstance(bc_point, tuple):
bc_point = as_vector(bc_point)
self.bc_point = bc_point

@firedrake_utils.cached_property
def nodes(self):
V = self.function_space()
x = firedrake.SpatialCoordinate(V.mesh())
xdist = x - self.bc_point

test = firedrake.TestFunction(V)
trial = firedrake.TrialFunction(V)
xphi = firedrake.assemble(ufl.inner(xdist * test, xdist * trial) * ufl.dx, diagonal=True)
phi = firedrake.assemble(ufl.inner(test, trial) * ufl.dx, diagonal=True)
with xphi.dat.vec as xu, phi.dat.vec as u:
xu.pointwiseDivide(xu, u)
min_index, min_value = xu.min() # Find the index of the DOF closest to bc_point

nodes = V.dof_dset.lgmap.applyInverse([min_index])
nodes = nodes[nodes >= 0]
return nodes

We use this to fix the pressure and auxiliary temperature at the origin:::

aux_bcs = [FixAtPointBC(Z.sub(1), 0, as_vector([0, 0])),
FixAtPointBC(Z.sub(2), 0, as_vector([0, 0]))]

:code:`FixAtPointBC` takes three arguments: the function space to fix, the value with which it
will be fixed, and the location at which to fix. Generally :code:`FixAtPointBC` will not fix
the function at exactly the supplied point; it will fix it at the finite element DOF closest
to that point. By default CG elements have DOFs on all mesh vertices, so if the supplied
point if a mesh vertex then CG fields will be fixed at exactly the supplied point.

.. warning::

A :code:`FixAtPointBC` does more than just fix the corresponding trial function
at the chosen DOF. It also ensures that the corresponding test function
(which is equal to one at that DOF and zero at all others)
will no longer be used in the discretized variational problem.
One must be sure that it is mathematically acceptable to do this.

In the present setting this is acceptable owing to the homogeneous Dirichlet
boundary conditions on :math:`u` and compatibility condition :math:`\int_{\Gamma} g \ {\rm d} s = 0`
on the Neumann data. The former ensures that the rows in the discretized Jacobian
corresponding to the incompressibility constraint are linearly dependent
(if there are :math:`M` rows, only :math:`M-1` of them are linearly independent, since
the boundary conditions on :math:`u` ensure that
:math:`\int_{\Omega} \nabla \cdot u {\rm d} x = 0` automatically).
Similarily the rows in the Jacobian corresponding to the temperature advection-diffusion
equation are linearly independent (again, if there are :math:`M` rows,
only :math:`M-1` of them are linearly independent).
The effect of :code:`FixAtPointBC` will be to remove one of the rows corresponding
to the incompressibility constraint and one corresponding to the temperature advection-diffusion
equation. Which row ends up getting removed is determined by the location of :code:`bc_point`,
but in the present setting removing a given row is mathematically equivalent to removing any one of the others.

One could envisage a more complicated scenario than the one in this demo, wherein the Neumann data
depends nonlinearly on some other problem unknowns, and it only satisfies the compatibility condition
approximately (e.g. up to some discretization error).
In this case one would have to be very careful when using :code:`FixAtPointBC` --
although similar cautionary behaviour would also have to be taken if using
:code:`nullspace` and :code:`transpose_nullspace` instead.

Finally, we form and solve the nonlinear variational problem for :math:`T_0 \in \{1, 10, 100, 1000, 10000 \}`::

NLVP = NonlinearVariationalProblem(F, z, bcs=[bc_u]+aux_bcs)
NLVS = NonlinearVariationalSolver(NLVP)

(u, p, T_aux, l) = z.subfunctions
File = VTKFile(f"output/boussinesq.pvd")

for i in range(0, 5):
T0.assign(10**(i))
l.assign(Constant(T0))
NLVS.solve()

u_out = assemble(project(u, Z.sub(0)))
p_out = assemble(project(p, Z.sub(1)))
T_out = assemble(project(T, Z.sub(2)))

u_out.rename("u")
p_out.rename("p")
T_out.rename("T")

File.write(u_out, p_out, T_out, time=i)

The temperature and stream lines for :math:`T_0=1` and :math:`T_0=10000` are displayed below on the left and right respectively.

+-------------------------+-------------------------+
| .. image:: T0_1.png | .. image:: T0_10000.png |
| :width: 100% | :width: 100% |
+-------------------------+-------------------------+

A Python script version of this demo can be found :demo:`here
<boussinesq.py>`.
Loading
Loading