Repository navigation
Demos for goal-oriented adaptivity #5445
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
pbrubeck
wants to merge
10
commits into
pbrubeck/goal-adaptive-callback
Choose a base branch
from
pbrubeck/goal-adaptive-callback-demos
base: pbrubeck/goal-adaptive-callback
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
1995960
Add goal-oriented adaptivity demos
pbrubeck 49654aa
Merge branch 'pbrubeck/goal-adaptive-callback' into pbrubeck/goal-ada…
pbrubeck 22d2a96
Goal-oriented demos: use code-blocks and get_marking_callback
pbrubeck 71d62a5
Merge branch 'pbrubeck/goal-adaptive-callback' into pbrubeck/goal-ada…
pbrubeck 2967a7f
Merge branch 'pbrubeck/goal-adaptive-callback' into pbrubeck/goal-ada…
pbrubeck 58a5674
Merge branch 'pbrubeck/goal-adaptive-callback' into pbrubeck/goal-ada…
pbrubeck a9931dd
Replace the elasticity DWR demo with a mixed-space test
pbrubeck 7f14530
Merge branch 'pbrubeck/goal-adaptive-callback' into pbrubeck/goal-ada…
pbrubeck a65022a
Merge remote-tracking branch 'origin/pbrubeck/goal-adaptive-callback'…
pbrubeck 84b039b
Use GoalOrientedMarker and goal_ options in the demo
pbrubeck File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
196 changes: 196 additions & 0 deletions
196
.../goal_oriented_nonlinear_p_laplacian/goal_oriented_nonlinear_p_laplacian.py.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,196 @@ | ||
| Goal-based adaptivity for stationary boundary value problems | ||
| ============================================================ | ||
|
|
||
| .. rst-class:: emphasis | ||
|
|
||
| The dual-weighted residual (DWR) method designs global and local error | ||
| estimators for a goal functional :math:`J(u)`, where :math:`u` solves a | ||
| partial differential equation. Deriving DWR for a | ||
| specific problem usually takes substantial expertise: the appropriate | ||
| adjoint equation, the residual formulation, and so on. This demo shows | ||
| how Firedrake implements DWR automatically for stationary boundary value | ||
| problems. | ||
|
|
||
| The demo was contributed by `Patrick Farrell | ||
| <mailto:patrick.farrell@maths.ox.ac.uk>`__, based on the MSc project of | ||
| `Joseph Flood <mailto:josephdflood01@gmail.com>`__. | ||
|
|
||
| The dual-weighted residual method :cite:`Becker:2001` designs global and local | ||
| error estimators for the error in a goal functional :math:`J(u)`. While | ||
| implementing DWR by hand involves substantial expertise, the high-level | ||
| symbolic UFL representation of the problem permits the *automation* of DWR | ||
| :cite:`Rognes:2013`. | ||
|
|
||
| Here we apply DWR to a nonlinear stationary boundary value problem, the | ||
| :math:`p`-Laplacian: | ||
|
|
||
| .. math:: | ||
| -\nabla \cdot \left( |\nabla u|^{p-2} \nabla u \right) = f \text{ in } \Omega, \quad u = 0 \text{ on } \partial \Omega. | ||
|
|
||
| We solve it on the unit square with a known analytical solution, so that we can | ||
| compute effectivity indices for our error estimates. Any Firedrake mesh can be | ||
| refined in place, so we start from a uniform mesh: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| from firedrake import * | ||
|
|
||
| mesh = UnitSquareMesh(8, 8) | ||
|
|
||
| degree = 2 | ||
| V = FunctionSpace(mesh, "CG", degree) | ||
| x, y = SpatialCoordinate(mesh) | ||
|
|
||
| p = Constant(5) | ||
| u_exact = x*(1-x)*y*(1-y)*exp(2*pi*x)*cos(pi*y) | ||
| f = -div(inner(grad(u_exact), grad(u_exact))**((p-2)/2) * grad(u_exact)) | ||
|
|
||
| The manufactured solution vanishes on all four edges, so the boundary condition | ||
| is homogeneous. The problem is strongly nonlinear, so for the purposes of this | ||
| demo we cheat and start from an initial guess close to the exact solution. The | ||
| integrand is not polynomial, so we ask for a richer quadrature rule than the | ||
| degree of the space alone would suggest: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| u = Function(V, name="Solution") | ||
| u.interpolate(0.99*u_exact) | ||
|
|
||
| v = TestFunction(V) | ||
| quadrature = {"degree": 2*degree + 2} | ||
| F = (inner(inner(grad(u), grad(u))**((p-2)/2) * grad(u), grad(v))*dx(**quadrature) | ||
| - inner(f, v)*dx(**quadrature)) | ||
| bcs = DirichletBC(V, 0, "on_boundary") | ||
|
|
||
| To apply goal-based adaptivity we need a goal functional. Here we use the | ||
| integral of the normal derivative of the solution over the top boundary, which | ||
| `UnitSquareMesh` numbers ``4``: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| n = FacetNormal(mesh) | ||
| J = inner(grad(u), n)*ds(4) | ||
|
|
||
| The estimate splits in two: the error committed by discretising, and the error | ||
| committed by not solving the algebraic system exactly. Because DWR measures | ||
| that algebraic error too, a very coarse tolerance on the nonlinear solver | ||
| costs us nothing: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| solver_parameters = { | ||
| "snes_atol": 1.0e-6, | ||
| "snes_rtol": 1.0e-1, | ||
| "snes_linesearch_type": "l2", | ||
| "ksp_type": "preonly", | ||
| "pc_type": "lu", | ||
| } | ||
|
|
||
| The DWR machinery localises the residual by projecting it onto cell-bubble and | ||
| facet-bubble spaces. The resulting local problems are diagonal because each | ||
| has one degree of freedom per cell or facet. | ||
|
|
||
| Now the adaptive loop itself. ``snes_adapt_sequence`` is the *maximum* number | ||
| of SOLVE--ESTIMATE--MARK--REFINE cycles. ``goal_atol`` stops it early once the | ||
| estimated error in the goal is small enough, and ``goal_rtol`` gives a tolerance | ||
| relative to the absolute value of the current goal functional on the | ||
| uncorrected, non-enriched solution. If both tolerances are set, the loop stops | ||
| when the estimate falls below the larger threshold. ``goal_marking_fraction`` | ||
| is the fraction of the total estimated error that Dörfler marking must | ||
| capture, and ``goal_monitor`` reports the estimate at each cycle: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| solver_parameters.update({ | ||
| "adaptor_criterion": "refine", | ||
| "snes_adapt_sequence": 10, | ||
| "goal_atol": 5.0e-3, | ||
| "goal_marking_fraction": 0.5, | ||
| "goal_monitor": None, | ||
| }) | ||
|
|
||
| The adaptive loop is handled inside the solver. It runs the configured | ||
| SOLVE--ESTIMATE--MARK--REFINE cycle, and the marking callback supplies the | ||
| cells to refine. We also pass the exact solution, which lets the monitor | ||
| report the true error and an effectivity index. Of course, this is not needed | ||
| in general: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| initial_dofs = V.dim() | ||
| problem = NonlinearVariationalProblem(F, u, bcs) | ||
| solver = NonlinearVariationalSolver( | ||
| problem, | ||
| solver_parameters=solver_parameters, | ||
| marking_callback=GoalOrientedMarker(J, exact_solution=u_exact), | ||
| ) | ||
| uh = solver.solve() | ||
|
|
||
| ``solver.solve()`` returns the solution on the final adapted mesh, in the | ||
| original degree-2 space. The enriched solution that the estimator computes is | ||
| not returned. ``solver.get_marking_callback().error_estimate`` is the | ||
| estimate :math:`\eta` of the error in the goal for the returned solution, and | ||
| it is the value that stopped the loop: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| print(f"degrees of freedom: {initial_dofs} -> {uh.function_space().dim()}") | ||
| print(f"error estimate: {solver.get_marking_callback().error_estimate:.6e}") | ||
|
|
||
| The loop terminates after eight refinements, short of the ten it was allowed, | ||
| because the estimate fell below ``goal_atol``. The effectivity indices | ||
|
|
||
| .. math:: | ||
|
|
||
| I = \frac{\eta}{J(u) - J(u_h)} | ||
|
|
||
| printed by ``goal_monitor`` stay close to one throughout, between roughly | ||
| :math:`0.83` and :math:`1.30`. The estimate is therefore a faithful measure of | ||
| the error it steers by. Tightening ``goal_atol`` refines further and drives the | ||
| estimate down accordingly. | ||
|
|
||
| Because the two parts of the estimate are reported separately, it is easy to see | ||
| which one is limiting. On the last cycle this run prints | ||
|
|
||
| .. code-block:: text | ||
|
|
||
| DWR: the solver error exceeds the discretisation error. Tighten the solver tolerances. | ||
|
|
||
| By then the mesh is fine enough that the discretisation error falls below the | ||
| algebraic error left by a ``snes_rtol`` of :math:`10^{-1}`. Further refinement | ||
| is of no use: the algebraic error limits the accuracy of the goal, not the | ||
| mesh. Tightening ``snes_rtol`` and ``snes_atol`` is what helps. Here the | ||
| estimate already sits within ``goal_atol``, so the warning is safe to ignore. | ||
|
|
||
| Finally we plot the adapted mesh. The refinement concentrates near the top | ||
| boundary, where the goal functional lives. Refinement driven by a global energy | ||
| norm would instead spread evenly over the domain: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| import matplotlib.pyplot as plt | ||
| from firedrake.pyplot import triplot | ||
|
|
||
| fig, axes = plt.subplots() | ||
| triplot(uh.function_space().mesh().unique(), axes=axes) | ||
| axes.set_aspect("equal") | ||
| axes.legend(loc="center left", bbox_to_anchor=(1.05, 0.5)) | ||
| fig.savefig("goal_oriented_nonlinear_p_laplacian_mesh.png", bbox_inches="tight") | ||
|
|
||
| .. image:: goal_oriented_nonlinear_p_laplacian_mesh.png | ||
| :width: 60% | ||
| :alt: The adapted mesh, refined near the top boundary. | ||
| :align: center | ||
|
|
||
| The estimator used here weights the primal residual by the dual error. Richer | ||
| variants exist. Symmetrising the estimate with the adjoint residual leaves a | ||
| remainder cubic in the errors rather than quadratic :cite:`Endtmayer:2024`. | ||
| That costs two further solves per cycle. | ||
|
|
||
| A python script version of this demo can be found :demo:`here | ||
| <goal_oriented_nonlinear_p_laplacian.py>`. | ||
|
|
||
| .. rubric:: References | ||
|
|
||
| .. bibliography:: demo_references.bib | ||
| :filter: docname in docnames | ||
Binary file added
BIN
+50.3 KB
...oal_oriented_nonlinear_p_laplacian/goal_oriented_nonlinear_p_laplacian_mesh.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.