-
Notifications
You must be signed in to change notification settings - Fork 191
AuxiliaryOperatorSNES
#5119
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
JHopeCollins
wants to merge
26
commits into
release
Choose a base branch
from
JHopeCollins/auxopsnes
base: release
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.
+705
−12
Open
AuxiliaryOperatorSNES
#5119
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
c713dec
AuxiliaryOperatorSNES
JHopeCollins 26f7c09
docs links
JHopeCollins a9f770b
AuxiliaryOperatorSNES
JHopeCollins 0d93801
docs links
JHopeCollins bbf35e9
Merge branch 'JHopeCollins/auxopsnes' of github.com:firedrakeproject/…
JHopeCollins c50f8b9
Add Allen-Cahn demo of NPC
danshapero 3004e63
Merge branch 'JHopeCollins/auxopsnes' of github.com:firedrakeproject/…
JHopeCollins 3066ff7
Merge branch 'release' into JHopeCollins/auxopsnes
JHopeCollins 3de898d
add AuxiliaryOperatorSNES demo to website and tests
JHopeCollins f12861a
updates to AuxiliaryOperatorSNES demo
JHopeCollins f37c18b
updates to AuxiliaryOperatorSNES demo
JHopeCollins 06fa563
Fix citation in Allen-Cahn demo
danshapero 6347def
Show some PETSc log output
danshapero b416461
Wordsmithing demo
danshapero 26994b0
Update AuxiliaryOperatorSNES docstring
JHopeCollins 696f38e
Explicitly pass through bcs from outer problem in AuxiliaryOperatorSN…
JHopeCollins 960e48f
Merge branch 'release' into JHopeCollins/auxopsnes
JHopeCollins 7a0f3f1
rename variables in AuxiliaryOperatorSNES
JHopeCollins a721c29
move imports to top of file in AuxiliaryOperatorSNES
JHopeCollins 233249d
reuse solver assembler in AuxiliaryOperatorSNES to reduce symbolic ov…
JHopeCollins 0855b07
AuxiliaryOperatorSNES demo words
JHopeCollins 8409dd0
Update firedrake/preconditioners/auxiliary_snes.py
JHopeCollins 193f3f3
remove cyclic import in auxiliary_snes.py
JHopeCollins 87fef34
Merge branch 'JHopeCollins/auxopsnes' of github.com:firedrakeproject/…
JHopeCollins 9269718
Describe NGMRES
danshapero 38065b6
Fixes for complex mode
danshapero 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
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,292 @@ | ||
| Nonlinear preconditioning applied to the Allen-Cahn equation | ||
| ============================================================ | ||
|
|
||
| Contributed by `Daniel Shapero <https://psc.apl.uw.edu/people/investigators/daniel-shapero/>`_ | ||
| and `Josh Hope-Collins <https://profiles.imperial.ac.uk/joshua.hope-collins13>`_. | ||
|
|
||
| In the other demonstrations on nonlinear PDE, we've used Newton's method to | ||
| solve finite- dimensional systems of nonlinear equations. Suppose we want to | ||
| solve a system | ||
|
|
||
| .. math:: | ||
|
|
||
| F(u) = 0. | ||
|
|
||
| At each step of Newton's method, we first compute a *search direction* | ||
| :math:`v` by solving the linear system | ||
|
|
||
| .. math:: | ||
|
|
||
| dF(u)v = -F(u). | ||
|
|
||
| We can then use a line search along :math:`v` to obtain the next candidate | ||
| solution. When we talked about preconditioning, we have always meant applying | ||
| a preconditioner to the linear system for the search direction. A linear | ||
| preconditioner transforms a linear system into an equivalent one that is | ||
| easier to solve. | ||
|
|
||
| For some highly nonlinear problems, however, Newton's method can stagnate or | ||
| fail to converge, even with globalization strategies such as a line search. | ||
| For example, if :math:`dF(u)` behaves badly, we might not be able to compute a | ||
| search direction at all. | ||
|
|
||
| An alternative strategy is to use *nonlinear preconditioning*, abbreviated as | ||
| NPC in the following. Nonlinear preconditioning does the same thing as linear | ||
| preconditioning: it transforms the problem into an equivalent one that is | ||
| (hopefully) easier to solve. Rather than work on the linear system for the | ||
| search direction, however, NPC works directly on the nonlinear system itself. | ||
| Typically NPC is applied within a higher-level nonlinear solver. For example, | ||
| there is a nonlinear analogue of GMRES. For a review of nonlinear solver and | ||
| preconditioning strategies beyond Newton, see :cite:`brune2015composing`. | ||
|
|
||
| Here we will demonstrate how to use nonlinear preconditioning for the steady- | ||
| state Allen-Cahn equation | ||
|
|
||
| .. math:: | ||
|
|
||
| F(u) = -\epsilon\Delta u + u^3 - u = 0 | ||
|
|
||
| on the unit interval with Dirichlet boundary conditions :math:`u(0) = -1` and | ||
| :math:`u(1) = +1`. The Jacobian of this residual is indefinite wherever | ||
| :math:`|u| < 1/\sqrt{3}`. Newton's method can fail to compute a search | ||
| direciton when starting from an initial guess that crosses this region. | ||
|
|
||
| This demo is adapted from this `Chebfun example`_. | ||
|
|
||
| .. _Chebfun example: https://www.chebfun.org/examples/ode-nonlin/AllenCahn.html | ||
|
|
||
| :: | ||
|
|
||
| import numpy as np | ||
| from firedrake import * | ||
|
|
||
| Here we use a domain of length 10, a small diffusion coefficient of 0.003, | ||
| and an initial guess that ramps from +1 at the left-hand boundary to -1 at | ||
| the right. | ||
|
|
||
| :: | ||
|
|
||
| nx = 128 | ||
| lx = 10.0 | ||
| eps = Constant(3e-3) | ||
|
|
||
| mesh = IntervalMesh(nx, lx) | ||
| Q = FunctionSpace(mesh, "CG", 1) | ||
|
|
||
| x, = SpatialCoordinate(mesh) | ||
| u_1 = Constant(1) | ||
| u_2 = Constant(-1) | ||
| Lx = Constant(lx) | ||
| initial_guess = (1 - x / Lx) * u_1 + x / Lx * u_2 | ||
|
|
||
| bcs = [DirichletBC(Q, u_1, [1]), DirichletBC(Q, u_2, [2])] | ||
|
|
||
| u = Function(Q) | ||
| u.interpolate(initial_guess) | ||
|
|
||
| In order to have a good baseline to compare against, we want to use as good | ||
| a nonlinear solution strategy as possible. Here we use Newton's method with | ||
| the *critical point* line search, which is specially adapted for problems | ||
| like Allen-Cahn which can be derived from minimizing some free energy. Even | ||
| with 10 iterations of a line search, Newton's method will fail. You can try | ||
| other line search methods (secant, backtracking, etc.) and find similar | ||
| outcomes. If you pump the number of line search iterations way up, you can | ||
| make Newton's method converge... to the wrong solution! | ||
|
|
||
| :: | ||
|
|
||
| v = TestFunction(Q) | ||
| F = (eps * inner(grad(u), grad(v)) + inner(u**3 - u, v)) * dx | ||
|
|
||
| problem = NonlinearVariationalProblem(F, u, bcs) | ||
|
|
||
| newton_parameters = { | ||
| "snes_type": "newtonls", | ||
| "snes_monitor": None, | ||
| "snes_converged_reason": None, | ||
| "snes_linesearch_type": "cp", | ||
| "snes_linesearch_max_it": 10, | ||
| "snes_linesearch_monitor": None, | ||
| } | ||
| solver = NonlinearVariationalSolver(problem, solver_parameters=newton_parameters) | ||
| try: | ||
| solver.solve() | ||
| except ConvergenceError as err: | ||
| print(err) | ||
| print("--------------------------") | ||
| print("Solver failed to converge!") | ||
| print("Resetting `u`") | ||
| u.interpolate(initial_guess) | ||
|
|
||
| The residual decreases at first but eventually diverges. Here's some of the PETSc | ||
| log output: | ||
|
|
||
| .. code-block:: console | ||
|
|
||
| $ python nonlinear_pc_allen_cahn.py | ||
| 0 SNES Function norm 2.439229081145e-01 | ||
| 1 SNES Function norm 5.663027251974e+01 | ||
| 2 SNES Function norm 1.667065795962e+01 | ||
| 3 SNES Function norm 4.864679262673e+00 | ||
| 4 SNES Function norm 1.388964354434e+00 | ||
| 5 SNES Function norm 3.744562754242e-01 | ||
| 6 SNES Function norm 8.820230192544e-02 | ||
| 7 SNES Function norm 3.682817028776e-02 | ||
| 8 SNES Function norm 5.405858541080e-02 | ||
| 9 SNES Function norm 5.368629859638e-02 | ||
| ... | ||
| 26 SNES Function norm 5.280237868253e-02 | ||
| 27 SNES Function norm 1.098413882561e+00 | ||
| 28 SNES Function norm 1.082944223664e+00 | ||
| 29 SNES Function norm 6.520280025999e+03 | ||
| Nonlinear firedrake_0_ solve did not converge due to DIVERGED_DTOL iterations 29 | ||
|
|
||
| In other scenarios, the solver doesn't explode as dramatically but rather | ||
| stagnates and exceeds the number of allowable Newton iterations. | ||
|
|
||
| To salvage the wreck, we'll use nonlinear preconditioning. Here we use | ||
| the simplest solution strategy possible: preconditioned nonlinear | ||
| Richardson iterations. | ||
| The idea is that if :math:`F(u)=0` is too difficult to solve, we can | ||
| instead solve a auxiliary operator :math:`G(u)=0` which is in some sense | ||
| *nearby* to :math:`F`, and use that solution to iterate towards a solution | ||
| for :math:`F(u)=0`. | ||
| At each iteration :math:`k` we solve the following system for the next | ||
| iterate :math:`u_{k+1}` using the value of the current iterate :math:`u_k`. | ||
|
|
||
| .. math:: | ||
|
|
||
| G_{k}(u_{k+1}) = G_{k}(u_{k}) - F(u_{k}), | ||
| \quad | ||
| G_{k}(u) = G(u; u_{k}). | ||
|
|
||
| We can note several properties of this iteration. Firstly, if | ||
| :math:`F(u_{*})=0` then :math:`u_{k}=u_{*}` is a fixed point of the iteration. | ||
| Secondly, we never have to solve :math:`F(u)=0`, we only have to evaluate | ||
| its residual at a given state. Thirdly, we can intuitively hope that if | ||
| :math:`G` is *close enough* to :math:`F` then the iteration will converge, | ||
| although proving this is much more difficult than in the linear case. | ||
| Lastly, we can define a different :math:`G_k` at each iteration by | ||
| parameterising with the current iterate, as we will see below. | ||
|
|
||
| Our approach for defining :math:`G` here is to hold back the linear part of | ||
| the reaction term in the Allen-Cahn equation to the value at the previous | ||
| iteration. In other words, at each step, we define the PDE | ||
|
|
||
| .. math:: | ||
|
|
||
| G(u; u_k) = -\epsilon\Delta u + u^3 - u_k = 0. | ||
|
|
||
| The Jacobian for this problem w.r.t. :math:`u` is symmetric and positive- | ||
| definite. We have good guarantees about the convergence of Newton's method | ||
| in that case, so we can reuse the solver parameters that we tried the first | ||
| time around for this inner problem. | ||
|
|
||
| Here we define a custom nonlinear preconditioner which inherits from | ||
| :class:`~firedrake.preconditioners.auxiliary_snes.AuxiliaryOperatorSNES`. | ||
| Similar to ``AuxiliaryOperatorPC``, we have to implement the ``form`` method. | ||
| This method returns (1) a residual form :math:`G(u; u^{k}, v)` where :math:`v` | ||
| is the test function and (2) the boundary conditions for this sub-problem. | ||
| The arguments that it takes are first the PETSc SNES object, | ||
| then the value :math:`u_k` of the solution at the previous iteration; | ||
| the current value :math:`u` to be solved for; and a test function. | ||
|
|
||
| :: | ||
|
|
||
| class AllenCahnAuxSNES(firedrake.AuxiliaryOperatorSNES): | ||
| def form(self, snes, u_k, u, v): | ||
| F, bcs = super().form(snes, u_k, u, v) | ||
| return (eps * inner(grad(u), grad(v)) + inner(u**3 - u_k, v)) * dx, bcs | ||
|
|
||
| The contract for the ``form`` method requires it to supply the boundary | ||
| conditions. We could have obtained the boundary conditions by pulling them out | ||
| of the global context. That will work for this particular set of solvers but | ||
| can create problems for others, for example when using the multigrid method. | ||
| Instead, we've opted to call the parent class's ``form`` method, which will | ||
| return the original variational form and boundary conditions. We then discard | ||
| the original variational form ``F``, which we aren't using here. For other | ||
| nonlinear preconditioners, we might instead be building the preconditioning | ||
| form by modifying ``F``. | ||
|
|
||
| We now set ``-snes_type nrichardson`` for nonlinear Richardson iterations, | ||
| and specify the additional parameters for the nonlinear preconditioner under | ||
| the key ``"npc"``. Firstly we need to specify our python SNES type, and then | ||
| in the ``"aux"`` key we specify the parameters to actually solve :math:`G` - | ||
| here we use Newton iterations. | ||
|
|
||
| :: | ||
|
|
||
| solver_parameters = { | ||
| "snes_type": "nrichardson", | ||
| "snes_monitor": None, | ||
| "snes_converged_reason": None, | ||
| "npc": { | ||
| "snes_type": "python", | ||
| "snes_python_type": f"{__name__}.AllenCahnAuxSNES", | ||
| "aux": newton_parameters, | ||
| }, | ||
| } | ||
|
|
||
| solver = NonlinearVariationalSolver(problem, solver_parameters=solver_parameters) | ||
|
JHopeCollins marked this conversation as resolved.
|
||
| solver.solve() | ||
|
|
||
| Now we actually converge! The convergence on nonlinear Richardson iterations is | ||
|
pbrubeck marked this conversation as resolved.
|
||
| usually linear, as opposed to the quadratic convergence of Newton, but with | ||
| suitable preconditioning they will usually have a wider basin of convergence than | ||
| Newton. From the log output, we can observe that the residual is decreasing by a | ||
| factor of 2 or more at each iteration. | ||
|
|
||
| .. code-block:: console | ||
|
|
||
| 0 SNES Function norm 2.439229081145e-01 | ||
| 1 SNES Function norm 2.405339859939e-01 | ||
| 2 SNES Function norm 1.540442351803e-01 | ||
| 3 SNES Function norm 7.166137071498e-02 | ||
| 4 SNES Function norm 2.854773301463e-02 | ||
| 5 SNES Function norm 1.070593887487e-02 | ||
| 6 SNES Function norm 3.943106335233e-03 | ||
| 7 SNES Function norm 1.451230558440e-03 | ||
| ... | ||
| 18 SNES Function norm 3.491990058865e-08 | ||
| 19 SNES Function norm 1.349411703695e-08 | ||
| 20 SNES Function norm 5.217958176918e-09 | ||
| 21 SNES Function norm 2.018693509573e-09 | ||
| Nonlinear firedrake_1_ solve converged due to CONVERGED_FNORM_RELATIVE iterations 21 | ||
|
|
||
| We've chosen to use nonlinear Richardson here because it's the simplest scheme | ||
| with the fewest knobs to turn. There are more sophisticated strategies which can | ||
| offer a faster convergence rate. For example, you can run this demo again with | ||
| `"snes_type": "ngmres"` to use a nonlinear variant of the generalised minimum | ||
| residual algorithm. NGMRES with the default options cuts the number of | ||
| iterations in half for this problem. But it has more algorithmic knobs to turn | ||
| and those can require tweaking depending on the problem. | ||
|
|
||
| The Allen-Cahn equation is derivable through minimization of the free energy | ||
| functional | ||
|
|
||
| .. math:: | ||
|
|
||
| E(u) = \int_\Omega\left(\frac{\epsilon}{2}|\nabla u|^2 + \frac{1}{4}(1 - u^2)^2\right)dx. | ||
|
|
||
| To close, let's evaluate the free energy at the starting guess and at the | ||
| computed solution. | ||
|
|
||
| :: | ||
|
|
||
| E = (0.5 * eps * inner(grad(u), grad(u)) + 0.25 * (1 - u**2) ** 2) * dx | ||
| E_initial = firedrake.assemble(firedrake.replace(E, {u: initial_guess})) | ||
| E_final = firedrake.assemble(E) | ||
| print(f"Initial free energy: {E_initial.real:0.04f}") | ||
| print(f"Final: {E_final.real:0.04f}") | ||
|
|
||
| .. code-block:: console | ||
|
|
||
| Initial free energy: 1.3339 | ||
| Final: 0.0534 | ||
|
|
||
| This demo can be found as a script in :demo:`nonlinear_pc_allen_cahn.py <nonlinear_pc_allen_cahn.py>`. | ||
|
|
||
| .. rubric:: References | ||
|
|
||
| .. bibliography:: demo_references.bib | ||
| :filter: docname in docnames | ||
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
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.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could try anderson as well, it reduces iterations down to 11 for this example
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered this but wanted to keep it simple at first. I could add Anderson or NGMRES at the end. But I was planning another demo showing more advanced techniques using the Navier-Stokes equations in a later PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about just a sentence along the lines of "we could improve the convergence by swapping to an accelerated SNES type such as Anderson or ngmres, which you may want to experiment with in practice/in your own application"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done and I added a bit more to mention that NGMRES has more choices to make.