diff --git a/.gitignore b/.gitignore index 0cb6bb20..aa2b4874 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,6 @@ cashocs.egg-info/ /tests/temp /tests/out *cashocs_remesh_*/ + +### Documentation +docs/source/user/demos/*/*.md diff --git a/demos/documented/cashocs_as_solver/control_solver/demo_control_solver.py b/demos/documented/cashocs_as_solver/control_solver/demo_control_solver.py index 114b2033..e11acf8e 100755 --- a/demos/documented/cashocs_as_solver/control_solver/demo_control_solver.py +++ b/demos/documented/cashocs_as_solver/control_solver/demo_control_solver.py @@ -1,24 +1,62 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_control_solver)= +# # cashocs as Solver for Optimal Control Problems # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# As a model problem we again consider the one from {ref}`demo_poisson`, but now +# we do not use cashocs to compute the adjoint equation and derivative of the (reduced) +# cost functional, but supply these terms ourselves. The optimization problem is +# given by # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/cashocs_as_solver/doc_control_solver.html. - -""" +# $$ +# \begin{align} +# &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 +# \text{ d}x + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x \\ +# &\text{ subject to } \qquad +# \begin{alignedat}[t]{2} +# -\Delta y &= u \quad &&\text{ in } \Omega,\\ +# y &= 0 \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# (see, e.g., [Tröltzsch - Optimal Control of Partial Differential Equations]( +# https://doi.org/10.1090/gsm/112) or [Hinze, Pinnau, Ulbrich, and Ulbrich - +# Optimization with PDE constraints]( +# https://doi.org/10.1007/978-1-4020-8839-1)). +# +# For the numerical solution of this problem we consider exactly the same setting as +# in {ref}`demo_poisson`, and most of the code will be identical to this. +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_control_solver.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Recapitulation of {ref}`demo_poisson` +# +# For using cashocs exclusively as solver, the procedure is very similar to regularly +# using it, with a few additions after defining the optimization problem. In particular, +# up to the initialization of the optimization problem, our code is exactly the same as +# in {ref}`demo_poisson`, i.e., we use +# + from fenics import * import cashocs @@ -42,21 +80,92 @@ ) ocp = cashocs.OptimalControlProblem(e, bcs, J, y, u, p, config=config) +# - + +# ### Supplying Custom Adjoint Systems and Derivatives +# +# When using cashocs as a solver, the user can specify their custom weak forms of +# the adjoint system and of the derivative of the reduced cost functional. For our +# optimization problem, the adjoint equation is given by +# +# $$ +# \begin{alignedat}{2} +# - \Delta p &= y - y_d \quad &&\text{ in } \Omega, \\ +# p &= 0 \quad &&\text{ on } \Gamma, +# \end{alignedat} +# $$ +# +# and the derivative of the cost functional is then given by +# +# $$ +# dJ(u)[h] = \int_\Omega (\alpha u + p) h \text{ d}x. +# $$ +# +# :::{note} +# For a detailed derivation and discussion of these objects we refer to +# [Tröltzsch - Optimal Control of Partial Differential Equations]( +# https://doi.org/10.1090/gsm/112) or [Hinze, Pinnau, Ulbrich, and Ulbrich - +# Optimization with PDE constraints](https://doi.org/10.1007/978-1-4020-8839-1). +# ::: +# +# To specify that cashocs should use these equations instead of the automatically +# computed ones, we have the following code. First, we specify the derivative +# of the reduced cost functional via dJ = Constant(alpha) * u * TestFunction(V) * dx + p * TestFunction(V) * dx +# Afterwards, the weak form for the adjoint system is given by + adjoint_form = ( inner(grad(p), grad(TestFunction(V))) * dx - (y - y_d) * TestFunction(V) * dx ) adjoint_bcs = bcs +# where we can "recycle" the homogeneous Dirichlet boundary conditions used for the +# state problem. +# +# For both objects, one has to define them as a single UFL form for cashocs, as with the +# state system and cost functional. In particular, the adjoint weak form has to be in +# the form of a nonlinear variational problem, so that +# {python}`fenics.solve(adjoint_form == 0, p, adjoint_bcs)` could be used to solve it. +# In particular, both forms have to include {py:class}`fenics.TestFunction` objects from +# the control space and adjoint space, respectively, and must not contain +# {py:class}`fenics.TrialFunction` objects. +# +# These objects are then supplied to the +# {py:class}`OptimalControlProblem ` via + ocp.supply_custom_forms(dJ, adjoint_form, adjoint_bcs) -ocp.solve() +# :::{note} +# One can also specify either the adjoint system or the derivative of the cost +# functional, using the methods {py:meth}`supply_adjoint_forms +# ` or {py:meth}`supply_derivatives +# `. +# However, this is potentially dangerous, due to the following. The adjoint system +# is a linear system, and there is no fixed convention for the sign of the adjoint +# state. Hence, supplying, e.g., only the adjoint system, might not be compatible with +# the derivative of the cost functional which cashocs computes. In effect, the sign +# is specified by the choice of adding or subtracting the PDE constraint from the +# cost functional for the definition of a Lagrangian function, which is used to +# determine the adjoint system and derivative. cashocs internally uses the convention +# that the PDE constraint is added, so that, internally, it computes not the adjoint +# state $p$ as defined by the equations given above, but $-p$ instead. +# Hence, it is recommended to either specify all respective quantities with the +# {py:meth}`supply_custom_forms ` +# method. +# ::: +# +# Finally, we can use the {py:meth}`solve ` method +# to solve the problem with the line +ocp.solve() -### Post Processing +# as in {ref}`demo_poisson`. +# +# We visualize the results with the lines +# + import matplotlib.pyplot as plt plt.figure(figsize=(15, 5)) @@ -78,3 +187,16 @@ plt.tight_layout() # plt.savefig('./img_poisson.png', dpi=150, bbox_inches='tight') +# - + +# The results are, of course, identical to {ref}`demo_poisson` and look as follows +# ![](/../../demos/documented/cashocs_as_solver/control_solver/img_control_solver.png) + +# :::{note} +# In case we have multiple state equations as in {ref}`demo_multiple_variables`, +# one has to supply ordered lists of adjoint equations and boundary conditions, +# analogously to the usual procedure for cashocs. +# +# In the case of multiple control variables, the derivatives of the reduced cost +# functional w.r.t. each of these have to be specified, again using an ordered list. +# ::: diff --git a/demos/documented/cashocs_as_solver/shape_solver/demo_shape_solver.py b/demos/documented/cashocs_as_solver/shape_solver/demo_shape_solver.py index acad3715..149e0573 100755 --- a/demos/documented/cashocs_as_solver/shape_solver/demo_shape_solver.py +++ b/demos/documented/cashocs_as_solver/shape_solver/demo_shape_solver.py @@ -1,24 +1,64 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_shape_solver)= +# # cashocs as Solver for Shape Optimization Problems # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# Let us now investigate how cashocs can be used exclusively as a solver for shape +# optimization problems. The procedure is very similar to the one discussed in +# {ref}`demo_control_solver`, but with some minor variations tailored to shape +# optimization. # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/shape_optimization/doc_shape_poisson.html. - -""" +# As a model shape optimization problem we consider the same as in +# {ref}`demo_shape_poisson`, i.e., +# +# $$ +# \begin{align} +# &\min_\Omega J(u, \Omega) = \int_\Omega u \text{ d}x \\ +# &\text{subject to} \qquad +# \begin{alignedat}[t]{2} +# -\Delta u &= f \quad &&\text{ in } \Omega,\\ +# u &= 0 \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# For the initial domain, we use the unit disc +# $\Omega = \{ x \in \mathbb{R}^2 \,\mid\, \lvert\lvert x \rvert\rvert_2 < 1 \}$ and the +# right-hand side $f$ is given by +# +# $$ +# f(x) = 2.5 \left( x_1 + 0.4 - x_2^2 \right)^2 + x_1^2 + x_2^2 - 1. +# $$ +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_shape_solver.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Recapitulation of {ref}`demo_shape_poisson` +# +# Analogously to {ref}`demo_control_solver`, the code we use in case cashocs is treated +# as a solver only is identical to the one of {ref}`demo_shape_poisson` up to the +# definition of the optimization problem. For the sake of completeness we recall the +# corresponding code in the following +# + from fenics import * import cashocs @@ -40,8 +80,40 @@ J = cashocs.IntegralFunctional(u * dx) sop = cashocs.ShapeOptimizationProblem(e, bcs, J, u, p, boundaries, config=config) +# - +# ### Supplying Custom Adjoint Systems and Shape Derivatives +# +# Now, our goal is to use custom UFL forms for the adjoint system and shape derivative. +# Note, that the adjoint system for the considered optimization problem is given by +# +# $$ +# \begin{alignedat}{2} +# - \Delta p &= 1 \quad &&\text{ in } \Omega, \\ +# p &= 0 \quad &&\text{ on } \Gamma, +# \end{alignedat} +# $$ +# +# and the corresponding shape derivative is given by +# +# $$ +# dJ(\Omega)[\mathcal{V}] = +# \int_{\Omega} \text{div}\left( \mathcal{V} \right) u \text{ d}x +# + \int_{\Omega} \left( \left( \text{div}(\mathcal{V})I +# - 2 \varepsilon(\mathcal{V}) \right) \nabla u \right) \cdot \nabla p \text{ d}x +# + \int_{\Omega} \text{div}\left( f \mathcal{V} \right) p \text{ d}x, +# $$ +# +# where $\varepsilon(\mathcal{V})$ is the symmetric part of the gradient of +# $\mathcal{V}$, given by $\varepsilon(\mathcal{V}) +# = \frac{1}{2} \left( D\mathcal{V} + D\mathcal{V}^\top \right)$. +# For details, we refer the reader to, e.g., [Delfour and Zolesio - +# Shapes and Geometries](https://doi.org/10.1137/1.9780898719826). +# +# To supply these weak forms to cashocs, we can use the following code. For the +# shape derivative, we write +# + def eps(u): return Constant(0.5) * (grad(u) + grad(u).T) @@ -56,17 +128,66 @@ def eps(u): * dx + div(f * vector_field) * p * dx ) +# - + +# Note, that we have to call the +# {py:meth}`get_vector_field ` method +# which returns the UFL object corresponding to $\mathcal{V}$ and which is to be used +# at its place. +# +# ::::{hint} +# Alternatively, one could define the variable {python}`vector_field` as follows +# :::python +# space = VectorFunctionSpace(mesh, 'CG', 1) +# vector_field = TestFunction(space) +# ::: +# +# which would yield identical results. However, the shorthand via the +# {py:meth}`get_vector_field ` +# is more convenient, as one does not have to remember to define the correct function +# space first. +# :::: +# +# For the adjoint system, the procedure is exactly the same as in +# {ref}`demo_control_solver` and we have the following code adjoint_form = inner(grad(p), grad(TestFunction(V))) * dx - TestFunction(V) * dx adjoint_bcs = bcs +# Again, the format is analogous to the format of the state system, but now we have to +# specify a {py:class}`fenics.TestFunction` object for the adjoint equation. +# +# Finally, the weak forms are supplied to cashocs with the line + sop.supply_custom_forms(dJ, adjoint_form, adjoint_bcs) -sop.solve() +# and the optimization problem is solved with +sop.solve() -### Post Processing +# :::{note} +# One can also specify either the adjoint system or the shape derivative of the cost +# functional, using the methods {py:meth}`supply_adjoint_forms +# ` or +# {py:meth}`supply_derivatives +# `. +# However, this is potentially dangerous, due to the following. The adjoint system +# is a linear system, and there is no fixed convention for the sign of the adjoint +# state. Hence, supplying, e.g., only the adjoint system, might not be compatible with +# the derivative of the cost functional which cashocs computes. In effect, the sign +# is specified by the choice of adding or subtracting the PDE constraint from the +# cost functional for the definition of a Lagrangian function, which is used to +# determine the adjoint system and derivative. cashocs internally uses the convention +# that the PDE constraint is added, so that, internally, it computes not the adjoint +# state $p$ as defined by the equations given above, but $-p$ instead. +# Hence, it is recommended to either specify all respective quantities with the +# {py:meth}`supply_custom_forms ` +# method. +# ::: +# +# We visualize the results with the code +# + import matplotlib.pyplot as plt plt.figure(figsize=(10, 5)) @@ -84,3 +205,14 @@ def eps(u): plt.tight_layout() # plt.savefig('./img_shape_solver.png', dpi=150, bbox_inches='tight') +# - + +# The result is, of course, completely identical to the one of {ref}`demo_shape_poisson` +# and looks as follows +# ![](/../../demos/documented/cashocs_as_solver/shape_solver/img_shape_solver.png) +# +# :::{note} +# In case multiple state equations are used, the corresponding adjoint systems +# also have to be specified as ordered lists, just as explained for optimal control +# problems in {ref}`demo_multiple_variables`. +# ::: diff --git a/demos/documented/misc/xdmf_io/demo_xdmf_io.py b/demos/documented/misc/xdmf_io/demo_xdmf_io.py index a5a78d29..b87242da 100644 --- a/demos/documented/misc/xdmf_io/demo_xdmf_io.py +++ b/demos/documented/misc/xdmf_io/demo_xdmf_io.py @@ -1,20 +1,16 @@ -# Copyright (C) 2020-2022 Sebastian Blauth -# -# This file is part of cashocs. -# -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/shape_optimization/doc_shape_poisson.html. +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- -""" +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` +# +# (demo_xdmf_io)= +# # Writing and Reading XDMF Files diff --git a/demos/documented/optimal_control/box_constraints/demo_box_constraints.py b/demos/documented/optimal_control/box_constraints/demo_box_constraints.py index 6b1722d4..b453abe7 100755 --- a/demos/documented/optimal_control/box_constraints/demo_box_constraints.py +++ b/demos/documented/optimal_control/box_constraints/demo_box_constraints.py @@ -1,25 +1,61 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_box_constraints)= +# # Control Constraints # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo, we take a deeper look at how control constraints can be treated in +# cashocs. To do so, we investigate the same problem as in {ref}`demo_poisson`, but +# now with the addition of box constraints for the control variable. This problem +# reads # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see -https://cashocs.readthedocs.io/en/latest/demos/optimal_control/doc_box_constraints.html. - -""" +# $$ +# \begin{align} +# &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 \text{ d}x +# + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x \\ +# &\text{ subject to } \qquad +# \begin{alignedat}[t]{2} +# -\Delta y &= u \quad &&\text{ in } \Omega,\\ +# y &= 0 \quad &&\text{ on } \Gamma, \\ +# u_a \leq u &\leq u_b \quad &&\text{ in } \Omega +# \end{alignedat} +# \end{align} +# $$ +# +# (see, e.g., [Tröltzsch - Optimal Control of Partial Differential Equations]( +# https://doi.org/10.1090/gsm/112) or [Hinze, Pinnau, Ulbrich, and Ulbrich - +# Optimization with PDE constraints](https://doi.org/10.1007/978-1-4020-8839-1). +# +# Here, the functions $u_a$ and $u_b$ are $L^\infty(\Omega)$ functions. As before, we +# consider as domain the unit square, i.e., $\Omega = (0, 1)^2$. +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_box_constraints.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# The beginning of the script is completely identical to the +# one of {ref}`previous example `, so we only restate the corresponding +# code in the following +# + from fenics import * import cashocs @@ -42,26 +78,71 @@ J = cashocs.IntegralFunctional( Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * u * u * dx ) +# - + +# ### Definition of the Control Constraints +# +# Here, we have nearly everything at hand to define the optimal control problem, the +# only missing ingredient are the box constraints, which we define now. For the purposes +# of this example, we consider a linear (in the x-direction) corridor for these +# constraints, as it highlights the capabilities of cashocs. Hence, we define the lower +# and upper bounds via u_a = interpolate(Expression("50*(x[0]-1)", degree=1), V) u_b = interpolate(Expression("50*x[0]", degree=1), V) +# which just corresponds to two functions, generated from {py:class}`fenics.Expression` +# objects via {py:func}`fenics.interpolate`. These are then put into the list +# {python}`cc`, which models the control constraints, i.e., + cc = [u_a, u_b] +# ::::{note} +# As an alternative way of specifying the box constraints, one can also use regular +# float or int objects, in case that they are constant. For example, the constraint that +# we only want to consider positive value for u, i.e., $0 \leq u \leq +\infty$ can be +# realized via +# +# :::python +# u_a = 0 +# u_b = float('inf') +# cc = [u_a, u_b] +# ::: +# +# and completely analogous with {python}`float('-inf')` for no constraint on the lower +# bound. Moreover, note that the specification of using either constant {python}`float` +# values and {py:class}`fenics.Function` objects can be mixed arbitrarily, so that one +# can, e.g., specify a constant value for the upper boundary and use a +# {py:class}`fenics.Function` on the lower one. +# :::: +# +# ### Setup of the optimization problem and its solution +# +# Now, we can set up the optimal control problem as we did before, using the additional +# keyword argument {python}`control_constraints` into which we put the list +# {python}`cc`, and then solve it via the {py:meth}`ocp.solve() +# ` method + ocp = cashocs.OptimalControlProblem( e, bcs, J, y, u, p, config=config, control_constraints=cc ) ocp.solve() +# To check that the box constraints are actually satisfied by our solution, we perform +# an assertion + +# + import numpy as np assert np.alltrue(u_a.vector()[:] <= u.vector()[:]) and np.alltrue( u.vector()[:] <= u_b.vector()[:] ) +# - +# which shows that they are indeed satisfied. The visualization is carried out +# analogously to before, via -### Post Processing - +# + import matplotlib.pyplot as plt plt.figure(figsize=(15, 5)) @@ -83,3 +164,7 @@ plt.tight_layout() # plt.savefig('./img_box_constraints.png', dpi=150, bbox_inches='tight') +# - + +# and should yield the following output +# ![](/../../demos/documented/optimal_control/box_constraints/img_box_constraints.png) diff --git a/demos/documented/optimal_control/constraints/demo_constraints.py b/demos/documented/optimal_control/constraints/demo_constraints.py index 061de52d..fe58126a 100644 --- a/demos/documented/optimal_control/constraints/demo_constraints.py +++ b/demos/documented/optimal_control/constraints/demo_constraints.py @@ -1,24 +1,61 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_constraints)= +# # Treatment of additional constraints # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo we investigate cashocs for solving PDE constrained optimization problems +# with additional constraints. To do so, we investigate the "mother problem" of PDE +# constrained optimization, i.e., # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/optimal_control/doc_constraints.html. - -""" +# $$ +# \begin{align} +# &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 +# \text{ d}x + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x \\ +# &\text{ subject to } \qquad +# \begin{alignedat}[t]{2} +# -\Delta y &= u \quad &&\text{ in } \Omega,\\ +# y &= 0 \quad &&\text{ on } \Gamma, \\ +# y &= c_{b,l} \quad &&\text{ on } \Omega_{b,l},\\ +# \int_{\Omega_{b,r}} y^2 \text{ d}x &= c_{b,r},\\ +# \int_{\Omega_{t,l}} y \text{ d}x &\geq c_{t,l},\\ +# y &\leq c_{t,r} \quad &&\text{ in } \Omega_{t,r}. +# \end{alignedat} +# \end{align} +# $$ +# +# Here, we have four additional constraints, each for one quarter of the unit square +# $\Omega = (0,1)^2$, indicated by $(b,l)$ for bottom left, $(b,r)$ for +# bottom right, $(t,l)$ for top left, and $(t,r)$ for top right. +# +# In the following, we will describe how to solve this problem +# using cashocs. +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_constraints.py +# ` and the +# corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# The beginning of the program is nearly the same as for {ref}`demo_poisson` +# + from fenics import * import cashocs @@ -41,25 +78,95 @@ J = cashocs.IntegralFunctional( Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * u * u * dx ) +# - + +# ### Definition of the additional constraints +# +# In the following, we define the additional constraints we want to consider together +# with the PDE constraint. In this case, we only have state constraints, but additional +# constraints on the control variables can be treated completely analogously. +# +# First, we define the four quarters of the unit square bottom_left = Expression("(x[0] <= 0.5) && (x[1] <= 0.5) ? 1.0 : 0.0", degree=0) bottom_right = Expression("(x[0] >= 0.5) && (x[1] <= 0.5) ? 1.0 : 0.0", degree=0) top_left = Expression("(x[0] <= 0.5) && (x[1] >= 0.5) ? 1.0 : 0.0", degree=0) top_right = Expression("(x[0] >= 0.5) && (x[1] >= 0.5) ? 1.0 : 0.0", degree=0) +# The four Expressions above are indicator functions for the respective quarters, which +# allows us to implement the constraints easily. +# +# Next, we define the pointwise equality constraint we have on the lower left quarter. +# To do so, we use {py:class}`cashocs.EqualityConstraint` as follows + pointwise_equality_constraint = cashocs.EqualityConstraint( bottom_left * y, 0.0, measure=dx ) + +# Here, the first argument is the left-hand side of the equality constraint, namely the +# indicator function for the lower left quarter multiplied by the state variable y. +# The next argument is the right-hand side of the equality constraint, i.e., +# $c_{b,l}$ which we choose as 0 in this example. Finally, the keyword argument +# {python}`measure` is used to specify the integration measure that should be used to +# define where the constraint is given. Typical examples are a volume measure (`dx`, as +# it is the case here) or surface measure (`ds`, which could be used if we wanted to +# pose the constraint only on the boundary). +# +# Let's move on to the next constraint. Again, we have an equality constraint, but now +# it is a scalar value which is constrained, and its given by the integral over some +# integrand. This is the general form in which cashocs can deal with such scalar +# constraints. Let's see how we can define this constraint in cashocs + integral_equality_constraint = cashocs.EqualityConstraint( bottom_right * pow(y, 2) * dx, 0.01 ) + +# Here, we again use the {py:class}`cashocs.EqualityConstraint` class, as before. The +# difference is that now, the first argument is the UFL form of the integrand, in this +# case $y^2$ multiplied by the indicator function of the bottom right quarter, i.e., +# the left-hand side of the constraint. The second and final argument for this +# constraint is right-hand side of the constraint, i.e., $c_{b,r}$, which we choose as +# {python}`0.01` in this example. +# +# Let's move on to the interesting case of inequality constraints. Let us first consider +# a setting similar to before, where the constraint's left-hand side is given by an +# integral over some integrand. We define this integral inequality constraint via the +# {py:class}`cashocs.InequalityConstraint` class + integral_inequality_constraint = cashocs.InequalityConstraint( top_left * y * dx, lower_bound=-0.025 ) + +# Here, as before, the first argument is the left-hand side of the constraint, i.e., the +# UFL form of the integrand, in this case $y$ times the indicator function of the top +# left quarter, which is to be integrated over the measure {python}`dx`. The second +# argument {python}`lower_bound = -0.025` specifies the lower bound for this inequality +# constraint, that means, that $c_{t,l} = -0.025$ in our case. +# +# Finally, let us take a look at the case of pointwise inequality constraint. This is, +# as before, implemented via the {py:class}`cashocs.InequalityConstraint` class + pointwise_inequality_constraint = cashocs.InequalityConstraint( top_right * y, upper_bound=0.25, measure=dx ) +# Here, again the first argument is the function on the left-hand side of the +# constraint, i.e., $y$ times the indicator function of the top right quarter. The +# second argument, {python}`upper_bound=0.25`, defines the right-hand side of the +# constraint, i.e., we choose $c_{t,r} = 0.25$. Finally, as for the pointwise equality +# constraint, we specify the integration measure for which the constraint is posed, in +# our case {python}`measure=dx`, as we consider the constraint pointwise in the domain +# $\Omega$. +# +# :::{note} +# For bilateral inequality constraints we can use both keyword arguments +# {python}`upper_bound` and {python}`lower_bound` to define both bounds for the +# constraint. +# ::: +# +# As is usual in cashocs, once we have defined multiple constraints, we gather them into +# a list to pass them to the optimization routines + constraints = [ pointwise_equality_constraint, integral_equality_constraint, @@ -67,14 +174,27 @@ pointwise_inequality_constraint, ] +# Finally, we define the optimization problem. As we deal with additional constraints, +# we do not use a {py:class}`cashocs.OptimalControlProblem`, but use a +# {py:class}`cashocs.ConstrainedOptimalControlProblem`, which can be used to deal with +# these additional constaints. As usual, we can solve the problem with its +# {py:meth}`solve ` method + problem = cashocs.ConstrainedOptimalControlProblem( e, bcs, J, y, u, p, constraints, config ) problem.solve(method="AL") +# :::{note} +# To be able to treat (nearly) arbitrary types of constraints, cashocs regularizes +# these using either an augmented Lagrangian method or a quadratic penalty method. +# Which method is used can be specified via the keyword argument {python}`method`, which +# is chosen to be an augmented Lagrangian method (`'AL'`) in this demo. +# ::: +# +# Finally, we visualize the result with the following code -### Post Processing - +# + import matplotlib.pyplot as plt plt.figure(figsize=(15, 5)) @@ -96,3 +216,7 @@ plt.tight_layout() # plt.savefig("./img_constraints.png", dpi=150, bbox_inches="tight") +# - + +# and the result should look like this +# ![](/../../demos/documented/optimal_control/constraints/img_constraints.png) diff --git a/demos/documented/optimal_control/control_boundary_conditions/demo_control_boundary_conditions.py b/demos/documented/optimal_control/control_boundary_conditions/demo_control_boundary_conditions.py index acf16d92..147fd70b 100755 --- a/demos/documented/optimal_control/control_boundary_conditions/demo_control_boundary_conditions.py +++ b/demos/documented/optimal_control/control_boundary_conditions/demo_control_boundary_conditions.py @@ -1,24 +1,55 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_control_boundary_conditions)= +# # Boundary conditions for control variables # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo we investigate cashocs for solving PDE constrained optimization problems +# with additional boundary conditions for the control variables. Our problem is given by # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/optimal_control/doc_control_boundary_conditions.html. - -""" +# $$ +# \begin{align} +# &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 +# \text{ d}x + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x \\ +# &\text{ subject to } \qquad +# \begin{alignedat}[t]{2} +# -\Delta y &= u \quad &&\text{ in } \Omega,\\ +# y &= 0 \quad &&\text{ on } \Gamma, \\ +# u &= u_{\Gamma} \quad &&\text{ on } \Gamma, +# \end{alignedat} +# \end{align} +# $$ +# +# Here, we consider the control variable $u$ in +# $H^1_\Gamma(\Omega) = \{ v \in H^1(\Omega) \vert v = u_\Gamma \text{ on } \Gamma \}$. +# +# In the following, we will describe how to solve this problem using cashocs. +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_constraints.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# The beginning of the program is nearly the same as for {ref}`demo_poisson` +# + from fenics import * import cashocs @@ -41,10 +72,26 @@ J = cashocs.IntegralFunctional( Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * u * u * dx ) +# - + +# ### Scalar product and boundary conditions for the control variable +# +# Now, we can first define the scalar product for the control variable $u$, which we +# choose as the standard $H^1_0(\Omega)$ scalar product. This can be implemented as +# follows in cashocs scalar_product = dot(grad(TrialFunction(V)), grad(TestFunction(V))) * dx + +# Moreover, we define the list of boundary conditions for the control variable as usual +# in FEniCS and cashocs with the help of {py:func}`cashocs.create_dirichlet_bcs` + control_bcs = cashocs.create_dirichlet_bcs(V, Constant(100.0), boundaries, [1, 2, 3, 4]) +# Here, we have chosen a value of $u_\Gamma = 100$ for this particular demo, in +# order to be able to visually see, whether the proposed method works. +# +# Finally, we can set up and solve the optimization problem as usual + ocp = cashocs.OptimalControlProblem( e, bcs, @@ -58,9 +105,14 @@ ) ocp.solve() +# where the only additional parts in comparison to {ref}`demo_poisson` are the keyword +# arguments {python}`riesz_scalar_products`, which was already covered in +# {ref}`demo_neumann_control`, and {python}`control_bcs_list`, which we have defined +# previously. +# +# After solving this problem with cashocs, we visualize the solution with the code -### Post Processing - +# + import matplotlib.pyplot as plt plt.figure(figsize=(15, 5)) @@ -81,4 +133,8 @@ plt.title("Desired state y_d") plt.tight_layout() -plt.savefig("./img_control_boundary_conditions.png", dpi=150, bbox_inches="tight") +# plt.savefig("./img_control_boundary_conditions.png", dpi=150, bbox_inches="tight") +# - + +# and the result should look like this +# ![](/../../demos/documented/optimal_control/control_boundary_conditions/img_control_boundary_conditions.png) diff --git a/demos/documented/optimal_control/dirichlet_control/demo_dirichlet_control.py b/demos/documented/optimal_control/dirichlet_control/demo_dirichlet_control.py index 96d67717..9bddd36f 100755 --- a/demos/documented/optimal_control/dirichlet_control/demo_dirichlet_control.py +++ b/demos/documented/optimal_control/dirichlet_control/demo_dirichlet_control.py @@ -1,24 +1,120 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_dirichlet_control)= +# # Dirichlet Boundary Control # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo, we investigate how Dirichlet boundary control is possible with +# cashocs. To do this, we have to employ the so-called Nitsche method, which we +# briefly recall in the following. Our model problem for this example is given by # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/optimal_control/doc_dirichlet_control.html. - -""" +# $$ +# \begin{align} +# &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 \text{ d}x + \frac{\alpha}{2} \int_{\Gamma} u^2 \text{ d}s \\ +# &\text{ subject to } \qquad +# \begin{alignedat}[t]{2} +# -\Delta y &= 0 \quad &&\text{ in } \Omega,\\ +# y &= u \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# In contrast to our previous problems, the control now enters the problem via a +# Dirichlet boundary condition. However, we cannot apply these via a +# {py:class}`fenics.DirichletBC`, because for cashocs to work properly, the controls, +# states, and adjoints are only allowed to appear in UFL forms. Nitsche's Method +# circumvents this problem by imposing the boundary conditions in the weak form +# directly. Let us first briefly recall this method. +# +# ## Nitsche's Method +# +# Consider the Laplace problem +# +# $$ +# \begin{alignedat}{2} +# -\Delta y &= 0 \quad &&\text{ in } \Omega,\\ +# y &= u \quad &&\text{ on } \Gamma. +# \end{alignedat} +# $$ +# +# We can derive a weak form for this equation in $H^1(\Omega)$ (not $H^1_0(\Omega)$) by +# multiplying the equation by a test function $p \in H^1(\Omega)$ and applying the +# divergence theorem +# +# $$ +# \int_\Omega - \Delta y p \text{ d}x = \int_\Omega \nabla y \cdot \nabla p \text{ d}x +# - \int_\Gamma (\nabla y \cdot n) p \text{ d}s. +# $$ +# +# This weak form is the starting point for Nitsche's method. First of all, observe that +# this weak form is not symmetric anymore. To restore symmetry of the problem, we can +# use the Dirichlet boundary condition and "add a zero" by adding +# $\int_\Gamma \nabla p \cdot n (y - u) \text{ d}s$. This gives the weak form +# +# $$ +# \int_\Omega \nabla y \cdot \nabla p \text{ d}x +# - \int_\Gamma (\nabla y \cdot n) p \text{ d}s +# - \int_\Gamma (\nabla p \cdot n) y \text{ d}s = +# \int_\Gamma (\nabla p \cdot n) u \text{ d}s. +# $$ +# +# However, one can show that this weak form is not coercive. Hence, Nitsche's method +# adds another zero to this weak form, namely $\int_\Gamma \eta (y - u) p \text{ d}s$, +# which yields the coercivity of the problem if $\eta$ is sufficiently large. Hence, +# we consider the following weak form +# +# $$ +# \int_\Omega \nabla y \cdot \nabla p \text{ d}x +# - \int_\Gamma (\nabla y \cdot n) p \text{ d}s +# - \int_\Gamma (\nabla p \cdot n) y \text{ d}s +# + \eta \int_\Gamma y p \text{ d}s = +# \int_\Gamma (\nabla p \cdot n) u \text{ d}s + \eta \int_\Gamma u p \text{ d}s, +# $$ +# +# and this is the form we implement for this problem. +# +# For a detailed introduction to Nitsche's method, we refer to +# [Assous and Michaeli - A numerical method for handling boundary and +# transmission conditions in some linear partial differential equations]( +# https://doi.org/10.1016/j.procs.2012.04.045>). +# +# :::{note} +# To ensure convergence of the method when the mesh is refined, the parameter +# $\eta$ is scaled in dependence with the mesh size. In particular, we use a scaling of +# the form +# +# $$ +# \eta = \frac{\bar{\eta}}{h}, +# $$ +# +# where $h$ is the diameter of the current mesh element. +# ::: +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_dirichlet_control.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# The beginning of the program is nearly the same as for {ref}`demo_poisson` +# + from fenics import * import numpy as np @@ -33,9 +129,34 @@ y = Function(V) p = Function(V) u = Function(V) +# - + +# The only difference is, that we now also define {python}`n`, which is the outer unit +# normal vector on $\Gamma$, and {python}`h`, which is the maximum length of an edge of +# the respective finite element (during assemly). +# +# Then, we define the Dirichlet boundary conditions, which are enforced strongly. +# As we use Nitsche's method to implement the boundary conditions on the entire +# boundary, there are no strongly enforced ones left, and we define bcs = [] +# ::::{hint} +# Alternatively, we could have also written +# +# :::python +# bcs = None +# ::: +# +# which yields exactly the same result, i.e., no strongly enforced Dirichlet +# boundary conditions. +# :::: +# +# ### Definition of the PDE and optimization problem via Nitsche's method +# +# We implement the weak form using Nitsche's method, as described above, which is given +# by the code segment + eta = Constant(1e4) e = ( inner(grad(y), grad(p)) * dx @@ -45,30 +166,49 @@ - Constant(1) * p * dx ) +# Finally, we can define the cost functional similarly to +# {ref}`demo_neumann_control` + y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) alpha = 1e-4 J = cashocs.IntegralFunctional( Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * u * u * ds ) +# As in {ref}`demo_neumann_control`, we have to define a scalar product on +# $L^2(\Gamma)$ to get meaningful results (as the control is only defined on the +# boundary), which we do with + scalar_product = TrialFunction(V) * TestFunction(V) * ds +# ### Solution of the optimization problem +# +# The optimal control problem is solved with the usual syntax + ocp = cashocs.OptimalControlProblem( e, bcs, J, y, u, p, config=config, riesz_scalar_products=scalar_product ) ocp.solve() +# In the end, we validate whether the boundary conditions are applied correctly +# using this approach. Therefore, we first compute the indices of all DOF's +# that lie on the boundary via bcs = cashocs.create_dirichlet_bcs(V, 1, boundaries, [1, 2, 3, 4]) bdry_idx = Function(V) [bc.apply(bdry_idx.vector()) for bc in bcs] mask = np.where(bdry_idx.vector()[:] == 1)[0] +# Then, we restrict both {python}`y` and {python}`u` to the boundary by + y_bdry = Function(V) u_bdry = Function(V) y_bdry.vector()[mask] = y.vector()[mask] u_bdry.vector()[mask] = u.vector()[mask] +# Finally, we compute the relative errors in the $L^\infty(\Gamma)$ and $L^2(\Gamma)$ +# norms and print the result + error_inf = ( np.max(np.abs(y_bdry.vector()[:] - u_bdry.vector()[:])) / np.max(np.abs(u_bdry.vector()[:])) @@ -77,14 +217,17 @@ error_l2 = ( np.sqrt(assemble((y - u) * (y - u) * ds)) / np.sqrt(assemble(u * u * ds)) * 100 ) - print("Error regarding the (weak) imposition of the boundary values") print("Error L^\infty: " + format(error_inf, ".3e") + " %") print("Error L^2: " + format(error_l2, ".3e") + " %") +# We see, that with {python}`eta = 1e4` we get a relative error of under 5e-3 % in the +# $L^\infty(\Omega)$ norm, and under 5e-4 in the $L^2(\Omega)$ norm, which is +# sufficient for applications. +# +# Finally, we visualize our results with the code -### Post Processing - +# + import matplotlib.pyplot as plt plt.figure(figsize=(15, 5)) @@ -106,3 +249,7 @@ plt.tight_layout() # plt.savefig('./img_dirichlet_control.png', dpi=150, bbox_inches='tight') +# - + +# and the resulting output should look like +# ![](/../../demos/documented/optimal_control/dirichlet_control/img_dirichlet_control.png) diff --git a/demos/documented/optimal_control/heat_equation/demo_heat_equation.py b/demos/documented/optimal_control/heat_equation/demo_heat_equation.py index 1b7f22eb..26e6ed40 100755 --- a/demos/documented/optimal_control/heat_equation/demo_heat_equation.py +++ b/demos/documented/optimal_control/heat_equation/demo_heat_equation.py @@ -1,24 +1,109 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_heat_equation)= +# # Distributed Control for Time Dependent Problems # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo we take a look at how time dependent problems can be treated with +# cashocs. To do so, we investigate a problem with a heat equation as PDE constraint, +# which was considered in [Blauth - Optimal Control and Asymptotic Analysis of the +# Cattaneo Model](https://nbn-resolving.org/urn:nbn:de:hbz:386-kluedo-53727). It reads # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/optimal_control/doc_heat_equation.html. - -""" +# $$ +# \begin{align} +# &\min\; J(y,u) = \frac{1}{2} \int_0^T \int_\Omega \left( y - y_d \right)^2 +# \text{ d}x \text{ d}t +# + \frac{\alpha}{2} \int_0^T \int_\Omega u^2 \text{ d}x \text{ d}t \\ +# &\text{ subject to }\qquad +# \begin{alignedat}[t]{2} +# \partial_t y - \Delta y &= u \quad &&\text{ in } (0,T) \times \Omega,\\ +# y &= 0 \quad &&\text{ on } (0,T) \times \Gamma, \\ +# y(0, \cdot) &= y^{(0)} \quad &&\text{ in } \Omega. +# \end{alignedat} +# \end{align} +# $$ +# +# Since FEniCS does not have any direct built-in support for time dependent problems, +# we first have to perform a semi-discretization of the PDE system in the temporal +# component (e.g. via finite differences), and then solve the resulting sequence of +# PDEs. +# +# In particular, for the use with cashocs, we have to create not a single weak form and +# {py:class}`fenics.Function`, that can be re-used, like one would in classical FEniCS +# programs, but we have to create the corresponding objects a-priori for each time step. +# +# For the domain of this problem, we once again consider the space time cylinder given +# by $(0,T) \times \Omega = (0,1) \times (0,1)^2$. And for the initial condition we use +# $y^{(0)} = 0$. +# +# ## Temporal Discretization +# +# For the temporal discretization, we use the implicit Euler scheme as this is +# unconditionally stable for the parabolic heat equation. This means, we discretize the +# interval $[0,T]$ by a grid with nodes +# $t_k, k=0,\dots, n,\; \text{ with }\; t_0 := 0\; \text{ and }\; t_n := T$. Then, we +# approximate the time derivative $\partial_t y(t_{k+1})$ at some time $t_{k+1}$ by the +# backward difference +# +# $$ +# \partial_t y(t_{k+1}) \approx \frac{y(t_{k+1}) - y(t_{k})}{\Delta t}, +# $$ +# +# where $\Delta t = t_{k+1} - t_{k}$, and thus get the sequence of PDEs +# +# $$ +# \begin{alignedat}{2} +# \frac{y_{k+1} - y_{k}}{\Delta t} - \Delta y_{k+1} &= +# u_{k+1} \quad &&\text{ in } \Omega \quad \text{ for } k=0,\dots,n-1,\\ +# y_{k+1} &= 0 \quad &&\text{ on } \Gamma \quad \text{ for } k=0,\dots,n-1, +# \end{alignedat} +# $$ +# +# Note, that $y_k \approx y(t_k), \text {and }\; u_k \approx u(t_k)$ are approximations +# of the continuous functions. The initial condition is included via +# +# $$ +# y_0 = y^{(0)}. +# $$ +# +# Moreover, for the cost functionals, we can discretize the temporal integrals using a +# rectangle rule. This means we approximate the cost functional via +# +# $$ +# J(y, u) \approx \frac{1}{2} \sum_{k=0}^{n-1} \Delta t +# \left( \int_\Omega \left( y_{k+1} - (y_d)_{k+1} \right)^2 \text{ d}x +# + \alpha \int_\Omega u_{k+1}^2 \text{ d}x \right). +# $$ +# +# Here, $(y_d)_k$ is an approximation of the desired state at time $t_k$. +# +# Let us now investigate how to solve this problem with cashocs. +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_heat_equation.py +# ` and +# the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# This section is the same as for all previous problems and is done via +# + from fenics import * import numpy as np @@ -27,23 +112,50 @@ config = cashocs.load_config("config.ini") mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(20) V = FunctionSpace(mesh, "CG", 1) +# - + +# Next up, we specify the temporal discretization via dt = 1 / 10 t_start = dt t_end = 1.0 t_array = np.linspace(t_start, t_end, int(1 / dt)) +# Here, {python}`t_array` is a numpy array containing all time steps. Note, that we do **not** +# include $t=0$ in the array. This is due to the fact, that the initial condition +# is prescribed and fixed. +# +# Due to the fact that we discretize the equation temporally, we do not only get a +# single {py:class}`fenics.Function` describing our state and control, but one +# {py:class}`fenics.Function` for each time step. Hence, we initialize these (together +# with the adjoint states) directly in lists + states = [Function(V) for i in range(len(t_array))] controls = [Function(V) for i in range(len(t_array))] adjoints = [Function(V) for i in range(len(t_array))] +# Note, that {python}`states[k]` corresponds to $y_{k+1}$ since indices start at +# {python}`0` in most programming languages (as it is the case in python). +# +# As the boundary conditions are not time dependent, we can initialize them now, and +# repeat them in a list, since they are the same for every state + bcs = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) bcs_list = [bcs for i in range(len(t_array))] +# To define the sequence of PDEs, we will use a loop over all time steps. But before we +# can do that, we first initialize empty lists for the state equations, the +# approximations of the desired state, and the summands of the cost functional + y_d = [] e = [] J_list = [] +# ### Definition of the optimization problem +# +# For the desired state, we define it with the help of a {py:class}`fenics.Expression` +# that is dependent on an additional parameter which models the time + alpha = 1e-5 y_d_expr = Expression( "exp(-20*(pow(x[0] - 0.5 - 0.25*cos(2*pi*t), 2) + pow(x[1] - 0.5 - 0.25*sin(2*pi*t), 2)))", @@ -51,16 +163,19 @@ t=0.0, ) +# Next, we have the following for loop for k in range(len(t_array)): t = t_array[k] y_d_expr.t = t y = states[k] + if k == 0: y_prev = Function(V) else: y_prev = states[k - 1] + p = adjoints[k] u = controls[k] @@ -71,6 +186,7 @@ ) e.append(state_eq) + y_d.append(interpolate(y_d_expr, V)) J_list.append( @@ -80,15 +196,80 @@ ) ) +# ::::{admonition} Description of the for-loop +# At the beginning, the 'current' time t is determined from {python}`t_array`, and the +# expression for the desired state is updated to reflect the current time with the code +# :::python +# t = t_array[k] +# y_d_expr.t = t +# ::: +# +# The following line sets the object {python}`y` to $y_{k+1}$ +# :::python +# y = states[k] +# ::: +# +# For the backward difference in the implicit Euler method, we also need $y_{k}$ +# which we define as follows +# :::python +# if k == 0: +# y_prev = Function(V) +# else: +# y_prev = states[k - 1] +# ::: +# +# which ensures that $y_0 = 0$, which corresponds to the initial condition +# $y^{(0)} = 0$. Hence, {python}`y_prev` indeed corresponds to $y_{k}$. +# +# Moreover, we get the current control and adjoint state via +# :::python +# p = adjoints[k] +# u = controls[k] +# ::: +# +# This allows us to define the state equation at time t as +# :::python +# state_eq = ( +# Constant(1 / dt) * (y - y_prev) * p * dx +# + inner(grad(y), grad(p)) * dx +# - u * p * dx +# ) +# ::: +# +# This is then appended to the list of state constraints +# :::python +# e.append(state_eq) +# ::: +# +# Further, we also put the current desired state into the respective list, i.e., +# :::python +# y_d.append(interpolate(y_d_expr, V)) +# ::: +# +# Finally, we can define the k-th summand of the cost functional via +# :::python +# J_list.append( +# cashocs.IntegralFunctional( +# Constant(0.5 * dt) * (y - y_d[k]) * (y - y_d[k]) * dx +# + Constant(0.5 * dt * alpha) * u * u * dx +# ) +# ) +# ::: +# +# and directly append this to the cost functional list. +# :::: + +# Finally, we can define an optimal control problem as before, and solve it as in the +# previous demos (see, e.g., {ref}`demo_poisson`) ocp = cashocs.OptimalControlProblem( e, bcs_list, J_list, states, controls, adjoints, config=config ) ocp.solve() +# For a postprocessing, we perform the following steps -### Post processing - +# + u_file = File("./visualization/u.pvd") y_file = File("./visualization/y.pvd") temp_u = Function(V) @@ -102,3 +283,6 @@ temp_y.vector()[:] = states[k].vector()[:] y_file << temp_y, t +# - + +# which saves the result in the directory `./visualization/` as paraview .pvd files. diff --git a/demos/documented/optimal_control/iterative_solvers/demo_iterative_solvers.py b/demos/documented/optimal_control/iterative_solvers/demo_iterative_solvers.py index a6464442..7ff660b8 100755 --- a/demos/documented/optimal_control/iterative_solvers/demo_iterative_solvers.py +++ b/demos/documented/optimal_control/iterative_solvers/demo_iterative_solvers.py @@ -1,24 +1,61 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_iterative_solvers)= +# # Iterative Solvers for State and Adjoint Systems # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# cashocs is also capable of using iterative solvers through the linear algebra +# backend PETSc. In this demo we show how this can be used. For the sake of simplicitiy, +# we consider the same setting as in {ref}`demo_poisson`, i.e. # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/optimal_control/doc_iterative_solvers.html. - -""" +# $$ +# \begin{align} +# &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 \text{ d}x +# + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x \\ +# &\text{ subject to } \qquad +# \begin{alignedat}[t]{2} +# -\Delta y &= u \quad &&\text{ in } \Omega,\\ +# y &= 0 \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# (see, e.g., [Tröltzsch - Optimal Control of Partial Differential Equations]( +# https://doi.org/10.1090/gsm/112) or [Hinze, Pinnau, Ulbrich, and Ulbrich - +# Optimization with PDE constraints](https://doi.org/10.1007/978-1-4020-8839-1). +# +# It is well-known, that the state problem, when discretized using a classical, +# conforming Ritz-Galerkin method, gives rise to a linear system with a symmetric and +# positive definite matrix. We use these properties in this demo by solving the state +# system with the conjugate gradient method. Moreover, the adjoint system is also a +# Poisson problem with right-hand side $y - y_d$, and so also gives rise to a symmetric +# and positive definite system, for which we also employ an iterative solver. +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_iterative_solvers.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# The initialization works exactly as in {ref}`demo_poisson` +# + from fenics import * import cashocs @@ -33,6 +70,12 @@ e = inner(grad(y), grad(p)) * dx - u * p * dx bcs = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) +# - + +# ### Definition of the iterative solvers +# +# The options for the state and adjoint systems are defined as follows. For the state +# system we have ksp_options = [ ["ksp_type", "cg"], @@ -43,6 +86,56 @@ ["ksp_max_it", 100], ] +# This is a list of lists, where the inner ones have either 1 or 2 entries, which +# correspond to the command line options for PETSc. For a detailed documentation of the +# possibilities, we refer to the [PETSc documentation]( +# https://petsc.org/release/overview/). Of particular interest are +# the pages for the [Krylov solvers]( +# https://petsc.org/release/docs/manualpages/KSP/) and +# [Preconditioners]( +# https://petsc.org/release/docs/manualpages/PC/). The +# relevant options for the command line are described under "Options Database Keys". +# +# ::::{note} +# For example, the first command +# :::python +# ["ksp_type", "cg"], +# ::: +# +# can be found in [KSPSetType]( +# https://petsc.org/release/docs/manualpages/KSP/KSPSetType/) +# and the corresponding options are shown under [KSPTYPE]( +# https://petsc.org/release/docs/manualpages/KSP/KSPType/). +# Here, we see that the above line corresponds to using the conjugate gradient method as +# krylov solver. The following two lines +# :::python +# ["pc_type", "hypre"], +# ["pc_hypre_type", "boomeramg"], +# ::: +# +# specify that we use the algebraic multigrid preconditioner BOOMERAMG from HYPRE. +# This is documented in [PCSetType]( +# https://petsc.org/release/docs/manualpages/PC/PCSetType/), +# [PCTYPE]( +# https://petsc.org/release/docs/manualpages/PC/PCType/), and +# [PCHYPRE]( +# https://petsc.org/release/docs/manualpages/PC/PCHYPRE/). +# Finally, the last three lines +# :::python +# ["ksp_rtol", 1e-10], +# ["ksp_atol", 1e-13], +# ["ksp_max_it", 100], +# ::: +# +# specify that we use a relative tolerance of 1e-10, an absolute one of 1e-13, and +# at most 100 iterations for each solve of the linear system, cf. [KSPSetTolerances]( +# https://petsc.org/release/docs/manualpages/KSP/KSPSetTolerances/). +# :::: +# +# Coming from the first optimize, then discretize view point, it is not required that +# the adjoint system should be solved exactly the same as the state system. This is why +# we can also define PETSc options for the adjoint system, which we do with + adjoint_ksp_options = [ ["ksp_type", "minres"], ["pc_type", "jacobi"], @@ -50,7 +143,33 @@ ["ksp_atol", 1e-15], ] +# As can be seen, we now use a completely different solver, namely MINRES (the minimal +# residual method) with a jacobi preconditioner. Finally, the tolerances for the adjoint +# solver can also be rather different from the ones of the state system, as is shown +# here. +# +# ::::{hint} +# To verify that the options indeed are used, one can supply the option +# :::python +# ['ksp_view'], +# ::: +# +# which shows the detailed settings of the solvers, and also +# :::python +# ['ksp_monitor_true_residual'], +# ::: +# +# which prints the residual of the method over its iterations. +# +# For multiple state and adjoint systems, one can proceed analogously to +# {ref}`demo_multiple_variables`, and one has to create a such a list of options for +# each component, and then put them into an additional list. +# :::: +# +# With these definitions, we can now proceed as in {ref}`demo_poisson` and solve the +# optimization problem with +# + y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) alpha = 1e-6 J = cashocs.IntegralFunctional( @@ -69,10 +188,21 @@ adjoint_ksp_options=adjoint_ksp_options, ) ocp.solve() +# - + +# :::{note} +# Note, that if the {python}`ksp_options` and {python}`adjoint_ksp_options` are not +# passed to the {py:class}`OptimalControlProblem ` or +# {python}`None`, which is the default value of these keyword parameters, then the +# direct solver MUMPS is used. Moreover, if one wants to use identical options for state +# and adjoint systems, then only the {python}`ksp_options` have to be passed. This is +# because {python}`adjoint_ksp_options` always mirrors the ksp_options in case that the +# input is {python}`None` for {python}`adjoint_ksp_options`. +# ::: +# +# We visualize the results of the optimization with the lines - -### Post Processing - +# + import matplotlib.pyplot as plt plt.figure(figsize=(15, 5)) @@ -94,3 +224,7 @@ plt.tight_layout() # plt.savefig('./img_iterative_solvers.png', dpi=150, bbox_inches='tight') +# - + +# and the result should look like this +# ![](/../../demos/documented/optimal_control/iterative_solvers/img_iterative_solvers.png) diff --git a/demos/documented/optimal_control/monolithic_problems/demo_monolithic_problems.py b/demos/documented/optimal_control/monolithic_problems/demo_monolithic_problems.py index d3bbbb4a..cf9672dd 100755 --- a/demos/documented/optimal_control/monolithic_problems/demo_monolithic_problems.py +++ b/demos/documented/optimal_control/monolithic_problems/demo_monolithic_problems.py @@ -1,55 +1,147 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_monolithic_problems)= +# # Coupled Problems - Monolithic Approach # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo we show how cashocs can be used with a coupled PDE constraint. +# For this demo, we consider a monolithic approach, whereas we investigate +# an approach based on a Picard iteration in {ref}`demo_picard_iteration`. # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/optimal_control/doc_monolithic_problems.html. - -""" +# As model example, we consider the following problem +# +# $$ +# \begin{align} +# &\min\; J((y,z),(u,v)) = \frac{1}{2} \int_\Omega \left( y - y_d \right)^2 +# \text{ d}x + \frac{1}{2} \int_\Omega \left( z - z_d \right)^2 \text{ d}x +# + \frac{\alpha}{2} \int_\Omega u^2 \text{ d}x + \frac{\beta}{2} \int_\Omega v^2 +# \text{ d}x \\ +# &\text{ subject to }\qquad +# \begin{alignedat}[t]{2} +# -\Delta y + z &= u \quad &&\text{ in } \Omega, \\ +# y &= 0 \quad &&\text{ on } \Gamma,\\ +# -\Delta z + y &= v \quad &&\text{ in } \Omega,\\ +# z &= 0 \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# In constrast to {ref}`demo_multiple_variables`, the system is now two-way coupled. +# To solve it, we employ a mixed finite element method in this demo. +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_monolithic_problems.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# The initialization for this example works as before, i.e., we use +# + from fenics import * import cashocs config = cashocs.load_config("config.ini") mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(50) +# - + +# For the mixed finite element method we have to define a +# {py:class}`fenics.MixedFunctionSpace`, via elem_1 = FiniteElement("CG", mesh.ufl_cell(), 1) elem_2 = FiniteElement("CG", mesh.ufl_cell(), 1) V = FunctionSpace(mesh, MixedElement([elem_1, elem_2])) +# The control variables get their own {py:class}`fenics.FunctionSpace` + U = FunctionSpace(mesh, "CG", 1) +# Then, the state and adjoint variables {python}`state` and {python}`adjoint` are +# defined + state = Function(V) adjoint = Function(V) + +# As these are part of a {py:class}`fenics.MixedFunctionSpace`, we can access their +# individual components by + y, z = split(state) p, q = split(adjoint) +# Similarly to {ref}`demo_multiple_variables`, {python}`p` is the adjoint state +# corresponding to {python}`y`, and {python}`q` is the one corresponding to {python}`z`. +# +# We then define the control variables as + u = Function(U) v = Function(U) controls = [u, v] +# Note, that we directly put the control variables {python}`u` and {python}`v` into a +# list {python}`controls`, which implies that {python}`u` is the first component of the +# control variable, and {python}`v` the second one. +# +# ::::{hint} +# An alternative way of specifying the controls would be to reuse the mixed function +# space and use +# :::python +# controls = Function(V) +# u, v = split(controls) +# ::: +# +# Although this formulation is slightly different (it uses a +# {py:class}`fenics.Function` for the controls, and not a list) the de-facto behavior of +# both methods is completely identical, just the interpretation is slightly different +# (since the individual components of the {py:class}`fenics.FunctionSpace` {python}`V` +# are also CG1 functions). +# :::: +# +# ### Definition of the mixed weak form +# +# Next, we define the mixed weak form. To do so, we first define the first equation +# and its Dirichlet boundary conditions + e_y = inner(grad(y), grad(p)) * dx + z * p * dx - u * p * dx bcs_y = cashocs.create_dirichlet_bcs(V.sub(0), Constant(0), boundaries, [1, 2, 3, 4]) +# and, in analogy, the second state equation + e_z = inner(grad(z), grad(q)) * dx + y * q * dx - v * q * dx bcs_z = cashocs.create_dirichlet_bcs(V.sub(1), Constant(0), boundaries, [1, 2, 3, 4]) +# To arrive at the mixed weak form of the entire syste, we have to add the state +# equations and Dirichlet boundary conditions + e = e_y + e_z bcs = bcs_y + bcs_z +# Note, that we can only have one state equation as we also have only a single state +# variable {python}`state`, and the number of state variables and state equations has +# to coincide, and the same is true for the boundary conditions, where also just a +# single list is required. +# +# ### Defintion of the optimization problem +# +# The cost functional can be specified in analogy to the one of +# {ref}`demo_multiple_variables` + y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) z_d = Expression("sin(4*pi*x[0])*sin(4*pi*x[1])", degree=1) alpha = 1e-6 @@ -61,14 +153,16 @@ + Constant(0.5 * beta) * v * v * dx ) +# Finally, we can set up the optimization problem and solve it + optimization_problem = cashocs.OptimalControlProblem( e, bcs, J, state, controls, adjoint, config=config ) optimization_problem.solve() +# We visualize the result with the code -### Post Processing - +# + y, z = state.split(True) import matplotlib.pyplot as plt @@ -106,3 +200,7 @@ plt.tight_layout() # plt.savefig('./img_monolithic_problems.png', dpi=150, bbox_inches='tight') +# - + +# so that the output should look like this +# ![](/../../demos/documented/optimal_control/monolithic_problems/img_monolithic_problems.png) diff --git a/demos/documented/optimal_control/multiple_variables/demo_multiple_variables.py b/demos/documented/optimal_control/multiple_variables/demo_multiple_variables.py index 4b4fbc64..d7105d38 100755 --- a/demos/documented/optimal_control/multiple_variables/demo_multiple_variables.py +++ b/demos/documented/optimal_control/multiple_variables/demo_multiple_variables.py @@ -1,24 +1,64 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_multiple_variables)= +# # Using Multiple Variables and PDEs # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo we show how cashocs can be used to treat multiple +# state equations as constraint. Additionally, this also highlights +# how the case of multiple controls can be treated. As model example, we consider the +# following problem # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/optimal_control/doc_multiple_variables.html. - -""" +# $$ +# \begin{align} +# &\min\; J((y,z), (u,v)) = \frac{1}{2} \int_\Omega \left( y - y_d \right) \text{ d}x + \frac{1}{2} \int_\Omega \left( z - z_d \right) \text{ d}x + \frac{\alpha}{2} \int_\Omega u^2 \text{ d}x + \frac{\beta}{2} \int_\Omega v^2 \text{ d}x \\ +# &\text{ subject to } \qquad +# \begin{alignedat}[t]{2} +# -\Delta y &= u \quad &&\text{ in } \Omega, \\ +# y &= 0 \quad &&\text{ on } \Gamma,\\ +# -\Delta z - y &= v \quad &&\text{ in } \Omega, \\ +# z &= 0 \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# For the sake of simplicity, we restrict this investigation to +# homogeneous boundary conditions as well as to a very simple one way +# coupling. More complex problems (using e.g. Neumann control or more +# difficult couplings) are straightforward to implement. +# +# In contrast to the previous examples, in the case where we have multiple state +# equations, which are either decoupled or only one-way coupled, the corresponding state +# equations are solved one after the other so that every input related to the state and +# adjoint variables has to be put into a ordered list, so that they can be treated +# properly, as is explained in the following. +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_multiple_variables.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# The initial setup is identical to the previous cases (see, {ref}`demo_poisson`), where +# we again use +# + from fenics import * import cashocs @@ -26,6 +66,14 @@ config = cashocs.load_config("config.ini") mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(50) V = FunctionSpace(mesh, "CG", 1) +# - + +# which defines the geometry and the function space. +# +# ### Definition of the problems +# +# We now first define the state equation corresponding to the state $y$. This is done in +# analogy to {ref}`demo_poisson` y = Function(V) p = Function(V) @@ -33,19 +81,52 @@ e_y = inner(grad(y), grad(p)) * dx - u * p * dx bcs_y = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) +# Similarly to before, {python}`p` is the adjoint state corresponding to {python}`y`. +# +# Next, we define the second state equation (which is for the state $z$) via + z = Function(V) q = Function(V) v = Function(V) e_z = inner(grad(z), grad(q)) * dx - (y + v) * q * dx bcs_z = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) +# Here, {python}`q` is the adjoint state corresponding to {python}`z`. +# +# In order to treat this one-way coupled with cashocs, we now have to specify what +# the state, adjoint, and control variables are. This is done by putting the +# corresponding {py:class}`fenics.Function` objects into ordered lists + states = [y, z] adjoints = [p, q] controls = [u, v] +# To define the corresponding state system, the state equations and Dirichlet boundary +# conditions also have to be put into an ordered list, i.e., + e = [e_y, e_z] bcs_list = [bcs_y, bcs_z] +# :::{note} +# It is important, that the ordering of the state and adjoint variables, as well +# as the state equations and boundary conditions is in the same way. This means, +# that {python}`e[i]` is the state equation for {python}`state[i]`, which is +# supplemented with Dirichlet boundary conditions defined in {python}`bcs_list[i]`, and +# has a corresponding adjoint state {python}`adjoints[i]`, for all {python}`i`. In +# analogy, the same holds true for the control variables, the scalar product of the +# control space, and the control constraints, i.e., {python}`controls[j]`, +# {python}`riesz_scalar_products[j]`, and {python}`control_constraints[j]` all have to +# belong to the same control variable. +# ::: +# +# Note that the control variables are completely independent of the state +# and adjoint ones, so that the relative ordering between these objects does +# not matter. +# +# ### Defintion of the cost functional and optimization problem +# +# For the optimization problem we now define the cost functional via + y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) z_d = Expression("sin(4*pi*x[0])*sin(4*pi*x[1])", degree=1) alpha = 1e-6 @@ -57,14 +138,16 @@ + Constant(0.5 * beta) * v * v * dx ) +# This setup is sufficient to now define the optimal control problem and solve it, via + ocp = cashocs.OptimalControlProblem( e, bcs_list, J, states, controls, adjoints, config=config ) ocp.solve() +# We visualize the results of the problem with the code -### Post Processing - +# + import matplotlib.pyplot as plt plt.figure(figsize=(15, 10)) @@ -101,3 +184,43 @@ plt.tight_layout() # plt.savefig('./img_multiple_variables.png', dpi=150, bbox_inches='tight') +# - + +# and the output should look as follows +# ![](/../../demos/documented/optimal_control/multiple_variables/img_multiple_variables.png) +# +# :::{note} +# Note, that the error between $z$ and $z_d$ is significantly larger +# that the error between $y$ and $y_d$. This is due to the fact that +# we use a different regularization parameter for the controls $u$ and $v$. +# For the former, which only acts on $y$, we have a regularization parameter +# of {python}`alpha = 1e-6`, and for the latter we have {python}`beta = 1e-4`. Hence, +# $v$ is penalized higher for being large, so that also $z$ is (significantly) +# smaller than $z_d$. +# ::: +# +# ::::{hint} +# Note, that for the case that we consider control constraints (see +# {ref}`demo_box_constraints`) or different Hilbert spaces, e.g., for boundary control +# (see {ref}`demo_neumann_control`), the corresponding control constraints have also to +# be put into a joint list, i.e., +# :::python +# cc_u = [u_a, u_b] +# cc_v = [v_a, v_b] +# cc = [cc_u, cc_v] +# ::: +# +# and the corresponding scalar products have to be treated analogously, i.e., +# :::python +# scalar_product_u = TrialFunction(V)*TestFunction(V)*dx +# scalar_product_v = TrialFunction(V)*TestFunction(V)*dx +# scalar_products = [scalar_product_u, scalar_produt_v] +# ::: +# :::: +# +# In summary, to treat multiple (control or state) variables, the +# corresponding objects simply have to placed into ordered lists which +# are then passed to the {py:class}`OptimalControlProblem ` +# instead of the "single" objects as in the previous examples. Note, that each +# individual object of these lists is allowed to be from a different function space, +# and hence, this enables different discretizations of state and adjoint systems. diff --git a/demos/documented/optimal_control/neumann_control/demo_neumann_control.py b/demos/documented/optimal_control/neumann_control/demo_neumann_control.py index a46d41d9..4023e268 100755 --- a/demos/documented/optimal_control/neumann_control/demo_neumann_control.py +++ b/demos/documented/optimal_control/neumann_control/demo_neumann_control.py @@ -1,24 +1,55 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_neumann_control)= +# # Neumann Boundary Control # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo we investigate an optimal control problem with a Neumann type boundary +# control. This problem reads # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/optimal_control/doc_neumann_control.html. - -""" +# $$ +# \begin{align} +# &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 \text{ d}x + \frac{\alpha}{2} \int_{\Gamma} u^2 \text{ d}s \\ +# &\text{ subject to } \qquad +# \begin{alignedat}[t]{2} +# -\Delta y + y &= 0 \quad &&\text{ in } \Omega,\\ +# n\cdot \nabla y &= u \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# (see, e.g., [Tröltzsch - Optimal Control of Partial Differential Equations]( +# https://doi.org/10.1090/gsm/112) or [Hinze, Pinnau, Ulbrich, and Ulbrich - +# Optimization with PDE constraints](https://doi.org/10.1007/978-1-4020-8839-1). +# Note that we cannot use a simple Poisson equation as constraint since this would not +# be compatible with the boundary conditions (i.e. not well-posed). +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_neumann_control.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# Initially, the code is again identical to the previous ones (see {ref}`demo_poisson` +# and {ref}`demo_box_constraints`), i.e., we have +# + from fenics import * import cashocs @@ -31,27 +62,83 @@ y = Function(V) p = Function(V) u = Function(V) +# - + +# ### Definition of the state equation +# +# Now, the definition of the state problem obviously differs from the previous two +# examples, and we use e = inner(grad(y), grad(p)) * dx + y * p * dx - u * p * ds +# which directly puts the Neumann boundary condition into the weak form. +# For this problem, we do not have Dirichlet boundary conditions, so that we +# use + bcs = [] +# ::::{hint} +# Alternatively, we could have also used +# :::python +# bcs = None +# ::: +# :::: +# +# ### Definition of the cost functional +# +# The definition of the cost functional is nearly identical to before, +# only the integration measure for the regularization term changes, so that we have + y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) alpha = 1e-6 J = cashocs.IntegralFunctional( Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * u * u * ds ) +# As the default Hilbert space for a control is $L^2(\Omega)$, we now +# also have to change this, to accommodate for the fact that the control +# variable u now lies in the space $L^2(\Gamma)$, i.e., it is +# only defined on the boundary. This is done by defining the scalar +# product of the corresponding Hilbert space, which we do with + scalar_product = TrialFunction(V) * TestFunction(V) * ds +# The scalar_product always has to be a symmetric, coercive and continuous +# bilinear form, so that it induces an actual scalar product on the +# corresponding space. +# +# ::::{note} +# This means, that we could also define an alternative scalar product for +# {ref}`demo_poisson`, using the space $H^1(\Omega)$ instead of +# $L^2(\Omega)$ with the following +# :::python +# scalar_product = ( +# inner(grad(TrialFunction(V)), grad(TestFunction(V))) * dx +# + TrialFunction(V) * TestFunction(V) * dx +# ) +# ::: +# +# This allows a great amount of flexibility in the choice of the control space. +# :::: +# +# ### Setup of the optimization problem and its solution +# +# With this, we can now define the optimal control problem with the +# additional keyword argument {python}`riesz_scalar_products` and solve it with the +# {py:meth}`ocp.solve() ` command + ocp = cashocs.OptimalControlProblem( e, bcs, J, y, u, p, config=config, riesz_scalar_products=scalar_product ) ocp.solve() +# Hence, in order to treat boundary control problems, the corresponding +# weak forms have to be modified accordingly, and one **has to** adapt the +# scalar products used to determine the gradients. +# +# We visualize the results with the lines -### Post Processing - +# + import matplotlib.pyplot as plt plt.figure(figsize=(15, 5)) @@ -73,3 +160,7 @@ plt.tight_layout() # plt.savefig('./img_neumann_control.png', dpi=150, bbox_inches='tight') +# - + +# and the output should look like this +# ![](/../../demos/documented/optimal_control/neumann_control/img_neumann_control.png) diff --git a/demos/documented/optimal_control/nonlinear_pdes/demo_nonlinear_pdes.py b/demos/documented/optimal_control/nonlinear_pdes/demo_nonlinear_pdes.py index dc65ba7b..a166de45 100755 --- a/demos/documented/optimal_control/nonlinear_pdes/demo_nonlinear_pdes.py +++ b/demos/documented/optimal_control/nonlinear_pdes/demo_nonlinear_pdes.py @@ -1,24 +1,66 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_nonlinear_pdes)= +# # Optimal Control with Nonlinear PDE Constraints # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo, we take a look at the case of nonlinear PDE constraints for optimization +# problems. As a model problem, we consider # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/optimal_control/doc_nonlinear_pdes.html. - -""" +# $$ +# \begin{align} +# &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 \text{ d}x +# + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x \\ +# &\text{ subject to } \qquad +# \begin{alignedat}[t]{2} +# -\Delta y + c y^3 &= u \quad &&\text{ in } \Omega,\\ +# y &= 0 \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# As this problem has a nonlinear PDE as state constraint, we have to modify the config +# file slightly. In particular, in the Section +# {ref}`StateSystem ` we have to write +# :::python +# is_linear = False +# ::: +# +# Note, that {ini}`is_linear = False` works for any problem, as linear equations are +# just a special case of nonlinear ones, and the corresponding nonlinear solver +# converges in a single iteration for these. However, in the opposite case, FEniCS will +# raise an error, so that an actually nonlinear equation cannot be solved using +# {ini}`is_linear = True`. Also, we briefly recall from {ref}`config_optimal_control`, +# that the default behavior is {ini}`is_linear = False`, so that this is not an issue. +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_nonlinear_pdes.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# Thanks to the high level interface for implementing weak formulations, this problem +# is tackled almost as easily as the one in {ref}`demo_poisson`. In particular, the +# entire initialization, up to the definition of the weak form of the PDE constraint, +# is identical, and we have +# + from fenics import * import cashocs @@ -30,12 +72,33 @@ y = Function(V) p = Function(V) u = Function(V) +# - + +# For the definition of the state constraints, we use essentially the same syntax as +# we would use for the problem in FEniCS, i.e., we write c = Constant(1e2) e = inner(grad(y), grad(p)) * dx + c * pow(y, 3) * p * dx - u * p * dx +# :::{note} +# In particular, the only difference between the cashocs implementation of this weak +# form and the FEniCS one is that, as before, we use {py:class}`fenics.Function` objects +# for both the state and adjoint variables, whereas we would use +# {py:class}`fenics.Function` objects for the state, and {py:class}`fenics.TestFunction` +# for the adjoint variable, which would actually play the role of the test function. +# Other than that, the syntax is, again, identical to the one of FEniCS. +# ::: +# +# Finally, the boundary conditions are defined as before + bcs = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) +# ### Solution of the optimization problem +# +# To define and solve the optimization problem, we now proceed exactly as in +# {ref}`demo_poisson`, and use + +# + y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) alpha = 1e-6 J = cashocs.IntegralFunctional( @@ -44,10 +107,11 @@ ocp = cashocs.OptimalControlProblem(e, bcs, J, y, u, p, config=config) ocp.solve() +# - +# We visualize the results with the lines -### Post Processing - +# + import matplotlib.pyplot as plt plt.figure(figsize=(15, 5)) @@ -69,3 +133,7 @@ plt.tight_layout() # plt.savefig('./img_nonlinear_pdes.png', dpi=150, bbox_inches='tight') +# - + +# and the result looks as follows +# ![](/../../demos/documented/optimal_control/nonlinear_pdes/img_nonlinear_pdes.png) diff --git a/demos/documented/optimal_control/picard_iteration/demo_picard_iteration.py b/demos/documented/optimal_control/picard_iteration/demo_picard_iteration.py index 21dbdb23..315a9fcd 100755 --- a/demos/documented/optimal_control/picard_iteration/demo_picard_iteration.py +++ b/demos/documented/optimal_control/picard_iteration/demo_picard_iteration.py @@ -1,24 +1,68 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_picard_iteration)= +# # Coupled Problems - Picard Iteration # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo we show how cashocs can be used with a coupled PDE constraint. For this, +# we consider an iterative approach, whereas we investigated a monolithic approach in +# {ref}`demo_monolithic_problems`. # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/optimal_control/doc_picard_iteration.html. - -""" +# As model example, we consider the following problem +# +# $$ +# \begin{align} +# &\min\; J((y,z),(u,v)) = \frac{1}{2} \int_\Omega \left( y - y_d \right)^2 +# \text{ d}x + \frac{1}{2} \int_\Omega \left( z - z_d \right)^2 \text{ d}x +# + \frac{\alpha}{2} \int_\Omega u^2 \text{ d}x + \frac{\beta}{2} \int_\Omega v^2 +# \text{ d}x \\ +# &\text{ subject to }\qquad +# \begin{alignedat}[t]{2} +# -\Delta y + z &= u \quad &&\text{ in } \Omega, \\ +# y &= 0 \quad &&\text{ on } \Gamma,\\ +# -\Delta z + y &= v \quad &&\text{ in } \Omega,\\ +# z &= 0 \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# Again, the system is two-way coupled. To solve it, we now employ a Picard iteration. +# Therefore, the two PDEs are solved subsequently, where the variables are frozen in +# between: At the beginning the first PDE is solved for $y$, with $z$ being fixed. +# Afterwards, the second PDE is solved for $z$ with $y$ fixed. This is then repeated +# until convergence is reached. +# +# :::{note} +# There is, however, no a-priori guarantee that the Picard iteration converges +# for a particular problem, unless a careful analysis is carried out by the user. +# Still, it is an important tool, which also often works well in practice. +# ::: +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_picard_iteration.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# The setup is as in {ref}`demo_poisson` +# + from fenics import * import cashocs @@ -26,28 +70,64 @@ config = cashocs.load_config("config.ini") mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(50) V = FunctionSpace(mesh, "CG", 1) +# - + +# However, compared to the previous examples, there is a major change in the config +# file. As we want to use the Picard iteration as solver for the state PDEs, we now +# specify +# :::python +# picard_iteration = True +# ::: +# +# see {download}`config.ini +# `. +# +# ### Definition of the state system +# +# The definition of the state system follows the same ideas as introduced in +# {ref}`demo_multiple_variables`: We define both state equations through their +# components, and then gather them in lists, which are passed to the +# {py:class}`OptimalControlProblem `. +# The state and adjoint variables are defined via y = Function(V) p = Function(V) z = Function(V) q = Function(V) +# The control variables are defined as + u = Function(V) v = Function(V) +# Next, we define the state system, using the weak forms from +# {ref}`demo_monolithic_problems` + +# + e_y = inner(grad(y), grad(p)) * dx + z * p * dx - u * p * dx bcs_y = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) e_z = inner(grad(z), grad(q)) * dx + y * q * dx - v * q * dx bcs_z = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) +# - +# Finally, we use the same procedure as in {ref}`demo_multiple_variables`, and +# put everything into (ordered) lists + +# + states = [y, z] adjoints = [p, q] controls = [u, v] e = [e_y, e_z] bcs = [bcs_y, bcs_z] +# - +# ### Definition of the optimization problem +# +# The cost functional is defined as in {ref}`demo_monolithic_problems`, the only +# difference is that {python}`y` and {python}`z` now are {py:class}`fenics.Function` objects, whereas +# they were generated with the {py:func}`fenics.split` command previously y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) z_d = Expression("sin(4*pi*x[0])*sin(4*pi*x[1])", degree=1) @@ -60,14 +140,16 @@ + Constant(0.5 * beta) * v * v * dx ) +# Finally, we set up the optimization problem and solve it + optimization_problem = cashocs.OptimalControlProblem( e, bcs, J, states, controls, adjoints, config=config ) optimization_problem.solve() +# We visualize the results with the code -### Post Processing - +# + import matplotlib.pyplot as plt plt.figure(figsize=(15, 10)) @@ -104,3 +186,22 @@ plt.tight_layout() # plt.savefig('./img_picard_iteration.png', dpi=150, bbox_inches='tight') +# - + +# and the result can be seen below +# ![](/../../demos/documented/optimal_control/picard_iteration/img_picard_iteration.png) +# +# :::{note} +# Comparing the output (especially in the early iterations) between the monlithic and Picard apporach +# we observe that both methods yield essentially the same results (up to machine precision). This validates +# the Picard approach. +# +# However, one should note that for this example, the Picard approach takes +# significantly longer to compute the optimizer. This is due to the fact that the +# individual PDEs have to be solved several times, whereas in the monolithic approach +# the state system is (slightly) larger, but has to be solved less often. However, the +# monolithic approach needs significantly more memory, so that the Picard iteration +# becomes feasible for very large problems. Further, the convergence properties of the +# Picard iteration are better, so that it may converge even when the monolithic approach +# fails. +# ::: diff --git a/demos/documented/optimal_control/poisson/demo_poisson.py b/demos/documented/optimal_control/poisson/demo_poisson.py index 08484ca7..716d38a6 100755 --- a/demos/documented/optimal_control/poisson/demo_poisson.py +++ b/demos/documented/optimal_control/poisson/demo_poisson.py @@ -1,53 +1,281 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_poisson)= +# # Distributed Control of a Poisson Problem # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo we investigate the basics of cashocs for +# optimal control problems. To do so, we investigate the "mother +# problem" of PDE constrained optimization, i.e., # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/optimal_control/doc_poisson.html. - -""" +# $$ +# \begin{align} +# &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 +# \text{ d}x + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x \\ +# &\text{ subject to } \qquad +# \begin{alignedat}[t]{2} +# -\Delta y &= u \quad &&\text{ in } \Omega,\\ +# y &= 0 \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# (see, e.g., [Tröltzsch - Optimal Control of Partial Differential Equations]( +# https://doi.org/10.1090/gsm/112) +# or [Hinze, Pinnau, Ulbrich, and Ulbrich - Optimization with PDE constraints]( +# https://doi.org/10.1007/978-1-4020-8839-1). +# +# For this first example, we do not consider control constraints, +# but search for an optimal control u in the entire space $L^2(\Omega)$, +# for the sake of simplicitiy. For the domain under consideration, we use the unit +# square $\Omega = (0, 1)^2$, since this is built into cashocs. +# +# In the following, we will describe how to solve this problem +# using cashocs. Moreover, we also detail alternative / equivalent FEniCS code which +# could be used to define the problem instead. +# +# ## Implementation +# +# The complete python code can be found in the file +# {download}`demo_poisson_myst.py +# ` +# and the corresponding config can be found in +# {download}`config.ini `. +# +# ### Initialization +# +# We begin by importing FEniCS and cashocs. For the sake of +# better readability we use a wildcard import for FEniCS +# + from fenics import * import cashocs +# - + +# Afterwards, we can specify the so-called log level of cashocs. This is done in the +# line + cashocs.set_log_level(cashocs.LogLevel.INFO) + +# :::{hint} +# There are a total of five levels of verbosity, given by +# {py:class}`cashocs.LogLevel.DEBUG`, {py:class}`cashocs.LogLevel.INFO`, +# {py:class}`cashocs.LogLevel.WARNING`, {py:class}`cashocs.LogLevel.ERROR`, +# and {py:class}`cashocs.LogLevel.CRITICAL`. The default value is {python}`INFO`, which +# would also be selected if the {py:func}`cashocs.set_log_level` method would not have +# been called. +# ::: +# +# Next, we have to load the config file which loads the user's input parameters into the +# script. For a detailed documentation of the config files and the parameters within, we +# refer to {ref}`config_optimal_control`. Note that the corresponding file is +# {download}`config.ini `. +# The config is then loaded via + config = cashocs.load_config("config.ini") + +# Next up, we have to define the state equation. This mostly works with usual FEniCS +# syntax. In cashocs, we can quickly generate meshes for squares and cubes, as well as +# import meshes generated by Gmsh, for more complex geometries. In this example we take +# a built-in unit square as example. This is generated via + mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(25) + +# The input for regular_mesh determines the number of elements that +# are placed along each axis of the square. Note, that the mesh could be +# further manipulated with additional, optional arguments, and we +# refer to {py:func}`regular_mesh ` for more infos. Note, +# that the {python}`subdomains` object is a (empty) {py:class}`MeshFunction`, and that +# {python}`boundaries` is a {py:class}`MeshFunction` that contains markers for the following +# boundaries +# +# - The left side of the square is marked by 1 +# - The right side is marked by 2 +# - The bottom is marked by 3 +# - The top is marked by 4, +# +# as defined in {py:func}`regular_mesh `. +# +# With the geometry defined, we create a function space with the classical +# FEniCS syntax + V = FunctionSpace(mesh, "CG", 1) +# which creates a function space of continuous, linear Lagrange elements. +# +# ### Definition of the state equation +# +# To describe the state system in cashocs, we use (almost) standard +# FEniCS syntax, and the differences will be highlighted in the +# following. First, we define a {py:class}`fenics.Function` {python}`y` that models our +# state variable $y$, and a {py:class}`fenics.Function` {python}`p` that models +# the corresponding adjoint variable $p$ via + y = Function(V) p = Function(V) + +# Next up, we analogously define the control variable as {py:class}`fenics.Function` +# {python}`u` + u = Function(V) +# This enables us to define the weak form of the state equation, +# which is tested not with a {py:class}`fenics.TestFunction` but with the adjoint +# variable {python}`p` via the classical FEniCS / UFL syntax + e = inner(grad(y), grad(p)) * dx - u * p * dx +# ::::{note} +# For the clasical definition of this weak form with FEniCS +# one would write the following code +# +# :::python +# y = TrialFunction(V) +# p = TestFunction(V) +# u = Function(V) +# a = inner(grad(y), grad(p)) * dx +# L = u * p * dx +# ::: +# +# as this is a linear problem. However, to have greater flexibility +# we have to treat the problems as being potentially nonlinear. +# In this case, the classical FEniCS formulation for this as +# nonlinear problem would be +# +# :::python +# y = Function(V) +# p = TestFunction(V) +# u = Function(V) +# F = inner(grad(y), grad(p)) * dx -u * p * dx +# ::: +# +# which could then be solved via the {py:func}`fenics.solve` interface. This +# formulation, which comes more naturally for nonlinear +# variational problems (see the +# [FEniCS examples](https://fenicsproject.org/docs/dolfin/latest/python/demos.html)) +# is closer to the one in cashocs. However, for the use with cashocs, the state variable +# $y$ **must not** be a {py:class}`fenics.TrialFunction`, and the adjoint variable +# $p$ **must not** be a {py:class}`fenics.TestFunction`. They **have to** be +# defined as regular {py:class}`fenics.Function` objects, otherwise the code will not +# work properly. +# :::: +# +# After defining the weak form of the state equation, we now +# specify the corresponding (homogeneous) Dirichlet boundary +# conditions via + bcs = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) +# This creates Dirichlet boundary conditions with value 0 at the +# boundaries 1,2,3, and 4, i.e., everywhere. +# +# ::::{hint} +# Classically, these boundary conditions could also be defined via +# +# :::python +# def boundary(x, on_bdry): +# return on_boundary +# bc = DirichletBC(V, Constant(0), boundary) +# ::: +# +# which would yield a single DirichletBC object, instead of the list returned by +# {py:func}`create_dirichlet_bcs `. Any of the many +# methods for defining the boundary conditions works here, as long as it is valid input +# for the {py:func}`fenics.solve` function. +# :::: +# +# With the above description, we see that defining the state system +# for cashocs is nearly identical to defining it with FEniCS, +# the only major difference lies in the definition of the state +# and adjoint variables as {py:class}`fenics.Function` objects, instead of +# {py:class}`fenics.TrialFunction` and {py:class}`fenics.TestFunction`. +# +# ### Definition of the cost functional +# +# Now, we have to define the optimal control problem which we do +# by first specifying the cost functional. To do so, we define the +# desired state $y_d$ as an {py:class}`fenics.Expression` {python}`y_d`, i.e., + y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) + +# Alternatively, {python}`y_d` could also be a {py:class}`fenics.Function` or any other +# object that is usable in an UFL form (e.g. generated with +# {py:func}`fenics.SpatialCoordinate`). +# +# Then, we define the regularization parameter $\alpha$ and the tracking-type +# cost functional via the commands + alpha = 1e-6 J = cashocs.IntegralFunctional( Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * u * u * dx ) +# The cost functional is defined via the {py:class}`cashocs.IntegralFunctional`, which +# means that we only have to define the cost functional's integrand, which will then +# further be treated by cashocs as needed for the optimization. These definitions are +# also classical in the sense that they would have to be performed in this (or a +# similar) way in FEniCS when one would want to evaluate the (reduced) cost functional, +# so that we have only very little overhead. +# +# ### Definition of the optimization problem and its solution +# +# Finally, we set up an +# {py:class}`OptimalControlProblem ` {python}`ocp` and +# then directly solve it with the method {py:meth}`ocp.solve() +# ` + ocp = cashocs.OptimalControlProblem(e, bcs, J, y, u, p, config=config) ocp.solve() +# ::::{hint} +# Note that the {py:meth}`solve ` command without +# any additional keyword arguments leads to cashocs using the settings defined in the +# config file. However, there are some options that can be directly set with keyword +# arguments for the {py:meth}`solve ` call. These +# are +# - {python}`algorithm` : Specifies which solution algorithm shall be used. +# - {python}`rtol` : The relative tolerance for the optimization algorithm. +# - {python}`atol` : The absolute tolerance for the optimization algorithm. +# - {python}`max_iter` : The maximum amount of iterations that can be carried out. +# +# Hence, we could also use the command +# +# :::python +# ocp.solve('lbfgs', 1e-3, 0.0, 100) +# ::: +# +# to solve the optimization problem with the L-BFGS method, a relative tolerance of +# {python}`1e-3`, no absolute tolerance, and a maximum of 100 iterations. +# +# The possible values for these arguments are the same as {ref}`the corresponding ones +# in the config file `. This just allows for some +# shortcuts, e.g., when one wants to quickly use a different solver. +# +# Note, that it is not strictly necessary to supply config files in cashocs. In this +# case, the user has to follow the above example and specify at least the solution +# algorithm via the {py:meth}`solve ` method. +# However, it is very strongly recommended to use config files with cashocs as +# they allow a detailed tuning of its behavior. +# :::: +# +# Finally, we visualize the results using matplotlib and the following code :: -### Post Processing - +# + import matplotlib.pyplot as plt plt.figure(figsize=(15, 5)) @@ -69,3 +297,8 @@ plt.tight_layout() # plt.savefig('./img_poisson.png', dpi=150, bbox_inches='tight') +# - + +# The output should look like this +# :::{image} /../../demos/documented/optimal_control/poisson/img_poisson.png +# ::: diff --git a/demos/documented/optimal_control/pre_post_hooks/config.ini b/demos/documented/optimal_control/pre_post_callbacks/config.ini similarity index 100% rename from demos/documented/optimal_control/pre_post_hooks/config.ini rename to demos/documented/optimal_control/pre_post_callbacks/config.ini diff --git a/demos/documented/optimal_control/pre_post_callbacks/demo_pre_post_callbacks.py b/demos/documented/optimal_control/pre_post_callbacks/demo_pre_post_callbacks.py new file mode 100644 index 00000000..386e9be4 --- /dev/null +++ b/demos/documented/optimal_control/pre_post_callbacks/demo_pre_post_callbacks.py @@ -0,0 +1,211 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` +# +# (demo_pre_post_hooks)= +# # Pre- and Post-Callbacks for the optimization +# +# ## Problem Formulation +# +# In this demo we show how one can use the flexibility of cashocs +# to "inject" their own code into the optimization, which is carried +# out before each solve of the state system (`pre_callback`) or after each +# gradient computation (`post_callback`). To do so, we investigate the following +# problem +# +# $$ +# \begin{align} +# &\min\; J(u, c) = \frac{1}{2} \int_\Omega \left\lvert u - u_d \right\rvert^2 +# \text{ d}x + +# \frac{\alpha}{2} \int_\Omega \left\lvert c \right\rvert^2 \text{ d}x \\ +# &\text{ subject to } \qquad +# \begin{alignedat}[t]{2} +# -\Delta u + Re (u\cdot \nabla) u + \nabla p &= c \quad &&\text{ in } \Omega, \\ +# \text{div}(u) &= 0 \quad &&\text{ in } \Omega,\\ +# u &= u_\text{dir} \quad &&\text{ on } \Gamma^\text{dir},\\ +# u &= 0 \quad &&\text{ on } \Gamma^\text{no slip},\\ +# p &= 0 \quad &&\text{ at } x^\text{pres}. +# \end{alignedat} +# \end{align} +# $$ +# +# In particular, the setting for this demo is very similar to the one of +# {ref}`demo_stokes`, but here we consider the nonlinear incompressible Navier-Stokes +# equations. +# +# In the following, we will describe how to solve this problem +# using cashocs, where we will use the {python}`pre_callback` functionality to implement +# a homotopy method for solving the Navier-Stokes equations. +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_constraints.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# The initial part of the code is nearly identical to the one of {ref}`demo_stokes`, but +# here we use the Navier-Stokes equations instead of the linear Stokes system + +# + +from fenics import * + +import cashocs + +config = cashocs.load_config("./config.ini") +mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(30) + +v_elem = VectorElement("CG", mesh.ufl_cell(), 2) +p_elem = FiniteElement("CG", mesh.ufl_cell(), 1) +V = FunctionSpace(mesh, MixedElement([v_elem, p_elem])) +U = VectorFunctionSpace(mesh, "CG", 1) + +up = Function(V) +u, p = split(up) +vq = Function(V) +v, q = split(vq) +c = Function(U) + +Re = Constant(1e2) + +e = ( + inner(grad(u), grad(v)) * dx + + Constant(Re) * dot(grad(u) * u, v) * dx + - p * div(v) * dx + - q * div(u) * dx + - inner(c, v) * dx +) + + +def pressure_point(x, on_boundary): + return near(x[0], 0) and near(x[1], 0) + + +no_slip_bcs = cashocs.create_dirichlet_bcs( + V.sub(0), Constant((0, 0)), boundaries, [1, 2, 3] +) +lid_velocity = Expression(("4*x[0]*(1-x[0])", "0.0"), degree=2) +bc_lid = DirichletBC(V.sub(0), lid_velocity, boundaries, 4) +bc_pressure = DirichletBC(V.sub(1), Constant(0), pressure_point, method="pointwise") +bcs = no_slip_bcs + [bc_lid, bc_pressure] + +alpha = 1e-5 +u_d = Expression( + ( + "sqrt(pow(x[0], 2) + pow(x[1], 2))*cos(2*pi*x[1])", + "-sqrt(pow(x[0], 2) + pow(x[1], 2))*sin(2*pi*x[0])", + ), + degree=2, +) +J = cashocs.IntegralFunctional( + Constant(0.5) * inner(u - u_d, u - u_d) * dx + + Constant(0.5 * alpha) * inner(c, c) * dx +) +# - + +# ### Callbacks +# +# Note, that we have chosen a Reynolds number of {python}`Re = 1e2` for this demo. +# In order to solve the Navier-Stokes equations for higher Reynolds numbers, it is +# often sensible to first solve the equations for a lower Reynolds number and then +# use this solution as initial guess for the original high Reynolds number problem. +# We can use this procedure in cashocs with its {python}`pre_callback` functionality. +# A {python}`pre_callback` is a function without arguments, which gets called each time +# before solving the state equation. In our case, the {python}`pre_callback` should +# solve the Navier-Stokes equations for a lower Reynolds number, so we define it as +# follows + + +def pre_callback(): + print("Solving low Re Navier-Stokes equations for homotopy.") + v, q = TestFunctions(V) + e = ( + inner(grad(u), grad(v)) * dx + + Constant(Re / 10.0) * dot(grad(u) * u, v) * dx + - p * div(v) * dx + - q * div(u) * dx + - inner(c, v) * dx + ) + cashocs.newton_solve(e, up, bcs, verbose=False) + + +# where we solve the Navier-Stokes equations with a lower Reynolds number of +# {python}`Re / 10.0`. Later on, we inject this {python}`pre_callback` into cashocs by +# using the +# {py:meth}`inject_pre_callback ` +# method. +# +# Additionally, cashocs implements the functionality of also performing a pre-defined +# action after each gradient computation, given by a so-called {python}`post_callback`. +# In our case, we just want to print a statement so that we can visualize what is +# happening. Therefore, we define our {python}`post_callback` as + + +def post_callback(): + print("Performing an action after computing the gradient.") + + +# Next, before we can inject these two callbacks, we first have to define the optimal +# control problem + +ocp = cashocs.OptimalControlProblem(e, bcs, J, up, c, vq, config=config) + +# Finally, we can inject both hooks via + +ocp.inject_pre_callback(pre_callback) +ocp.inject_post_callback(post_callback) + +# ::::{note} +# We can also save one line and use the code +# :::python +# ocp.inject_pre_post_hook(pre_hook, post_hook) +# ::: +# +# which is equivalent to the above two lines. +# :::: +# +# And in the end, we solve the problem with + +ocp.solve() + +# We visualize the results with the lines + +# + +u, p = up.split(True) +import matplotlib.pyplot as plt + +plt.figure(figsize=(15, 5)) + +plt.subplot(1, 3, 1) +fig = plot(c) +plt.colorbar(fig, fraction=0.046, pad=0.04) +plt.title("Control variable c") + +plt.subplot(1, 3, 2) +fig = plot(u) +plt.colorbar(fig, fraction=0.046, pad=0.04) +plt.title("State variable u") + +plt.subplot(1, 3, 3) +fig = plot(u_d, mesh=mesh) +plt.colorbar(fig, fraction=0.046, pad=0.04) +plt.title("Desired state u_d") + +plt.tight_layout() +# plt.savefig("./img_pre_post_callbacks.png", dpi=150, bbox_inches="tight") +# - + +# and the results are given below +# ![](/../../demos/documented/optimal_control/pre_post_callbacks/img_pre_post_callbacks.png) diff --git a/demos/documented/optimal_control/pre_post_hooks/img_pre_post_hooks.png b/demos/documented/optimal_control/pre_post_callbacks/img_pre_post_callbacks.png similarity index 100% rename from demos/documented/optimal_control/pre_post_hooks/img_pre_post_hooks.png rename to demos/documented/optimal_control/pre_post_callbacks/img_pre_post_callbacks.png diff --git a/demos/documented/optimal_control/pre_post_hooks/demo_pre_post_hooks.py b/demos/documented/optimal_control/pre_post_hooks/demo_pre_post_hooks.py deleted file mode 100644 index b25336f1..00000000 --- a/demos/documented/optimal_control/pre_post_hooks/demo_pre_post_hooks.py +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright (C) 2020-2022 Sebastian Blauth -# -# This file is part of cashocs. -# -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/optimal_control/doc_pre_post_hooks.html. - -""" - -from fenics import * - -import cashocs - -config = cashocs.load_config("./config.ini") -mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(30) - -v_elem = VectorElement("CG", mesh.ufl_cell(), 2) -p_elem = FiniteElement("CG", mesh.ufl_cell(), 1) -V = FunctionSpace(mesh, MixedElement([v_elem, p_elem])) -U = VectorFunctionSpace(mesh, "CG", 1) - -up = Function(V) -u, p = split(up) -vq = Function(V) -v, q = split(vq) -c = Function(U) - -Re = Constant(1e2) - -e = ( - inner(grad(u), grad(v)) * dx - + Constant(Re) * dot(grad(u) * u, v) * dx - - p * div(v) * dx - - q * div(u) * dx - - inner(c, v) * dx -) - - -def pressure_point(x, on_boundary): - return near(x[0], 0) and near(x[1], 0) - - -no_slip_bcs = cashocs.create_dirichlet_bcs( - V.sub(0), Constant((0, 0)), boundaries, [1, 2, 3] -) -lid_velocity = Expression(("4*x[0]*(1-x[0])", "0.0"), degree=2) -bc_lid = DirichletBC(V.sub(0), lid_velocity, boundaries, 4) -bc_pressure = DirichletBC(V.sub(1), Constant(0), pressure_point, method="pointwise") -bcs = no_slip_bcs + [bc_lid, bc_pressure] - -alpha = 1e-5 -u_d = Expression( - ( - "sqrt(pow(x[0], 2) + pow(x[1], 2))*cos(2*pi*x[1])", - "-sqrt(pow(x[0], 2) + pow(x[1], 2))*sin(2*pi*x[0])", - ), - degree=2, -) -J = cashocs.IntegralFunctional( - Constant(0.5) * inner(u - u_d, u - u_d) * dx - + Constant(0.5 * alpha) * inner(c, c) * dx -) - - -def pre_callback(): - print("Solving low Re Navier-Stokes equations for homotopy.") - v, q = TestFunctions(V) - e = ( - inner(grad(u), grad(v)) * dx - + Constant(Re / 10.0) * dot(grad(u) * u, v) * dx - - p * div(v) * dx - - q * div(u) * dx - - inner(c, v) * dx - ) - cashocs.newton_solve(e, up, bcs, verbose=False) - - -def post_callback(): - print("Performing an action after computing the gradient.") - - -ocp = cashocs.OptimalControlProblem(e, bcs, J, up, c, vq, config=config) -ocp.inject_pre_callback(pre_callback) -ocp.inject_post_callback(post_callback) -ocp.solve() - - -### Post Processing - -u, p = up.split(True) -import matplotlib.pyplot as plt - -plt.figure(figsize=(15, 5)) - -plt.subplot(1, 3, 1) -fig = plot(c) -plt.colorbar(fig, fraction=0.046, pad=0.04) -plt.title("Control variable c") - -plt.subplot(1, 3, 2) -fig = plot(u) -plt.colorbar(fig, fraction=0.046, pad=0.04) -plt.title("State variable u") - -plt.subplot(1, 3, 3) -fig = plot(u_d, mesh=mesh) -plt.colorbar(fig, fraction=0.046, pad=0.04) -plt.title("Desired state u_d") - -plt.tight_layout() -# plt.savefig("./img_pre_post_hooks.png", dpi=150, bbox_inches="tight") diff --git a/demos/documented/optimal_control/scalar_control_tracking/demo_scalar_control_tracking.py b/demos/documented/optimal_control/scalar_control_tracking/demo_scalar_control_tracking.py index fdac3071..d0fc3cd8 100755 --- a/demos/documented/optimal_control/scalar_control_tracking/demo_scalar_control_tracking.py +++ b/demos/documented/optimal_control/scalar_control_tracking/demo_scalar_control_tracking.py @@ -1,24 +1,62 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_scalar_control_tracking)= +# # Tracking of Scalar Functionals for Optimal Control Problems # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo we investigate cashocs functionality of tracking scalar functionals +# such as cost functional values and other quanitites, which typically +# arise after integration. For this, we investigate the problem # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/optimal_control/doc_scalar_control_tracking.html. - -""" +# $$ +# \begin{align} +# &\min\; J(y,u) = \frac{1}{2} \left( \int_{\Omega} y^2 +# \text{ d}x - C_{des} \right)^2 \\ +# &\text{ subject to } \qquad +# \begin{alignedat}[t]{2} +# -\Delta y &= u \quad &&\text{ in } \Omega,\\ +# y &= 0 \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# For this example, we do not consider control constraints, +# but search for an optimal control u in the entire space $L^2(\Omega)$, +# for the sake of simplicitiy. For the domain under consideration, we use the unit +# square $\Omega = (0, 1)^2$, since this is built into cashocs. +# +# In the following, we will describe how to solve this problem +# using cashocs. Moreover, we also detail alternative / equivalent FEniCS code which +# could be used to define the problem instead. +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_scalar_control_tracking.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### The state problem +# +# The difference to {ref}`demo_poisson` is that the cost functional does now track the +# value of the $L^2$ norm of $y$ against a desired value of $C_{des}$, +# and not the state $y$ itself. Other than that, the corresponding PDE constraint +# and its setup are completely analogous to {ref}`demo_poisson` +# + from fenics import * import cashocs @@ -36,19 +74,73 @@ e = inner(grad(y), grad(p)) * dx - u * p * dx bcs = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) +# - + +# ### Definition of the scalar tracking type cost functional +# +# To define the desired tracking type functional, note that cashocs implements the +# functional for the following kind of cost functionals +# +# $$ +# \begin{aligned} +# J(y,u) &= \frac{1}{2} \vert \int_{\Sigma} f(y,u) \text{ d}m +# - C_{des} \vert^2 \\ +# \end{aligned} +# $$ +# +# where $\Sigma$ is some part of the domain $\Omega$, e.g. the $\Omega$ itself, a +# subdomain, or its boundary $\Gamma$, and $\text{d}m$ is the corresponding integration +# measure. +# +# To define such a cost functional, we only need to define the integrands, i.e., +# +# $$ +# f(y,u) \text{ d}m +# $$ +# +# which we will do in the UFL of FEniCS, as well as the goals of the tracking type +# functionals, i.e., +# +# $$ +# C_{des}. +# $$ +# +# To do so, we use the {py:class}`cashocs.ScalarTrackingFunctional` class. We first +# define the integrand $f(y,u) \text{d}m = y^2 \text{d}x$ via integrand = y * y * dx + +# and define $C_{des}$ as + tracking_goal = 1.0 + +# With these definitions, we can define the cost functional as + J_tracking = cashocs.ScalarTrackingFunctional(integrand, tracking_goal) +# :::{note} +# The factor in front of the quadratic term can also be adapted, by using the keyword +# argument {python}`weight` of {py:class}`cashocs.ScalarTrackingFunctional`. Note that +# the default factor is {python}`0.5`, and that each weight will be multiplied by this +# value. +# ::: +# +# Finally, we set up our optimization problem and solve it with the +# {py:meth}`solve ` method of the optimization +# problem + ocp = cashocs.OptimalControlProblem(e, bcs, J_tracking, y, u, p, config=config) ocp.solve() +# To verify, that our approach is correct, we also print the value of the integral, +# which we want to track + print("L2-Norm of y squared: " + format(assemble(y * y * dx), ".3e")) -### Post Processing +# Finally, we visualize the result with the lines +# + import matplotlib.pyplot as plt plt.figure(figsize=(10, 5)) @@ -65,3 +157,7 @@ plt.tight_layout() # plt.savefig('./img_scalar_control_tracking.png', dpi=150, bbox_inches='tight') +# - + +# and the result looks as follows +# ![](/../../demos/documented/optimal_control/scalar_control_tracking/img_scalar_control_tracking.png) diff --git a/demos/documented/optimal_control/sparse_control/demo_sparse_control.py b/demos/documented/optimal_control/sparse_control/demo_sparse_control.py index e14e2a71..672e6b88 100755 --- a/demos/documented/optimal_control/sparse_control/demo_sparse_control.py +++ b/demos/documented/optimal_control/sparse_control/demo_sparse_control.py @@ -1,24 +1,54 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_sparse_control)= +# # Sparse Control # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo, we investigate a possibility for obtaining sparse optimal controls. +# To do so, we use a sparsity promoting $L^1$ regularization. Hence, our model problem +# for this demo is given by # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/optimal_control/doc_sparse_control.html. - -""" +# $$ +# \begin{align} +# &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 \text{ d}x +# + \frac{\alpha}{2} \int_{\Omega} \lvert u \rvert \text{ d}x \\ +# &\text{ subject to } \qquad +# \begin{alignedat}[t]{2} +# -\Delta y &= u \quad &&\text{ in } \Omega,\\ +# y &= 0 \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# This is basically the same problem as in {ref}`demo_poisson`, but the regularization +# is now not the $L^2$ norm squared, but just the $L^1$ norm. +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_sparse_control.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# The implementation of this problem is completely analogous to the one of +# {ref}`demo_poisson`, the only difference is the definition of the cost functional. +# + from fenics import * import cashocs @@ -36,16 +66,34 @@ y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) alpha = 1e-4 +# - + +# Next, we define the cost function, now using the mentioned $L^1$ norm for the +# regularization + J = cashocs.IntegralFunctional( Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * abs(u) * dx ) +# ::::{note} +# Note that for the regularization term we now do not use +# {python}`Constant(0.5*alpha)*u*u*dx`, which corresponds to the $L^2(\Omega)$ norm +# squared, but rather +# :::python +# Constant(0.5 * alpha) * abs(u) * dx +# ::: +# +# which corresponds to the $L^1(\Omega)$ norm. Other than that, the code is identical. +# :::: +# +# We solve the problem analogously to the previous demos + ocp = cashocs.OptimalControlProblem(e, bcs, J, y, u, p, config=config) ocp.solve() +# and we visualize the results with the code -### Post Processing - +# + import matplotlib.pyplot as plt plt.figure(figsize=(15, 5)) @@ -67,3 +115,12 @@ plt.tight_layout() # plt.savefig('./img_sparse_control.png', dpi=150, bbox_inches='tight') +# - + +# which yields the following output +# ![](/../../demos/documented/optimal_control/sparse_control/img_sparse_control.png) +# +# :::{note} +# The oscillations in between the peaks for the control variable {python}`u` are just +# numerical noise, which comes from the discretization error. +# ::: diff --git a/demos/documented/optimal_control/state_constraints/demo_state_constraints.py b/demos/documented/optimal_control/state_constraints/demo_state_constraints.py index c7074d2e..109025dc 100755 --- a/demos/documented/optimal_control/state_constraints/demo_state_constraints.py +++ b/demos/documented/optimal_control/state_constraints/demo_state_constraints.py @@ -1,24 +1,76 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_state_constraints)= +# # Optimal Control with State Constraints # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo we investigate how state constraints can be handled in cashocs. Thanks to +# the high level interface for solving (control-constrained) optimal control problems, +# the state constrained case can be treated (approximately) using a Moreau-Yosida +# regularization, which we show in the following. As model problem, we consider the +# following one # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/optimal_control/doc_state_constraints.html. - -""" +# $$ +# \begin{align} +# &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 \text{ d}x +# + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x \\ +# &\text{ subject to } \qquad +# \begin{alignedat}[t]{2} +# -\Delta y &= u \quad &&\text{ in } \Omega,\\ +# y &= 0 \quad &&\text{ on } \Gamma, \\ +# y &\leq \bar{y} \quad &&\text{ in } \Omega, +# \end{alignedat} +# \end{align} +# $$ +# +# see, e.g., [Hinze, Pinnau, Ulbrich, and Ulbrich - +# Optimization with PDE constraints](https://doi.org/10.1007/978-1-4020-8839-1). +# +# ## Moreau-Yosida regularization +# +# Instead of solving this problem directly, the Moreau-Yosida regularization instead +# solves a sequence of problems without state constraints which are of the form +# +# $$ +# \min J_\gamma(y, u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 \text{ d}x +# + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x +# + \frac{1}{2\gamma} \int_\Omega \vert \max\left( 0, \hat{\mu} +# + \gamma (y - \bar{y}) \right) \vert^2 \text{ d}x +# $$ +# +# for $\gamma \to +\infty$. We employ a simple homotopy method, and solve the problem +# for one value of $\gamma$, and then use this solution as initial guess for the next +# higher value of $\gamma$. As initial guess we use the solution of the unconstrained +# problem. For a detailed discussion of the Moreau-Yosida regularization, we refer the +# reader to, e.g., [Hinze, Pinnau, Ulbrich, and Ulbrich - +# Optimization with PDE constraints](https://doi.org/10.1007/978-1-4020-8839-1). +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_state_constraints.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### The initial guess for the homotopy +# +# As mentioned earlier, we first solve the unconstrained problem to get an initial +# guess for the homotopy method. This is done in complete analogy to {ref}`demo_poisson` +# + from fenics import * import numpy as np @@ -43,11 +95,26 @@ J_init = cashocs.IntegralFunctional(J_init_form) ocp_init = cashocs.OptimalControlProblem(e, bcs, J_init, y, u, p, config=config) ocp_init.solve() - +# - + +# :::{note} +# Cashocs automatically updates the user input during the runtime of the optimization +# algorithm. Hence, after the {py:meth}`ocp_init.solve() +# ` command has returned, the solution is already +# stored in {python}`u`. +# ::: +# +# ### The regularized problems +# +# For the homotopy method with the Moreau-Yosida regularization, we first define the +# upper bound for the state $\bar{y}$ and select a sequence of values for $\gamma$ via y_bar = 1e-1 gammas = [pow(10, i) for i in np.arange(1, 9, 3)] +# Solving the regularized problems is then as simple as writing a {python}`for` loop + +# + for gamma in gammas: J_form = J_init_form + cashocs._utils.moreau_yosida_regularization( @@ -57,15 +124,49 @@ ocp_gamma = cashocs.OptimalControlProblem(e, bcs, J, y, u, p, config=config) ocp_gamma.solve() +# - + +# Here, we use a {python}`for` loop, define the new cost functional (with the new value of +# $\gamma$), set up the optimal control problem and solve it, as previously. +# +# :::{hint} +# Note that we could have also defined {python}`y_bar` as a {py:class}`fenics.Function` +# or {py:class}`fenics.Expression`, and the method would have worked exactly the same, +# the corresponding object just has to be a valid input for an UFL form. +# ::: +# +# ::::{note} +# We could have also defined the Moreau-Yosida regularization of the inequality +# constraint directly, with the following code +# :::python +# J = cashocs.IntegralFunctional( +# J_init_form +# + Constant(1 / (2 * gamma)) * pow(Max(0, Constant(gamma) * (y - y_bar)), 2) * dx +# ) +# ::: +# +# However, this is directly implemented in +# {py:func}`cashocs.moreau_yosida_regularization`, which is why we use this function in +# the demo. +# :::: +# +# ### Validation of the method +# +# Finally, we perform a post-processing to see whether the state constraint is +# (approximately) satisfied. Therefore, we compute the maximum value of {python}`y`, +# and compute the relative error between this and {python}`y_bar` y_max = np.max(y.vector()[:]) error = abs(y_max - y_bar) / abs(y_bar) * 100 print("Maximum value of y: " + str(y_max)) print("Relative error between y_max and y_bar: " + str(error) + " %") +# As the error is about 0.01 %, we observe that the regularization indeed works +# as expected, and this tolerance is sufficiently low for practical applications. +# +# The visualization of the solution is computed with the lines -### Post Processing - +# + import matplotlib.pyplot as plt plt.figure(figsize=(15, 5)) @@ -87,3 +188,7 @@ plt.tight_layout() # plt.savefig('./img_state_constraints.png', dpi=150, bbox_inches='tight') +# - + +# and looks as follows +# ![](/../../demos/documented/optimal_control/state_constraints/img_state_constraints.png) diff --git a/demos/documented/optimal_control/stokes/demo_stokes.py b/demos/documented/optimal_control/stokes/demo_stokes.py index 4b7ebab0..e45d8d30 100755 --- a/demos/documented/optimal_control/stokes/demo_stokes.py +++ b/demos/documented/optimal_control/stokes/demo_stokes.py @@ -1,44 +1,126 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_stokes)= +# # Distributed Control of a Stokes Problem # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo we investigate how cashocs can be used to treat a different kind +# of PDE constraint, in particular, we investigate a Stokes problem. The optimization +# problem reads as follows # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/optimal_control/doc_stokes.html. - -""" +# $$ +# \begin{align} +# &\min\; J(u, c) = \frac{1}{2} \int_\Omega \left\lvert u - u_d \right\rvert^2 +# \text{ d}x + +# \frac{\alpha}{2} \int_\Omega \left\lvert c \right\rvert^2 \text{ d}x \\ +# &\text{ subject to } \qquad +# \begin{alignedat}[t]{2} +# -\Delta u + \nabla p &= c \quad &&\text{ in } \Omega, \\ +# \text{div}(u) &= 0 \quad &&\text{ in } \Omega,\\ +# u &= u_\text{dir} \quad &&\text{ on } \Gamma^\text{dir},\\ +# u &= 0 \quad &&\text{ on } \Gamma^\text{no slip},\\ +# p &= 0 \quad &&\text{ at } x^\text{pres}. +# \end{alignedat} +# \end{align} +# $$ +# +# In contrast to the other demos, here we denote by $u$ the velocity of a fluid and by +# $p$ its pressure, which are the two state variables. The control is now denoted by $c$ +# and acts as a volume source for the system. The tracking type cost functional again +# aims at getting the velocity u close to some desired velocity $u_d$. +# +# For this example, the geometry is again given by $\Omega = (0,1)^2$, and we take a +# look at the setting of the well known lid driven cavity benchmark here. In particular, +# the boundary conditions are classical no slip boundary conditions at the left, right, +# and bottom sides of the square. On the top (or the lid), a velocity $u_\text{dir}$ is +# prescribed, pointing into the positive x-direction. +# Note, that since this problem has Dirichlet conditions on the entire boundary, the +# pressure is only determined up to a constant, and hence we have to specify another +# condition to ensure uniqueness. For this demo we choose another Dirichlet condition, +# specifying the value of the pressure at a single point in the domain. Alternatively, +# we could have also required that, e.g., the integral of the velocity $u$ over $\Omega$ +# vanishes (the implementation would then only be slightly longer, but not as +# intuitive). +# An example of how to treat such an additional constraint in FEniCS and cashocs +# can be found in {ref}`demo_inverse_tomography`. +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_stokes.py +# `, +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# The initialization is the same as in {ref}`demo_poisson`, i.e., +# + from fenics import * import cashocs config = cashocs.load_config("./config.ini") mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(30) +# - + +# For the solution of the Stokes (and adjoint Stokes) system, which have a saddle point +# structure, we have to choose LBB stable elements or a suitable stabilization, see e.g. +# [Ern and Guermond - Theory and Practice of Finite Elements]( +# https://doi.org/10.1007/978-1-4757-4355-5). For this demo, we use the classical +# Taylor-Hood elements of piecewise quadratic Lagrange elements for the velocity, and +# piecewise linear ones for the pressure, which are LBB-stable. These are defined as v_elem = VectorElement("CG", mesh.ufl_cell(), 2) p_elem = FiniteElement("CG", mesh.ufl_cell(), 1) V = FunctionSpace(mesh, MixedElement([v_elem, p_elem])) U = VectorFunctionSpace(mesh, "CG", 1) +# Moreover, we have defined the control space {python}`U` as +# {py:class}`fenics.FunctionSpace` with piecewise linear Lagrange elements. +# +# Next, we set up the corresponding function objects, as follows + up = Function(V) u, p = split(up) vq = Function(V) v, q = split(vq) c = Function(U) +# Here, {python}`up` plays the role of the state variable, having components {python}`u` +# and {python}`p`, which are extracted using the {py:func}`fenics.split` command. The +# adjoint state {python}`vq` is structured in exactly the same fashion. See +# {ref}`demo_monolithic_problems` for more details. Similarly to there, {python}`v` will +# play the role of the adjoint velocity, and {python}`q` the one of the adjoint +# pressure. +# +# Next up is the definition of the Stokes system. This can be done via + e = inner(grad(u), grad(v)) * dx - p * div(v) * dx - q * div(u) * dx - inner(c, v) * dx +# :::{note} +# Note, that we have chosen to consider the incompressibility condition with a negative +# sign. This is used to make sure that the resulting system is symmetric (but +# indefinite) which can simplify its solution. Using the positive sign for the +# divergence constraint would instead lead to a non-symmetric but positive-definite +# system. +# ::: +# +# The boundary conditions for this system can be defined as follows + def pressure_point(x, on_boundary): return near(x[0], 0) and near(x[1], 0) @@ -52,6 +134,22 @@ def pressure_point(x, on_boundary): bc_pressure = DirichletBC(V.sub(1), Constant(0), pressure_point, method="pointwise") bcs = no_slip_bcs + [bc_lid, bc_pressure] +# Here, we first define the point $x^\text{pres}$, where the pressure is set to 0. +# Afterwards, we use the cashocs function {py:func}`create_dirichlet_bcs +# ` to quickly create the no slip conditions at the left, +# right, and bottom of the cavity. Next, we define the Dirichlet velocity $u_\text{dir}$ +# for the lid of the cavity as a {py:class}`fenics.Expression`, and create a +# corresponding boundary condition. Finally, the Dirichlet condition for the pressure is +# defined. Note that in order to make this work, one has to specify the keyword argument +# {python}`method='pointwise'`. +# +# ### Defintion of the optimization problem +# +# The definition of the optimization problem is in complete analogy to the previous +# ones we considered. The only difference is the fact that we now have to use +# {py:func}`fenics.inner` to multiply the vector valued functions {python}`u`, +# {python}`u_d` and {python}`c`. + alpha = 1e-5 u_d = Expression( ( @@ -65,13 +163,21 @@ def pressure_point(x, on_boundary): + Constant(0.5 * alpha) * inner(c, c) * dx ) +# As in {ref}`demo_monolithic_problems`, we then set up the optimization problem +# {python}`ocp` and solve it with the command +# {py:meth}`ocp.solve() ` + ocp = cashocs.OptimalControlProblem(e, bcs, J, up, c, vq, config=config) ocp.solve() - -### Post Processing +# For post-processing, we then create deep copies of the single components of the state +# and the adjoint variables with u, p = up.split(True) + +# and we then visualize the results with + +# + import matplotlib.pyplot as plt plt.figure(figsize=(15, 5)) @@ -93,3 +199,7 @@ def pressure_point(x, on_boundary): plt.tight_layout() # plt.savefig('./img_stokes.png', dpi=150, bbox_inches='tight') +# - + +# so that the results look like this +# ![](/../../demos/documented/optimal_control/stokes/img_stokes.png) diff --git a/demos/documented/shape_optimization/custom_scalar_product/demo_custom_scalar_product.py b/demos/documented/shape_optimization/custom_scalar_product/demo_custom_scalar_product.py index 753bf4e9..3c166be8 100755 --- a/demos/documented/shape_optimization/custom_scalar_product/demo_custom_scalar_product.py +++ b/demos/documented/shape_optimization/custom_scalar_product/demo_custom_scalar_product.py @@ -1,24 +1,61 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_custom_scalar_product)= +# # Custom Scalar Products for Shape Gradient Computation # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo, we show how to supply a custom bilinear form for the computation +# of the shape gradient with cashocs. For the sake of simplicity, we again consider +# our model problem from {ref}`demo_shape_poisson`, given by # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/shape_optimization/doc_custom_scalar_product.html. - -""" +# $$ +# \begin{align} +# &\min_\Omega J(u, \Omega) = \int_\Omega u \text{ d}x \\ +# &\text{subject to} \qquad +# \begin{alignedat}[t]{2} +# -\Delta u &= f \quad &&\text{ in } \Omega,\\ +# u &= 0 \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# For the initial domain, we use the unit disc +# $\Omega = \{ x \in \mathbb{R}^2 \,\mid\, \lvert\lvert x \rvert\rvert_2 < 1 \}$ and the +# right-hand side $f$ is given by +# +# $$ +# f(x) = 2.5 \left( x_1 + 0.4 - x_2^2 \right)^2 + x_1^2 + x_2^2 - 1. +# $$ +# +# ## Implementation +# +# The complete python code can be found in the file +# {download}`demo_custom_scalar_product.py +# `, +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# The demo program closely follows the one from {ref}`demo_shape_poisson`, so that up +# to the definition of the {py:class}`ShapeOptimizationProblem +# `, the code is identical to the one in +# {ref}`demo_shape_poisson`, and given by +# + from fenics import * import cashocs @@ -38,13 +75,39 @@ bcs = DirichletBC(V, Constant(0), boundaries, 1) J = cashocs.IntegralFunctional(u * dx) +# - + +# ### Definition of the scalar product +# +# To define the scalar product that shall be used for the shape optimization, we can +# proceed analogously to {ref}`demo_neumann_control` and define the corresponding +# bilinear form in FEniCS. However, note that one has to use a +# {py:class}`fenics.VectorFunctionSpace` with piecewise linear Lagrange elements, i.e., +# one has to define the corresponding function space as VCG = VectorFunctionSpace(mesh, "CG", 1) + +# With this, we can now define the bilinear form as follows + shape_scalar_product = ( inner((grad(TrialFunction(VCG))), (grad(TestFunction(VCG)))) * dx + inner(TrialFunction(VCG), TestFunction(VCG)) * dx ) +# ::::{note} +# Note that we cannot use the formulation +# :::python +# shape_scalar_product = inner((grad(TrialFunction(VCG))), (grad(TestFunction(VCG))))*dx +# ::: +# +# as this would not yield a coercive bilinear form for this problem. This is due to +# the fact that the entire boundary of $\Omega$ is variable. Hence, we actually +# need this second term. +# :::: +# +# Finally, we can set up the {py:class}`ShapeOptimizationProblem +# ` and solve it with the lines + sop = cashocs.ShapeOptimizationProblem( e, bcs, @@ -57,9 +120,9 @@ ) sop.solve() +# We visualize the result using matplotlib -### Post Processing - +# + import matplotlib.pyplot as plt plt.figure(figsize=(10, 5)) @@ -77,3 +140,7 @@ plt.tight_layout() # plt.savefig('./img_custom_scalar_product.png', dpi=150, bbox_inches='tight') +# - + +# The result of the optimization looks like this +# ![](/../../demos/documented/shape_optimization/custom_scalar_product/img_custom_scalar_product.png) diff --git a/demos/documented/shape_optimization/eikonal_stiffness/demo_eikonal_stiffness.py b/demos/documented/shape_optimization/eikonal_stiffness/demo_eikonal_stiffness.py index 6c4b7d72..325326eb 100755 --- a/demos/documented/shape_optimization/eikonal_stiffness/demo_eikonal_stiffness.py +++ b/demos/documented/shape_optimization/eikonal_stiffness/demo_eikonal_stiffness.py @@ -1,24 +1,118 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_eikonal_stiffness)= +# # Computing the Shape Stiffness via Distance to the Boundaries # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# We are solving the same problem as in {ref}`demo_shape_stokes`, but now use +# a different approach for computing the stiffness of the shape gradient. +# Recall, that the corresponding (regularized) shape optimization problem is given by # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/shape_optimization/doc_shape_stokes.html. - -""" +# $$ +# \begin{align} +# \min_\Omega J(u, \Omega) = &\int_{\Omega^\text{flow}} Du : Du\ \text{ d}x + +# \frac{\mu_\text{vol}}{2} \left( \int_\Omega 1 \text{ d}x +# - \text{vol}(\Omega_0) \right)^2 \\ +# &+ \frac{\mu_\text{bary}}{2} \left\lvert \frac{1}{\text{vol}(\Omega)} +# \int_\Omega x \text{ d}x - \text{bary}(\Omega_0) \right\rvert^2 \\ +# &\text{subject to } \qquad +# \begin{alignedat}[t]{2} +# - \Delta u + \nabla p &= 0 \quad &&\text{ in } \Omega, \\ +# \text{div}(u) &= 0 \quad &&\text{ in } \Omega, \\ +# u &= u^\text{in} \quad &&\text{ on } \Gamma^\text{in}, \\ +# u &= 0 \quad &&\text{ on } \Gamma^\text{wall} \cup \Gamma^\text{obs}, \\ +# \partial_n u - p n &= 0 \quad &&\text{ on } \Gamma^\text{out}. +# \end{alignedat} +# \end{align} +# $$ +# +# For a background on the stiffness of the shape gradient, we refer to +# {ref}`config_shape_shape_gradient`, where it is defined as the parameter +# $\mu$ used in the computation of the shape gradient. Note that the distance +# computation is done via an eikonal equation, hence the name of the demo. +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_eikonal_stiffness.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Changes in the config file +# +# In order to compute the stiffness $\mu$ based on the distance to selected boundaries, +# we only have to change the configuration file we are using, the python code +# for solving the shape optimization problem with cashocs stays exactly as +# it was in {ref}`demo_shape_stokes`. +# +# To use the stiffness computation based on the distance to the boundary, we add the +# following lines to the config file +# :::ini +# use_distance_mu = True +# dist_min = 0.05 +# dist_max = 1.25 +# mu_min = 5e2 +# mu_max = 1.0 +# smooth_mu = false +# boundaries_dist = [4] +# ::: +# +# The first line +# :::ini +# use_distance_mu = True +# ::: +# +# ensures that the stiffness will be computed based on the distance to the boundary. +# +# The next four lines then specify the behavior of this computation. In particular, +# we have the following behavior for $\mu$ +# +# $$ +# \mu = \begin{cases} +# \mu_\mathrm{min} \quad \text{ if } \delta \leq \delta_\mathrm{min},\\ +# \mu_\mathrm{max} \quad \text{ if } \delta \geq \delta_\mathrm{max} +# \end{cases} +# $$ +# +# where $\delta$ denotes the distance to the boundary and $\delta_\mathrm{min}$ +# and $\delta_\mathrm{max}$ correspond to {ini}`dist_min` and {ini}`dist_max`, +# respectively. +# +# The values in-between are given by interpolation. Either a linear, continuous +# interpolation is used, or a smooth $C^1$ interpolation given by a third order +# polynomial. These can be selected with the option +# :::ini +# smooth_mu = False +# ::: +# +# where {ini}`smooth_mu = True` uses the third order polynomial, and +# {ini}`smooth_mu = False` uses the linear function. +# +# Finally, the line +# :::ini +# boundaries_dist = [4] +# ::: +# +# specifies, which boundaries are considered for the distance computation. These are +# again specified using the boundary markers, as it was previously explained in +# {ref}`config_shape_shape_gradient`. +# +# For the sake of completeness, here is the code for solving the problem +# + from fenics import * import cashocs @@ -49,9 +143,6 @@ sop = cashocs.ShapeOptimizationProblem(e, bcs, J, up, vq, boundaries, config=config) sop.solve() - -### Post Processing - import matplotlib.pyplot as plt plt.figure(figsize=(15, 3)) @@ -77,3 +168,7 @@ plt.tight_layout() # plt.savefig("./img_eikonal_stiffness.png", dpi=150, bbox_inches="tight") +# - + +# The results should look like this +# ![](/../../demos/documented/shape_optimization/shape_stokes/img_shape_stokes.png) diff --git a/demos/documented/shape_optimization/inverse_tomography/demo_inverse_tomography.py b/demos/documented/shape_optimization/inverse_tomography/demo_inverse_tomography.py index c77b56c3..bba046b2 100755 --- a/demos/documented/shape_optimization/inverse_tomography/demo_inverse_tomography.py +++ b/demos/documented/shape_optimization/inverse_tomography/demo_inverse_tomography.py @@ -1,31 +1,110 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_inverse_tomography)= +# # Inverse Problem in Electric Impedance Tomography # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# For this demo, we investigate an inverse problem in the setting of electric +# impedance tomography. It is based on the one used in the preprint +# [Blauth - Nonlinear Conjugate Gradient Methods for PDE Constrained Shape Optimization +# Based on Steklov-Poincaré-Type Metrics](https://arxiv.org/abs/2007.12891). The +# problem reads # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/shape_optimization/doc_inverse_tomography.html. - -""" +# $$ +# \begin{align} +# &\min_{\Omega} J(u, \Omega) = \sum_{i=1}^{M} \frac{\nu_i}{2} +# \int_{\partial D} \left( u_i - m_i \right)^2 \text{ d}s \\ +# &\text{subject to} \qquad +# \begin{alignedat}[t]{2} +# -\kappa^\text{in} \Delta u_i^\text{in} &= 0 \quad &&\text{ in } \Omega,\\ +# -\kappa^\text{out} \Delta u_i^\text{out} &= 0 +# \quad &&\text{ in } D \setminus \Omega, \\ +# \kappa^\text{out} \partial_n u^\text{out}_i &= f_i +# \quad &&\text{ on } \partial D, \\ +# u^\text{out}_i &= u^\text{in}_i \quad &&\text{ on } \Gamma, \\ +# \kappa^\text{out} \partial_{n^\text{in}} u^\text{out}_i &= +# \kappa^\text{in} \partial_{n^\text{in}} u^\text{in}_i +# \quad &&\text{ on } \Gamma, \\ +# \int_{\partial D} u_i^\text{out} \text{ d}s &= 0. +# \end{alignedat} +# \end{align} +# $$ +# +# The setting is as follows. +# We have an object $\Omega$ that is located inside another one, $D \setminus \Omega$. +# Our goal is to identify the shape of the interior from measurements of the electric +# potential, denoted by $u$, on the outer boundary of $D$, to which we have +# access. In particular, we consider the case of having several measurements +# at our disposal, as indicated by the index $i$. The PDE constraint now models +# the electric potential in experiment $i$, namely $u_i$, which results +# from an application of the electric current $f_i$ on the outer boundary $\partial D$. +# Our goal of identifying the interior body $\Omega$ is modeled by the +# tracking type cost functional, which measures the $L^2(\Gamma)$ distance +# between the simulated electric potential $u_i$ and the measured one, $m_i$. +# In particular, note that the outer boundaries, i.e. $\partial D$ are fixed, and +# only the internal boundary $\Gamma = \partial \Omega$ is deformable. +# For a detailed description of this problem as well as its physical interpretation, +# we refer to the preprint [Blauth - Nonlinear Conjugate Gradient Methods for PDE +# Constrained Shape Optimization Based on Steklov-Poincaré-Type Metrics]( +# https://arxiv.org/abs/2007.12891). +# +# For our demo, we use as domain the unit square $D = (0,1)^2$, and the initial +# geometry of $\Omega$ is a square inside $D$. We consider the case of +# three measurements, so that $M = 3$, given by +# +# $$ +# f_1 = 1 \quad \text{ on } \Gamma^l \cup \Gamma^r +# \qquad \text{ and } \qquad f_1 = -1 \quad \text{ on } \Gamma^t \cup \Gamma^b,\\ +# f_2 = 1 \quad \text{ on } \Gamma^l \cup \Gamma^t +# \qquad \text{ and } \qquad f_2 = -1 \quad \text{ on } \Gamma^r \cup \Gamma^b,\\ +# f_3 = 1 \quad \text{ on } \Gamma^l \cup \Gamma^b +# \qquad \text{ and } \qquad f_3 = -1 \quad \text{ on } \Gamma^r \cup \Gamma^t, +# $$ +# +# where $\Gamma^l, \Gamma^r, \Gamma^t, \Gamma^b$ are the left, right, top, and +# bottom sides of the unit square. +# +# ## Implementation +# +# The complete python code can be found in the file +# {download}`demo_inverse_tomography.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization and generation of synthetic measurements +# +# We start our code by importing FEniCS and cashocs +# + from fenics import * import cashocs +# - + +# Next, we directly define the sample values of $\kappa^\text{in}$ and +# $\kappa^\text{out}$ since these are needed later on + kappa_out = 1e0 kappa_in = 1e1 +# In the next part, we generate synthetic measurements, which correspond to $m_i$. +# To do this, we define a function {py:func}`generate_measurements()` as follows + def generate_measurements(): mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh( @@ -63,13 +142,66 @@ def generate_measurements(): return [m1, m2, m3] +# :::{admonition} Description of the function +# The code executed in {python}`generate_measurements()` is used to solve the +# state problem on a reference domain, given by the mesh `./mesh/reference.xdmf`. +# This mesh has the domain $\Omega$ as a circle in the center of the unit +# square. To distinguish between these two, we note that $D \setminus \Omega$ +# has the index / marker 1 and that $\Omega$ has the index / marker 2 in +# the corresponding GMSH file, which is then imported into {python}`subdomains`. +# +# Note, that we have to use a mixed finite element method to incorporate the +# integral constraint on the electric potential. The second component of the +# corresponding {py:class}`fenics.FunctionSpace` {python}`V` is just a scalar, +# one-dimensional, real element. The actual PDE constraint is then given by the part +# +# ```python +# ( +# kappa_out * inner(grad(u), grad(v)) * dx(1) +# + kappa_in * inner(grad(u), grad(v)) * dx(2) +# + ... +# ) +# ``` +# +# and the integral constraint is realized with the saddle point formulation +# +# ```python +# u * d * ds + v * c * ds +# ``` +# +# The right hand sides {python}`L1`, {python}`L2`, and {python}`L3` are just given by +# the Neumann boundary conditions as specified above. +# +# Finally, these PDEs are then solved via the {py:func}`fenics.solve` command, +# and then only the actual solution of the PDE (and not the Lagrange multiplier +# for the integral constraint) is returned. +# ::: +# +# As usual, we load the config into cashocs with the line + config = cashocs.load_config("./config.ini") + +# Afterwards, we import the mesh into cashocs + mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh("./mesh/mesh.xdmf") + +# Next, we define the {py:class}`fenics.FunctionSpace` object, which consists of +# CG1 elements together with a scalar, real element, which acts as a Lagrange multiplier +# for the integral constraint + cg_elem = FiniteElement("CG", mesh.ufl_cell(), 1) r_elem = FiniteElement("R", mesh.ufl_cell(), 0) V = FunctionSpace(mesh, MixedElement([cg_elem, r_elem])) +# Next, we compute the synthetic measurements via + measurement_data = generate_measurements() + +# In order to ensure that the measurements can be used and stay the same while the mesh +# undergoes changes in shape optimization, we have to use callbacks, which are explained +# in full detail in {ref}`demo_pre_post_hooks`. + +# + measurements = [ Function(V.sub(0).collapse()), Function(V.sub(0).collapse()), @@ -82,6 +214,18 @@ def pre_callback(): LagrangeInterpolator.interpolate(meas, measurement_data[i]) +# - + +# Here, the function {python}`pre_callback` is used to interpolate the fixed +# measurements to functions on the deformed geometry. +# +# ### The PDE constraint +# +# Let us now investigate how the PDE constraint is defined. As we have a mixed +# finite element problem due to the integral constraint, we proceed similarly to +# {ref}`demo_monolithic_problems` and define the first state equation with the following +# lines + uc1 = Function(V) u1, c1 = split(uc1) pd1 = Function(V) @@ -95,6 +239,9 @@ def pre_callback(): - Constant(-1) * p1 * (ds(1) + ds(2)) ) +# The remaining two experiments are defined completely analogously: + +# + uc2 = Function(V) u2, c2 = split(uc2) pd2 = Function(V) @@ -120,26 +267,81 @@ def pre_callback(): - Constant(1) * p3 * (ds(3) + ds(1)) - Constant(-1) * p3 * (ds(2) + ds(4)) ) +# - + +# Finally, we group together the state equations as well as the state and adjoint +# variables to (ordered) lists, as in {ref}`demo_multiple_variables` e = [e1, e2, e3] u = [uc1, uc2, uc3] p = [pd1, pd2, pd3] +# Since the problem only has Neumann boundary conditions, we use + bcs = [[], [], []] +# to specify this. +# +# ### The shape optimization problem +# +# The cost functional is then defined by first creating the individual summands, +# and then adding them to a list: + +# + J1 = cashocs.IntegralFunctional(Constant(0.5) * pow(u1 - measurements[0], 2) * ds) J2 = cashocs.IntegralFunctional(Constant(0.5) * pow(u2 - measurements[1], 2) * ds) J3 = cashocs.IntegralFunctional(Constant(0.5) * pow(u3 - measurements[2], 2) * ds) J = [J1, J2, J3] +# - + +# where we use a coefficient of $\nu_i = 1$ for all cases. +# +# Before we can define the shape optimization properly, we have to take a look at the +# config file to specify which boundaries are fixed, and which are deformable. There, +# we have the following lines +# :::ini +# [ShapeGradient] +# shape_bdry_def = [] +# shape_bdry_fix = [1, 2, 3, 4] +# ::: +# +# Note, that the boundaries {python}`1, 2, 3, 4` are the sides of the unit square, as +# defined in the .geo file for the geometry (located in the `./mesh/` directory), and +# they are fixed due to the problem definition (recall that $\partial D$ is fixed). +# However, at the first glance it seems that there is no deformable boundary. This +# is, however, wrong. In fact, there is still an internal boundary, namely $\Gamma$, +# which is not specified here, and which is, thus, deformable (this is the default +# behavior). +# +# ::::{warning} +# As stated in {ref}`config_shape_optimization`, we have to use the config file +# setting +# +# ```ini +# use_pull_back = False +# ``` +# +# This is due to the fact that the measurements are defined / computed on a different +# mesh / geometry than the remaining objects, and FEniCS is not able to do some +# computations in this case. However, note that the cost functional is posed on +# $\partial D$ only, which is fixed anyway. Hence, the deformation field +# vanishes there, and the corresponding diffeomorphism, which maps between the +# deformed and original domain, is just the identity mapping. In particular, +# no material derivatives are needed for the measurements, which is why it +# is safe to use {ini}`use_pull_back = False` for this particular problem. +# :::: +# +# The shape optimization problem can now be created as in {ref}`demo_shape_poisson` +# and can be solved as easily, with the commands sop = cashocs.ShapeOptimizationProblem(e, bcs, J, u, p, boundaries, config=config) sop.inject_pre_callback(pre_callback) sop.solve() +# We perform a post-processing of the results with the lines -### Post Processing - +# + import matplotlib.pyplot as plt DG0 = FunctionSpace(mesh, "DG", 0) @@ -168,3 +370,10 @@ def pre_callback(): plt.tight_layout() # plt.savefig('./img_inverse_tomography.png', dpi=150, bbox_inches='tight') +# - + +# and the results should look like this +# ![](/../../demos/documented/shape_optimization/inverse_tomography/img_inverse_tomography.png) +# +# and we observe that we are indeed able to identify the shape of the circle which +# was used to create the measurements. diff --git a/demos/documented/shape_optimization/p_laplacian/demo_p_laplacian.py b/demos/documented/shape_optimization/p_laplacian/demo_p_laplacian.py index 5538d0eb..f2697c0d 100644 --- a/demos/documented/shape_optimization/p_laplacian/demo_p_laplacian.py +++ b/demos/documented/shape_optimization/p_laplacian/demo_p_laplacian.py @@ -1,24 +1,110 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_p_laplacian)= +# # Shape Optimization with the p-Laplacian # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo, we take a look at yet another possibility to compute the shape gradient +# and to use this method for solving shape optimization problems. Here, we investigate +# the approach of [Müller, Kühl, Siebenborn, Deckelnick, Hinze, and Rung]( +# https://doi.org/10.1007/s00158-021-03030-x) and use the $p$-Laplacian in order to +# compute the shape gradient. +# As a model problem, we consider the following one, as in {ref}`demo_shape_poisson`: # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/shape_optimization/doc_p_laplacian.html. - -""" +# $$ +# \begin{align} +# &\min_\Omega J(u, \Omega) = \int_\Omega u \text{ d}x \\ +# &\text{subject to} \qquad +# \begin{alignedat}[t]{2} +# -\Delta u &= f \quad &&\text{ in } \Omega,\\ +# u &= 0 \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# For the initial domain, we use the unit disc +# $\Omega = \{ x \in \mathbb{R}^2 \,\mid\, \lvert\lvert x \rvert\rvert_2 < 1 \}$ and the +# right-hand side $f$ is given by +# +# $$ +# f(x) = 2.5 \left( x_1 + 0.4 - x_2^2 \right)^2 + x_1^2 + x_2^2 - 1. +# $$ +# +# ## Implementation +# +# The complete python code can be found in the file +# {download}`demo_p_laplacian.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Source Code +# +# The python source code for this example is completely identical to the one in +# {ref}`demo_shape_poisson`, and we will repeat the code at the end of the tutorial. +# The only changes occur in +# the configuration file, which we cover below. +# +# ### Changes in the Configuration File +# +# All the relevant changes appear in the ShapeGradient Section of the config file, +# where we now add the following three lines +# +# ```ini +# [ShapeGradient] +# use_p_laplacian = True +# p_laplacian_power = 10 +# p_laplacian_stabilization = 0.0 +# ``` +# +# Here, {ini}`use_p_laplacian` is a boolean flag which indicates that we want to +# override the default behavior and use the $p$ Laplacian to compute the shape gradient +# instead of linear elasticity. In particular, this means that we solve the following +# equation to determine the shape gradient $\mathcal{G}$ +# +# $$ +# \begin{aligned} +# &\text{Find } \mathcal{G} \text{ such that } \\ +# &\qquad \int_\Omega \mu \left( \nabla \mathcal{G} : \nabla \mathcal{G} +# \right)^{\frac{p-2}{2}} \nabla \mathcal{G} : \nabla \mathcal{V} +# + \delta \mathcal{G} \cdot \mathcal{V} \text{ d}x = dJ(\Omega)[\mathcal{V}] \\ +# &\text{for all } \mathcal{V}. +# \end{aligned} +# $$ +# +# Here, $dJ(\Omega)[\mathcal{V}]$ is the shape derivative. The parameter $p$ is defined +# via the config file parameter {ini}`p_laplacian_power`, and is 10 for this example. +# Finally, it is possible to use a stabilized formulation of the $p$-Laplacian equation +# shown above, where the stabilization parameter is determined via the config line +# parameter {ini}`p_laplacian_stabilization`, which should be small (e.g. in the order +# of {python}`1e-3`). Moreover, $\mu$ is the stiffness parameter, which can be specified +# via the config file parameters {ini}`mu_def` and {ini}`mu_fixed` and works as usually +# (cf. {ref}`demo_shape_poisson`:). Finally, we have added the possibility to use the +# damping parameter $\delta$, which is specified via the config file parameter +# {ini}`damping_factor`, also in the Section ShapeGradient. +# +# :::{note} +# Note that the $p$-Laplace methods are only meant to work with the gradient descent +# method. Other methods, such as BFGS or NCG methods, might be able to work on certain +# problems, but you might encounter strange behavior of the methods. +# ::: +# +# Finally, the code for the demo looks as follows +# + from fenics import * import cashocs @@ -44,9 +130,6 @@ sop = cashocs.ShapeOptimizationProblem(e, bcs, J, u, p, boundaries, config=config) sop.solve() - -### Post Processing - import matplotlib.pyplot as plt plt.figure(figsize=(10, 5)) @@ -64,3 +147,7 @@ plt.tight_layout() # plt.savefig("./img_p_laplacian.png", dpi=150, bbox_inches="tight") +# - + +# and the results of the optimization look like this +# ![](/../../demos/documented/shape_optimization/shape_poisson/img_shape_poisson.png) diff --git a/demos/documented/shape_optimization/regularization/demo_regularization.py b/demos/documented/shape_optimization/regularization/demo_regularization.py index 01f00354..bbe4dc15 100755 --- a/demos/documented/shape_optimization/regularization/demo_regularization.py +++ b/demos/documented/shape_optimization/regularization/demo_regularization.py @@ -1,24 +1,68 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_regularization)= +# # Regularization for Shape Optimization Problems # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo, we investigate how we can use regularizations for shape optimization +# problems in cashocs. For our model problem, we use one similar to the one in +# {ref}`demo_shape_poisson`, but which has additional regularization terms, i.e., # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/shape_optimization/doc_regularization.html. - -""" +# $$ +# \begin{align} +# \min_\Omega J(u, \Omega) = &\int_\Omega u \text{ d}x + +# \alpha_\text{vol} \int_\Omega 1 \text{ d}x + +# \alpha_\text{surf} \int_\Gamma 1 \text{ d}s \\ +# &+ +# \frac{\mu_\text{vol}}{2} \left( \int_\Omega 1 \text{ d}x +# - \text{vol}_\text{des} \right)^2 + +# \frac{\mu_\text{surf}}{2} \left( \int_\Gamma 1 \text{ d}s +# - \text{surf}_\text{des} \right)^2 \\ +# &+ \frac{\mu_\text{curv}}{2} \int_\Gamma \kappa^2 \text{ d}s \\ +# &\text{subject to} \qquad +# \begin{alignedat}[t]{2} +# -\Delta u &= f \quad &&\text{ in } \Omega,\\ +# u &= 0 \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# Here, $\kappa$ is the mean curvature. For the initial domain, we use the unit disc +# $\Omega = \{ x \in \mathbb{R}^2 \,\mid\, \lvert\lvert x \rvert\rvert_2 < 1 \}$ and the +# right-hand side $f$ is given by +# +# $$ +# f(x) = 2.5 \left( x_1 + 0.4 - x_2^2 \right)^2 + x_1^2 + x_2^2 - 1, +# $$ +# +# as in {ref}`demo_shape_poisson`. +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_regularization.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# The initial code, including the defition of the PDE constraint, is identical to +# {ref}`demo_shape_poisson`, and uses the following code +# + from fenics import * import cashocs @@ -36,20 +80,67 @@ e = inner(grad(u), grad(p)) * dx - f * p * dx bcs = DirichletBC(V, Constant(0), boundaries, 1) +# - + +# ### Cost functional and regularization +# +# The only difference to {ref}`demo_shape_poisson` comes now, in the definition +# of the cost functional which includes the additional regularization terms. +# +# The first two summands of the cost functional can then be defined as alpha_vol = 1e-1 alpha_surf = 1e-1 - J = cashocs.IntegralFunctional( u * dx + Constant(alpha_vol) * dx + Constant(alpha_surf) * ds ) +# The remaining two parts are specified via {download}`config.ini +# `, where +# the following lines are relevant +# +# ```ini +# [Regularization] +# factor_volume = 1.0 +# target_volume = 1.5 +# use_initial_volume = False +# factor_surface = 1.0 +# target_surface = 4.5 +# use_initial_surface = False +# factor_curvature = 1e-4 +# ``` +# +# This sets the factor $\mu_\text{vol}$ to {python}`1.0`, $\text{vol}_\text{des}$ +# to {python}`1.5`, $\mu_\text{surf}$ to {python}`1.0`, $\text{surf}_\text{des}$ +# to {python}`4.5`, and $\mu_\text{curv}$ to {python}`1e-4`. Note that +# {ini}`use_initial_volume` and {ini}`use_initial_surface` have to be set to +# {python}`False`, otherwise the corresponding quantities of the initial +# geometry would be used instead of the ones prescribed in the config file. +# The resulting regularization terms are then treated by cashocs, but are, except +# for these definitions in the config file, invisible for the user. +# +# ::::{note} +# cashocs can also treat the last two terms directly, making use of the +# {py:class}`ScalarTrackingFunctional `. Therefore, +# one would use +# :::python +# J_vol = cashocs.ScalarTrackingFunctional(Constant(1.0) * dx, 1.5, weight=1.0) +# J_surf = cashocs.ScalarTrackingFunctional(Constant(1.0) * ds, 4.5, weight = 1.0) +# ::: +# +# However, cashocs is not able to treat the curvature regularization directly, this can +# only be achieved via the config file option, see the {ref}`Section Regularization +# `. +# :::: +# +# Finally, we solve the problem as in {ref}`demo_shape_poisson` with the lines + sop = cashocs.ShapeOptimizationProblem(e, bcs, J, u, p, boundaries, config=config) sop.solve() +# and we perform a post-processing with the lines -### Post Processing - +# + import matplotlib.pyplot as plt plt.figure(figsize=(10, 5)) @@ -67,3 +158,7 @@ plt.tight_layout() # plt.savefig('./img_regularization.png', dpi=150, bbox_inches='tight') +# - + +# The results should look like this +# ![](/../../demos/documented/shape_optimization/regularization/img_regularization.png) diff --git a/demos/documented/shape_optimization/remeshing/demo_remeshing.py b/demos/documented/shape_optimization/remeshing/demo_remeshing.py index 56f728f9..c0e0a305 100755 --- a/demos/documented/shape_optimization/remeshing/demo_remeshing.py +++ b/demos/documented/shape_optimization/remeshing/demo_remeshing.py @@ -1,30 +1,153 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_remeshing)= +# # Remeshing with cashocs # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this tutorial, we take a close look at how remeshing works in cashocs. To keep +# this discussion simple, we take a look at the model problem already investigated +# in {ref}`demo_shape_poisson`, i.e., # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/shape_optimization/doc_remeshing.html. - -""" +# $$ +# \begin{align} +# &\min_\Omega J(u, \Omega) = \int_\Omega u \text{ d}x \\ +# &\text{subject to} \qquad +# \begin{alignedat}[t]{2} +# -\Delta u &= f \quad &&\text{ in } \Omega,\\ +# u &= 0 \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# As before, we use the unit disc +# $\Omega = \{ x \in \mathbb{R}^2 \,\mid\, \lvert\lvert x \rvert\rvert_2 < 1 \}$ +# as initial geometry and the right-hand side $f$ is given by +# +# $$ +# f(x) = 2.5 \left( x_1 + 0.4 - x_2^2 \right)^2 + x_1^2 + x_2^2 - 1. +# $$ +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_remeshing.py +# `, +# and the corresponding config can be found in {download}`config.ini +# `. +# The corresponding mesh files are {download}`./mesh/mesh.geo +# ` and +# {download}`./mesh/mesh.msh +# `. +# +# ### Pre-Processing with GMSH +# +# Before we can start with the actual cashocs implementation of remeshing, we have +# to take a closer look at how we can define a geometry with Gmsh. For this, .geo +# files are used. +# +# :::{hint} +# A detailed documentation and tutorials regarding the generation of geometries +# and meshes with Gmsh can be found [here](https://gmsh.info/doc/texinfo/gmsh.html). +# ::: +# +# The file {download}`./mesh/mesh.geo +# ` +# describes our geometry. +# +# :::{important} +# Any user defined variables that should be also kept for the remeshing, such +# as the characteristic lengths, must be lower-case, so that cashocs can distinguish +# them from the other GMSH commands. Any user defined variable starting with an upper +# case letter is not considered for the .geo file created for remeshing and will, +# thus, probably cause an error. +# +# In our case of the .geo file, the characteristic length is defined as {cpp}`lc`, +# and this is used to specify the (local) size of the discretization via so-called +# size fields. Note, that this variable is indeed taken into consideration for +# the remeshing as it starts with a lower case letter. +# ::: +# +# The resulting mesh file was created over the command line +# with the command +# +# ```bash +# gmsh ./mesh/mesh.geo -o ./mesh/mesh.msh -2 +# ``` +# +# :::{note} +# For the purpose of this tutorial it is recommended to leave the `./mesh/mesh.msh` +# file as it is. In particular, carrying out the above command will overwrite +# the file and is, thus, not recommended. The command just highlights, how one +# would / could use GMSH to define their own geometries and meshes for cashocs +# or FEniCS. +# ::: +# +# The resulting file is {download}`./mesh/mesh.msh +# `. +# This .msh file can be converted to the .xdmf format by using +# {py:func}`cashocs.convert` or alternatively, via the command line +# +# ```bash +# cashocs-convert ./mesh/mesh.msh ./mesh/mesh.xdmf +# ``` +# +# To ensure that cashocs also finds these files, we have to specify them in the file +# {download}`config.ini +# `. +# For this, we have the following lines +# +# ```ini +# [Mesh] +# mesh_file = ./mesh/mesh.xdmf +# gmsh_file = ./mesh/mesh.msh +# geo_file = ./mesh/mesh.geo +# remesh = True +# show_gmsh_output = True +# ``` +# +# With this, we have specified the paths to the mesh files and also enabled the +# remeshing as well as the verbose output of GMSH to the terminal, as explained in +# {ref}`the corresponding documentation of the config files `. +# +# :::{note} +# Note, that the paths given in the config file can be either absolute or relative. +# In the latter case, they have to be relative to the location of the cashocs script +# which is used to solve the problem. +# ::: +# +# With this, we can now focus on the implementation in python. +# +# ### Initialization +# +# The program starts as {ref}`demo_shape_poisson`, with importing FEniCS and cashocs +# + from fenics import * import cashocs +# - + +# Afterwards, we specify the path to the mesh file + mesh_file = "./mesh/mesh.xdmf" +# In order to be able to use a remeshing, we have to parametrize the inputs for +# {py:class}`cashocs.ShapeOptimizationProblem` w.r.t. to the mesh file. We do so with +# the following function, which we explain more detailed later on + def parametrization(mesh_file: str): config = cashocs.load_config("./config.ini") @@ -49,12 +172,80 @@ def parametrization(mesh_file: str): return args, kwargs +# ::::{admonition} Description of the {python}`parametrization` function +# +# The code inside the {python}`parametrization` function looks nearly identical to the +# setup of the problem considered in {ref}`demo_shape_poisson`. First, we load the +# config file and define the mesh with the commands +# :::python +# config = cashocs.load_config("./config.ini") +# +# mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh(mesh_file) +# ::: +# +# Then, we define the {py:class}`fenics.FunctionSpace` and the +# {py:class}`fenics.Function` objects used for the state and adjoint variables +# :::python +# V = FunctionSpace(mesh, "CG", 1) +# u = Function(V) +# p = Function(V) +# ::: +# +# In the following lines, we define the UFL form of the right-hand side +# :::python +# x = SpatialCoordinate(mesh) +# f = 2.5 * pow(x[0] + 0.4 - pow(x[1], 2), 2) + pow(x[0], 2) + pow(x[1], 2) - 1 +# ::: +# +# Next, we define the weak form of the PDE constraint and the corresponding boundary +# conditions +# :::python +# e = inner(grad(u), grad(p)) * dx - f * p * dx +# bcs = DirichletBC(V, Constant(0), boundaries, 1) +# ::: +# +# and then the cost functional +# :::python +# J = cashocs.IntegralFunctional(u * dx) +# ::: +# +# with this, we have defined all arguments that are required for the +# {py:class}`cashocs.ShapeOptimizationProblem`. In order to make them usable, we return +# them in two objects, the first being the tuple {python}`args`, which defines the +# positional parameters of the {py:class}`cashocs.ShapeOptimizationProblem`. The second +# return object is the dictionary {python}`kwargs` containing the keyword arguments. +# These should be usable analogously to {python}`*args` and {python}`**kwargs`, i.e., +# the unpacking operators {python}`*` and {python}`**` should yield the respective +# arguments. In particular, the return values of the {python}`parametrization` function +# have to be valid inputs so that +# :::python +# mesh_file = ... +# args, kwargs = parametrization(mesh_file) +# sop = cashocs.ShapeOptimizationProblem(*args, **kwargs) +# ::: +# +# is well-defined. Therefore, in our code, we write +# :::python +# args = (e, bcs, J, u, p, boundaries) +# kwargs = {"config": config} +# +# return args, kwargs +# ::: +# :::: +# +# ### The shape optimization problem +# +# To define the shape optimization problem, we now have to pass the +# {python}`parametrization` function as well as the {python}`mesh_file` to its +# constructor. Solving the problem is now, again, as easy as calling its {py:meth}`solve +# ` method. + sop = cashocs.ShapeOptimizationProblem(parametrization, mesh_file) sop.solve() +# We visualize the result with the lines -### Post Processing - +# + import matplotlib.pyplot as plt plt.figure(figsize=(10, 5)) @@ -72,3 +263,21 @@ def parametrization(mesh_file: str): plt.tight_layout() # plt.savefig('./img_remeshing.png', dpi=150, bbox_inches='tight') +# - + +# and get the following results +# ![](/../../demos/documented/shape_optimization/remeshing/img_remeshing.png) +# +# ::::{note} +# The example for remeshing is somewhat artificial, as the problem does not +# actually need remeshing. Therefore, the tolerances used in the config file, i.e., +# +# ```ini +# tol_lower = 0.1 +# tol_upper = 0.25 +# ``` +# +# are comparatively large. However, this problem still shows all relevant +# aspects of remeshing in cashocs and can, thus, be transferred to "harder" +# problems that require remeshing. +# :::: diff --git a/demos/documented/shape_optimization/scaling/demo_scaling.py b/demos/documented/shape_optimization/scaling/demo_scaling.py index 5d42b12e..bece7201 100755 --- a/demos/documented/shape_optimization/scaling/demo_scaling.py +++ b/demos/documented/shape_optimization/scaling/demo_scaling.py @@ -1,24 +1,66 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_scaling)= +# # Scaling of the Cost Functional # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo, we take a look at how cashocs can be used to scale cost functionals, +# which is particularly useful in case one uses multiple terms to define the cost +# functional and wants to weight them appropriately, e.g., if there are multiple +# competing objectives. This demo investigates this problem by considering a slightly +# modified version of the model shape optimization problem from +# {ref}`demo_shape_poisson`, i.e., # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/shape_optimization/doc_scaling.html. - -""" +# $$ +# \begin{align} +# &\min_\Omega J(u, \Omega) = \alpha \int_\Omega u \text{ d}x +# + \beta \int_\Omega 1 \text{ d}x \\ +# &\text{subject to} \qquad +# \begin{alignedat}[t]{2} +# -\Delta u &= f \quad &&\text{ in } \Omega,\\ +# u &= 0 \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# For the initial domain, we use the unit disc +# $\Omega = \{ x \in \mathbb{R}^2 \,\mid\, \lvert\lvert x \rvert\rvert_2 < 1 \}$ +# and the right-hand side $f$ is given by +# +# $$ +# f(x) = 2.5 \left( x_1 + 0.4 - x_2^2 \right)^2 + x_1^2 + x_2^2 - 1. +# $$ +# +# Our goal is to choose the parameters $\alpha$ and $\beta$ in such a way +# that the magnitude of the second term is twice as big as the value of the first term +# for the initial geometry. +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_scaling.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# The definition of the PDE constraint is completely identical to the one described in +# {ref}`demo_shape_poisson`, which we recall here for the sake of completeness +# + from fenics import * import cashocs @@ -36,21 +78,74 @@ e = inner(grad(u), grad(p)) * dx - f * p * dx bcs = DirichletBC(V, Constant(0), boundaries, 1) +# - + +# ### Definition of the Cost Functional +# +# Our goal is to choose $\alpha$ and $\beta$ in such a way that +# +# $$ +# \begin{aligned} +# \left\lvert \alpha \int_{\Omega_0} u \text{ d}x \right\rvert &= C_1,\\ +# \left\lvert \beta \int_{\Omega_0} 1 \text{ d}x \right\rvert &= C_2, +# \end{aligned} +# $$ +# +# where $\Omega_0$ is the intial geometry. Here, we choose the value $C_1 = 1$ and +# $C_2 = 2$ without loss of generality. +# This would then achieve our goal of having the second term (a volume regularization) +# being twice as large as the first term in magnitude. +# +# To implement this in cashocs, we do not specify a single UFL form for the cost +# functional as in all previous demos, instead we supply a list of UFL forms into which +# we put every single term of the cost functional. These are then scaled automatically +# by cashocs and then added to obtain the actual cost functional. +# +# Hence, let us first define the individual terms of the cost functional we consider, +# and place them into a list J_1 = cashocs.IntegralFunctional(u * dx) J_2 = cashocs.IntegralFunctional(Constant(1) * dx) J_list = [J_1, J_2] +# Afterwards, we have to create a second list which includes the values that the +# terms of {python}`J_list` should have on the initial geometry + desired_weights = [1, 2] +# Finally, these are supplied to the shape optimization problem as follows: +# {python}`J_list` is passed for the usual {python}`cost_functional_form` +# parameter, and {python}`desired_weights` enters the optimization problem as keyword +# argument of the same name, i.e., + sop = cashocs.ShapeOptimizationProblem( e, bcs, J_list, u, p, boundaries, config=config, desired_weights=desired_weights ) sop.solve() +# :::{note} +# Since the first term of the cost functional, i.e., $\int_\Omega u \text{ d}x$, is +# negative, the initial function value for our choice of scaling is +# $-1 + 2 = 1$. +# ::: +# +# :::{note} +# If a cost functional is close to zero for the initial domain, the scaling is +# disabled for this term, and instead the respective term is just multiplied +# by the corresponding factor in {python}`desired_weights`. cashocs issues an info +# message in this case. +# ::: +# +# :::{note} +# The scaling of the cost functional works completely analogous for optimal control +# problems: There, one also has to supply a list of the individual terms of the cost +# functional and use the keyword argument {python}`desired_weights` in order to define +# and supply the desired magnitude of the terms for the initial iteration. +# ::: +# +# We visualize our results with the code -### Post Processing - +# + import matplotlib.pyplot as plt plt.figure(figsize=(10, 5)) @@ -67,4 +162,8 @@ plt.title("State variable u") plt.tight_layout() -plt.savefig("./img_scaling.png", dpi=150, bbox_inches="tight") +# plt.savefig("./img_scaling.png", dpi=150, bbox_inches="tight") +# - + +# and obtain the results shown below +# ![](/../../demos/documented/shape_optimization/scaling/img_scaling.png) diff --git a/demos/documented/shape_optimization/shape_poisson/demo_shape_poisson.py b/demos/documented/shape_optimization/shape_poisson/demo_shape_poisson.py index 423bbe5b..a84aa6de 100755 --- a/demos/documented/shape_optimization/shape_poisson/demo_shape_poisson.py +++ b/demos/documented/shape_optimization/shape_poisson/demo_shape_poisson.py @@ -1,52 +1,147 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_shape_poisson)= +# # Shape Optimization with a Poisson Problem # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo, we investigate the basics of cashocs for shape optimization problems. +# As a model problem, we investigate the following one from +# [Etling, Herzog, Loayza, Wachsmuth - First and Second Order Shape Optimization Based +# on Restricted Mesh Deformations](https://doi.org/10.1137/19M1241465) # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/shape_optimization/doc_shape_poisson.html. - -""" +# $$ +# \begin{align} +# &\min_\Omega J(u, \Omega) = \int_\Omega u \text{ d}x \\ +# &\text{subject to} \qquad +# \begin{alignedat}[t]{2} +# -\Delta u &= f \quad &&\text{ in } \Omega,\\ +# u &= 0 \quad &&\text{ on } \Gamma. +# \end{alignedat} +# \end{align} +# $$ +# +# For the initial domain, we use the unit disc +# $\Omega = \{ x \in \mathbb{R}^2 \,\mid\, \lvert\lvert x \rvert\rvert_2 < 1 \}$ and the +# right-hand side $f$ is given by +# +# $$ +# f(x) = 2.5 \left( x_1 + 0.4 - x_2^2 \right)^2 + x_1^2 + x_2^2 - 1. +# $$ +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_shape_poisson.py +# `, +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# We start the problem by using a wildcard import for FEniCS, and by importing cashocs +# + from fenics import * import cashocs +# - + +# As for the case of optimal control problems, we can specify the verbosity of cashocs +# with the line + cashocs.set_log_level(cashocs.LogLevel.INFO) +# which is documented at {py:func}`cashocs.set_log_level` (cf. {ref}`demo_poisson`). +# +# Similarly to the optimal control case, we also require config files for shape +# optimization problems in cashocs. A detailed discussion of the config files +# for shape optimization is given in {ref}`config_shape_optimization`. +# We read the config file with the {py:func}`load_config ` command + config = cashocs.load_config("./config.ini") +# Next, we have to define the mesh. We load the mesh (which was previously generated +# with Gmsh and converted to xdmf with {py:func}`cashocs.convert` + mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh("./mesh/mesh.xdmf") +# ::::{note} +# In {download}`config.ini +# `, +# in the section {ref}`ShapeGradient `, there is +# the line +# +# ```ini +# [ShapeGradient] +# shape_bdry_def = [1] +# ``` +# +# which specifies that the boundary marked with {python}`1` is deformable. For our +# example this is exactly what we want, as this means that the entire boundary +# is variable, due to the fact that the entire boundary is marked by {python}`1` in the +# Gmsh file. For a detailed documentation we refer to {ref}`the corresponding +# documentation of the ShapeGradient section +# `. +# :::: +# +# After having defined the initial geometry, we define a +# {py:class}`fenics.FunctionSpace` consisting of piecewise linear Lagrange elements via + V = FunctionSpace(mesh, "CG", 1) u = Function(V) p = Function(V) +# This also defines our state variable $u$ as {python}`u`, and the adjoint state $p$ is +# given by {python}`p`. +# +# :::{note} +# As remarked in {ref}`demo_poisson`, in classical FEniCS syntax we would use a +# {py:class}`fenics.TrialFunction` for {python}`u` and a {py:class}`fenics.TestFunction` +# for {python}`p`. However, for cashocs this must not be the case. Instead, the state +# and adjoint variables have to be {py:class}`fenics.Function` objects. +# ::: +# +# The right-hand side of the PDE constraint is then defined as + x = SpatialCoordinate(mesh) f = 2.5 * pow(x[0] + 0.4 - pow(x[1], 2), 2) + pow(x[0], 2) + pow(x[1], 2) - 1 +# which allows us to define the weak form of the state equation via + e = inner(grad(u), grad(p)) * dx - f * p * dx bcs = DirichletBC(V, Constant(0), boundaries, 1) +# ### The optimization problem and its solution +# +# We are now almost done, the only thing left to do is to define the cost functional + J = cashocs.IntegralFunctional(u * dx) +# and the shape optimization problem + sop = cashocs.ShapeOptimizationProblem(e, bcs, J, u, p, boundaries, config=config) -sop.solve() +# This can then be solved in complete analogy to {ref}`demo_poisson` with +# the {py:meth}`sop.solve() ` command + +sop.solve() -### Post Processing +# We visualize the result with the following code +# + import matplotlib.pyplot as plt plt.figure(figsize=(10, 5)) @@ -64,3 +159,29 @@ plt.tight_layout() # plt.savefig('./img_shape_poisson.png', dpi=150, bbox_inches='tight') +# - + +# and the result should look like this +# ![](/../../demos/documented/shape_optimization/shape_poisson/img_shape_poisson.png) +# +# :::{note} +# As in {ref}`demo_poisson` we can specify some keyword +# arguments for the {py:meth}`solve ` command. +# If none are given, then the settings from the config file are used, but if +# some are given, they override the parameters specified +# in the config file. In particular, these arguments are +# +# > - {python}`algorithm` : Specifies which solution algorithm shall be used. +# > - {python}`rtol` : The relative tolerance for the optimization algorithm. +# > - {python}`atol` : The absolute tolerance for the optimization algorithm. +# > - {python}`max_iter` : The maximum amount of iterations that can be carried out. +# +# The possible choices for these parameters are discussed in detail in +# {ref}`config_shape_optimization_routine` and the documentation of the +# {py:func}`solve ` method. +# +# As before, it is not strictly necessary to supply config files to cashocs, but +# it is very strongly recommended to do so. In case one does not supply a config +# file, one has to at least specify the solution algorithm in the call to +# the {py:meth}`solve ` method. +# ::: diff --git a/demos/documented/shape_optimization/shape_stokes/demo_shape_stokes.py b/demos/documented/shape_optimization/shape_stokes/demo_shape_stokes.py index 660e026c..2e6b75fd 100755 --- a/demos/documented/shape_optimization/shape_stokes/demo_shape_stokes.py +++ b/demos/documented/shape_optimization/shape_stokes/demo_shape_stokes.py @@ -1,57 +1,252 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_shape_stokes)= +# # Optimization of an Obstacle in Stokes Flow # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# For this tutorial, we investigate a problem similarly to the ones considered in, e.g., +# [Blauth - Nonlinear Conjugate Gradient Methods for PDE Constrained Shape Optimization +# Based on Steklov-Poincaré-Type Metrics](https://arxiv.org/abs/2007.12891) and +# [Schulz and Siebenborn - Computational Comparison of Surface Metric for PDE +# Constrained Shape Optimization](https://doi.org/10.1515/cmam-2016-0009), which reads: # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/shape_optimization/doc_shape_stokes.html. - -""" +# $$ +# \begin{align} +# &\min_\Omega J(u, \Omega) = \int_{\Omega^\text{flow}} Du : Du\ \text{ d}x \\ +# &\text{subject to } \qquad +# \begin{alignedat}[t]{2} +# - \Delta u + \nabla p &= 0 \quad &&\text{ in } \Omega, \\ +# \text{div}(u) &= 0 \quad &&\text{ in } \Omega, \\ +# u &= u^\text{in} \quad &&\text{ on } \Gamma^\text{in}, \\ +# u &= 0 \quad &&\text{ on } \Gamma^\text{wall} \cup \Gamma^\text{obs}, \\ +# \partial_n u - p n &= 0 \quad &&\text{ on } \Gamma^\text{out}. +# \end{alignedat} +# \end{align} +# $$ +# +# Here, we have an inflow boundary condition on the inlet $\Gamma^\text{in}$, +# a no-slip boundary condition on the wall boundary $\Gamma^\text{wall}$ as well +# as the obstacle boundary $\Gamma^\text{obs}$, and a do-nothing condition +# on the outlet $\Gamma^\text{out}$. +# +# This problem is supplemented with the geometrical constraints +# +# $$ +# \begin{align} +# \text{vol}(\Omega) = \int_\Omega 1 \text{ d}x &= \int_{\Omega_0} 1 \text{ d}x +# = \text{vol}(\Omega_0), \\ +# \text{bary}(\Omega) = \frac{1}{\text{vol}(\Omega)} \int_\Omega x \text{ d}x +# &= \frac{1}{\text{vol}(\Omega_0)} \int_{\Omega_0} x \text{ d}x +# = \text{bary}(\Omega_0), +# \end{align} +# $$ +# +# where $\Omega_0$ denotes the initial geometry. This models that both the volume +# and the barycenter of the geometry should remain fixed during the optimization. +# To treat this problem with cashocs, we regularize the constraints in the sense +# of a quadratic penalty function. See {ref}`the corresponding section in the +# documentation of the config files for shape optimization +# ` as well as {ref}`demo_regularization`. In particular, +# the regularized problem reads +# +# $$ +# \begin{align} +# \min_\Omega J(u, \Omega) = &\int_{\Omega^\text{flow}} Du : Du\ \text{ d}x + +# \frac{\mu_\text{vol}}{2} \left( \int_\Omega 1 \text{ d}x - +# \text{vol}(\Omega_0) \right)^2 \\ +# &+ \frac{\mu_\text{bary}}{2} \left\lvert \frac{1}{\text{vol}(\Omega)} +# \int_\Omega x \text{ d}x - \text{bary}(\Omega_0) \right\rvert^2 \\ +# &\text{subject to } \qquad +# \begin{alignedat}[t]{2} +# - \Delta u + \nabla p &= 0 \quad &&\text{ in } \Omega, \\ +# \text{div}(u) &= 0 \quad &&\text{ in } \Omega, \\ +# u &= u^\text{in} \quad &&\text{ on } \Gamma^\text{in}, \\ +# u &= 0 \quad &&\text{ on } \Gamma^\text{wall} \cup \Gamma^\text{obs}, \\ +# \partial_n u - p n &= 0 \quad &&\text{ on } \Gamma^\text{out}. +# \end{alignedat} +# \end{align}. +# $$ +# +# where we have no additional geometrical constraints. However, the parameters +# $\mu_\text{vol}$ and $\mu_\text{bary}$ have to be sufficiently large, +# so that the regularized constraints are satisfied approximately. +# +# Note, that the domain $\Omega$ denotes the domain of the obstacle, but that +# the cost functional involves an integral over the flow domain $\Omega^\text{flow}$. +# Therefore, we switch our point of view and optimize the geometry of the flow instead +# (which of course includes the optimization of the "hole" generated by the obstacle). +# In particular, only the boundary of the obstacle $\Gamma^\text{obs}$ is deformable, +# all remaining boundaries are fixed. Hence, it is equivalent to pose the geometrical +# constraints on $\Omega$ and $\Omega^\text{flow}$, so that we can work completely with +# the flow domain. +# +# As (initial) domain, we consider the rectangle $(-2, 2) \times (-3, 3)$ +# without a circle centered in the origin with radius $0.5$. The correspondig +# geometry is created with Gmsh, and can be found in the `./mesh/` directory. +# +# ## Implementation +# +# The complete python code can be found in the file {download}`demo_shape_stokes.py +# `, +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### Initialization +# +# As for the previous tutorial problems, we start by importing FEniCS and cashocs and +# load the configuration file +# + from fenics import * import cashocs config = cashocs.load_config("./config.ini") +# - + +# Afterwards we import the (initial) geometry with the {py:func}`import_mesh +# ` command + mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh("./mesh/mesh.xdmf") +# ::::{note} +# As defined in `./mesh/mesh.geo`, we have the following boundaries +# +# - The inlet boundary $\Gamma^\text{in}$, which has index {python}`1` +# - The wall boundary $\Gamma^\text{wall}$, which have index {python}`2` +# - The outlet boundary $\Gamma^\text{out}$, which has index {python}`3` +# - The boundary of the obstacle $\Gamma^\text{obs}$, which has index {python}`4` +# +# To include the fact that only the obstacle boundary, with index {python}`4`, is +# deformable, we have the following lines in the config file +# +# ```ini +# [ShapeGradient] +# shape_bdry_def = [4] +# shape_bdry_fix = [1,2,3] +# ``` +# +# which ensures that only $\Gamma^\text{obs}$ is deformable. +# :::: +# +# ### State system +# +# The definition of the state system is analogous to the one we considered in +# {ref}`demo_stokes`. Here, we, too, use LBB stable Taylor-Hood elements, which are +# defined as + v_elem = VectorElement("CG", mesh.ufl_cell(), 2) p_elem = FiniteElement("CG", mesh.ufl_cell(), 1) V = FunctionSpace(mesh, MixedElement([v_elem, p_elem])) +# For the weak form of the PDE, we have the same code as in {ref}`demo_stokes` + +# + up = Function(V) u, p = split(up) vq = Function(V) v, q = split(vq) e = inner(grad(u), grad(v)) * dx - p * div(v) * dx - q * div(u) * dx +# - + +# The Dirichlet boundary conditions are slightly different, though. For the inlet +# velocity $u^\text{in}$ we use a parabolic profile u_in = Expression(("-1.0/4.0*(x[1] - 2.0)*(x[1] + 2.0)", "0.0"), degree=2) bc_in = DirichletBC(V.sub(0), u_in, boundaries, 1) + +# The wall and obstacle boundaries get a no-slip boundary condition, each, with the line + bc_no_slip = cashocs.create_dirichlet_bcs( V.sub(0), Constant((0, 0)), boundaries, [2, 4] ) + +# Finally, all Dirichlet boundary conditions are gathered into the list {python}`bcs` + bcs = [bc_in] + bc_no_slip +# :::{note} +# The outflow boundary condition is of Neumann type and already included in the +# weak form of the problem. +# ::: +# +# ### Cost functional and optimization problem +# +# The cost functional is easily defined with the line + J = cashocs.IntegralFunctional(inner(grad(u), grad(u)) * dx) +# ::::{note} +# The additional regularization terms are defined similarly to +# {ref}`demo_regularization`. +# In the config file, we have the following (relevant) lines: +# +# ```ini +# [Regularization] +# factor_volume = 1e4 +# use_initial_volume = True +# factor_barycenter = 1e5 +# use_initial_barycenter = True +# ``` +# +# This ensures that we use $\mu_\text{vol} = 1e4$ and $\mu_\text{bary} = 1e5$, +# which are comparatively large parameters, so that the geometrical constraints are +# satisfied with high accuracy. +# Moreover, the boolean flags {ini}`use_initial_volume` and +# {ini}`use_initial_barycenter`, which are both set to `True`, ensure that we actually +# use the volume and barycenter of the initial geometry. +# :::: +# +# Finally, we solve the shape optimization problem as previously with the commands + sop = cashocs.ShapeOptimizationProblem(e, bcs, J, up, vq, boundaries, config=config) sop.solve() +# :::{note} +# For the definition of the shape gradient, we use the same parameters for the +# linear elasticity equations as in [Blauth - Nonlinear Conjugate Gradient Methods for +# PDE Constrained Shape Optimization Based on Steklov-Poincaré-Type Metrics]( +# https://arxiv.org/abs/2007.12891) and [Schulz and Siebenborn - Computational +# Comparison of Surface Metric for PDE Constrained Shape Optimization]( +# https://doi.org/10.1515/cmam-2016-0009). These are defined in the config file +# in the {ref}`ShapeGradient section ` +# +# ```ini +# [ShapeGradient] +# lambda_lame = 0.0 +# damping_factor = 0.0 +# mu_fix = 1 +# mu_def = 5e2 +# ``` +# +# so that we use a rather high stiffness for the elements at the deformable boundary, +# which is also discretized finely, and a rather low stiffness for the fixed +# boundaries. +# ::: +# +# :::{note} +# This demo might take a little longer than the others, depending on the machine +# it is run on. This is normal, and caused by the finer discretization of the geometry +# compared to the previous problems. +# ::: +# +# We visualize the optimized geometry with the lines -### Post Processing - +# + import matplotlib.pyplot as plt plt.figure(figsize=(15, 3)) @@ -77,3 +272,7 @@ plt.tight_layout() # plt.savefig('./img_shape_stokes.png', dpi=150, bbox_inches='tight') +# - + +# and the result is shown below +# ![](/../../demos/documented/shape_optimization/shape_stokes/img_shape_stokes.png) diff --git a/demos/documented/shape_optimization/space_mapping_semilinear_transmission/demo_space_mapping_semilinear_transmission.py b/demos/documented/shape_optimization/space_mapping_semilinear_transmission/demo_space_mapping_semilinear_transmission.py index 56248e7d..62286abc 100644 --- a/demos/documented/shape_optimization/space_mapping_semilinear_transmission/demo_space_mapping_semilinear_transmission.py +++ b/demos/documented/shape_optimization/space_mapping_semilinear_transmission/demo_space_mapping_semilinear_transmission.py @@ -1,35 +1,148 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_space_mapping_semilinear_transmission)= +# # Space Mapping Shape Optimization - Semilinear Transmission Problem # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# In this demo we detail the space mapping capabilities of cashocs. The space mapping +# technique is used to optimize a fine (detailed, costly, expensive) model with the help +# of a coarse (cheaper, approximate) one. In particular, only coarse model optimizations +# are required, whereas for the fine model we only require simulations. This makes the +# technique very attractive when using commercial solvers for the fine model, while +# using, e.g., cashocs, as solver for the coarse model optimization problem. +# For an overview over the space mapping technique, we refer the reader, e.g., to +# [Bakr, Bandler, Madsen, Sondergaard - An Introduction to the Space Mapping Technique]( +# https://doi.org/10.1023/A:1016086220943) and [Echeverria and Hemker - Space mapping +# and defect correction](https://doi.org/10.2478/cmam-2005-0006). For a detailed +# description of the methods in the context of shape optimization, we refer to +# [Blauth - Space Mapping for PDE Constrained Shape Optimization]( +# https://arxiv.org/abs/2208.05747). # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/shape_optimization/doc_shape_poisson.html. +# For this example, we consider the one investigated numerically in Section 4.2 of +# [Blauth - Space Mapping for PDE Constrained Shape Optimization]( +# https://arxiv.org/abs/2208.05747). +# Let us now state the semi linear transmission which we want to solve. It is given by +# +# $$ +# \begin{align} +# &\min_\Omega J(u_f, \Omega) = \int_D (u_f - u_\mathrm{des})^2 \text{ d}x \\ +# &\text{subject to} \qquad +# \begin{alignedat}[t]{2} +# -\nabla (\alpha \nabla u_f) + \beta u_f^3 &= f \quad &&\text{ in } D,\\ +# u_f &= 0 \quad &&\text{ on } \partial D,\\ +# [\![ u_f ]\!]_{\Gamma} &= 0,\\ +# [\![ \alpha \partial_n u_f ]\!]_{\Gamma} &= 0. +# \end{alignedat} +# \end{align} +# $$ +# +# Here, $\alpha$ is given by $\alpha(x) = \chi_\Omega(x) \alpha_1 + (1 - \chi_\Omega(x)) +# \alpha_2$ and $f$ is given by $f(x) = \chi_\Omega(x) f_1 + (1 - \chi_\Omega(x)) f_2$. +# The above optimization problem plays the role of the fine model. Note that the +# difficulty of this problem comes from the nonlinear term in the state equation, which +# makes the PDE constraint harder to solver than a linear equation. Therefore, we want +# to use an approximate model which is easier to solve. Our coarse model for this +# problem is given by +# +# $$ +# \begin{align} +# &\min_\Omega J(u_c, \Omega) = \int_D (u_c - u_\mathrm{des})^2 \text{ d}x \\ +# &\text{subject to} \qquad +# \begin{alignedat}[t]{2} +# -\nabla (\alpha \nabla u_c) &= f \quad &&\text{ in } D,\\ +# u_c &= 0 \quad &&\text{ on } \partial D,\\ +# [\![ u_c ]\!]_{\Gamma} &= 0,\\ +# [\![ \alpha \partial_n u_c ]\!]_{\Gamma} &= 0. +# \end{alignedat} +# \end{align} +# $$ +# +# The difference between the fine and the coarse model optimization problems is now only +# the difference in the PDE constraints, i.e., the coarse model problem is the same as +# the fine model with $\beta = 0$. This can also be interpreted as linearization of the +# fine model around $u = 0$. +# +# :::{note} +# Note that we also need a misalignment function in order to match the fine and coarse +# model outputs. For this problem, a natural choice is to use the misalignment function -""" +# $$ +# r(u, v) = \int_D (u - v)^2 \text{ d}x. +# $$ +# +# This leads to a parameter extraction step in which the following subproblem has to be +# solved +# +# $$ +# \begin{align} +# &\min_\Omega J(u_p, \Omega) = \int_D (u_p - u_f)^2 \text{ d}x \\ +# &\text{subject to} \qquad +# \begin{alignedat}[t]{2} +# - \nabla (\alpha \nabla u_p) &= f \quad &&\text{ in } D,\\ +# u_p &= 0 \quad &&\text{ on } \partial D,\\ +# [\![ u_p ]\!]_{\Gamma} &= 0,\\ +# [\![ \alpha \partial_n u_p ]\!]_{\Gamma} &= 0. +# \end{alignedat} +# \end{align} +# $$ +# ::: +# +# ## Implementation +# +# The complete python code can be found in the file +# {download}`demo_space_mapping_semilinear_transmission.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### The coarse model +# +# We start our description of the space mapping technqiue with the implementation of the +# coarse model. For this, we can use standard cashocs syntax, with the difference that +# we will not put the cost functional, etc., into a +# {py:class}`cashocs.ShapeOptimizationProblem`, but we will use a +# {py:class}`CoarseModel ` for the +# coarse model. +# +# As usual, we begin the script with importing cashocs and fenics +# + import os from fenics import * import cashocs +# - + +# Next, we define the space mapping module we are using in the line + space_mapping = cashocs.space_mapping.shape_optimization + +# and, further, we decrease the verbosity of cashocs so that we can see only the output +# of the space mapping routine + cashocs.set_log_level(cashocs.LogLevel.ERROR) + +# Finally, we define the path to the current directory (where the script is located) via + dir = os.path.dirname(os.path.realpath(__file__)) -# define model parameters and load config +# Next, we define some model parameters and load the configuration file for the problem + alpha_1 = 1.0 alpha_2 = 10.0 f_1 = 1.0 @@ -37,7 +150,10 @@ beta = 100.0 cfg = cashocs.load_config("config.ini") +# In the next step, we define our desired state $u_\mathrm{des}$ as solution of the fine +# model state constraint with a given geometry $\Omega$ +# + def create_desired_state(alpha_1, alpha_2, beta, f_1, f_2): mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh( "./mesh/reference.xdmf" @@ -59,8 +175,21 @@ def create_desired_state(alpha_1, alpha_2, beta, f_1, f_2): u_des_fixed = create_desired_state(alpha_1, alpha_2, beta, f_1, f_2) +# - -# define the coarse model +# Here, {python}`u_des_fixed` plays the role of the fixed desired state, which is given +# on a different mesh than the one we consider for the optimization later on. +# +# :::{note} +# As {python}`u_des_fixed` is given on another mesh and represents a fixed state, it has to be +# re-interpolated during each iteration of the optimization algorithms. This is due to +# the fact that, otherwise, it would be moved along with the mesh / geometry that is to +# be optimized and then become distorted. Therefore, a re-interpolation has to happen. +# ::: +# +# Now, we can define the coarse model, in analogy to {ref}`demo_shape_poisson` + +# + mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh("./mesh/mesh.xdmf") V = FunctionSpace(mesh, "CG", 1) @@ -76,8 +205,18 @@ def create_desired_state(alpha_1, alpha_2, beta, f_1, f_2): bcs = cashocs.create_dirichlet_bcs(V, Constant(0.0), boundaries, [1, 2, 3, 4]) J = cashocs.IntegralFunctional(Constant(0.5) * pow(u - u_des, 2) * dx) coarse_model = space_mapping.CoarseModel(F, bcs, J, u, p, boundaries, config=cfg) +# - + +# As mentioned earlier, we now use the {py:class}`CoarseModel +# ` instead of +# {py:class}`ShapeOptimizationProblem `. +# +# ### The fine model +# +# After defining the coarse model, we can now define the fine model by overloading the +# {py:class}`FineModel ` class + -# define the fine model class FineModel(space_mapping.FineModel): def __init__(self, mesh, alpha_1, alpha_2, beta, f_1, f_2, u_des_fixed): super().__init__(mesh) @@ -123,22 +262,124 @@ def solve_and_evaluate(self) -> None: self.u = u +# Let us now take a deeper look at the fine model +# +# ::::{admonition} Description of the FineModel +# +# The {py:meth}`__init__` method of the fine model initializes the model and saves the +# parameters to make them accessible to the class. +# Users have to overload the {py:meth}`solve_and_evaluate +# ` method of +# the {py:class}`FineModel ` class +# so that the fine model is actually solved and the cost function value is computed +# during the call to this method. +# +# Let us go over some implementation details of the fine model's +# {py:meth}`solve_and_evaluate +# ` +# method, as defined here. First, an iteration counter is incremented with the line +# :::python +# self.iter += 1 +# ::: +# +# Next, the fine model mesh is saved to a file. This is done in order to be able to +# re-import it with the correct physical tags as defined with Gmsh. This is done with +# the lines +# :::python +# cashocs.io.write_out_mesh( +# self.mesh, "./mesh/mesh.msh", f"./mesh/fine/mesh_{self.iter}.msh" +# ) +# cashocs.convert( +# f"{dir}/mesh/fine/mesh_{self.iter}.msh", f"{dir}/mesh/fine/mesh.xdmf" +# ) +# mesh, self.subdomains, boundaries, dx, ds, dS = cashocs.import_mesh( +# "./mesh/fine/mesh.xdmf" +# ) +# ::: +# +# In the following lines, the fine model state constraint is defined and then solved +# :::python +# V = FunctionSpace(mesh, "CG", 1) +# u = Function(V) +# u_des = Function(V) +# v = TestFunction(V) +# F = ( +# Constant(self.alpha_1) * dot(grad(u), grad(v)) * dx(1) +# + Constant(self.alpha_2) * dot(grad(u), grad(v)) * dx(2) +# + Constant(self.beta) * pow(u, 3) * v * dx +# - Constant(self.f_1) * v * dx(1) +# - Constant(self.f_2) * v * dx(2) +# ) +# bcs = cashocs.create_dirichlet_bcs(V, Constant(0.0), boundaries, [1, 2, 3, 4]) +# cashocs.newton_solve(F, u, bcs, verbose=False) +# ::: +# +# After solving the fine model PDE constraint, we re-interpolate the desired state to +# the current mesh with the line +# :::python +# LagrangeInterpolator.interpolate(u_des, self.u_des_fixed) +# ::: +# +# Here, {python}`u_des` is the desired state on the fine model mesh. Finally, we +# evaluate the cost functional and store the solution of the PDE constraint with the +# lines +# :::python +# self.cost_functional_value = assemble(Constant(0.5) * pow(u - u_des, 2) * dx) +# self.u = u +# ::: +# :::: +# +# :::{attention} +# Users have to overwrite the attribute {python}`cost_functional_value` of the fine +# model class since the space mapping algorithm makes usage of this attribute. +# ::: +# +# :::{note} +# In the {py:meth}`solve_and_evaluate +# ` method, we +# do not have to use the same discretization of the geometry for the coarse and fine +# models. In particular, we could remesh the geometry with a finer discretization. For +# an overview of how this can be done, we refer to +# {ref}`demo_space_mapping_uniform_flow_distribution`. +# ::: +# +# After having define the fine model class, we instantiate it and define a placeholder +# function for the solution of the fine model + fine_model = FineModel(mesh, alpha_1, alpha_2, beta, f_1, f_2, u_des_fixed) u_fine = Function(V) +# As mentioned earlier, due to the fact that the geometry changes during the +# optimization, the desired state has to be re-interpolated to the changing mesh in each +# iteration. We do so by using a callback function which is defined as + def callback(): LagrangeInterpolator.interpolate(u_des, u_des_fixed) LagrangeInterpolator.interpolate(u_fine, fine_model.u) -# define the parameter extraction step +# ### Parameter Extraction +# +# As mentioned in the beginning, in order to perform the space mapping, we have to +# establish a connection between the coarse and the fine models. This is done via the +# parameter extraction step, which we now detail. For this, a new cost functional (the +# misalignment function) has to be defined, and the corresponding optimization problem +# (constrained by the coarse model) is solved in each space mapping iteration. For our +# problem, this is done via + u_param = Function(V) J_param = cashocs.IntegralFunctional(Constant(0.5) * pow(u_param - u_fine, 2) * dx) parameter_extraction = space_mapping.ParameterExtraction( coarse_model, J_param, u_param, config=cfg, mode="initial" ) +# but of course other approaches are possible. +# +# ### Space Mapping Problem and Solution +# +# Finally, we have all ingredients available to define the space mapping problem and +# solve it. This is done with the lines problem = space_mapping.SpaceMappingProblem( fine_model, @@ -156,7 +397,14 @@ def callback(): problem.inject_pre_callback(callback) problem.solve() -# Post-processing +# There, we first define the problem, then inject the callback function we defined above +# so that the required re-interpolation takes place, and solve the problem with the call +# of it's {py:meth}`solve +# ` method. +# +# Finally, we perform a post-processing of the results with the code + +# + import matplotlib.pyplot as plt plt.figure(figsize=(15, 5)) @@ -178,3 +426,17 @@ def callback(): # plt.savefig( # "./img_space_mapping_semilinear_transmission.png", dpi=150, bbox_inches="tight" # ) +# - + +# and the result should look as follows +# ![](/../../demos/documented/shape_optimization/space_mapping_semilinear_transmission/img_space_mapping_semilinear_transmission.png) +# +# :::{note} +# The left image shows the optimized geometry with the coarse model, the middle image +# shows the optimized geometry with the fine model (with the space mapping technique), +# and the right image shows the reference geometry, which we were trying to reconstruct. +# We can see that using the coarse model alone as approximation of the original problem +# does not work sufficiently well as we recover some kind of rotated peanut shape, +# instead of a rotated ellipse. However, we see that the space mapping approach works +# very well for recovering the desired ellipse. +# ::: diff --git a/demos/documented/shape_optimization/space_mapping_uniform_flow_distribution/demo_space_mapping_uniform_flow_distribution.py b/demos/documented/shape_optimization/space_mapping_uniform_flow_distribution/demo_space_mapping_uniform_flow_distribution.py index 99df123a..ebe9cfa4 100644 --- a/demos/documented/shape_optimization/space_mapping_uniform_flow_distribution/demo_space_mapping_uniform_flow_distribution.py +++ b/demos/documented/shape_optimization/space_mapping_uniform_flow_distribution/demo_space_mapping_uniform_flow_distribution.py @@ -1,24 +1,138 @@ -# Copyright (C) 2020-2022 Sebastian Blauth +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.4 +# --- + +# ```{eval-rst} +# .. include:: ../../../global.rst +# ``` # -# This file is part of cashocs. +# (demo_space_mapping_uniform_flow_distribution)= +# # Space Mapping Shape Optimization - Uniform Flow Distribution # -# cashocs is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# ## Problem Formulation # -# cashocs is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# Let us consider another example of the space mapping technique, this time with an +# application to fluid dynamics. For an introduction to the space mapping technique, we +# refer the reader, e.g., to [Bakr, Bandler, Madsen, Sondergaard - An Introduction to +# the Space Mapping Technique](https://doi.org/10.1023/A:1016086220943) and [Echeverria +# and Hemker - Space mapping and defect correction]( +# https://doi.org/10.2478/cmam-2005-0006). For a detailed description of the methods in +# the context of shape optimization, we refer to [Blauth - Space Mapping for PDE +# Constrained Shape Optimization](https://arxiv.org/abs/2208.05747). # -# You should have received a copy of the GNU General Public License -# along with cashocs. If not, see . - -"""For the documentation of this demo see https://cashocs.readthedocs.io/en/latest/demos/shape_optimization/doc_shape_poisson.html. - -""" +# The example is taken from [Blauth - Space Mapping for PDE Constrained Shape +# Optimization](https://arxiv.org/abs/2208.05747) and we refer the reader to this +# publication for a detailed discussion of the problem setup. +# +# Assume that we have a pipe system consisting of one inlet $\Gamma_\mathrm{in}$ and +# three outlets $\Gamma_\mathrm{out}^i$ for $i=1,2,3$. The geometry of the pipe is +# denoted by $\Omega$ and the wall of the pipe is denoted by $\Gamma_\mathrm{wall}$. A +# sketch of this problem is shown below +# +# ![](/../../demos/documented/shape_optimization/space_mapping_uniform_flow_distribution/reference_pipe.png) +# +# We consider the optimization of the pipe system so that the flow of some fluid becomes +# uniform over all three outlets. Therefore, let $u$ denote the fluid's velocity. The +# outlet flow rate $q_\mathrm{out}^i(u)$ over pipe $i$ is defined as +# +# $$ +# q_\mathrm{out}^i(u) = \int_{\Gamma_\mathrm{out}^i} u \cdot n \text{ d}s +# $$ +# +# Therefore, we can define our optimization problem as follows +# +# $$ +# \begin{align} +# &\min_\Omega J(u_f, \Omega) = \frac{1}{2} \sum_{i=1}^{3} \left( +# q_\mathrm{out}^i(u_f) - q_\mathrm{des} \right)^2 \\ +# &\text{subject to} \qquad +# \begin{alignedat}[t]{2} +# -\Delta u_f + \mathrm{Re} (u_f \cdot \nabla) u_f + \nabla p_f &= 0 +# \quad &&\text{ in } \Omega,\\ +# \text{div}(u_f) &= 0 \quad &&\text{ in } \Omega,\\ +# u_f &= u_\mathrm{in} \quad &&\text{ on } \Gamma_\mathrm{in},\\ +# u_f &= 0 \quad &&\text{ on } \Gamma_\mathrm{wall},\\ +# p_f &= 0 \quad &&\text{ on } \Gamma_\mathrm{out},\\ +# u_f \times n &= 0 \quad &&\text{ on } \Gamma_\mathrm{out}. +# \end{alignedat} +# \end{align} +# $$ +# +# Here, we use the incompressible Navier-Stokes equations as PDE constraint. Note that +# the above model plays the role of the fine model, which is the problem we are +# interested in solving. +# +# However, as solving the nonlinear Navier-Stokes equations can be difficult and +# time-consuming, we use a coarse model consisting of the linear Stokes system, so that +# the coarse model optimization problem is given by +# +# $$ +# \begin{align} +# &\min_\Omega J(u_c, \Omega) = \frac{1}{2} \sum_{i=1}^{3} \left( +# q_\mathrm{out}^i(u_c) - q_\mathrm{des} \right)^2 \\ +# &\text{subject to} \qquad +# \begin{alignedat}[t]{2} +# -\Delta u_c + \nabla p_c &= 0 \quad &&\text{ in } \Omega,\\ +# \text{div}(u_c) &= 0 \quad &&\text{ in } \Omega,\\ +# u_c &= u_\mathrm{in} \quad &&\text{ on } \Gamma_\mathrm{in},\\ +# u_c &= 0 \quad &&\text{ on } \Gamma_\mathrm{wall},\\ +# p_c &= 0 \quad &&\text{ on } \Gamma_\mathrm{out},\\ +# u_c \times n &= 0 \quad &&\text{ on } \Gamma_\mathrm{out}. +# \end{alignedat} +# \end{align} +# $$ +# +# :::{note} +# Again, we need a misalignment function to match the fine and coarse model responses, +# see {ref}`demo_space_mapping_semilinear_transmission`. For this problem, we use the +# misalignment function +# +# $$ +# r(u,v) = \frac{1}{2} \sum_{i=1}^3 \left( q_\mathrm{out}^i(u) +# - q_\mathrm{out}^i(v) \right)^2 +# $$ +# +# so that the parameter extraction problem which has to be solved in each iteration of +# the space mapping method reads +# +# $$ +# \begin{align} +# &\min_\Omega J(u_p, \Omega) = \frac{1}{2} \sum_{i=1}^{3} \left( +# q_\mathrm{out}^i(u_p) +# - q_\mathrm{out}^i(u_f) \right)^2 \\ +# &\text{subject to} \qquad +# \begin{alignedat}[t]{2} +# -\Delta u_p + \nabla p_p &= 0 \quad &&\text{ in } \Omega,\\ +# \text{div}(u_p) &= 0 \quad &&\text{ in } \Omega,\\ +# u_p &= u_\mathrm{in} \quad &&\text{ on } \Gamma_\mathrm{in},\\ +# u_p &= 0 \quad &&\text{ on } \Gamma_\mathrm{wall},\\ +# p_p &= 0 \quad &&\text{ on } \Gamma_\mathrm{out},\\ +# u_p \times n &= 0 \quad &&\text{ on } \Gamma_\mathrm{out}. +# \end{alignedat} +# \end{align} +# $$ +# ::: +# +# ## Implementation +# +# The complete python code can be found in the file +# {download}`demo_space_mapping_uniform_flow_distribution.py +# ` +# and the corresponding config can be found in {download}`config.ini +# `. +# +# ### The coarse model +# +# As in {ref}`demo_space_mapping_semilinear_transmission`, we start with the +# implementation of the coarse model. We start by importing the required packages +# + import ctypes import os import subprocess @@ -27,14 +141,25 @@ import cashocs +# - + +# and we will detail why we require the packages when they are used. Next, we define the +# space mapping module, set up the log level, and define the current working directory + space_mapping = cashocs.space_mapping.shape_optimization cashocs.set_log_level(cashocs.LogLevel.ERROR) dir = os.path.dirname(os.path.realpath(__file__)) +# We then define the Reynolds number $\mathrm{Re} = 100$ and load the configuration file +# for the problem + Re = 100.0 cfg = cashocs.load_config("./config.ini") -# define the coarse model +# In the next steps, we define the coarse model, in analogy to all of the previous demos +# (see, e.g., {ref}`demo_shape_stokes` + +# + mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh("./mesh/mesh.xdmf") n = FacetNormal(mesh) @@ -63,8 +188,46 @@ J = [cashocs.ScalarTrackingFunctional(dot(u, n) * ds(i), q_in / 3) for i in range(5, 8)] coarse_model = space_mapping.CoarseModel(F, bcs, J, up, vq, boundaries, config=cfg) +# - + +# :::{note} +# The difference between the standard cashocs syntax and the syntax for space mapping is +# that we now use {py:class}`CoarseModel +# ` instead of the usual +# {py:class}`ShapeOptimizationProblem `. +# ::: +# +# ::::{note} +# The boundary conditions consist of a prescribed flow at the inlet. In order to be +# compatible with the incompressibility condition, we need to have that the outlet flow +# rates sum up to the inlet flow rate, so that mass is neither created nor destroyed. Of +# course, one could use a target output flow rate which does not satisfy this condition, +# but this would be unphysical. Therefore, the boundary condition on the inlet is +# defined as +# +# ```python +# u_in = Expression(("6.0*(0.0 - x[1])*(x[1] + 1.0)", "0.0"), degree=2) +# bc_in = DirichletBC(V.sub(0), u_in, boundaries, 1) +# q_in = -assemble(dot(u_in, n) * ds(1)) +# ``` +# +# and the cost functional is given by +# +# ```python +# J = [cashocs.ScalarTrackingFunctional(dot(u, n) * ds(i), q_in / 3) for i in range(5, 8)] +# ``` +# +# so that the target outlet flow rate is given by {python}`q_in / 3`, which means that +# we have a uniform flow distribution. For more details regrading the usage of +# {py:class}`ScalarTrackingFunctional `, we refer to +# {ref}`demo_scalar_control_tracking`. +# :::: +# +# ### The fine model +# +# As next step, we define the fine model optimization problem as follows -# define the fine model +# + class FineModel(space_mapping.FineModel): def __init__(self, mesh, Re, q_in, output_list): super().__init__(mesh) @@ -143,8 +306,165 @@ def solve_and_evaluate(self): fine_model = FineModel(mesh, Re, q_in, output_list) +# - + +# Again, the fine model problem is defined by subclassing {py:class}`FineModel +# ` and overwriting its +# {py:meth}`solve_and_evaluate ` +# method. +# +# ::::{admonition} Description of the FineModel +# Let us investigate the fine model in more details in the following. The fine models +# initialization starts with a call to its parent's {py:meth}`__init__` method, where +# the mesh is passed +# :::python +# super().__init__(mesh) +# ::: +# +# Next, a list of tracking goals is defined using the {python}`ctypes` module +# :::python +# self.tracking_goals = [ctypes.c_double(0.0) for _ in range(5, 8)] +# ::: +# +# :::{note} +# The {python}`ctypes` module allows us to make floats in python mutable, i.e., to +# behave like pointers. This is needed for the parameter extraction step, where we want +# to find a coarse model "optimum" which achieves the same flow rates as the current +# iterate of the fine model. In particular, the list {python}`self.tracking_goals` will +# be used later as input for the parameter extraction. +# ::: +# +# Afterwards, we have standard initializations of an iteration counter, the Reynolds +# number, the inlet flow rate, and an output list, which will be used to save the +# progress of the space mapping method +# :::python +# self.iter = 0 +# self.Re = Re +# self.q_in = q_in +# self.output_list = output_list +# ::: +# +# Let us now take a look at the core of the fine model, its {py:meth}`solve_and_evaluate +# ` +# method. It starts by incrementing the iteration counter +# :::python +# self.iter += 1 +# ::: +# +# Next, the current mesh is exported to two Gmsh .msh files. The first is used for a +# possible post-processing (so that the evolution of the geometries is saved) whereas +# the second is used to define the fine model mesh +# :::python +# cashocs.io.write_out_mesh( +# self.mesh, "./mesh/mesh.msh", f"./mesh/fine/mesh_{self.iter}.msh" +# ) +# cashocs.io.write_out_mesh(self.mesh, "./mesh/mesh.msh", f"./mesh/fine/mesh.msh") +# ::: +# +# Note that we do not use the same discretization for the fine and coarse model in this +# problem, but we remesh the geometry of the fine model using a higher resolution. To do +# so, the following Gmsh command is used, which is invoked via the {python}`subprocess` +# module +# :::python +# subprocess.run( +# ["gmsh", "./mesh/fine.geo", "-2", "-o", "./mesh/fine/fine.msh"], +# check=True, +# stdout=subprocess.DEVNULL, +# stderr=subprocess.DEVNULL, +# ) +# ::: +# +# Finally, the mesh generated with the above command is converted to XDMF with +# {py:func}`` +# :::python +# cashocs.convert("./mesh/fine/fine.msh", "./mesh/fine/fine.xdmf") +# ::: +# +# Now that we have the geometry of the problem, it is loaded into python and we define +# the Taylor-Hood Function Space for the Navier-Stokes system +# :::python +# mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh( +# "./mesh/fine/fine.xdmf" +# ) +# n = FacetNormal(mesh) +# v_elem = VectorElement("CG", mesh.ufl_cell(), 2) +# p_elem = FiniteElement("CG", mesh.ufl_cell(), 1) +# V = FunctionSpace(mesh, v_elem * p_elem) +# +# up = Function(V) +# u, p = split(up) +# v, q = TestFunctions(V) +# ::: +# +# Next, we define the weak form of the problem and its boundary conditions +# :::python +# F = ( +# inner(grad(u), grad(v)) * dx +# + Constant(self.Re) * inner(grad(u) * u, v) * dx +# - p * div(v) * dx +# - q * div(u) * dx +# ) +# +# u_in = Expression(("6.0*(0.0 - x[1])*(x[1] + 1.0)", "0.0"), degree=2) +# bc_in = DirichletBC(V.sub(0), u_in, boundaries, 1) +# bcs_wall = cashocs.create_dirichlet_bcs( +# V.sub(0), Constant((0.0, 0.0)), boundaries, [2, 3, 4] +# ) +# bc_out = DirichletBC(V.sub(0).sub(0), Constant(0.0), boundaries, 5) +# bc_pressure = DirichletBC(V.sub(1), Constant(0.0), boundaries, 5) +# bcs = [bc_in +# ::: +# +# The problem is then solved with {py:func}`` +# :::python +# cashocs.newton_solve(F, up, bcs, verbose=False) +# ::: +# +# Finally, after having solved the problem, we first save the solution for later +# visualization by +# :::python +# self.u, p = up.split(True) +# +# file = File(f"./pvd/u_{self.iter}.pvd") +# file.write(self.u) +# ::: +# +# Next, we evaluate the cost functional with the lines +# :::python +# J_list = [ +# cashocs.ScalarTrackingFunctional(dot(self.u, n) * ds(i), self.q_in / 3) +# for i in range(5, 8) +# ] +# self.cost_functional_value = cashocs._utils.summation( +# [J.evaluate() for J in J_list] +# ) +# ::: +# +# Finally, we save the values of the outlet flow rate first to our list +# {python}`self.output_list` and second to the list {python}`self.tracking_goals`, so +# that the parameter extraction can see the updated flow rates +# :::python +# self.flow_values = [assemble(dot(self.u, n) * ds(i)) for i in range(5, 8)] +# self.output_list.append(self.flow_values) +# +# for idx in range(len(self.tracking_goals)): +# self.tracking_goals[idx].value = self.flow_values[idx] +# ::: +# +# Note that we have to overwrite {python}`self.tracking_goals[idx].value` as +# {python}`self.tracking_goals[idx]` is a {py:class}`ctypes.double` object. +# :::: +# +# :::{attention} +# As already mentioned in {ref}`demo_space_mapping_semilinear_transmission`, users have +# to update the attribute {python}`cost_functional_value` of the fine model in order +# for the space mapping method to be able to use the value. +# ::: +# +# ### Parameter Extraction +# +# Now, we are finally ready to define the parameter extraction. This is done via -# define the parameter extraction step up_param = Function(V) u_param, p_param = split(up_param) J_param = [ @@ -157,6 +477,15 @@ def solve_and_evaluate(self): coarse_model, J_param, up_param, config=cfg ) +# :::{note} +# The parameter extraction uses the previously defined list +# {python}`fine_model.tracking_goals` of type {py:class}`ctypes.double` for the +# definition of its cost functional. +# ::: +# +# ### Space Mapping Problem and Solution +# +# In the end, we can now define the space mapping problem and solve it with the lines problem = space_mapping.SpaceMappingProblem( fine_model, @@ -173,7 +502,9 @@ def solve_and_evaluate(self): problem.solve() -# Post-processing +# We visualize the results with the code + +# + import matplotlib.pyplot as plt plt.figure(figsize=(20, 20)) @@ -198,3 +529,25 @@ def solve_and_evaluate(self): # plt.savefig( # "./img_space_mapping_uniform_flow_distribution.png", dpi=150, bbox_inches="tight" # ) +# - + +# and the output is shown below +# ![](/../../demos/documented/shape_optimization/space_mapping_uniform_flow_distribution/img_space_mapping_uniform_flow_distribution.png) +# +# :::{note} +# On the left side of the above image the results for the coarse model are shown, +# whereas the results for the fine model are shown on the left side. On the top of the +# figure we see the geometries used for the models, and on the bottom the velocity +# is shown. +# +# We observe that the coarse model uses a much coarser discretization in comparison with +# the fine model. Moreover, we can also nicely see that the coarse model optimal +# geometry works fine for optimizing the Stokes system, but that the behavior of the +# fine model is vastly different due to the inertial effects of the Navier-Stokes +# system. However, the space mapping technique still allows us to solve the fine model +# problem with the help of the approximate coarse model, where we only require +# simulations of the fine model. +# +# For a more thorough discussion of the results, we refer the reader to [Blauth - Space +# Mapping for PDE Constrained Shape Optimization](https://arxiv.org/abs/2208.05747). +# ::: diff --git a/docs/requirements.txt b/docs/requirements.txt index e4753a64..b78576ea 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -8,3 +8,5 @@ sphinx-design==0.3 setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability pygments>=2.7.4 # not directly required, pinned by Snyk to avoid a vulnerability typing_extensions +myst-parser==0.18.1 +jupytext==1.14.4 diff --git a/docs/source/conf.py b/docs/source/conf.py index f0226e7b..9dfbd490 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,6 +14,11 @@ import sys sys.path.insert(0, os.path.abspath("../..")) +sys.path.insert(0, os.path.abspath(".")) + +import jupytext_process + +jupytext_process.process() # -- Project information ----------------------------------------------------- @@ -39,6 +44,7 @@ "sphinx_copybutton", "sphinx.ext.viewcode", "sphinx_design", + "myst_parser", ] napoleon_google_docstring = True @@ -126,3 +132,19 @@ autosummary_generate = True autosummary_imported_members = True + +myst_enable_extensions = ["dollarmath", "colon_fence"] + +rst_prolog = """ +.. role:: ini(code) + :language: ini + :class: highlight + +.. role:: python(code) + :language: python + :class: highlight + +.. role:: cpp(code) + :language: cpp + :class: highlight +""" diff --git a/docs/source/docutils.conf b/docs/source/docutils.conf new file mode 100755 index 00000000..1bf4d832 --- /dev/null +++ b/docs/source/docutils.conf @@ -0,0 +1,2 @@ +[restructuredtext parser] +syntax_highlight = short diff --git a/docs/source/global.rst b/docs/source/global.rst new file mode 100755 index 00000000..eabfeb1b --- /dev/null +++ b/docs/source/global.rst @@ -0,0 +1,11 @@ +.. role:: ini(code) + :language: ini + :class: highlight + +.. role:: python(code) + :language: python + :class: highlight + +.. role:: cpp(code) + :language: cpp + :class: highlight diff --git a/docs/source/jupytext_process.py b/docs/source/jupytext_process.py new file mode 100644 index 00000000..15695d2d --- /dev/null +++ b/docs/source/jupytext_process.py @@ -0,0 +1,41 @@ +""" +Created on 21/12/2022, 08.39 + +@author: blauths +""" + +import pathlib + +import jupytext + + +def process(): + """Convert light format demo Python files into MyST flavoured markdown and + ipynb using Jupytext. These files can then be included in Sphinx + documentation""" + # Directories to scan + subdirs = [pathlib.Path("../../demos/documented")] + + # Iterate over subdirectories containing demos + for subdir in subdirs: + # Make demo doc directory + demo_dir = pathlib.Path("./user/demos") + demo_dir.mkdir(parents=True, exist_ok=True) + + # Process each demo using jupytext/myst + for demo in subdir.glob("**/demo*.py"): + # for demo in subdir.glob("**/demo_space_mapping_semilinear_transmission.py"): + python_demo = jupytext.read(demo) + myst_text = jupytext.writes(python_demo, fmt="myst") + + # myst-parser does not process blocks with {code-cell} + myst_text = myst_text.replace("{code-cell}", "python") + myst_file = (demo_dir / demo.parent.parent.name / demo.name).with_suffix( + ".md" + ) + with open(myst_file, "w") as fw: + fw.write(myst_text) + + +if __name__ == "__main__": + process() diff --git a/docs/source/user/demos/cashocs_as_solver/doc_control_solver.rst b/docs/source/user/demos/cashocs_as_solver/doc_control_solver.rst deleted file mode 100644 index dad3e061..00000000 --- a/docs/source/user/demos/cashocs_as_solver/doc_control_solver.rst +++ /dev/null @@ -1,164 +0,0 @@ -.. _demo_control_solver: - -cashocs as Solver for Optimal Control Problems -============================================== - -Problem Formulation -------------------- - -As a model problem we again consider the one from :ref:`demo_poisson`, but now -we do not use cashocs to compute the adjoint equation and derivative of the (reduced) -cost functional, but supply these terms ourselves. The optimization problem is -given by - -.. math:: - - &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 - \text{ d}x + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x \\ - &\text{ subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta y &= u \quad &&\text{ in } \Omega,\\ - y &= 0 \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - - -(see, e.g., `Tröltzsch, Optimal Control of Partial Differential Equations -`_ -or `Hinze, Pinnau, Ulbrich, and Ulbrich, Optimization with PDE constraints -`_). - -For the numerical solution of this problem we consider exactly the same setting as -in :ref:`demo_poisson`, and most of the code will be identical to this. - -Implementation --------------- -The complete python code can be found in the file :download:`demo_control_solver.py `, -and the corresponding config can be found in :download:`config.ini `. - -Recapitulation of :ref:`demo_poisson` -************************************* - -For using cashocs exclusively as solver, the procedure is very similar to regularly -using it, with a few additions after defining the optimization problem. In particular, -up to the initialization of the optimization problem, our code is exactly the same as -in :ref:`demo_poisson`, i.e., we use :: - - from fenics import * - - import cashocs - - config = cashocs.load_config("config.ini") - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(25) - V = FunctionSpace(mesh, "CG", 1) - - y = Function(V) - p = Function(V) - u = Function(V) - - e = inner(grad(y), grad(p)) * dx - u * p * dx - - bcs = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) - - y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) - alpha = 1e-6 - J = Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * u * u * dx - - ocp = cashocs.OptimalControlProblem(e, bcs, J, y, u, p, config) - - -Supplying Custom Adjoint Systems and Derivatives -************************************************ - -When using cashocs as a solver, the user can specify their custom weak forms of -the adjoint system and of the derivative of the reduced cost functional. For our -optimization problem, the adjoint equation is given by - -.. math:: - - \begin{alignedat}{2} - - \Delta p &= y - y_d \quad &&\text{ in } \Omega, \\ - p &= 0 \quad &&\text{ on } \Gamma, - \end{alignedat} - -and the derivative of the cost functional is then given by - -.. math:: - - dJ(u)[h] = \int_\Omega (\alpha u + p) h \text{ d}x - -.. note:: - - For a detailed derivation and discussion of these objects we refer to - `Tröltzsch, Optimal Control of Partial Differential Equations - `_ - or `Hinze, Pinnau, Ulbrich, and Ulbrich, Optimization with PDE constraints - `_. - - -To specify that cashocs should use these equations instead of the automatically -computed ones, we have the following code. First, we specify the derivative -of the reduced cost functional via :: - - dJ = Constant(alpha) * u * TestFunction(V) * dx + p * TestFunction(V) * dx - - - -Afterwards, the weak form for the adjoint system is given by :: - - adjoint_form = ( - inner(grad(p), grad(TestFunction(V))) * dx - (y - y_d) * TestFunction(V) * dx - ) - adjoint_bcs = bcs - -where we can "recycle" the homogeneous Dirichlet boundary conditions used for the state -problem. - -For both objects, one has to define them as a single UFL form for cashocs, as with the -state system and cost functional. In particular, the adjoint weak form has to be in -the form of a nonlinear variational problem, so that ``fenics.solve(adjoint_form == 0, p, adjoint_bcs)`` -could be used to solve it. In particular, both forms have to include :py:class:`fenics.TestFunction` -objects from the control space and adjoint space, respectively, and must not contain -:py:class:`fenics.TrialFunction` objects. - -These objects are then supplied to the :py:class:`OptimalControlProblem ` -via :: - - ocp.supply_custom_forms(dJ, adjoint_form, adjoint_bcs) - - -.. note:: - - One can also specify either the adjoint system or the derivative of the cost functional, using - the methods :py:meth:`supply_adjoint_forms ` - or :py:meth:`supply_derivatives `. - However, this is potentially dangerous, due to the following. The adjoint system - is a linear system, and there is no fixed convention for the sign of the adjoint state. - Hence, supplying, e.g., only the adjoint system, might not be compatible with the - derivative of the cost functional which cashocs computes. In effect, the sign - is specified by the choice of adding or subtracting the PDE constraint from the - cost functional for the definition of a Lagrangian function, which is used to - determine the adjoint system and derivative. cashocs internally uses the convention - that the PDE constraint is added, so that, internally, it computes not the adjoint state - :math:`p` as defined by the equations given above, but :math:`-p` instead. - Hence, it is recommended to either specify all respective quantities with the - :py:meth:`supply_custom_forms ` method. - - -Finally, we can use the :py:meth:`solve ` method -to solve the problem with the line :: - - ocp.solve() - -as in :ref:`demo_poisson`. The results are, of course, identical to :ref:`demo_poisson` and look as -follows - -.. image:: /../../demos/documented/cashocs_as_solver/control_solver/img_control_solver.png - -.. note:: - - In case we have multiple state equations as in :ref:`demo_multiple_variables`, - one has to supply ordered lists of adjoint equations and boundary conditions, - analogously to the usual procedure for cashocs. - - In the case of multiple control variables, the derivatives of the reduced cost functional - w.r.t. each of these have to be specified, again using an ordered list. diff --git a/docs/source/user/demos/cashocs_as_solver/doc_shape_solver.rst b/docs/source/user/demos/cashocs_as_solver/doc_shape_solver.rst deleted file mode 100644 index eeceebc8..00000000 --- a/docs/source/user/demos/cashocs_as_solver/doc_shape_solver.rst +++ /dev/null @@ -1,171 +0,0 @@ -.. _demo_shape_solver: - -cashocs as Solver for Shape Optimization Problems -================================================= - -Problem Formulation -------------------- - -Let us now investigate how cashocs can be used exclusively as a solver for shape optimization -problems. The procedure is very similar to the one discussed in :ref:`demo_control_solver`, -but with some minor variations tailored to shape optimization. - -As a model shape optimization problem we consider the same as in :ref:`demo_shape_poisson`, i.e., - -.. math:: - - &\min_\Omega J(u, \Omega) = \int_\Omega u \text{ d}x \\ - &\text{subject to} \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta u &= f \quad &&\text{ in } \Omega,\\ - u &= 0 \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - - -For the initial domain, we use the unit disc :math:`\Omega = \{ x \in \mathbb{R}^2 \,\mid\, \lvert\lvert x \rvert\rvert_2 < 1 \}`, and the right-hand side :math:`f` is given by - -.. math:: f(x) = 2.5 \left( x_1 + 0.4 - x_2^2 \right)^2 + x_1^2 + x_2^2 - 1. - - -Implementation --------------- -The complete python code can be found in the file :download:`demo_shape_solver.py `, -and the corresponding config can be found in :download:`config.ini `. - -Recapitulation of :ref:`demo_shape_poisson` -******************************************* - -Analogously to :ref:`demo_control_solver`, the code we use in case cashocs is treated as -a solver only is identical to the one of :ref:`demo_shape_poisson` up to the definition -of the optimization problem. For the sake of completeness we recall the corresponding code in the -following :: - - from fenics import * - - import cashocs - - config = cashocs.load_config("./config.ini") - - mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh("./mesh/mesh.xdmf") - - V = FunctionSpace(mesh, "CG", 1) - u = Function(V) - p = Function(V) - - x = SpatialCoordinate(mesh) - f = 2.5 * pow(x[0] + 0.4 - pow(x[1], 2), 2) + pow(x[0], 2) + pow(x[1], 2) - 1 - - e = inner(grad(u), grad(p)) * dx - f * p * dx - bcs = DirichletBC(V, Constant(0), boundaries, 1) - - J = u * dx - - sop = cashocs.ShapeOptimizationProblem(e, bcs, J, u, p, boundaries, config) - - -Supplying Custom Adjoint Systems and Shape Derivatives -****************************************************** - -Now, our goal is to use custom UFL forms for the adjoint system and shape derivative. -Note, that the adjoint system for the considered optimization problem is given by - -.. math:: - - \begin{alignedat}{2} - - \Delta p &= 1 \quad &&\text{ in } \Omega, \\ - p &= 0 \quad &&\text{ on } \Gamma, - \end{alignedat} - -and the corresponding shape derivative is given by - -.. math:: - - dJ(\Omega)[\mathcal{V}] = \int_{\Omega} \text{div}\left( \mathcal{V} \right) u \text{ d}x - + \int_{\Omega} \left( \left( \text{div}(\mathcal{V})I - 2 \varepsilon(\mathcal{V}) \right) \nabla u \right) \cdot \nabla p \text{ d}x - + \int_{\Omega} \text{div}\left( f \mathcal{V} \right) p \text{ d}x, - - -where :math:`\varepsilon(\mathcal{V})` is the symmetric part of the gradient of :math:`\mathcal{V}` -given by :math:`\varepsilon(\mathcal{V}) = \frac{1}{2} \left( D\mathcal{V} + D\mathcal{V}^\top \right)`. -For details, we refer the reader to, e.g., `Delfour and Zolesio, Shapes and Geometries `_. - -To supply these weak forms to cashocs, we can use the following code. For the -shape derivative, we write :: - - def eps(u): - return Constant(0.5) * (grad(u) + grad(u).T) - - - vector_field = sop.get_vector_field() - dJ = ( - div(vector_field) * u * dx - - inner( - (div(vector_field) * Identity(2) - 2 * eps(vector_field)) * grad(u), - grad(p), - ) - * dx - + div(f * vector_field) * p * dx - ) - -Note, that we have to call the :py:meth:`get_vector_field ` method -which returns the UFL object corresponding to :math:`\mathcal{V}` and which is to be used -at its place. - -.. hint:: - - Alternatively, one could define the variable ``vector_field`` as follows:: - - space = VectorFunctionSpace(mesh, 'CG', 1) - vector_field = TestFunction(space) - - which would yield identical results. However, the shorthand via the - :py:meth:`get_vector_field ` - is more convenient, as one does not have to remember to define the correct function - space first. - -For the adjoint system, the procedure is exactly the same as in :ref:`demo_control_solver` -and we have the following code :: - - adjoint_form = inner(grad(p), grad(TestFunction(V))) * dx - TestFunction(V) * dx - adjoint_bcs = bcs - -Again, the format is analogous to the format of the state system, but now we have to -specify a :py:class:`fenics.TestFunction` object for the adjoint equation. - -Finally, the weak forms are supplied to cashocs with the line :: - - sop.supply_custom_forms(dJ, adjoint_form, adjoint_bcs) - -and the optimization problem is solved with :: - - sop.solve() - -.. note:: - - One can also specify either the adjoint system or the shape derivative of the cost functional, using - the methods :py:meth:`supply_adjoint_forms ` - or :py:meth:`supply_derivatives `. - However, this is potentially dangerous, due to the following. The adjoint system - is a linear system, and there is no fixed convention for the sign of the adjoint state. - Hence, supplying, e.g., only the adjoint system, might not be compatible with the - derivative of the cost functional which cashocs computes. In effect, the sign - is specified by the choice of adding or subtracting the PDE constraint from the - cost functional for the definition of a Lagrangian function, which is used to - determine the adjoint system and derivative. cashocs internally uses the convention - that the PDE constraint is added, so that, internally, it computes not the adjoint state - :math:`p` as defined by the equations given above, but :math:`-p` instead. - Hence, it is recommended to either specify all respective quantities with the - :py:meth:`supply_custom_forms ` method. - - -The result is, of course, completely identical to the one of :ref:`demo_shape_poisson` and looks -as follows - -.. image:: /../../demos/documented/cashocs_as_solver/shape_solver/img_shape_solver.png - - -.. note:: - - In case multiple state equations are used, the corresponding adjoint systems - also have to be specified as ordered lists, just as explained for optimal control - problems in :ref:`demo_multiple_variables`. diff --git a/docs/source/user/demos/cashocs_as_solver/solver_index.rst b/docs/source/user/demos/cashocs_as_solver/index.rst similarity index 86% rename from docs/source/user/demos/cashocs_as_solver/solver_index.rst rename to docs/source/user/demos/cashocs_as_solver/index.rst index ce103f1d..b1c51dbb 100644 --- a/docs/source/user/demos/cashocs_as_solver/solver_index.rst +++ b/docs/source/user/demos/cashocs_as_solver/index.rst @@ -10,5 +10,5 @@ can choose to supply these forms themselves. :maxdepth: 1 :caption: Corresponding Demos: - doc_control_solver - doc_shape_solver + demo_control_solver.md + demo_shape_solver.md diff --git a/docs/source/user/demos/misc/doc_xdmf_io.rst b/docs/source/user/demos/misc/doc_xdmf_io.rst deleted file mode 100755 index a45b4f67..00000000 --- a/docs/source/user/demos/misc/doc_xdmf_io.rst +++ /dev/null @@ -1,4 +0,0 @@ -Writing and reading XDMF files via cashocs -========================================== - -In this tutorial, we investigate how cashocs can be used to write out XDMF files containing parts of the solution and, more interestingly, how these files can be read again for post-processing. This demo uses :ref:`demo_shape_poisson` as an example problem and performs a post-processing of the obtained geometries and solution files, visualizing them from python. diff --git a/docs/source/user/demos/misc/index.rst b/docs/source/user/demos/misc/index.rst index ed2488c8..b6b6c8d1 100755 --- a/docs/source/user/demos/misc/index.rst +++ b/docs/source/user/demos/misc/index.rst @@ -6,5 +6,5 @@ In this part of the tutorial, we showcase several other features of cashocs, whi .. toctree:: :maxdepth: 1 :caption: List of all miscellaneous tutorials: - - doc_xdmf_io + + demo_xdmf_io.md diff --git a/docs/source/user/demos/optimal_control/doc_box_constraints.rst b/docs/source/user/demos/optimal_control/doc_box_constraints.rst deleted file mode 100755 index 30e4411c..00000000 --- a/docs/source/user/demos/optimal_control/doc_box_constraints.rst +++ /dev/null @@ -1,134 +0,0 @@ -.. _demo_box_constraints: - -Control Constraints -=================== - - -Problem Formulation -------------------- - -In this demo, we take a deeper look at how control constraints can be treated in -cashocs. To do so, we investigate the same problem as in :ref:`demo_poisson`, but -now with the addition of box constraints for the control variable. This problem -reads - - -.. math:: - - &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 \text{ d}x + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x \\ - &\text{ subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta y &= u \quad &&\text{ in } \Omega,\\ - y &= 0 \quad &&\text{ on } \Gamma, \\ - u_a \leq u &\leq u_b \quad &&\text{ in } \Omega - \end{alignedat} \right. - - -(see, e.g., `Tröltzsch, Optimal Control of Partial Differential Equations `_ -or `Hinze, Pinnau, Ulbrich, and Ulbrich, Optimization with PDE constraints `_. - -Here, the functions :math:`u_a` and :math:`u_b` are :math:`L^\infty(\Omega)` -functions. As before, we consider -as domain the unit square, i.e., :math:`\Omega = (0, 1)^2`. - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_box_constraints.py `, -and the corresponding config can be found in :download:`config.ini `. - -Initialization -************** - -The beginning of the script is completely identical to the -one of :ref:`previous example `, so we only restate the corresponding -code in the following :: - - from fenics import * - - import cashocs - - config = cashocs.load_config("config.ini") - - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(20) - V = FunctionSpace(mesh, "CG", 1) - - y = Function(V) - p = Function(V) - u = Function(V) - - e = inner(grad(y), grad(p)) * dx - u * p * dx - - bcs = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) - - y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) - alpha = 1e-6 - J = cashocs.IntegralFunctional( - Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * u * u * dx - ) - -Definition of the control constraints -************************************* - - -Here, we have nearly everything at hand to define the optimal -control problem, the only missing ingredient are the box constraints, -which we define now. For the purposes of this example, we -consider a linear (in the x-direction) corridor for these -constraints, as it highlights the capabilities of cashocs. -Hence, we define the lower and upper bounds via :: - - u_a = interpolate(Expression("50*(x[0]-1)", degree=1), V) - u_b = interpolate(Expression("50*x[0]", degree=1), V) - -which just corresponds to two functions, generated from -:py:class:`fenics.Expression` objects via :py:func:`fenics.interpolate`. These are then put -into the list ``cc``, which models the control constraints, i.e., :: - - cc = [u_a, u_b] - - -.. note:: - - As an alternative way of specifying the box constraints, one - can also use regular float or int objects, in case that they - are constant. For example, the constraint that we only want to - consider positive value for u, i.e., :math:`0 \leq u \leq +\infty` can - be realized via :: - - u_a = 0 - u_b = float('inf') - cc = [u_a, u_b] - - and completely analogous with ``float('-inf')`` for no constraint - on the lower bound. Moreover, note that the specification of using either - constant ``float`` values and :py:class:`fenics.Function` objects - can be mixed arbitrarily, so that one can, e.g., specify a constant value for - the upper boundary and use a :py:class:`fenics.Function` on the lower one. - -Setup of the optimization problem and its solution -************************************************** - -Now, we can set up the optimal control problem as we did before, -using the additional keyword argument ``control_constraints`` into which -we put the list ``cc``, and then solve it via the :py:meth:`ocp.solve() ` -method :: - - ocp = cashocs.OptimalControlProblem(e, bcs, J, y, u, p, config, control_constraints=cc) - ocp.solve() - -To check that the box constraints are actually satisfied by our -solution, we perform an assertion :: - - import numpy as np - - assert np.alltrue(u_a.vector()[:] <= u.vector()[:]) and np.alltrue( - u.vector()[:] <= u_b.vector()[:] - ) - - -which shows that they are indeed satisfied. The visualization is carried out analogously -to before, and should yield the following result - -.. image:: /../../demos/documented/optimal_control/box_constraints/img_box_constraints.png - diff --git a/docs/source/user/demos/optimal_control/doc_config.rst b/docs/source/user/demos/optimal_control/doc_config.rst index 682f4545..e14b7464 100755 --- a/docs/source/user/demos/optimal_control/doc_config.rst +++ b/docs/source/user/demos/optimal_control/doc_config.rst @@ -33,7 +33,9 @@ can be found at :ref:`the end of this page `. Section Mesh ------------ The mesh section consists, for optimal control problems, only of a path to the -.xdmf version of the mesh file :: +.xdmf version of the mesh file + +.. code-block:: ini mesh_file = ../mesh/mesh.xdmf @@ -52,7 +54,9 @@ Section StateSystem The state system section is used to detail how the state and adjoint systems are solved. This includes settings for a damped Newton method and a Picard iteration. -In the following, we go over each parameter in detail. First, we have :: +In the following, we go over each parameter in detail. First, we have + +.. code-block:: ini is_linear = True @@ -60,43 +64,55 @@ This is a boolean parameter which indicates, whether the state system is linear. This is used to speed up some computations. Note, that the program will always work when this is set to False, as it treats the linear problem in a nonlinear fashion and the corresponding solver converges in one iteration. However, using -``is_linear = True`` +:ini:`is_linear = True` on a nonlinear state system throws an error related to FEniCS. The default value -for this parameter is ``False``. +for this parameter is :ini:`is_linear = False`. -The next parameter is defined via :: +The next parameter is defined via + +.. code-block:: ini newton_rtol = 1e-11 This parameter determines the relative tolerance for the Newton solver that is used to solve a nonlinear state system. Subsequently, we can also define the -absolute tolerance for the Newton solver via :: +absolute tolerance for the Newton solver via + +.. code-block:: ini newton_atol = 1e-13 -Note, that the default values are ``newton_rtol = 1e-11`` and ``newton_atol = 1e-13``. +Note, that the default values are :ini:`newton_rtol = 1e-11` and :ini:`newton_atol = 1e-13`. -The parameter ``newton_iter``, which is defined via :: +The parameter :ini:`newton_iter`, which is defined via + +.. code-block:: ini newton_iter = 50 controls how many iterations the Newton method is allowed to make before it -terminates. This defaults to 50. +terminates. This defaults to :ini:`newton_iter = 50`. + +Moreover, we have the boolean :ini:`newton_damped` -Moreover, we have the boolean ``newton_damped`` :: +.. code-block:: ini newton_damped = True -which determines whether a damping should be used (in case this is ``True``) or not -(otherwise). This parameter defaults to ``False`` if nothing is given. +which determines whether a damping should be used (in case this is :ini:`newton_damped = True`) or not +(otherwise). This parameter defaults to :ini:`newton_damped = False` if nothing is given. + +Additionally, we have the boolean parameter :ini:`newton_inexact`, defined via -Additionally, we have the boolean parameter ``newton_inexact``, defined via :: +.. code-block:: ini newton_inexact = False -which sets up an inexact Newton method for solving nonlinear problems in case this is ``True``. The default is ``False``. +which sets up an inexact Newton method for solving nonlinear problems in case this is :ini:`newton_inexact = True`. The default is :ini:`newton_inexact = False`. -The parameter :: +The parameter + +.. code-block:: ini newton_verbose = False @@ -104,39 +120,47 @@ is used to make the Newton solver's output verbose. This is disabled by default. This concludes the settings for Newton's method. -Next up, we have the parameters controlling the Picard iteration. First, we have :: +Next up, we have the parameters controlling the Picard iteration. First, we have + +.. code-block:: ini picard_iteration = False This is another boolean flag, used to determine, whether the state system -shall be solved using a Picard iteration (if this is ``True``) or not -(if this is ``False``). For a single state equation (i.e. one single state +shall be solved using a Picard iteration (if this is :ini:`picard_iteration = True`) or not +(if this is :ini:`picard_iteration = False`). For a single state equation (i.e. one single state variable) both options are equivalent. The difference is only active when considering a coupled system with multiple state variables that is coupled. The -default value for this parameter is ``False``. +default value for this parameter is :ini:`picard_iteration = False`. -The tolerances for the Picard iteration are defined via :: +The tolerances for the Picard iteration are defined via + +.. code-block:: ini picard_rtol = 1e-10 picard_atol = 1e-12 The first parameter determines the relative tolerance used for the Picard iteration, in case it is enabled, and the second one determines the absolute -tolerance. Their default value are given by ``picard_rtol = 1e-10`` and -``picard_atol = 1e-12``. +tolerance. Their default value are given by :ini:`picard_rtol = 1e-10` and +:ini:`picard_atol = 1e-12`. + +The maximum number of iterations of the method can be set via -The maximum number of iterations of the method can be set via :: +.. code-block:: ini picard_iter = 10 -and the default value for this parameter is ``picard_iter = 50``. +and the default value for this parameter is :ini:`picard_iter = 50`. -The parmater ``picard_verbose`` enables verbose output of the convergence of the -Picard iteration, and is set as follows :: +The parmater :ini:`picard_verbose` enables verbose output of the convergence of the +Picard iteration, and is set as follows + +.. code-block:: ini picard_verbose = False -Its default value is ``False``. +Its default value is :ini:`False`. @@ -148,24 +172,28 @@ Section OptimizationRoutine The following section is used to specify general parameters for the solution algorithm, which can be customized here. The first parameter determines the -choice of the particular algorithm, via :: +choice of the particular algorithm, via + +.. code-block:: ini algorithm = lbfgs The possible choices are given by - - ``gd`` or ``gradient_descent`` : a gradient descent method + - :ini:`algorithm = gd` or :ini:`algorithm = gradient_descent` : a gradient descent method - - ``cg``, ``conjugate_gradient``, ``ncg``, ``nonlinear_cg`` : nonlinear CG methods + - :ini:`algorithm = cg`, :ini:`algorithm = conjugate_gradient`, :ini:`algorithm = ncg`, :ini:`algorithm = nonlinear_cg` : nonlinear CG methods - - ``lbfgs`` or ``bfgs`` : limited memory BFGS method + - :ini:`algorithm = lbfgs` or :ini:`algorithm = bfgs` : limited memory BFGS method - - ``newton`` : a truncated Newton method + - :ini:`algorithm = newton` : a truncated Newton method Note, that there is no default value, so that this always has to be specified by the user. -The next line of the config file is given by :: +The next line of the config file is given by + +.. code-block:: ini rtol = 1e-4 @@ -174,38 +202,48 @@ In the case where no control constraints are present, this uses the "classical" norm of the gradient of the cost functional as measure. In case there are box constraints present, it uses the stationarity measure (see `Kelley, Iterative Methods for Optimization `_ as measure. -Analogously, we also have the line :: +Analogously, we also have the line + +.. code-block:: ini atol = 0.0 This determines the absolute tolerance for the solution algorithm. The default -tolerances for the optimization algorithm are given by ``rtol = 1e-3`` and -``atol = 0.0``. +tolerances for the optimization algorithm are given by :ini:`rtol = 1e-3` and +:ini:`atol = 0.0`. + +Next up, we have -Next up, we have :: +.. code-block:: ini maximum_iterations = 100 This parameter determines the maximum number of iterations carried out by the solution algorithm before it is terminated. It defaults to -``maximum_iterations = 100``. +:ini:`maximum_iterations = 100`. -The initial step size for the Armijo line search can be set via :: +The initial step size for the Armijo line search can be set via + +.. code-block:: ini initial_stepsize = 1.0 This can have an important effect on performance of the gradient descent and nonlinear cg methods, as they do not include a built-in scaling of the step size. The default -value is ``initial_stepsize = 1.0``. +value is :ini:`initial_stepsize = 1.0`. + +The next parameter is given by -The next parameter is given by :: +.. code-block:: ini safeguard_stepsize = True This parameter can be used to activate safeguarding of the initial stepsize for line search methods. This helps to choose an apropriate stepsize for the initial iteration even if the problem is poorly scaled. -The next paramter, ``epsilon_armijo``, is defined as follows :: +The next paramter, :ini:`epsilon_armijo`, is defined as follows + +.. code-block:: ini epsilon_armijo = 1e-4 @@ -216,45 +254,53 @@ sufficient decrease, via where u is the current optimization variable, d is the search direction, t is the step size, and g is the current gradient. Note, that :math:`\varepsilon` -corresponds to the parameter ``epsilon_armijo``. -A value of 1e-4 is recommended and commonly used (see `Nocedal and Wright, +corresponds to the parameter :ini:`epsilon_armijo`. +A value of 1e-4 is recommended and commonly used (see `Nocedal and Wright - Numerical Optimization `_), so that -we use ``epsilon_armijo = 1e-4`` as default value. +we use :ini:`epsilon_armijo = 1e-4` as default value. + +In the following line, the parameter :ini:`beta_armijo` is defined -In the following line, the parameter ``beta_armijo`` is defined :: +.. code-block:: ini beta_armijo = 2 This parameter determines the factor by the which the step size is decreased if the Armijo condition is not satisfied, i.e., we get :math:`t = \frac{t}{\beta}`as new -step size, where :math:`\beta` corresponds to ``beta_armijo``. The default value -for this parameter is ``beta_armijo = 2.0``. +step size, where :math:`\beta` corresponds to :ini:`beta_armijo`. The default value +for this parameter is :ini:`beta_armijo = 2.0`. Next, we have a set of two parameters which detail the methods used for computing gradients in cashocs. -These parameters are :: +These parameters are + +.. code-block:: ini gradient_method = direct -as well as :: +as well as + +.. code-block:: ini gradient_tol = 1e-9 -The first parameter, ``gradient_method`` can be either ``direct`` or ``iterative``. In the former case, a +The first parameter, :ini:`gradient_method` can be either :ini:`gradient_method = direct` or :ini:`gradient_method = iterative`. In the former case, a direct solver is used to compute the gradient (using a Riesz projection) and in the latter case, an -iterative solver is used to do so. In case we have ``gradient_method = iterative``, the parameter -``gradient_tol`` is used to specify the (relative) tolerance for the iterative solver, in the other case +iterative solver is used to do so. In case we have :ini:`gradient_method = iterative`, the parameter +:ini:`gradient_tol` is used to specify the (relative) tolerance for the iterative solver, in the other case the parameter is not used. -Finally, we have the parameter ``soft_exit``, which is defined as :: +Finally, we have the parameter :ini:`soft_exit`, which is defined as + +.. code-block:: ini soft_exit = False -This parameter determines, whether we get a hard (``False``) or soft (``True``) exit +This parameter determines, whether we get a hard (:ini:`soft_exit = False`) or soft (:ini:`soft_exit = True`) exit of the optimization routine in case it does not converge. In case of a hard exit an Exception is raised and the script does not complete. However, it can be beneficial -to still have the subsequent code be processed, which happens in case ``soft_exit = True``. +to still have the subsequent code be processed, which happens in case :ini:`soft_exit = True`. Note, however, that in this case the returned results are **NOT** optimal, -as defined by the user input parameters. Hence, the default value is ``soft_exit = False``. +as defined by the user input parameters. Hence, the default value is :ini:`soft_exit = False`. The following sections describe parameters that belong to the certain solution @@ -266,19 +312,25 @@ algorithms. Section LineSearch ------------------ -In this section, parameters regarding the line search can be specified. The type of the line search can be chosen via the parameter :: +In this section, parameters regarding the line search can be specified. The type of the line search can be chosen via the parameter + +.. code-block:: ini method = armijo -Possible options are ``armijo``, which performs a simple backtracking line search based on the armijo rule with fixed steps (think of halving the stepsize in each iteration), and ``polynomial``, which uses polynomial models of the cost functional restricted to the line to generate "better" guesses for the stepsize. The default is ``armijo``. However, this will change in the future and users are encouraged to try the new polynomial line search models. +Possible options are :ini:`method = armijo`, which performs a simple backtracking line search based on the armijo rule with fixed steps (think of halving the stepsize in each iteration), and :ini:`method = polynomial`, which uses polynomial models of the cost functional restricted to the line to generate "better" guesses for the stepsize. The default is :ini:`method = armijo`. -The next parameter, ``polynomial_model``, specifies, which type of polynomials are used to generate new trial stepsizes. It is set via :: +The next parameter, :ini:`polynomial_model`, specifies, which type of polynomials are used to generate new trial stepsizes. It is set via + +.. code-block:: ini polynomial_model = cubic -The parameter can either be ``quadratic`` or ``cubic``. If this is ``quadratic``, a quadratic interpolation polynomial along the search direction is generated and this is minimized analytically to generate a new trial stepsize. Here, only the current function value, the direction derivative of the cost functional in direction of the search direction, and the most recent trial stepsize are used to generate the polynomial. In case that ``polynomial_model`` is chosen to be ``cubic``, the last two trial stepsizes (when available) are used in addition to the current cost functional value and the directional derivative, to generate a cubic model of the one-dimensional cost functional, which is then minimized to compute a new trial stepsize. +The parameter can either be :ini:`polynomial_model = quadratic` or :ini:`polynomial_model = cubic`. If this is :ini:`polynomial_model = quadratic`, a quadratic interpolation polynomial along the search direction is generated and this is minimized analytically to generate a new trial stepsize. Here, only the current function value, the direction derivative of the cost functional in direction of the search direction, and the most recent trial stepsize are used to generate the polynomial. In case that :ini:`polynomial_model = cubic`, the last two trial stepsizes (when available) are used in addition to the current cost functional value and the directional derivative, to generate a cubic model of the one-dimensional cost functional, which is then minimized to compute a new trial stepsize. + +For the polynomial models, we also have a safeguarding procedure, which ensures that trial stepsizes cannot be chosen too large or too small, and which can be configured with the following two parameters. The trial stepsizes generate by the polynomial models are projected to the interval :math:`[\beta_{low} \alpha, \beta_{high} \alpha]`, where :math:`\alpha` is the previous trial stepsize and :math:`\beta_{low}, \beta_{high}` are factors which can be set via the parameters :ini:`factor_low` and :ini:`factor_high`. In the config file, this can look like this -For the polynomial models, we also have a safeguarding procedure, which ensures that trial stepsizes cannot be chosen too large or too small, and which can be configured with the following two parameters. The trial stepsizes generate by the polynomial models are projected to the interval :math:`[\beta_{low} \alpha, \beta_{high} \alpha]`, where :math:`\alpha` is the previous trial stepsize and :math:`\beta_{low}, \beta_{high}` are factors which can be set via the parameters ``factor_low`` and ``factor_high``. In the config file, this can look like this :: +.. code-block:: ini factor_high = 0.5 factor_low = 0.1 @@ -292,29 +344,35 @@ Section AlgoLBFGS For the L-BFGS method we have the following parameters. First, we have -``bfgs_memory_size``, which is set via :: +:ini:`bfgs_memory_size`, which is set via + +.. code-block:: ini bfgs_memory_size = 2 and determines the size of the memory of the L-BFGS method. E.g., the command above specifies that information of the previous two iterations shall be used. -The case ``bfgs_memory_size = 0`` yields the classical gradient descent method, -whereas ``bfgs_memory_size > maximum_iterations`` gives rise to the classical -BFGS method with unlimited memory. The default behavior is ``bfgs_memory_size = 5``. +The case :ini:`bfgs_memory_size = 0` yields the classical gradient descent method, +whereas :python:`bfgs_memory_size > maximum_iterations` gives rise to the classical +BFGS method with unlimited memory. The default behavior is :ini:`bfgs_memory_size = 5`. -Second, we have the parameter ``use_bfgs_scaling``, that is set via :: +Second, we have the parameter :ini:`use_bfgs_scaling`, that is set via + +.. code-block:: ini use_bfgs_scaling = True This determines, whether one should use a scaling of the initial Hessian approximation -(see `Nocedal and Wright, Numerical Optimization `_). +(see `Nocedal and Wright - Numerical Optimization `_). This is usually very beneficial and should be kept enabled, which it is by default. -Third, we have the parameter ``bfgs_periodic_restart``, which is set in the line :: +Third, we have the parameter :ini:`bfgs_periodic_restart`, which is set in the line + +.. code-block:: ini bfgs_periodic_restart = 0 -This is a non-negative integer value, which indicates the number of BFGS iterations, before a reinitialization takes place. In case that this is ``0`` (which is the default), no restarts are performed. +This is a non-negative integer value, which indicates the number of BFGS iterations, before a reinitialization takes place. In case that this is :ini:`bfgs_periodic_restart = 0` (which is the default), no restarts are performed. .. _config_ocp_algocg: @@ -322,57 +380,67 @@ Section AlgoCG -------------- -The parameter :: +The parameter + +.. code-block:: ini cg_method = PR determines which of the nonlinear cg methods shall be used. Available are -- ``FR`` : the Fletcher-Reeves method +- :ini:`cg_method = FR` : the Fletcher-Reeves method -- ``PR`` : the Polak-Ribiere method +- :ini:`cg_method = PR` : the Polak-Ribiere method -- ``HS`` : the Hestenes-Stiefel method +- :ini:`cg_method = HS` : the Hestenes-Stiefel method -- ``DY`` : the Dai-Yuan method +- :ini:`cg_method = DY` : the Dai-Yuan method -- ``HZ`` : the Hager-Zhang method +- :ini:`cg_method = HZ` : the Hager-Zhang method -The default value for this parameter is ``cg_method = FR``. +The default value for this parameter is :ini:`cg_method = FR`. After the definition of the particular cg method, we now have parameters determining -restart strategies for these method. First up, we have the line :: +restart strategies for these method. First up, we have the line + +.. code-block:: ini cg_periodic_restart = False This parameter determines, whether the CG method should be restarted with a gradient step periodically, which can lead to faster convergence. The amount of iterations -between restarts is then determined by :: +between restarts is then determined by + +.. code-block:: ini cg_periodic_its = 5 In this example, the NCG method is restarted after 5 iterations. The default behavior -is given by ``cg_periodic_restart = False`` and ``cg_periodic_its = 10``. This means, +is given by :ini:`cg_periodic_restart = False` and :ini:`cg_periodic_its = 10`. This means, if neither of the parameters is specified, no periodic restarting takes place. If, -however, only ``cg_periodic_restart = True`` is set, the default number of iterations -before a restart will be ``cg_periodic_its = 10``, unless ``cg_periodic_its`` is +however, only :ini:`cg_periodic_restart = True` is set, the default number of iterations +before a restart will be :ini:`cg_periodic_its = 10`, unless :ini:`cg_periodic_its` is defined, too. Another possibility to restart NCG methods is based on a relative criterion -(see `Nocedal and Wright, +(see `Nocedal and Wright - Numerical Optimization, Chapter 5.2 `_). -This is enabled via the boolean flag :: +This is enabled via the boolean flag + +.. code-block:: ini cg_relative_restart = False and the corresponding relative tolerance (which should lie in :math:`(0,1)`) -is determined via :: +is determined via + +.. code-block:: ini cg_restart_tol = 0.5 Note, that this relative restart reinitializes the iteration with a gradient step in case subsequent gradients are not "sufficiently" orthogonal anymore. The -default behavior is given by ``cg_relative_restart = False`` and ``cg_restart_tol = 0.25``. +default behavior is given by :ini:`cg_relative_restart = False` and :ini:`cg_restart_tol = 0.25`. .. _config_ocp_algonewton: @@ -381,46 +449,54 @@ Section AlgoTNM The parameters for the truncated Newton method are determined in the following. -First up, we have :: +First up, we have + +.. code-block:: ini inner_newton = cg which determines the Krylov method for the solution of the Newton problem. Should be one of -- ``cg`` : A linear conjugate gradient method +- :ini:`inner_newton = cg` : A linear conjugate gradient method -- ``cr`` : A conjugate residual method +- :ini:`inner_newton = cr` : A conjugate residual method Note, that these Krylov solvers are streamlined for symmetric linear operators, which the Hessian is (should be also positive definite for a minimizer so that the conjugate gradient method should yield good results when initialized not too far from the optimum). The conjugate residual does not require positive definiteness of the operator, so that it might perform slightly better when the -initial guess is further away from the optimum. The default value is ``inner_newton = cr``. +initial guess is further away from the optimum. The default value is :ini:`inner_newton = cr`. + +Then, we have the following line -Then, we have the following line :: +.. code-block:: ini inner_newton_rtol = 1e-15 This determines the relative tolerance of the iterative Krylov solver for the -Hessian problem. This is set to ``inner_newton_rtol = 1e-15`` by default. +Hessian problem. This is set to :ini:`inner_newton_rtol = 1e-15` by default. Moreover, we can also specify the absolute tolerance for the iterative solver for the -Hessian problem, with the line :: +Hessian problem, with the line + +.. code-block:: ini inner_newton_atol = 1e-15 analogously to the relative tolerance above. -In the final line, the paramter ``max_it_inner_newton`` is defined via :: +In the final line, the paramter :ini:`max_it_inner_newton` is defined via + +.. code-block:: ini max_it_inner_newton = 50 This parameter determines how many iterations of the Krylov solver are performed before the inner iteration is terminated. Note, that the approximate solution -of the Hessian problem is used after ``max_it_inner_newton`` iterations regardless -of whether this is converged or not. This defaults to ``max_it_inner_newton = 50``. +of the Hessian problem is used after :ini:`max_it_inner_newton` iterations regardless +of whether this is converged or not. This defaults to :ini:`max_it_inner_newton = 50`. .. _config_ocp_output: @@ -429,66 +505,84 @@ Section Output -------------- This section determines the behavior of cashocs regarding output, both in the -terminal and w.r.t. output files. The first line of this section reads :: +terminal and w.r.t. output files. The first line of this section reads + +.. code-block:: ini verbose = True -The parameter ``verbose`` determines, whether the solution algorithm generates a verbose +The parameter :ini:`verbose` determines, whether the solution algorithm generates a verbose output in the console, useful for monitoring its convergence. This is set to -``verbose = True`` by default. +:ini:`verbose = True` by default. + +Next up, we define the parameter :ini:`save_results` -Next up, we define the parameter ``save_results`` :: +.. code-block:: ini save_results = True If this parameter is set to True, the history of the optimization is saved in a .json file located in the same folder as the optimization script. This is -very useful for postprocessing the results. This defaults to ``save_results = True``. +very useful for postprocessing the results. This defaults to :ini:`save_results = True`. + +Moreover, we define the parameter :ini:`save_txt` -Moreover, we define the parameter ``save_txt`` :: +.. code-block:: ini save_txt = True This saves the output of the optimization, which is usually shown in the terminal, to a .txt file, which is human-readable. -We define the parameter ``save_state`` in the line :: +We define the parameter :ini:`save_state` in the line + +.. code-block:: ini save_state = False -If ``save_state`` is set to True, the state variables are saved to .xdmf files +If :ini:`save_state = True`, the state variables are saved to .xdmf files in a folder named "xdmf", located in the same directory as the optimization script. These can be visualized with `Paraview `_. This parameter -defaults to ``save_state = False``. +defaults to :ini:`save_state = False`. + +The next parameter is :ini:`save_adjoint`, which is given in the line -The next parameter is ``save_adjoint``, which is given in the line :: +.. code-block:: ini save_adjoint = False -Analogously to the previous parameter, if ``save_adjoint`` is True, the adjoint -variables are saved to .xdmf files. The default value is ``save_adjoint = False``. +Analogously to the previous parameter, if :ini:`save_adjoint = True`, the adjoint +variables are saved to .xdmf files. The default value is :ini:`save_adjoint = False`. -The next parameter is given by ``save_gradient``, which is given in the line :: +The next parameter is given by :ini:`save_gradient`, which is given in the line + +.. code-block:: ini save_gradient = False -This boolean flag ensures that a paraview with the computed gradients is saved in ``result_dir/xdmf``. The main purpose of this is for debugging. +This boolean flag ensures that a paraview with the computed gradients is saved in `result_dir/xdmf`. The main purpose of this is for debugging. Finally, we can specify in which directory the results should be stored with the -parameter ``result_dir``, which is given in this config file by :: +parameter :ini:`result_dir`, which is given in this config file by + +.. code-block:: ini result_dir = ./results The path given there can be either relative or absolute. In this example, the working directory of the python script is chosen. -The parameter ``precision``, which is set via :: +The parameter :ini:`precision`, which is set via + +.. code-block:: ini precision = 3 is an integer parameter which determines how many significant digits are printed in the output to the console and / or the result file. -Moreover, we have the parameter ``time_suffix``, which adds a suffix to the result directory based on the current time. It is controlled by the line :: +Moreover, we have the parameter :ini:`time_suffix`, which adds a suffix to the result directory based on the current time. It is controlled by the line + +.. code-block:: ini time_suffix = False @@ -502,18 +596,6 @@ Summary Finally, an overview over all parameters and their default values can be found in the following. -[Mesh] -****** - -.. list-table:: - :header-rows: 1 - - * - Parameter - - Default value - - Remarks - * - mesh_file - - - - optional, see :py:func:`import_mesh ` [StateSystem] ************* @@ -521,46 +603,33 @@ in the following. .. list-table:: :header-rows: 1 - * - Parameter - - Default value + * - Parameter = Default value - Remarks - * - is_linear - - ``False`` - - using ``True`` gives an error for nonlinear problems - * - newton_rtol - - ``1e-11`` + * - :ini:`is_linear = False` + - using :ini:`is_linear = True` gives an error for nonlinear problems + * - :ini:`newton_rtol = 1e-11` - relative tolerance for Newton's method - * - newton_atol - - ``1e-13`` + * - :ini:`newton_atol = 1e-13` - absolute tolerance for Newton's method - * - newton_iter - - ``50`` + * - :ini:`newton_iter = 50` - maximum iterations for Newton's method - * - newton_damped - - ``False`` - - if ``True``, damping is enabled - * - newton_inexact - - ``False`` - - if ``True``, an inexact Newton's method is used - * - newton_verbose - - ``False`` - - ``True`` enables verbose output of Newton's method - * - picard_iteration - - ``False`` - - ``True`` enables Picard iteration; only has an effect for multiple + * - :ini:`newton_damped = False` + - if :ini:`newton_damped = True`, damping is enabled + * - :ini:`newton_inexact = False` + - if :ini:`newton_inexact = True`, an inexact Newton's method is used + * - :ini:`newton_verbose = False` + - :ini:`newton_verbose = True` enables verbose output of Newton's method + * - :ini:`picard_iteration = False` + - :ini:`picard_iteration = True` enables Picard iteration; only has an effect for multiple variables - * - picard_rtol - - ``1e-10`` + * - :ini:`picard_rtol = 1e-10` - relative tolerance for Picard iteration - * - picard_atol - - ``1e-12`` + * - :ini:`picard_atol = 1e-12` - absolute tolerance for Picard iteration - * - picard_iter - - ``50`` + * - :ini:`picard_iter = 50` - maximum iterations for Picard iteration - * - picard_verbose - - ``False`` - - ``True`` enables verbose output of Picard iteration + * - :ini:`picard_verbose = False` + - :ini:`picard_verbose = True` enables verbose output of Picard iteration [OptimizationRoutine] ********************* @@ -568,30 +637,22 @@ in the following. .. list-table:: :header-rows: 1 - * - Parameter - - Default value + * - Parameter = Default value - Remarks - * - algorithm - - + * - :ini:`algorithm` - has to be specified by the user; see :py:meth:`solve ` - * - rtol - - ``1e-3`` + * - :ini:`rtol = 1e-3` - relative tolerance for the optimization algorithm - * - atol - - ``0.0`` + * - :ini:`atol = 0.0` - absolute tolerance for the optimization algorithm - * - maximum iterations - - ``100`` + * - :ini:`maximum iterations = 100` - maximum iterations for the optimization algorithm - * - gradient_method - - ``direct`` - - specifies the solver for computing the gradient, can be either ``direct`` or ``iterative`` - * - gradient_tol - - ``1e-9`` + * - :ini:`gradient_method = direct` + - specifies the solver for computing the gradient, can be either :ini:`gradient_method = direct` or :ini:`gradient_method = iterative` + * - :ini:`gradient_tol = 1e-9` - the relative tolerance in case an iterative solver is used to compute the gradient. - * - soft_exit - - ``False`` - - if ``True``, the optimization algorithm does not raise an exception if + * - :ini:`soft_exit = False` + - if :ini:`soft_exit = True`, the optimization algorithm does not raise an exception if it did not converge @@ -601,32 +662,23 @@ in the following. .. list-table:: :header-rows: 1 - * - Parameter - - Default value + * - Parameter = Default value - Remarks - * - method - - ``armijo`` - - ``armijo`` is a simple backtracking line search, whereas ``polynomial`` uses polynomial models to compute trial stepsizes. - * - initial_stepsize - - ``1.0`` + * - :ini:`method = armio` + - :ini:`method = armijo` is a simple backtracking line search, whereas :ini:`method = polynomial` uses polynomial models to compute trial stepsizes. + * - :ini:`initial_stepsize = 1.0` - initial stepsize for the first iteration in the Armijo rule - * - epsilon_armijo - - ``1e-4`` + * - :ini:`epsilon_armijo = 1e-4` - - * - beta_armijo - - ``2.0`` + * - :ini:`beta_armijo = 2.0` - - * - safeguard_stepsize - - ``True`` + * - :ini:`safeguard_stepsize = True` - De(-activates) a safeguard against poor scaling - * - polynomial_model - - ``cubic`` - - This specifies, whether a ``cubic`` or ``quadratic`` model is used for computing trial stepsizes - * - factor_high - - ``0.5`` + * - :ini:`polynomial_model = cubic` + - This specifies, whether a cubic or quadratic model is used for computing trial stepsizes + * - :ini:`factor_high = 0.5` - Safeguard for stepsize, upper bound - * - factor_low - - ``0.1`` + * - :ini:`factor_low = 0.1` - Safeguard for stepsize, lower bound [AlgoLBFGS] @@ -635,17 +687,13 @@ in the following. .. list-table:: :header-rows: 1 - * - Parameter - - Default value + * - Parameter = Default value - Remarks - * - bfgs_memory_size - - ``5`` + * - :ini:`bfgs_memory_size = 5` - memory size of the L-BFGS method - * - use_bfgs_scaling - - ``True`` - - if ``True``, uses a scaled identity mapping as initial guess for the inverse Hessian - * - bfgs_periodic_restart - - ``0`` + * - :ini:`use_bfgs_scaling = True` + - if :ini:`use_bfgs_scaling = True`, uses a scaled identity mapping as initial guess for the inverse Hessian + * - :ini:`bfgs_periodic_restart = 0` - specifies, after how many iterations the method is restarted. If this is 0, no restarting is done. @@ -655,23 +703,17 @@ in the following. .. list-table:: :header-rows: 1 - * - Parameter - - Default value + * - Parameter = Default value - Remarks - * - cg_method - - ``FR`` + * - :ini:`cg_method = FR` - specifies which nonlinear CG method is used - * - cg_periodic_restart - - ``False`` - - if ``True``, enables periodic restart of NCG method - * - cg_periodic_its - - ``10`` + * - :ini:`cg_periodic_restart = False` + - if :ini:`cg_periodic_restart = True`, enables periodic restart of NCG method + * - :ini:`cg_periodic_its = 10` - specifies, after how many iterations the NCG method is restarted, if applicable - * - cg_relative_restart - - ``False`` - - if ``True``, enables restart of NCG method based on a relative criterion - * - cg_restart_tol - - ``0.25`` + * - :ini:`cg_relative_restart = False` + - if :ini:`cg_relative_restart = True`, enables restart of NCG method based on a relative criterion + * - :ini:`cg_restart_tol = 0.25` - the tolerance of the relative restart criterion, if applicable [AlgoTNM] @@ -680,20 +722,15 @@ in the following. .. list-table:: :header-rows: 1 - * - Parameter - - Default value + * - Parameter = Default value - Remarks - * - inner_newton - - ``cr`` + * - :ini:`inner_newton = cr` - inner iterative solver for the truncated Newton method - * - inner_newton_rtol - - ``1e-15`` + * - :ini:`inner_newton_rtol = 1e-15` - relative tolerance for the inner iterative solver - * - inner_newton_atol - - ``0.0`` + * - :ini:`inner_newton_atol = 0.0` - absolute tolerance for the inner iterative solver - * - max_it_inner_newton - - ``50`` + * - :ini:`max_it_inner_newton = 50` - maximum iterations for the inner iterative solver [Output] @@ -702,38 +739,28 @@ in the following. .. list-table:: :header-rows: 1 - * - Parameter - - Default value + * - Parameter = Default value - Remarks - * - verbose - - ``True`` - - if ``True``, the history of the optimization is printed to the console - * - save_results - - ``True`` - - if ``True``, the history of the optimization is saved to a .json file - * - save_txt - - ``True`` - - if ``True``, the history of the optimization is saved to a human readable .txt file - * - save_state - - ``False`` - - if ``True``, the history of the state variables over the optimization is + * - :ini:`verbose = True` + - if :ini:`verbose = True`, the history of the optimization is printed to the console + * - :ini:`save_results = True` + - if :ini:`save_results = True`, the history of the optimization is saved to a .json file + * - :ini:`save_txt = True` + - if :ini:`save_txt = True`, the history of the optimization is saved to a human readable .txt file + * - :ini:`save_state = False` + - if :ini:`save_state = True`, the history of the state variables over the optimization is saved in .xdmf files - * - save_adjoint - - ``False`` - - if ``True``, the history of the adjoint variables over the optimization is + * - :ini:`save_adjoint = False` + - if :ini:`save_adjoint = True`, the history of the adjoint variables over the optimization is saved in .xdmf files - * - save_gradient - - ``False`` - - if ``True``, the history of the gradient(s) over the optimization is saved in .xdmf files - * - result_dir - - ``./`` + * - :ini:`save_gradient = False` + - if :ini:`save_gradient = True`, the history of the gradient(s) over the optimization is saved in .xdmf files + * - :ini:`result_dir = ./` - path to the directory, where the output should be placed - * - precision - - 3 + * - :ini:`precision = 3` - number of significant digits to be printed - * - time_suffix - - ``False`` - - Boolean flag, which adds a suffix to ``result_dir`` based on the current time + * - :ini:`time_suffix = False` + - Boolean flag, which adds a suffix to :ini:`result_dir` based on the current time This concludes the documentation of the config files for optimal control problems. diff --git a/docs/source/user/demos/optimal_control/doc_constraints.rst b/docs/source/user/demos/optimal_control/doc_constraints.rst deleted file mode 100755 index d206c443..00000000 --- a/docs/source/user/demos/optimal_control/doc_constraints.rst +++ /dev/null @@ -1,185 +0,0 @@ -.. _demo_constraints: - -Treatment of additional constraints -=================================== - -Problem Formulation -------------------- - -In this demo we investigate cashocs for solving PDE constrained optimization problems -with additional constraints. To do so, we investigate the "mother -problem" of PDE constrained optimization, i.e., - -.. math:: - - &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 - \text{ d}x + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x \\ - &\text{ subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta y &= u \quad &&\text{ in } \Omega,\\ - y &= 0 \quad &&\text{ on } \Gamma, \\ - y &= c_{b,l} \quad &&\text{ on } \Omega_{b,l},\\ - \int_{\Omega_{b,r}} y^2 \text{ d}x &= c_{b,r},\\ - \int_{\Omega_{t,l}} y \text{ d}x &\geq c_{t,l},\\ - y &\leq c_{t,r} \quad &&\text{ in } \Omega_{t,r}. - \end{alignedat} \right. - - - -Here, we have four additional constraints, each for one quarter of the unit -square :math:`\Omega = (0,1)^2`, indicated by (b,l) for bottom left, (b,r) for -bottom right, (t,l) for top left, and (t,r) for top right. - -In the following, we will describe how to solve this problem -using cashocs. - -Implementation --------------- -The complete python code can be found in the file :download:`demo_constraints.py `, -and the corresponding config can be found in :download:`config.ini `. - -Initialization -************** - -The beginning of the program is nearly the same as for :ref:`demo_poisson` :: - - from fenics import * - - import cashocs - - - cashocs.set_log_level(cashocs.LogLevel.INFO) - config = cashocs.load_config("config.ini") - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(32) - V = FunctionSpace(mesh, "CG", 1) - - y = Function(V) - p = Function(V) - u = Function(V) - - e = inner(grad(y), grad(p)) * dx - u * p * dx - - bcs = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) - - y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) - alpha = 1e-4 - J = cashocs.IntegralFunctional( - Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * u * u * dx - ) - - -Definition of the additional constraints -**************************************** - -In the following, we define the additional constraints we want to consider together with -the PDE constraint. In this case, we only have state constraints, but additional constraints -on the control variables can be treated completely analogously. - -First, we define the four quarters of the unit square :: - - bottom_left = Expression("(x[0] <= 0.5) && (x[1] <= 0.5) ? 1.0 : 0.0", degree=0) - bottom_right = Expression("(x[0] >= 0.5) && (x[1] <= 0.5) ? 1.0 : 0.0", degree=0) - top_left = Expression("(x[0] <= 0.5) && (x[1] >= 0.5) ? 1.0 : 0.0", degree=0) - top_right = Expression("(x[0] >= 0.5) && (x[1] >= 0.5) ? 1.0 : 0.0", degree=0) - -The four Expressions above are indicator functions for the respective quarters, which -allows us to implement the constraints easily. - -Next, we define the pointwise equality constraint we have on the lower left quarter. -To do so, we use :py:class:`cashocs.EqualityConstraint` as follows :: - - pointwise_equality_constraint = cashocs.EqualityConstraint( - bottom_left * y, 0.0, measure=dx - ) - -Here, the first argument is the left-hand side of the equality constraint, namely the -indicator function for the lower left quarter multiplied by the state variable y. -The next argument is the right-hand side of the equality constraint, i.e., -:math:`c_{b,l}` which we choose as 0 in this example. -Finally, the keyword argument ``measure`` is used to specify the integration measure -that should be used to define where the constraint is given. Typical examples are a volume -measure (``dx``, as it is the case here) or surface measure (``ds``, which could be used -if we wanted to pose the constraint only on the boundary). - -Let's move on to the next constraint. Again, we have an equality constraint, but now -it is a scalar value which is constrained, and its given by the integral over some -integrand. This is the general form in which cashocs can deal with such scalar constraints. -Let's see how we can define this constraint in cashocs :: - - integral_equality_constraint = cashocs.EqualityConstraint( - bottom_right * pow(y, 2) * dx, 0.01 - ) - -Here, we again use the :py:class:`cashocs.EqualityConstraint` class, as before. The -difference is that now, the first argument is the UFL form of the integrand, in this -case :math:`y^2` multiplied by the indicator function of the bottom right quarter, i.e., -the left-hand side of the constraint. -The second and final argument for this constraint is right-hand side of the constraint, -i.e., :math:`c_{b,r}`, which we choose as ``0.01`` in this example. - -Let's move on to the interesting case of inequality constraints. Let us first consider -a setting similar to before, where the constraint's left-hand side is given by an integral -over some integrand. We define this integral inequality constraint via the -:py:class:`cashocs.InequalityConstraint` class :: - - integral_inequality_constraint = cashocs.InequalityConstraint( - top_left * y * dx, lower_bound=-0.025 - ) - -Here, as before, the first argument is the left-hand side of the constraint, i.e., the UFL -form of the integrand, in this case :math:`y` times the indicator function of the top left -quarter, which is to be integrated over the measure ``dx``. The second argument -``lower_bound = -0.025`` specifies the lower bound for this inequality constraint, that means, -that :math:`c_{t,l} = -0.025` in our case. - -Finally, let us take a look at the case of pointwise inequality constraint. This is, -as before, implemented via the :py:class:`cashocs.InequalityConstraint` class :: - - pointwise_inequality_constraint = cashocs.InequalityConstraint( - top_right * y, upper_bound=0.25, measure=dx - ) - -Here, again the first argument is the function on the left-hand side of the constraint, -i.e., y times the indicator function of the top right quarter. The second argument, -``upper_bound=0.25``, defines the right-hand side of the constraint, i.e., we choose -:math:`c_{t,r} = 0.25`. Finally, as for the pointwise equality constraint, we specify -the integration measure for which the constraint is posed, in our case -``measure=dx``, as we consider the constraint pointwise in the domain :math:`Omega`. - -.. note:: - - For bilateral inequality constraints we can use both keyword arguments ``upper_bound`` - and ``lower_bound`` to define both bounds for the constraint. - -As is usual in cashocs, once we have defined multiple constraints, we gather them into -a list to pass them to the optimization routines :: - - constraints = [ - pointwise_equality_constraint, - integral_equality_constraint, - integral_inequality_constraint, - pointwise_inequality_constraint, - ] - -Finally, we define the optimization problem. As we deal with additional constraints, -we do not use a :py:class:`cashocs.OptimalControlProblem`, but use a -:py:class:`cashocs.ConstrainedOptimalControlProblem`, which can be used to deal with -these additional constaints. As usual, we can the solve the problem with the -:py:meth:`solve ` method of the problem :: - - problem = cashocs.ConstrainedOptimalControlProblem( - e, bcs, J, y, u, p, constraints, config - ) - problem.solve(method="AL") - -.. note:: - - To be able to treat (nearly) arbitrary _typing of constraints, cashocs regularizes - these using either an augmented Lagrangian method or a quadratic penalty method. - Which method is used can be specified via the keyword argument ``method``, which - is chosen to be an augmented Lagrangian method (``'AL'``) in this demo. - -Finally, we visualize the result, which should look like this - - -.. image:: /../../demos/documented/optimal_control/constraints/img_constraints.png diff --git a/docs/source/user/demos/optimal_control/doc_control_boundary_conditions.rst b/docs/source/user/demos/optimal_control/doc_control_boundary_conditions.rst deleted file mode 100755 index 7737fb21..00000000 --- a/docs/source/user/demos/optimal_control/doc_control_boundary_conditions.rst +++ /dev/null @@ -1,105 +0,0 @@ -.. _demo_control_boundary_conditions: - -Boundary conditions for control variables -========================================= - -Problem Formulation -------------------- - -In this demo we investigate cashocs for solving PDE constrained optimization problems -with additional boundary conditions for the control variables. Our problem is given by - -.. math:: - - &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 - \text{ d}x + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x \\ - &\text{ subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta y &= u \quad &&\text{ in } \Omega,\\ - y &= 0 \quad &&\text{ on } \Gamma, \\ - u &= u_{\Gamma} \quad &&\text{ on } \Gamma, - \end{alignedat} \right. - - -Here, we consider the control variable :math:`u` in -:math:`H^1_\Gamma(\Omega) = \{ v \in H^1(\Omega) \vert v = u_\Gamma \text{ on } \Gamma \}`. - -In the following, we will describe how to solve this problem -using cashocs. - - - -Implementation --------------- -The complete python code can be found in the file -:download:`demo_constraints.py `, -and the corresponding config can be found in -:download:`config.ini `. - -Initialization -************** - -The beginning of the program is nearly the same as for :ref:`demo_poisson` :: - - from fenics import * - - import cashocs - - - cashocs.set_log_level(cashocs.LogLevel.INFO) - config = cashocs.load_config("config.ini") - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(32) - V = FunctionSpace(mesh, "CG", 1) - - y = Function(V) - p = Function(V) - u = Function(V) - - e = inner(grad(y), grad(p)) * dx - u * p * dx - - bcs = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) - - y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) - alpha = 1e-4 - J = cashocs.IntegralFunctional( - Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * u * u * dx - ) - -The scalar product and boundary conditions for the control variable -******************************************************************* - -Now, we can first define the scalar product for the control variable :math:`u`, which we choose -as the standard :math:`H^1_0(\Omega)` scalar product. This can be implemented as follows in cashocs :: - - scalar_product = dot(grad(TrialFunction(V)), grad(TestFunction(V))) * dx - -Moreover, we define the list of boundary conditions for the control variable as usual in FEniCS and -cashocs :: - - control_bcs = cashocs.create_dirichlet_bcs(V, Constant(100.0), boundaries, [1, 2, 3, 4]) - -Here, we have chosen a value of :math:`u_\Gamma = 100` for this particular demo, in order to be able to -visually see, whether the proposed method works. - -Finally, we can set up and solve the optimization problem as usual :: - - ocp = cashocs.OptimalControlProblem( - e, - bcs, - J, - y, - u, - p, - config, - riesz_scalar_products=scalar_product, - control_bcs_list=control_bcs, - ) - ocp.solve() - -where the only additional parts in comparison to :ref:`demo_poisson` are the keyword arguments -``riesz_scalar_products``, which was already covered in :ref:`demo_neumann_control`, and ``control_bcs_list``, -which we have defined previously. - -After solving this problem with cashocs, we visualize the solution, which should look as follows - -.. image:: /../../demos/documented/optimal_control/control_boundary_conditions/img_control_boundary_conditions.png diff --git a/docs/source/user/demos/optimal_control/doc_dirichlet_control.rst b/docs/source/user/demos/optimal_control/doc_dirichlet_control.rst deleted file mode 100755 index 611b7d07..00000000 --- a/docs/source/user/demos/optimal_control/doc_dirichlet_control.rst +++ /dev/null @@ -1,204 +0,0 @@ -.. _demo_dirichlet_control: - -Dirichlet Boundary Control -========================== - -Problem Formulation -------------------- - -In this demo, we investigate how Dirichlet boundary control is possible with -cashocs. To do this, we have to employ the so-called Nitsche method, which we -briefly recall in the following. Our model problem for this example is given by - -.. math:: - - &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 \text{ d}x + \frac{\alpha}{2} \int_{\Gamma} u^2 \text{ d}s \\ - &\text{ subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta y &= 0 \quad &&\text{ in } \Omega,\\ - y &= u \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - - -In contrast to our previous problems, the control now enters the problem via a -Dirichlet boundary condition. However, we cannot apply these via a :py:class:`fenics.DirichletBC`, -because for cashocs to work properly, the controls, -states, and adjoints are only allowed to appear in UFL forms. Nitsche's Method -circumvents this problem by imposing the boundary conditions in the weak form -directly. Let us first briefly recall this method. - -Nitsche's method -**************** - -Consider the Laplace problem - -.. math:: - \begin{alignedat}{2} - -\Delta y &= 0 \quad &&\text{ in } \Omega,\\ - y &= u \quad &&\text{ on } \Gamma. - \end{alignedat} - -We can derive a weak form for this equation in :math:`H^1(\Omega)` -(not :math:`H^1_0(\Omega)`) by multiplying the equation by a test function -:math:`p \in H^1(\Omega)` and applying the divergence theorem - -.. math:: \int_\Omega - \Delta y p \text{ d}x = \int_\Omega \nabla y \cdot \nabla p \text{ d}x - \int_\Gamma (\nabla y \cdot n) p \text{ d}s. - -This weak form is the starting point for Nitsche's method. First of all, observe that -this weak form is not symmetric anymore. To restore symmetry of the problem, we can -use the Dirichlet boundary condition and "add a zero" by adding :math:`\int_\Gamma \nabla p \cdot n (y - u) \text{ d}s`. This gives the weak form - -.. math:: \int_\Omega \nabla y \cdot \nabla p \text{ d}x - \int_\Gamma (\nabla y \cdot n) p \text{ d}s - \int_\Gamma (\nabla p \cdot n) y \text{ d}s = \int_\Gamma (\nabla p \cdot n) u \text{ d}s. - -However, one can show that this weak form is not coercive. Hence, Nitsche's method -adds another zero to this weak form, namely :math:`\int_\Gamma \eta (y - u) p \text{ d}s`, -which yields the coercivity of the problem if :math:`\eta` is sufficiently large. Hence, -we consider the following weak form - -.. math:: \int_\Omega \nabla y \cdot \nabla p \text{ d}x - \int_\Gamma (\nabla y \cdot n) p \text{ d}s - \int_\Gamma (\nabla p \cdot n) y \text{ d}s + \eta \int_\Gamma y p \text{ d}s = \int_\Gamma (\nabla p \cdot n) u \text{ d}s + \eta \int_\Gamma u p \text{ d}s, - -and this is the form we implement for this problem. - -For a detailed introduction to Nitsche's method, we refer to -`Assous and Michaeli, A numerical method for handling boundary and -transmission conditions in some linear partial differential equations -`_. - -.. note:: - - To ensure convergence of the method when the mesh is refined, the parameter - :math:`\eta` is scaled in dependence with the mesh size. In particular, we use - a scaling of the form - - .. math:: - - \eta = \frac{\bar{\eta}}{h}, - - where :math:`h` is the diameter of the current mesh element. - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_dirichlet_control.py `, -and the corresponding config can be found in :download:`config.ini `. - -Initialization -************** - -The beginning of the program is nearly the same as for :ref:`demo_poisson` :: - - from fenics import * - import numpy as np - - import cashocs - - config = cashocs.load_config("config.ini") - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(50) - n = FacetNormal(mesh) - h = MaxCellEdgeLength(mesh) - V = FunctionSpace(mesh, "CG", 1) - - y = Function(V) - p = Function(V) - u = Function(V) - -The only difference is, that we now also define ``n``, which is the outer unit -normal vector on :math:`\Gamma`, and ``h``, which is the maximum length of an -edge of the respective finite element (during assemly). - -Then, we define the Dirichlet boundary conditions, which are enforced strongly. -As we use Nitsche's method to implement the boundary conditions on the entire -boundary, there are no strongly enforced ones left, and we define :: - - bcs = [] - -.. hint:: - - Alternatively, we could have also written :: - - bcs = None - - which yields exactly the same result, i.e., no strongly enforced Dirichlet - boundary conditions. - - -Definition of the PDE and optimization problem via Nitsche's method -******************************************************************* - -Afterwards, we implement the weak form using Nitsche's method, as described above, which -is given by the code segment :: - - eta = Constant(1e4) - e = ( - inner(grad(y), grad(p)) * dx - - inner(grad(y), n) * p * ds - - inner(grad(p), n) * (y - u) * ds - + eta / h * (y - u) * p * ds - - Constant(1) * p * dx - ) - -Finally, we can define the optimization problem similarly to :ref:`demo_neumann_control` :: - - y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) - alpha = 1e-4 - J = cashocs.IntegralFunctional( - Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * u * u * ds - ) - -As for :ref:`demo_neumann_control`, we have to define a scalar product on -:math:`L^2(\Gamma)` to get meaningful results (as the control is only defined on the boundary), -which we do with :: - - scalar_product = TrialFunction(V) * TestFunction(V) * ds - -Solution of the optimization problem -************************************ - -The optimal control problem is solved with the usual syntax :: - - ocp = cashocs.OptimalControlProblem( - e, bcs, J, y, u, p, config, riesz_scalar_products=scalar_product - ) - ocp.solve() - -The result should look like this - -.. image:: /../../demos/documented/optimal_control/dirichlet_control/img_dirichlet_control.png - -.. note:: - - In the end, we validate whether the boundary conditions are applied correctly - using this approach. Therefore, we first compute the indices of all DOF's - that lie on the boundary via :: - - bcs = cashocs.create_dirichlet_bcs(V, 1, boundaries, [1, 2, 3, 4]) - bdry_idx = Function(V) - [bc.apply(bdry_idx.vector()) for bc in bcs] - mask = np.where(bdry_idx.vector()[:] == 1)[0] - - Then, we restrict both ``y`` and ``u`` to the boundary by :: - - y_bdry = Function(V) - u_bdry = Function(V) - y_bdry.vector()[mask] = y.vector()[mask] - u_bdry.vector()[mask] = u.vector()[mask] - - Finally, we compute the relative errors in the :math:`L^\infty(\Gamma)` and - :math:`L^2(\Gamma)` norms and print the result :: - - error_inf = ( - np.max(np.abs(y_bdry.vector()[:] - u_bdry.vector()[:])) - / np.max(np.abs(u_bdry.vector()[:])) - * 100 - ) - error_l2 = ( - np.sqrt(assemble((y - u) * (y - u) * ds)) / np.sqrt(assemble(u * u * ds)) * 100 - ) - - print("Error regarding the (weak) imposition of the boundary values") - print("Error L^\infty: " + format(error_inf, ".3e") + " %") - print("Error L^2: " + format(error_l2, ".3e") + " %") - - We see, that with ``eta = 1e4`` we get a relative error of under 5e-3 % in the - :math:`L^\infty(\Omega)` norm, and under 5e-4 in the :math:`L^2(\Omega)` norm, which is - sufficient for applications. diff --git a/docs/source/user/demos/optimal_control/doc_heat_equation.rst b/docs/source/user/demos/optimal_control/doc_heat_equation.rst deleted file mode 100755 index ee540775..00000000 --- a/docs/source/user/demos/optimal_control/doc_heat_equation.rst +++ /dev/null @@ -1,244 +0,0 @@ -.. _demo_heat_equation: - -Distributed Control for Time Dependent Problems -=============================================== - -Problem Formulation -------------------- - -In this demo we take a look at how time dependent problems can be treated with cashocs. -To do so, we investigate a problem with a heat equation as PDE constraint, which -was considered in `Blauth, Optimal Control and Asymptotic Analysis of the Cattaneo Model -`_. -It reads - -.. math:: - - &\min\; J(y,u) = \frac{1}{2} \int_0^T \int_\Omega \left( y - y_d \right)^2 \text{ d}x \text{ d}t + \frac{\alpha}{2} \int_0^T \int_\Omega u^2 \text{ d}x \text{ d}t \\ - &\text{ subject to }\quad \left\lbrace \quad - \begin{alignedat}{2} - \partial_t y - \Delta y &= u \quad &&\text{ in } (0,T) \times \Omega,\\ - y &= 0 \quad &&\text{ on } (0,T) \times \Gamma, \\ - y(0, \cdot) &= y^{(0)} \quad &&\text{ in } \Omega. - \end{alignedat} \right. - - -Since FEniCS does not have any direct built-in support for time dependent problems, -we first have to perform a semi-discretization of the PDE system in the temporal -component (e.g. via finite differences), and then solve the resulting sequence of PDEs. - -In particular, for the use with cashocs, we have to create not a single weak form and -:py:class:`fenics.Function`, that can be re-used, like one would in classical FEniCS programs, but -we have to create the corresponding objects a-priori for each time step. - -For the domain of this problem, we once again consider the space time cylinder given by :math:`(0,T) \times \Omega = (0,1) \times (0,1)^2`. -And for the initial condition we use :math:`y^{(0)} = 0`. - -Temporal discretization -*********************** - -For the temporal discretization, we use the implicit Euler scheme as this is unconditionally stable for the parabolic heat equation. This means, we discretize the -interval :math:`[0,T]` by a grid with nodes :math:`t_k, k=0,\dots, n,\; \text{ with }\; t_0 := 0\; \text{ and }\; t_n := T`. Then, we approximate the time derivative -:math:`\partial_t y(t_{k+1})` at some time :math:`t_{k+1}` by the backward difference - -.. math:: \partial_t y(t_{k+1}) \approx \frac{y(t_{k+1}) - y(t_{k})}{\Delta t}, - -where :math:`\Delta t = t_{k+1} - t_{k}`, and thus get the sequence of PDEs - -.. math:: - \begin{alignedat}{2} - \frac{y_{k+1} - y_{k}}{\Delta t} - \Delta y_{k+1} &= u_{k+1} \quad &&\text{ in } \Omega \quad \text{ for } k=0,\dots,n-1,\\ - y_{k+1} &= 0 \quad &&\text{ on } \Gamma \quad \text{ for } k=0,\dots,n-1, - \end{alignedat} - - -Note, that :math:`y_k \approx y(t_k), \text {and }\; u_k \approx u(t_k)` are approximations of the -continuous functions. The initial condition is included via - -.. math:: - y_0 = y^{(0)} - -Moreover, for the cost functionals, we can discretize the temporal integrals using a -rectangle rule. This means we approximate the cost functional via - -.. math:: J(y, u) \approx \frac{1}{2} \sum_{k=0}^{n-1} \Delta t \left( \int_\Omega \left( y_{k+1} - (y_d)_{k+1} \right)^2 \text{ d}x + \alpha \int_\Omega u_{k+1}^2 \text{ d}x \right). - - -Here, :math:`(y_d)_k` is an approximation of the desired state at time :math:`t_k`. - -Let us now investigate how to solve this problem with cashocs. - - -Implementation --------------- -The complete python code can be found in the file :download:`demo_heat_equation.py `, -and the corresponding config can be found in :download:`config.ini `. - - -Initialization -************** - -This section is the same as for all previous problems and is done via :: - - from fenics import * - import numpy as np - - import cashocs - - config = cashocs.load_config("config.ini") - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(20) - V = FunctionSpace(mesh, "CG", 1) - -Next up, we specify the temporal discretization via :: - - dt = 1 / 10 - t_start = dt - t_end = 1.0 - t_array = np.linspace(t_start, t_end, int(1 / dt)) - -Here, ``t_array`` is a numpy array containing all time steps. Note, that we do **not** -include t=0 in the array. This is due to the fact, that the initial condition -is prescribed and fixed. Due to the fact that we discretize the equation temporally, -we do not only get a single :py:class:`fenics.Function` describing our state and control, but -one :py:class:`fenics.Function` for each time step. Hence, we initialize these -(together with the adjoint states) directly in lists :: - - states = [Function(V) for i in range(len(t_array))] - controls = [Function(V) for i in range(len(t_array))] - adjoints = [Function(V) for i in range(len(t_array))] - -Note, that ``states[k]`` corresponds to :math:`y_{k+1}` since indices start at -0 in most programming languages (as it is the case in python). - -As the boundary conditions are not time dependent, we can initialize them now, and -repeat them in a list, since they are the same for every state :: - - bcs = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) - bcs_list = [bcs for i in range(len(t_array))] - -To define the sequence of PDEs, we will use a loop over all time steps. But before we -can do that, we first initialize empty lists for the state equations, the -approximations of the desired state, and the summands of the cost functional :: - - y_d = [] - e = [] - J_list = [] - -Definition of the optimization problem -************************************** - -For the desired state, we define it with the help of a :py:class:`fenics.Expression`, that is -dependent on an additional parameter which models the time :: - - alpha = 1e-5 - y_d_expr = Expression( - "exp(-20*(pow(x[0] - 0.5 - 0.25*cos(2*pi*t), 2) + pow(x[1] - 0.5 - 0.25*sin(2*pi*t), 2)))", - degree=1, - t=0.0, - ) - -Next, we have the following for loop, which we describe in detail after stating it here :: - - for k in range(len(t_array)): - t = t_array[k] - y_d_expr.t = t - - y = states[k] - if k == 0: - y_prev = Function(V) - else: - y_prev = states[k - 1] - p = adjoints[k] - u = controls[k] - - state_eq = ( - Constant(1 / dt) * (y - y_prev) * p * dx - + inner(grad(y), grad(p)) * dx - - u * p * dx - ) - - e.append(state_eq) - y_d.append(interpolate(y_d_expr, V)) - - J_list.append( - cashocs.IntegralFunctional( - Constant(0.5 * dt) * (y - y_d[k]) * (y - y_d[k]) * dx - + Constant(0.5 * dt * alpha) * u * u * dx - ) - ) - -.. note:: - - At the beginning, the 'current' time t is determined from ``t_array``, and the - expression for the desired state is updated to reflect the current time. - The line :: - - y = states[k] - - sets the object ``y`` to :math:`y_{k+1}`. For the backward difference in the implicit Euler method, we also need - :math:`y_{k}` which we define by the if condition :: - - if k == 0: - y_prev = Function(V) - else: - y_prev = states[k - 1] - - which ensures that :math:`y_0 = 0`, which corresponds to the initial condition - :math:`y^{(0)} = 0`. Hence, ``y_prev`` indeed corresponds to :math:`y_{k}`. - Moreover, we get the current control and adjoint state via :: - - p = adjoints[k] - u = controls[k] - - This allow us to define the state equation at time t as :: - - state_eq = ( - Constant(1 / dt) * (y - y_prev) * p * dx - + inner(grad(y), grad(p)) * dx - - u * p * dx - ) - - This is then appended to the list of state constraints :: - - e.append(state_eq) - - Further, we also put the current desired state into the respective list, i.e., :: - - y_d.append(interpolate(y_d_expr, V)) - - Finally, we can define the k-th summand of the cost functional via :: - - J_list.append( - cashocs.IntegralFunctional( - Constant(0.5 * dt) * (y - y_d[k]) * (y - y_d[k]) * dx - + Constant(0.5 * dt * alpha) * u * u * dx - ) - ) - - and directly append this to the cost functional list. - -Finally, we can define an optimal control problem as before, and solve it as in the previous demos (see, e.g., :ref:`demo_poisson`) :: - - ocp = cashocs.OptimalControlProblem( - e, bcs_list, J_list, states, controls, adjoints, config - ) - ocp.solve() - -For a postprocessing, which visualizes the resulting optimal control and optimal state, -the following lines are added at the end :: - - u_file = File("./visualization/u.pvd") - y_file = File("./visualization/y.pvd") - temp_u = Function(V) - temp_y = Function(V) - - for k in range(len(t_array)): - t = t_array[k] - - temp_u.vector()[:] = controls[k].vector()[:] - u_file << temp_u, t - - temp_y.vector()[:] = states[k].vector()[:] - y_file << temp_y, t - -which saves the result in the directory ``./visualization/`` as paraview .pvd files. diff --git a/docs/source/user/demos/optimal_control/doc_iterative_solvers.rst b/docs/source/user/demos/optimal_control/doc_iterative_solvers.rst deleted file mode 100755 index 75c5fa1e..00000000 --- a/docs/source/user/demos/optimal_control/doc_iterative_solvers.rst +++ /dev/null @@ -1,171 +0,0 @@ -.. _demo_iterative_solvers: - -Iterative Solvers for State and Adjoint Systems -=============================================== - -Problem Formulation -------------------- - -cashocs is also capable of using iterative solvers through the linear algebra -backend PETSc. In this demo we show how this can be used. For the sake of simplicitiy, -we consider the same setting as in :ref:`demo_poisson`, i.e. - -.. math:: - - &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 \text{ d}x + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x \\ - &\text{ subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta y &= u \quad &&\text{ in } \Omega,\\ - y &= 0 \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - -(see, e.g., `Tröltzsch, Optimal Control of Partial Differential Equations `_ -or `Hinze, Pinnau, Ulbrich, and Ulbrich, Optimization with PDE constraints `_. - -It is well-known, that the state problem, when discretized using a classical, conforming -Ritz-Galerkin method, gives rise to a linear system with a symmetric and positive definite -Matrix. We use these properties in this demo by solving the state system with the -conjugate gradient method. Moreover, the adjoint system is also a Poisson problem with -right-hand side :math:`y - y_d`, and so also gives rise to a symmetric and positive definite system, -for which we also employ iterative solvers. - - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_iterative_solvers.py `, -and the corresponding config can be found in :download:`config.ini `. - - -Initialization -************** - -The initialization works exactly as in :ref:`demo_poisson` :: - - from fenics import * - - import cashocs - - config = cashocs.load_config("config.ini") - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(50) - V = FunctionSpace(mesh, "CG", 1) - - y = Function(V) - p = Function(V) - u = Function(V) - - e = inner(grad(y), grad(p)) * dx - u * p * dx - bcs = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) - -Definition of the iterative solvers -*********************************** - -The options for the state and adjoint systems are defined as follows. For the state -system we have :: - - ksp_options = [ - ["ksp_type", "cg"], - ["pc_type", "hypre"], - ["pc_hypre_type", "boomeramg"], - ["ksp_rtol", 1e-10], - ["ksp_atol", 1e-13], - ["ksp_max_it", 100], - ] - -This is a list of lists, where the inner ones have either 1 or 2 entries, -which correspond to the command line options for PETSc. For a detailed documentation -of the possibilities, we refer to the `PETSc documentation `_. Of particular interest are the pages for the -`Krylov solvers `_ and `Preconditioners -`_. The relevant options for the command line are described -under "Options Database Keys". - -.. note:: - - For example, the first command :: - - ["ksp_type", "cg"], - - can be found in `KSPSetType `_, and the corresponding options are shown - under `KSPTYPE `_. Here, we see that the above line corresponds to using the - conjugate gradient method as krylov solver. The following two lines :: - - ["pc_type", "hypre"], - ["pc_hypre_type", "boomeramg"], - - specify that we use the algebraic multigrid preconditioner BOOMERAMG from HYPRE. - This is documented in `PCSetType `_, - `PCTYPE `_, and - `PCHYPRETYPE `_. Finally, the last three lines :: - - ["ksp_rtol", 1e-10], - ["ksp_atol", 1e-13], - ["ksp_max_it", 100], - - specify that we use a relative tolerance of 1e-10, an absolute one of 1e-13, and - at most 100 iterations for each solve of the linear system, cf. `KSPSetTolerances - `_. - -Coming from the first optimize, then discretize view point, it is not required that -the adjoint system should be solved exactly the same as the state system. This is why we -can also define PETSc options for the adjoint system, which we do with :: - - adjoint_ksp_options = [ - ["ksp_type", "minres"], - ["pc_type", "jacobi"], - ["ksp_rtol", 1e-6], - ["ksp_atol", 1e-15], - ] - -As can be seen, we now use a completely different solver, namely MINRES -(the minimal residual method) with a jacobi -preconditioner. Finally, the tolerances for the adjoint solver can also be -rather different from the ones of the state system, as is shown here. - -.. hint:: - - To verify that the options indeed are used, one can supply the option :: - - ['ksp_view'], - - which shows the detailed settings of the solvers, and also :: - - ['ksp_monitor_true_residual'], - - which prints the residual of the method over its iterations. - - For multiple state and adjoint systems, one can proceed analogously to :ref:`demo_multiple_variables`, and one has to create a such a list of options for each component, and then put them into an additional list. - - -With these definitions, we can now proceed as in :ref:`demo_poisson` and solve the optimization problem with :: - - y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) - alpha = 1e-6 - J = cashocs.IntegralFunctional( - Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * u * u * dx - ) - - ocp = cashocs.OptimalControlProblem( - e, - bcs, - J, - y, - u, - p, - config, - ksp_options=ksp_options, - adjoint_ksp_options=adjoint_ksp_options, - ) - ocp.solve() - -.. note:: - - Note, that if the ``ksp_options`` and ``adjoint_ksp_options`` are not passed - to the :py:class:`OptimalControlProblem ` or ``None``, which - is the default value of these keyword parameters, then the direct solver MUMPS is used. - Moreover, if one wants to use identical options for state and adjoint systems, then only - the ``ksp_options`` have to be passed. This is because of the fact that ``adjoint_ksp_options`` - always mirrors the ksp_options in case that the input is ``None`` for ``adjoint_ksp_options``. - -The result of the optimization looks very much like that of :ref:`demo_poisson` - -.. image:: /../../demos/documented/optimal_control/iterative_solvers/img_iterative_solvers.png diff --git a/docs/source/user/demos/optimal_control/doc_monolithic_problems.rst b/docs/source/user/demos/optimal_control/doc_monolithic_problems.rst deleted file mode 100755 index acc48042..00000000 --- a/docs/source/user/demos/optimal_control/doc_monolithic_problems.rst +++ /dev/null @@ -1,147 +0,0 @@ -.. _demo_monolithic_problems: - -Coupled Problems - Monolithic Approach -====================================== - - -Problem Formulation -------------------- - -In this demo we show how cashocs can be used with a coupled PDE constraint. -For this demo, we consider a monolithic approach, whereas we investigate -an approach based on a Picard iteration in :ref:`demo_picard_iteration`. - -As model example, we consider the -following problem - -.. math:: - - &\min\; J((y,z),(u,v)) = \frac{1}{2} \int_\Omega \left( y - y_d \right)^2 \text{ d}x + \frac{1}{2} \int_\Omega \left( z - z_d \right)^2 \text{ d}x + \frac{\alpha}{2} \int_\Omega u^2 \text{ d}x + \frac{\beta}{2} \int_\Omega v^2 \text{ d}x \\ - &\text{ subject to }\quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta y + z &= u \quad &&\text{ in } \Omega, \\ - y &= 0 \quad &&\text{ on } \Gamma,\\ - -\Delta z + y &= v \quad &&\text{ in } \Omega,\\ - z &= 0 \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - -In constrast to :ref:`demo_multiple_variables`, the system is now two-way coupled. -To solve it, we employ a mixed finite element method in this demo. - - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_monolithic_problems.py `, -and the corresponding config can be found in :download:`config.ini `. - -Initialization and variable definitions -*************************************** - -The initialization for this example works as before, i.e., we use :: - - from fenics import * - - import cashocs - - config = cashocs.load_config("config.ini") - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(50) - -For the mixed finite element method we have to define a :py:class:`fenics.MixedFunctionSpace`, via :: - - elem_1 = FiniteElement("CG", mesh.ufl_cell(), 1) - elem_2 = FiniteElement("CG", mesh.ufl_cell(), 1) - V = FunctionSpace(mesh, MixedElement([elem_1, elem_2])) - -The control variables get their own :py:class:`fenics.FunctionSpace` :: - - U = FunctionSpace(mesh, "CG", 1) - -Then, the state and adjoint variables ``state`` and ``adjoint`` are defined :: - - state = Function(V) - adjoint = Function(V) - -As these are part of a :py:class:`fenics.MixedFunctionSpace`, we can access their -individual components by :: - - y, z = split(state) - p, q = split(adjoint) - -Similarly to :ref:`demo_multiple_variables`, ``p`` is the adjoint state corresponding -to ``y``, and ``q`` is the one corresponding to ``z``. - -We then define the control variables as :: - - u = Function(U) - v = Function(U) - controls = [u, v] - -Note, that we directly put the control variables ``u`` and ``v`` into a list -``controls``, which implies that ``u`` is the first component of the control -variable, and ``v`` the second one. - -.. hint:: - - An alternative way of specifying the controls would be to reuse the mixed function space and use :: - - controls = Function(V) - u, v = split(controls) - - Allthough this formulation is slightly different (it uses a :py:class:`fenics.Function` for the controls, and not a list) - the de-facto behavior of both methods is completely identical, just the interpretation is slightly - different (since the individual components of the ``V`` :py:class:`fenics.FunctionSpace` are also CG1 functions). - -Definition of the mixed weak form -********************************* - -Next, we define the mixed weak form. To do so, we first define the first equation -and its Dirichlet boundary conditions :: - - e_y = inner(grad(y), grad(p)) * dx + z * p * dx - u * p * dx - bcs_y = cashocs.create_dirichlet_bcs(V.sub(0), Constant(0), boundaries, [1, 2, 3, 4]) - - -and, in analogy, the second state equation :: - - e_z = inner(grad(z), grad(q)) * dx + y * q * dx - v * q * dx - bcs_z = cashocs.create_dirichlet_bcs(V.sub(1), Constant(0), boundaries, [1, 2, 3, 4]) - - -To arrive at the mixed weak form of the entire syste, we have to add the state equations -and Dirichlet boundary conditions :: - - e = e_y + e_z - bcs = bcs_y + bcs_z - -Note, that we can only have one state equation as we also have only a single state variable ``state``, -and the number of state variables and state equations has to coincide, and the same -is true for the boundary conditions, where also just a single list is required. - - -Defintion of the optimization problem -************************************* - -The cost functional can be specified in analogy to the one of :ref:`demo_multiple_variables` :: - - y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) - z_d = Expression("sin(4*pi*x[0])*sin(4*pi*x[1])", degree=1) - alpha = 1e-6 - beta = 1e-6 - J = cashocs.IntegralFunctional( - Constant(0.5) * (y - y_d) * (y - y_d) * dx - + Constant(0.5) * (z - z_d) * (z - z_d) * dx - + Constant(0.5 * alpha) * u * u * dx - + Constant(0.5 * beta) * v * v * dx - ) - -Finally, we can set up the optimization problem and solve it :: - - optimization_problem = cashocs.OptimalControlProblem( - e, bcs, J, state, controls, adjoint, config - ) - optimization_problem.solve() - -The result should look like this - -.. image:: /../../demos/documented/optimal_control/monolithic_problems/img_monolithic_problems.png diff --git a/docs/source/user/demos/optimal_control/doc_multiple_variables.rst b/docs/source/user/demos/optimal_control/doc_multiple_variables.rst deleted file mode 100755 index b19bf548..00000000 --- a/docs/source/user/demos/optimal_control/doc_multiple_variables.rst +++ /dev/null @@ -1,173 +0,0 @@ -.. _demo_multiple_variables: - -Using Multiple Variables and PDEs -================================= - - -Problem Formulation -------------------- - -In this demo we show how cashocs can be used to treat multiple -state equations as constraint. Additionally, this also highlights -how the case of multiple controls can be treated. As model example, we consider the -following problem - -.. math:: - - &\min\; J((y,z), (u,v)) = \frac{1}{2} \int_\Omega \left( y - y_d \right) \text{ d}x + \frac{1}{2} \int_\Omega \left( z - z_d \right) \text{ d}x + \frac{\alpha}{2} \int_\Omega u^2 \text{ d}x + \frac{\beta}{2} \int_\Omega v^2 \text{ d}x \\ - &\text{ subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta y &= u \quad &&\text{ in } \Omega, \\ - y &= 0 \quad &&\text{ on } \Gamma,\\ - -\Delta z - y &= v \quad &&\text{ in } \Omega, \\ - z &= 0 \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - - -For the sake of simplicity, we restrict this investigation to -homogeneous boundary conditions as well as to a very simple one way -coupling. More complex problems (using e.g. Neumann control or more -difficult couplings) are straightforward to implement. - -In contrast to the previous examples, in the case where we have multiple state equations, which are -either decoupled or only one-way coupled, the corresponding state equations are solved one after the other -so that every input related to the state and adjoint variables has to be put into a ordered list, so -that they can be treated properly, as is explained in the following. - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_multiple_variables.py `, -and the corresponding config can be found in :download:`config.ini `. - -Initialization -************** - -The initial setup is identical to the previous cases (see, :ref:`demo_poisson`), where we again use :: - - from fenics import * - - import cashocs - - config = cashocs.load_config("config.ini") - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(50) - V = FunctionSpace(mesh, "CG", 1) - -which defines the geometry and the function space. - -Defintion of the Problems -************************* - -We now first define the state equation corresponding to the state :math:`y`. This -is done in analogy to :ref:`demo_poisson` :: - - y = Function(V) - p = Function(V) - u = Function(V) - e_y = inner(grad(y), grad(p)) * dx - u * p * dx - bcs_y = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) - -Similarly to before, ``p`` is the adjoint state corresponding to ``y``. - -Next, we define the second state equation (which is for the state :math:`z`) via :: - - z = Function(V) - q = Function(V) - v = Function(V) - e_z = inner(grad(z), grad(q)) * dx - (y + v) * q * dx - bcs_z = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) - -Here, ``q`` is the adjoint state corresponding to ``z``. - -In order to treat this one-way coupled with cashocs, we now have to specify what -the state, adjoint, and control variables are. This is done by putting the -corresponding :py:class:`fenics.Function` objects into ordered lists :: - - states = [y, z] - adjoints = [p, q] - controls = [u, v] - -To define the corresponding state system, the state equations and Dirichlet boundary -conditions also have to be put into an ordered list, i.e., :: - - e = [e_y, e_z] - bcs_list = [bcs_y, bcs_z] - -.. note:: - - It is important, that the ordering of the state and adjoint variables, as well - as the state equations and boundary conditions is in the same way. This means, - that ``e[i]`` is the state equation for ``state[i]``, which is supplemented - with Dirichlet boundary conditions defined in ``bcs_list[i]``, and has a corresponding - adjoint state ``adjoints[i]``, for all ``i``. In analogy, the same holds true - for the control variables, the scalar product of the control space, and the - control constraints, i.e., ``controls[j]``, ``riesz_scalar_products[j]``, and - ``control_constraints[j]`` all have to belong to the same control variable. - - - -Note, that the control variables are completely independent of the state -and adjoint ones, so that the relative ordering between these objects does -not matter. - -Defintion of the cost functional and optimization problem -********************************************************* - - -For the optimization problem we now define the cost functional via :: - - y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) - z_d = Expression("sin(4*pi*x[0])*sin(4*pi*x[1])", degree=1) - alpha = 1e-6 - beta = 1e-4 - J = cashocs.IntegralFunctional( - Constant(0.5) * (y - y_d) * (y - y_d) * dx - + Constant(0.5) * (z - z_d) * (z - z_d) * dx - + Constant(0.5 * alpha) * u * u * dx - + Constant(0.5 * beta) * v * v * dx - ) - -This setup is sufficient to now define the optimal control problem and solve -it, via :: - - ocp = cashocs.OptimalControlProblem(e, bcs_list, J, states, controls, adjoints, config) - ocp.solve() - -The result should look like this - -.. image:: /../../demos/documented/optimal_control/multiple_variables/img_multiple_variables.png - - -.. note:: - - Note, that the error between :math:`z` and :math:`z_d` is significantly larger - that the error between :math:`y` and :math:`y_d`. This is due to the fact that - we use a different regularization parameter for the controls :math:`u` and :math:`v`. - For the former, which only acts on :math:`y`, we have a regularization parameter - of ``alpha = 1e-6``, and for the latter we have ``beta = 1e-4``. Hence, :math:`v` - is penalized higher for being large, so that also :math:`z` is (significantly) - smaller than :math:`z_d`. - -.. hint:: - - Note, that for the case that we consider control constraints (see :ref:`demo_box_constraints`) - or different Hilbert spaces, e.g., for boundary control (see :ref:`demo_neumann_control`), - the corresponding control constraints have also to be put into a joint list, i.e., :: - - cc_u = [u_a, u_b] - cc_v = [v_a, v_b] - cc = [cc_u, cc_v] - - and the corresponding scalar products have to be treated analogously, i.e., :: - - scalar_product_u = TrialFunction(V)*TestFunction(V)*dx - scalar_product_v = TrialFunction(V)*TestFunction(V)*dx - scalar_products = [scalar_product_u, scalar_produt_v] - - -In summary, to treat multiple (control or state) variables, the -corresponding objects simply have to placed into ordered lists which -are then passed to the :py:class:`OptimalControlProblem ` -instead of the "single" objects as in the previous examples. Note, that each -individual object of these lists is allowed to be from a different function space, -and hence, this enables different discretizations of state and adjoint systems. diff --git a/docs/source/user/demos/optimal_control/doc_neumann_control.rst b/docs/source/user/demos/optimal_control/doc_neumann_control.rst deleted file mode 100755 index 56fe092f..00000000 --- a/docs/source/user/demos/optimal_control/doc_neumann_control.rst +++ /dev/null @@ -1,132 +0,0 @@ -.. _demo_neumann_control: - -Neumann Boundary Control -======================== - - -Problem Formulation -------------------- - -In this demo we investigate an optimal control problem with -a Neumann type boundary control. This problem reads - -.. math:: - - &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 \text{ d}x + \frac{\alpha}{2} \int_{\Gamma} u^2 \text{ d}s \\ - &\text{ subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta y + y &= 0 \quad &&\text{ in } \Omega,\\ - n\cdot \nabla y &= u \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - - -(see, e.g., `Tröltzsch, Optimal Control of Partial Differential Equations `_ -or `Hinze, Pinnau, Ulbrich, and Ulbrich, Optimization with PDE constraints `_. -Note, that we cannot use a simple Poisson equation as constraint -since this would not be compatible with the boundary conditions -(i.e. not well-posed). - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_neumann_control.py `, -and the corresponding config can be found in :download:`config.ini `. - -Initialization -************** - -Initially, the code is again identical to the previous ones (see :ref:`demo_poisson` and :ref:`demo_box_constraints`), -i.e., we have :: - - from fenics import * - - import cashocs - - config = cashocs.load_config("config.ini") - - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(25) - V = FunctionSpace(mesh, "CG", 1) - - y = Function(V) - p = Function(V) - u = Function(V) - - -Definition of the state equation -******************************** - -Now, the definition of the state problem obviously differs from the -previous two examples, and we use :: - - e = inner(grad(y), grad(p)) * dx + y * p * dx - u * p * ds - - -which directly puts the Neumann boundary condition into the weak form. -For this problem, we do not have Dirichlet boundary conditions, so that we -use :: - - bcs = [] - -.. hint:: - - Alternatively, we could have also used :: - - bcs = None - - -Definition of the cost functional -********************************* - -The definition of the cost functional is nearly identical to before, -only the integration measure for the regularization term changes, so that we have :: - - y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) - alpha = 1e-6 - J = cashocs.IntegralFunctional( - Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * u * u * ds - ) - -As the default Hilbert space for a control is :math:`L^2(\Omega)`, we now -also have to change this, to accommodate for the fact that the control -variable u now lies in the space :math:`L^2(\Gamma)`, i.e., it is -only defined on the boundary. This is done by defining the scalar -product of the corresponding Hilbert space, which we do with :: - - scalar_product = TrialFunction(V) * TestFunction(V) * ds - -The scalar_product always has to be a symmetric, coercive and continuous -bilinear form, so that it induces an actual scalar product on the -corresponding space. - -.. note:: - - This means, that we could also define an alternative scalar product for - :ref:`demo_poisson`, using the space :math:`H^1(\Omega)` instead of - :math:`L^2(\Omega)` with the following :: - - scalar_product = inner(grad(TrialFunction(V)), grad(TestFunction(V)))*dx + TrialFunction(V)*TestFunction(V)*dx - - This allows a great amount of flexibility in the choice of the control space. - -Setup of the optimization problem and its solution -************************************************** - - -With this, we can now define the optimal control problem with the -additional keyword argument ``riesz_scalar_products`` and solve it with the -:py:meth:`ocp.solve() ` command :: - - - ocp = cashocs.OptimalControlProblem( - e, bcs, J, y, u, p, config, riesz_scalar_products=scalar_product - ) - ocp.solve() - - -Hence, in order to treat boundary control problems, the corresponding -weak forms have to be modified accordingly, and one **has to** adapt the -scalar products used to determine the gradients. - -The resulting visualization of the result looks as follows - -.. image:: /../../demos/documented/optimal_control/neumann_control/img_neumann_control.png diff --git a/docs/source/user/demos/optimal_control/doc_nonlinear_pdes.rst b/docs/source/user/demos/optimal_control/doc_nonlinear_pdes.rst deleted file mode 100755 index f2008842..00000000 --- a/docs/source/user/demos/optimal_control/doc_nonlinear_pdes.rst +++ /dev/null @@ -1,97 +0,0 @@ -.. _demo_nonlinear_pdes: - -Optimal Control with Nonlinear PDE Constraints -============================================== - -Problem Formulation -------------------- - -In this demo, we take a look at the case of nonlinear PDE constraints for optimization -problems. As a model problem, we consider - -.. math:: - - &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 \text{ d}x + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x \\ - &\text{ subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta y + c y^3 &= u \quad &&\text{ in } \Omega,\\ - y &= 0 \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - - -As this problem has a nonlinear PDE as state constraint, we have to modify the config -file slightly. In particular, in the Section :ref:`StateSystem ` -we have to write :: - - is_linear = False - -Note, that ``is_linear = False`` works for any problem, as linear equations are just a special case -of nonlinear ones, and the corresponding nonlinear solver converges in a single iteration for these. -However, in the opposite case, FEniCS will raise an error, so that an actually nonlinear -equation cannot be solved using ``is_linear = True``. Also, we briefly recall from -:ref:`config_optimal_control`, that the default behavior is ``is_linear = False``, -so that this is not an issue. - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_nonlinear_pdes.py `, -and the corresponding config can be found in :download:`config.ini `. - -Initialization -************** - -Thanks to the high level interface for implementing weak formulations, this problem -is tackled almost as easily as the one in :ref:`demo_poisson`. In particular, the entire initialization, -up to the definition of the weak form of the PDE constraint, is identical, and we have :: - - from fenics import * - - import cashocs - - config = cashocs.load_config("config.ini") - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(25) - V = FunctionSpace(mesh, "CG", 1) - - y = Function(V) - p = Function(V) - u = Function(V) - -For the definition of the state constraints, we use essentially the same syntax as -we would use for the problem in FEniCS, i.e., we write :: - - c = Constant(1e2) - e = inner(grad(y), grad(p)) * dx + c * pow(y, 3) * p * dx - u * p * dx - -.. note:: - - In particular, the only difference between the cashocs implementation of this weak form - and the FEniCS one is that, as before, we use :py:class:`fenics.Function` objects for both the state and - adjoint variables, whereas we would use :py:class:`fenics.Function` objects for the state, and - :py:class:`fenics.TestFunction` for the adjoint variable, which would actually play the role of the - test function. Other than that, the syntax is, again, identical to the one of - FEniCS. - -Finally, the boundary conditions are defined as before :: - - bcs = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) - - -Solution of the optimization problem -************************************ - -To define and solve the optimization problem, we now proceed exactly as in -:ref:`demo_poisson`, and use :: - - y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) - alpha = 1e-6 - J = cashocs.IntegralFunctional( - Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * u * u * dx - ) - - ocp = cashocs.OptimalControlProblem(e, bcs, J, y, u, p, config) - ocp.solve() - -The results looks like this - -.. image:: /../../demos/documented/optimal_control/nonlinear_pdes/img_nonlinear_pdes.png diff --git a/docs/source/user/demos/optimal_control/doc_picard_iteration.rst b/docs/source/user/demos/optimal_control/doc_picard_iteration.rst deleted file mode 100755 index c05ec9d6..00000000 --- a/docs/source/user/demos/optimal_control/doc_picard_iteration.rst +++ /dev/null @@ -1,144 +0,0 @@ -.. _demo_picard_iteration: - -Coupled Problems - Picard Iteration -=================================== - -Problem Formulation -------------------- - -In this demo we show how cashocs can be used with a coupled PDE constraint. -For this, we consider a iterative approach, whereas we investigated -a monolithic approach in :ref:`demo_monolithic_problems`. - -As model example, we consider the -following problem - -.. math:: - - &\min\; J((y,z),(u,v)) = \frac{1}{2} \int_\Omega \left( y - y_d \right)^2 \text{ d}x + \frac{1}{2} \int_\Omega \left( z - z_d \right)^2 \text{ d}x + \frac{\alpha}{2} \int_\Omega u^2 \text{ d}x + \frac{\beta}{2} \int_\Omega v^2 \text{ d}x \\ - &\text{ subject to }\quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta y + z &= u \quad &&\text{ in } \Omega, \\ - y &= 0 \quad &&\text{ on } \Gamma,\\ - -\Delta z + y &= v \quad &&\text{ in } \Omega,\\ - z &= 0 \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - -Again, the system is two-way coupled. To solve it, we now employ a Picard iteration. Therefore, -the two PDEs are solved subsequently, where the variables are frozen in between: At the beginning -the first PDE is solved for :math:`y`, with :math:`z` being fixed. Afterwards, the second PDE is solved for :math:`z` -with :math:`y` fixed. This is then repeated -until convergence is reached. - -.. note:: - - There is, however, no a-priori guarantee that the Picard iteration converges - for a particular problem, unless a careful analysis is carried out by the user. - Still, it is an important tool, which also often works well in practice. - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_picard_iteration.py `, -and the corresponding config can be found in :download:`config.ini `. - -Initialization -************** - -The setup is as in :ref:`demo_poisson` :: - - from fenics import * - - import cashocs - - config = cashocs.load_config("config.ini") - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(50) - V = FunctionSpace(mesh, "CG", 1) - -However, compared to the previous examples, there is a major change in the config file. As we want to use -the Picard iteration as solver for the state PDEs, we now specify :: - - picard_iteration = True - -see :download:`config.ini `. - - -Definition of the state system -****************************** - -The definition of the state system follows the same ideas as introduced in -:ref:`demo_multiple_variables`: We define both state equations through their components, -and then gather them in lists, which are passed to the :py:class:`OptimalControlProblem `. -The state and adjoint variables are defined via :: - - y = Function(V) - p = Function(V) - z = Function(V) - q = Function(V) - -The control variables are defined as :: - - u = Function(V) - v = Function(V) - -Next, we define the state system, using the weak forms from -:ref:`demo_monolithic_problems` :: - - e_y = inner(grad(y), grad(p)) * dx + z * p * dx - u * p * dx - bcs_y = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) - - e_z = inner(grad(z), grad(q)) * dx + y * q * dx - v * q * dx - bcs_z = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) - -Finally, we use the same procedure as in :ref:`demo_multiple_variables`, and -put everything into (ordered) lists :: - - states = [y, z] - adjoints = [p, q] - controls = [u, v] - - e = [e_y, e_z] - bcs = [bcs_y, bcs_z] - - -Definition of the optimization problem -************************************** - -The cost functional is defined as in :ref:`demo_monolithic_problems`, the only -difference is that ``y`` and ``z`` now are :py:class:`fenics.Function` objects, whereas they -were generated with the :py:func:`fenics.split` command previously :: - - y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) - z_d = Expression("sin(4*pi*x[0])*sin(4*pi*x[1])", degree=1) - alpha = 1e-6 - beta = 1e-6 - J = cashocs.IntegralFunctional( - Constant(0.5) * (y - y_d) * (y - y_d) * dx - + Constant(0.5) * (z - z_d) * (z - z_d) * dx - + Constant(0.5 * alpha) * u * u * dx - + Constant(0.5 * beta) * v * v * dx - ) - -Finally, we set up the optimization problem and solve it :: - - optimization_problem = cashocs.OptimalControlProblem( - e, bcs, J, states, controls, adjoints, config - ) - optimization_problem.solve() - -The result should look like this - -.. image:: /../../demos/documented/optimal_control/picard_iteration/img_picard_iteration.png - -.. note:: - - Comparing the output (especially in the early iterations) between the monlithic and Picard apporach - we observe that both methods yield essentially the same results (up to machine precision). This validates - the Picard approach. - - However, one should note that for this example, the Picard approach takes significantly longer to - compute the optimizer. This is due to the fact that the individual PDEs have to be solved several - times, whereas in the monolithic approach the state system is (slightly) larger, but has to be solved - less often. However, the monolithic approach needs significantly more memory, so that the Picard - iteration becomes feasible for very large problems. Further, the convergence properties of the - Picard iteration are better, so that it may converge even when the monolithic approach fails. diff --git a/docs/source/user/demos/optimal_control/doc_poisson.rst b/docs/source/user/demos/optimal_control/doc_poisson.rst deleted file mode 100755 index 393ce736..00000000 --- a/docs/source/user/demos/optimal_control/doc_poisson.rst +++ /dev/null @@ -1,282 +0,0 @@ -.. _demo_poisson: - -Distributed Control of a Poisson Problem -======================================== - - -Problem Formulation -------------------- - -In this demo we investigate the basics of cashocs for -optimal control problems. To do so, we investigate the "mother -problem" of PDE constrained optimization, i.e., - -.. math:: - - &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 - \text{ d}x + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x \\ - &\text{ subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta y &= u \quad &&\text{ in } \Omega,\\ - y &= 0 \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - - -(see, e.g., `Tröltzsch, Optimal Control of Partial Differential Equations -`_ -or `Hinze, Pinnau, Ulbrich, and Ulbrich, Optimization with PDE constraints -`_). - -For this first example, we do not consider control constraints, -but search for an optimal control u in the entire space :math:`L^2(\Omega)`, -for the sake of simplicitiy. For the domain under consideration, we use the unit square -:math:`\Omega = (0, 1)^2`, since this is built into cashocs. - -In the following, we will describe how to solve this problem -using cashocs. Moreover, -we also detail alternative / equivalent FEniCS code which could -be used to define the problem instead. - -Implementation --------------- -The complete python code can be found in the file :download:`demo_poisson.py `, -and the corresponding config can be found in :download:`config.ini `. - -Initialization -************** - -We begin by importing FEniCS and cashocs. For the sake of -better readability we use a wildcard import for FEniCS :: - - from fenics import * - - import cashocs - - -Afterwards, we can specify the so-called log level of cashocs. This is done in the line :: - - cashocs.set_log_level(cashocs.LogLevel.INFO) - -.. hint:: - - There are a total of five levels of verbosity, given by :py:class:`cashocs.LogLevel.DEBUG`, - :py:class:`cashocs.LogLevel.INFO`, :py:class:`cashocs.LogLevel.WARNING`, :py:class:`cashocs.LogLevel.ERROR`, - and :py:class:`cashocs.LogLevel.CRITICAL`. The default value is ``INFO``, which would also - be selected if the :py:func:`cashocs.set_log_level` method would not have been called. - -Next, we have to load the config file which loads the user's -input parameters into the script. For a detailed documentation -of the config files and the parameters within, we refer to :ref:`config_optimal_control`. -Note, that the corresponding file is :download:`config.ini ` -. The config is then loaded via :: - - config = cashocs.load_config("config.ini") - - -Next up, we have to define the state equation. This mostly -works with usual FEniCS syntax. In cashocs, we can quickly -generate meshes for squares and cubes, as well as import -meshes generated by GMSH, for more complex geometries. In this -example we take a built-in unit square as example. This is generated -via :: - - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(25) - -The input for regular_mesh determines the number of elements that -are placed along each axis of the square. Note, that the mesh could be -further manipulated with additional, optional arguments, and we -refer to :py:func:`regular_mesh ` for more infos. Note, -that the ``subdomains`` object is a (empty) ``MeshFunction``, and that -``boundaries`` is a ``MeshFunction`` that contains markers for the following -boundaries - - - The left side of the square is marked by 1 - - The right side is marked by 2 - - The bottom is marked by 3 - - The top is marked by 4, - -as defined in :py:func:`regular_mesh `. - -With the geometry defined, we create a function space with the classical -FEniCS syntax :: - - V = FunctionSpace(mesh, "CG", 1) - - -which creates a function space of continuous, linear Lagrange -elements. - - -Definition of the state equation -******************************** - -To describe the state system in cashocs, we use (almost) standard -FEniCS syntax, and the differences will be highlighted in the -following. First, we define a :py:class:`fenics.Function` ``y`` that models our -state variable :math:`y`, and a :py:class:`fenics.Function` ``p`` that models -the corresponding adjoint variable :math:`p` via :: - - y = Function(V) - p = Function(V) - -Next up, we analogously define the control variable as :py:class:`fenics.Function` ``u`` :: - - u = Function(V) - -This enables us to define the weak form of the state equation, -which is tested not with a :py:class:`fenics.TestFunction` but with the adjoint -variable ``p`` via the classical FEniCS / UFL syntax :: - - e = inner(grad(y), grad(p)) * dx - u * p * dx - -.. note:: - For the clasical definition of this weak form with FEniCS - one would write the following code :: - - y = TrialFunction(V) - p = TestFunction(V) - u = Function(V) - a = inner(grad(y), grad(p))*dx - L = u*p*dx - - as this is a linear problem. However, to have greater flexibility - we have to treat the problems as being potentially nonlinear. - In this case, the classical FEniCS formulation for this as - nonlinear problem would be :: - - y = Function(V) - p = TestFunction(V) - u = Function(V) - F = inner(grad(y), grad(p))*dx -u*p*dx - - which could then be solved via the :py:func:`fenics.solve` interface. This - formulation, which comes more naturally for nonlinear - variational problems (see the `FEniCS examples `_) - is closer to the one in cashocs. However, - for the use with cashocs, the state variable y **must not** - be a :py:class:`fenics.TrialFunction`, and the adjoint variable p **must not** - be a :py:class:`fenics.TestFunction`. They **have to** be defined as regular - :py:class:`fenics.Function` objects, otherwise the code will not work properly. - -After defining the weak form of the state equation, we now -specify the corresponding (homogeneous) Dirichlet boundary -conditions via :: - - bcs = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) - - -This creates Dirichlet boundary conditions with value 0 at the -boundaries 1,2,3, and 4, i.e., everywhere. - -.. hint:: - - Classically, these boundary conditions could also be defined - via :: - - def boundary(x, on_bdry): - return on_boundary - bc = DirichletBC(V, Constant(0), boundary) - - which would yield a single DirichletBC object, instead of - the list returned by :py:func:`create_dirichlet_bcs `. Any of the many methods for - defining the boundary conditions works here, as long as it - is valid input for the :py:func:`fenics.solve` function. - -With the above description, we see that defining the state system -for cashocs is nearly identical to defining it with FEniCS, -the only major difference lies in the definition of the state -and adjoint variables as :py:class:`fenics.Function` objects, instead of :py:class:`fenics.TrialFunction` -and :py:class:`fenics.TestFunction`. - -Definition of the cost functional -********************************* - - -Now, we have to define the optimal control problem which we do -by first specifying the cost functional. To do so, we define the -desired state :math:`y_d` as an :py:class:`fenics.Expression` ``y_d``, i.e., :: - - y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) - -Alternatively, ``y_d`` could also be a :py:class:`fenics.Function` or any other object -that is usable in an UFL form (e.g. generated with :py:func:`fenics.SpatialCoordinate`). - -Then, we define the regularization parameter :math:`\alpha` and the tracking-type -cost functional via the commands :: - - alpha = 1e-6 - J = cashocs.IntegralFunctional( - Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * u * u * dx - ) - -The cost functional is defined via the :py:class:`cashocs.IntegralFunctional`, which means that -we only have to define the cost functional's integrand, which will then further be treated by cashocs -as needed for the optimization. -These definitions are also classical in the sense that they -would have to be performed in this (or a similar) way in FEniCS -when one would want to evaluate the (reduced) cost functional, -so that we have only very little overhead. - -Definition of the optimization problem and its solution -******************************************************* - -Finally, we set up an :py:class:`OptimalControlProblem ` ``ocp`` and then -directly solve it with the the method :py:meth:`ocp.solve() -` :: - - ocp = cashocs.OptimalControlProblem(e, bcs, J, y, u, p, config) - ocp.solve() - -.. hint:: - Note, that the :py:meth:`solve ` command without any additional keyword arguments leads to - cashocs using the settings defined in the config file. However, there are some options - that can be directly set with keyword arguments for the :py:meth:`solve ` - call. These are - - - ``algorithm`` : Specifies which solution algorithm shall be used. - - ``rtol`` : The relative tolerance for the optimization algorithm. - - ``atol`` : The absolute tolerance for the optimization algorithm. - - ``max_iter`` : The maximum amount of iterations that can be carried out. - - Hence, we could also use the command :: - - ocp.solve('lbfgs', 1e-3, 0.0, 100) - - to solve the optimization problem with the L-BFGS method, a relative tolerance - of 1e-3, no absolute tolerance, and a maximum of 100 iterations. - - The possible values for these arguments are the same as :ref:`the corresponding ones in the config file - `. This just allows for some shortcuts, e.g., when one wants to quickly use a different solver. - - Note, that it is not strictly necessary to supply config files in cashocs. In this - case, the user has to follow the above example and specify at least the solution - algorithm via the :py:meth:`solve ` method. - However, it is very strongly recommended to use config files with cashocs as - they allow a detailed tuning of its behavior. - - -Finally, we visualize the results using matplotlib and the following code :: - - import matplotlib.pyplot as plt - plt.figure(figsize=(16,9)) - - plt.subplot(1, 3, 1) - fig = plot(u) - plt.colorbar(fig, fraction=0.046, pad=0.04) - plt.title('Control variable u') - - plt.subplot(1,3,2) - fig = plot(y) - plt.colorbar(fig, fraction=0.046, pad=0.04) - plt.title('State variable y') - - plt.subplot(1,3,3) - fig = plot(interpolate(y_d, V)) - plt.colorbar(fig, fraction=0.046, pad=0.04) - plt.title('Desired state y_d') - - plt.tight_layout() - -The output should look like this - -.. image:: /../../demos/documented/optimal_control/poisson/img_poisson.png diff --git a/docs/source/user/demos/optimal_control/doc_pre_post_hooks.rst b/docs/source/user/demos/optimal_control/doc_pre_post_hooks.rst deleted file mode 100755 index fa297130..00000000 --- a/docs/source/user/demos/optimal_control/doc_pre_post_hooks.rst +++ /dev/null @@ -1,159 +0,0 @@ -.. _demo_pre_post_hooks: - -Pre- and Post-Hooks for the optimization -======================================== - -Problem Formulation -------------------- - -In this demo we show how one can use the flexibility of cashocs -to "inject" their own code into the optimization, which is carried -out before each solve of the state system (``pre_hook``) or after each -gradient computation (``post_hook``). To do so, we investigate the following -problem - -.. math:: - - &\min\; J(u, c) = \frac{1}{2} \int_\Omega \left\lvert u - u_d \right\rvert^2 \text{ d}x + \frac{\alpha}{2} \int_\Omega \left\lvert c \right\rvert^2 \text{ d}x \\ - &\text{ subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta u + Re (u\cdot \nabla) u + \nabla p &= c \quad &&\text{ in } \Omega, \\ - \text{div}(u) &= 0 \quad &&\text{ in } \Omega,\\ - u &= u_\text{dir} \quad &&\text{ on } \Gamma^\text{dir},\\ - u &= 0 \quad &&\text{ on } \Gamma^\text{no slip},\\ - p &= 0 \quad &&\text{ at } x^\text{pres}. - \end{alignedat} \right. - - -In particular, the setting for this demo is very similar to the one of -:ref:`demo_stokes`, but here we consider the nonlinear incompressible Navier-Stokes equations. - -In the following, we will describe how to solve this problem -using cashocs, where we will use the ``pre_hook`` functionality to implement -a homotopy for solving the Navier-Stokes equations. - -Implementation --------------- -The complete python code can be found in the file :download:`demo_constraints.py `, -and the corresponding config can be found in :download:`config.ini `. - -Initialization -************** - -The initial part of the code is nearly identical to the one of :ref:`demo_stokes`, but here we use the -Navier-Stokes equations instead of the linear Stokes system :: - - from fenics import * - - import cashocs - - config = cashocs.load_config("./config.ini") - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(30) - - v_elem = VectorElement("CG", mesh.ufl_cell(), 2) - p_elem = FiniteElement("CG", mesh.ufl_cell(), 1) - V = FunctionSpace(mesh, MixedElement([v_elem, p_elem])) - U = VectorFunctionSpace(mesh, "CG", 1) - - up = Function(V) - u, p = split(up) - vq = Function(V) - v, q = split(vq) - c = Function(U) - - Re = Constant(1e2) - - e = ( - inner(grad(u), grad(v)) * dx - + Constant(Re) * dot(grad(u) * u, v) * dx - - p * div(v) * dx - - q * div(u) * dx - - inner(c, v) * dx - ) - - - def pressure_point(x, on_boundary): - return near(x[0], 0) and near(x[1], 0) - - - no_slip_bcs = cashocs.create_dirichlet_bcs( - V.sub(0), Constant((0, 0)), boundaries, [1, 2, 3] - ) - lid_velocity = Expression(("4*x[0]*(1-x[0])", "0.0"), degree=2) - bc_lid = DirichletBC(V.sub(0), lid_velocity, boundaries, 4) - bc_pressure = DirichletBC(V.sub(1), Constant(0), pressure_point, method="pointwise") - bcs = no_slip_bcs + [bc_lid, bc_pressure] - - alpha = 1e-5 - u_d = Expression( - ( - "sqrt(pow(x[0], 2) + pow(x[1], 2))*cos(2*pi*x[1])", - "-sqrt(pow(x[0], 2) + pow(x[1], 2))*sin(2*pi*x[0])", - ), - degree=2, - ) - J = cashocs.IntegralFunctional( - Constant(0.5) * inner(u - u_d, u - u_d) * dx - + Constant(0.5 * alpha) * inner(c, c) * dx - ) - -pre-hooks -********* - -Note, that we have chosen a Reynolds number of ``Re = 1e2`` for this demo. -In order to solve the Navier-Stokes equations for higher Reynolds numbers, it is -often sensible to first solve the equations for a lower Reynolds number and then -use this solution as initial guess for the original high Reynolds number problem. -We can use this procedure in cashocs with its ``pre_hook`` functionality. -A ``pre_hook`` is a function without arguments, which gets called each time before solving the -state equation. In our case, the ``pre_hook`` should solve the Navier-Stokes equations for a lower -Reynolds number, so we define it as follows :: - - def pre_hook(): - print("Solving low Re Navier-Stokes equations for homotopy.") - v, q = TestFunctions(V) - e = ( - inner(grad(u), grad(v)) * dx - + Constant(Re / 10.0) * dot(grad(u) * u, v) * dx - - p * div(v) * dx - - q * div(u) * dx - - inner(c, v) * dx - ) - cashocs.newton_solve(e, up, bcs, verbose=False) - -where we solve the Navier-Stokes equations with a lower Reynolds number of ``Re / 10.0``. -Later on, we inject this ``pre_hook`` into cashocs by using the -:py:meth:`inject_pre_hook ` method. - -Additionally, cashocs implements the functionality of also performing a pre-defined action -after each gradient computation, given by a so-called ``post_hook``. In our case, we just want -to print a statement so that we can visualize what is happening. Therefore, we define our ``post_hook`` -as :: - - def post_hook(): - print("Performing an action after computing the gradient.") - -Next, before we can inject these two hooks, we first have to define the optimal control problem :: - - ocp = cashocs.OptimalControlProblem(e, bcs, J, up, c, vq, config) - -Finally, we can inject both hooks via :: - - ocp.inject_pre_hook(pre_hook) - ocp.inject_post_hook(post_hook) - -.. note:: - - We can also save one line and use the code :: - - ocp.inject_pre_post_hook(pre_hook, post_hook) - - which is equivalent to the above two lines. - -And in the end, we solve the problem with :: - - ocp.solve() - -The resulting optimized control and state look as follows - -.. image:: /../../demos/documented/optimal_control/pre_post_hooks/img_pre_post_hooks.png diff --git a/docs/source/user/demos/optimal_control/doc_scalar_control_tracking.rst b/docs/source/user/demos/optimal_control/doc_scalar_control_tracking.rst deleted file mode 100755 index f31f8368..00000000 --- a/docs/source/user/demos/optimal_control/doc_scalar_control_tracking.rst +++ /dev/null @@ -1,130 +0,0 @@ -.. _demo_scalar_control_tracking: - -Tracking of Scalar Functionals for Optimal Control Problems -=========================================================== - - -Problem Formulation -------------------- - -In this demo we investigate cashocs functionality of tracking scalar functionals -such as cost functional values and other quanitites, which typically -arise after integration. For this, we investigate the problem - -.. math:: - - &\min\; J(y,u) = \frac{1}{2} \left( \int_{\Omega} y^2 - \text{ d}x - C_{des} \right)^2 \\ - &\text{ subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta y &= u \quad &&\text{ in } \Omega,\\ - y &= 0 \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - - -For this example, we do not consider control constraints, -but search for an optimal control u in the entire space :math:`L^2(\Omega)`, -for the sake of simplicitiy. For the domain under consideration, we use the unit square -:math:`\Omega = (0, 1)^2`, since this is built into cashocs. - -In the following, we will describe how to solve this problem -using cashocs. Moreover, -we also detail alternative / equivalent FEniCS code which could -be used to define the problem instead. - -Implementation --------------- -The complete python code can be found in the file :download:`demo_scalar_control_tracking.py `, -and the corresponding config can be found in :download:`config.ini `. - - -The state problem -***************** - -The difference to :ref:`demo_poisson` is that the cost functional does now track the -value of the :math:`L^2` norm of :math:`y` against a desired value of :math:`C_{des}`, -and not the state :math:`y` itself. Other than that, the corresponding PDE constraint -and its setup are completely analogous to :ref:`demo_poisson` :: - - from fenics import * - - import cashocs - - cashocs.set_log_level(cashocs.LogLevel.INFO) - config = cashocs.load_config("config.ini") - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(25) - V = FunctionSpace(mesh, "CG", 1) - - y = Function(V) - p = Function(V) - u = Function(V) - u.vector()[:] = 1.0 - - e = inner(grad(y), grad(p)) * dx - u * p * dx - - bcs = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) - - -Definition of the scalar tracking type cost functional -****************************************************** - -To define the desired tracking type functional, note that cashocs implements the -functional for the following kind of cost functionals - -.. math:: - - \begin{aligned} - J(y,u) &= \frac{1}{2} \left\lvert \int_{\Sigma} f(y,u) \text{ d}m - C_{des} \right\rvert^2 \\ - \end{aligned} - -where :math:`\Sigma` is some part of the domain :math:`\Omega`, e.g. the :math:`\Omega` itself, a subdomain, -or its boundary :math:`\Gamma`, and :math:`\text{d}m` is the corresponding integration measure. - -To define such a cost functional, we only need to define the integrands, i.e., - -.. math:: - - f(y,u) \text{ d}m - -which we will do in the UFL of FEniCS, as well as the goals of the tracking type functionals, i.e., - -.. math:: - - C_{des}. - -To do so, we use the :py:class:`cashocs.ScalarTrackingFunctional` class. We first define the -integrand :math:`f(y,u) \text{d}m = y^2 \text{d}x` via :: - - integrand = y * y * dx - -and define :math:`C_{des}` as :: - - tracking_goal = 1.0 - -With these definitions, we can define the cost functional as :: - - J_tracking = cashocs.ScalarTrackingFunctional(integrand, tracking_goal) - -.. note:: - - The factor in front of the quadratic term can also be adapted, by using the keyword argument - ``weight`` of :py:class:`cashocs.ScalarTrackingFunctional`. - Note, that the default factor is ``0.5``, and that each weight will be multiplied by this value. - - - -Finally, we set up our optimization problem and solve it with the -:py:meth:`solve ` method of the optimization problem :: - - ocp = cashocs.OptimalControlProblem(e, bcs, J_tracking, y, u, p, config) - ocp.solve() - -To verify, that our approach is correct, we also print the value of the integral, which we want -to track :: - - print("L2-Norm of y squared: " + format(assemble(y * y * dx), ".3e")) - - -Finally, we visualize the results using matplotlib, which yields the following - -.. image:: /../../demos/documented/optimal_control/scalar_control_tracking/img_scalar_control_tracking.png diff --git a/docs/source/user/demos/optimal_control/doc_sparse_control.rst b/docs/source/user/demos/optimal_control/doc_sparse_control.rst deleted file mode 100755 index 4aa6a069..00000000 --- a/docs/source/user/demos/optimal_control/doc_sparse_control.rst +++ /dev/null @@ -1,75 +0,0 @@ -.. _demo_sparse_control: - -Sparse Control -============== - -In this demo, we investigate a possibility for obtaining sparse optimal controls. -To do so, we use a sparsity promoting :math:`L^1` regularization. Hence, our model problem -for this demo is given by - -.. math:: - - &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 \text{ d}x + \frac{\alpha}{2} \int_{\Omega} \lvert u \rvert \text{ d}x \\ - &\text{ subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta y &= u \quad &&\text{ in } \Omega,\\ - y &= 0 \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - - -This is basically the same problem as in :ref:`demo_poisson`, but the regularization is now not the :math:`L^2` norm squared, but just the :math:`L^1` norm. - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_sparse_control.py `, -and the corresponding config can be found in :download:`config.ini `. - - - -The implementation of this problem is completely analogous to the one of :ref:`demo_poisson`, -the only difference is the definition of the cost functional. We state the entire code -in the following. The only difference between this implementation and -the one of :ref:`demo_poisson` is in line 18, and is highlighted here. - -.. code-block:: python - :linenos: - :emphasize-lines: 18 - - from fenics import * - - import cashocs - - config = cashocs.load_config("config.ini") - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(50) - V = FunctionSpace(mesh, "CG", 1) - - y = Function(V) - p = Function(V) - u = Function(V) - - e = inner(grad(y), grad(p)) * dx - u * p * dx - bcs = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) - - y_d = Expression("sin(2*pi*x[0])*sin(2*pi*x[1])", degree=1) - alpha = 1e-4 - J = cashocs.IntegralFunctional( - Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * abs(u) * dx - ) - - ocp = cashocs.OptimalControlProblem(e, bcs, J, y, u, p, config) - ocp.solve() - -Note, that for the regularization term we now do not use ``Constant(0.5*alpha)*u*u*dx``, -which corresponds to the :math:`L^2(\Omega)` norm squared, but rather :: - - Constant(0.5 * alpha) * abs(u) * dx - -which corresponds to the :math:`L^1(\Omega)` norm. Other than that, the code is identical. -The visualization of the code also shows, that we have indeed a sparse control - -.. image:: /../../demos/documented/optimal_control/sparse_control/img_sparse_control.png - -.. note:: - The oscillations in between the peaks for the control variable ``u`` are just numerical noise, which comes - from the discretization error. diff --git a/docs/source/user/demos/optimal_control/doc_state_constraints.rst b/docs/source/user/demos/optimal_control/doc_state_constraints.rst deleted file mode 100755 index 73df3b21..00000000 --- a/docs/source/user/demos/optimal_control/doc_state_constraints.rst +++ /dev/null @@ -1,146 +0,0 @@ -.. _demo_state_constraints: - -Optimal Control with State Constraints -====================================== - -Problem Formulation -------------------- - -In this demo we investigate how state constraints can be handled in cashocs. Thanks to -the high level interface for solving (control-constrained) optimal control problems, -the state constrained case can be treated (approximately) using a Moreau-Yosida -regularization, which we show in the following. As model problem, we consider the -following one - -.. math:: - - &\min\; J(y,u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 \text{ d}x + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x \\ - &\text{ subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta y &= u \quad &&\text{ in } \Omega,\\ - y &= 0 \quad &&\text{ on } \Gamma, \\ - y &\leq \bar{y} \quad &&\text{ in } \Omega, - \end{alignedat} \right. - - -see, e.g., `Hinze, Pinnau, Ulbrich, and Ulbrich, Optimization with PDE constraints `_. - -Moreau-Yosida regularization -**************************** - -Instead of solving this problem directly, the Moreau-Yosida regularization instead solves -a sequence of problems without state constraints which are of the form - -.. math:: \min J_\gamma(y, u) = \frac{1}{2} \int_{\Omega} \left( y - y_d \right)^2 \text{ d}x + \frac{\alpha}{2} \int_{\Omega} u^2 \text{ d}x + \frac{1}{2\gamma} \int_\Omega \lvert \max\left( 0, \hat{\mu} + \gamma (y - \bar{y}) \right) \rvert^2 \text{ d}x - -for :math:`\gamma \to +\infty`. We employ a simple homotopy method, and solve the problem for one value of :math:`\gamma`, and then use this solution as initial guess for the next -higher value of :math:`\gamma`. As initial guess we use the solution of the unconstrained -problem. For a detailed discussion of the Moreau-Yosida regularization, we refer the -reader to, e.g., `Hinze, Pinnau, Ulbrich, and Ulbrich, Optimization with PDE constraints -`_. - - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_state_constraints.py `, -and the corresponding config can be found in :download:`config.ini `. - -The initial guess for the homotopy -********************************** - -As mentioned earlier, we first solve the unconstrained problem to get an initial -guess for the homotopy method. This is done in complete analogy to :ref:`demo_poisson` :: - - from fenics import * - import numpy as np - - import cashocs - - config = cashocs.load_config("config.ini") - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(25) - V = FunctionSpace(mesh, "CG", 1) - - y = Function(V) - p = Function(V) - u = Function(V) - - e = inner(grad(y), grad(p)) * dx - u * p * dx - bcs = cashocs.create_dirichlet_bcs(V, Constant(0), boundaries, [1, 2, 3, 4]) - - y_d = Expression("sin(2*pi*x[0]*x[1])", degree=1) - alpha = 1e-3 - J_init_form = ( - Constant(0.5) * (y - y_d) * (y - y_d) * dx + Constant(0.5 * alpha) * u * u * dx - ) - J_init = cashocs.IntegralFunctional(J_init_form) - ocp_init = cashocs.OptimalControlProblem(e, bcs, J_init, y, u, p, config) - ocp_init.solve() - - -.. note:: - - Cashocs automatically updates the user input during the runtime of the optimization - algorithm. Hence, after the :py:meth:`ocp_init.solve() ` - command has returned, the solution is already stored in ``u``. - -The regularized problems -************************ - -For the homotopy method with the Moreau-Yosida regularization, we first define the upper -bound for the state :math:`\bar{y}` and select a sequence of values for :math:`\gamma` via :: - - y_bar = 1e-1 - gammas = [pow(10, i) for i in np.arange(1, 9, 3)] - -Solving the regularized problems is then as simple as writing a ``for`` loop :: - - for gamma in gammas: - - J_form = J_init_form + cashocs._utils.moreau_yosida_regularization( - y, gamma, dx, upper_threshold=y_bar - ) - J = cashocs.IntegralFunctional(J_form) - - ocp_gamma = cashocs.OptimalControlProblem(e, bcs, J, y, u, p, config) - ocp_gamma.solve() - -Here, we use a ``for`` loop, define the new cost functional (with the new value of :math:`\gamma`), -set up the optimal control problem and solve it, as previously. - -.. hint:: - - Note, that we could have also defined ``y_bar`` as a :py:class:`fenics.Function` - or :py:class:`fenics.Expression`, and - the method would have worked exactly the same, the corresponding object just has to - be a valid input for an UFL form. - -.. note:: - We could have also defined the Moreau-Yosida regularization of the inequality constraint - directly, with the following code :: - - J = cashocs.IntegralFunctional( - J_init_form - + Constant(1 / (2 * gamma)) * pow(Max(0, Constant(gamma) * (y - y_bar)), 2) * dx - ) - - However, this is directly implemented in :py:func:`cashocs.moreau_yosida_regularization`, which is why we use this function in the demo. - -Validation of the method -************************ - -Finally, we perform a post processing to see whether the state constraint is -(approximately) satisfied. Therefore, we compute the maximum value of ``y``, -and compute the relative error between this and ``y_bar`` :: - - y_max = np.max(y.vector()[:]) - error = abs(y_max - y_bar) / abs(y_bar) * 100 - print("Maximum value of y: " + str(y_max)) - print("Relative error between y_max and y_bar: " + str(error) + " %") - -As the error is about 0.01 %, we observe that the regularization indeed works -as expected, and this tolerance is sufficiently low for practical applications. - -The visualization of the solution looks as follows - -.. image:: /../../demos/documented/optimal_control/state_constraints/img_state_constraints.png diff --git a/docs/source/user/demos/optimal_control/doc_stokes.rst b/docs/source/user/demos/optimal_control/doc_stokes.rst deleted file mode 100755 index 6b2f1d2d..00000000 --- a/docs/source/user/demos/optimal_control/doc_stokes.rst +++ /dev/null @@ -1,158 +0,0 @@ -.. _demo_stokes: - -Distributed Control of a Stokes Problem -======================================= - -Problem Formulation -------------------- - -In this demo we investigate how cashocs can be used to treat a different kind -of PDE constraint, in particular, we investigate a Stokes problem. The optimization -problem reads as follows - -.. math:: - - &\min\; J(u, c) = \frac{1}{2} \int_\Omega \left\lvert u - u_d \right\rvert^2 \text{ d}x + \frac{\alpha}{2} \int_\Omega \left\lvert c \right\rvert^2 \text{ d}x \\ - &\text{ subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta u + \nabla p &= c \quad &&\text{ in } \Omega, \\ - \text{div}(u) &= 0 \quad &&\text{ in } \Omega,\\ - u &= u_\text{dir} \quad &&\text{ on } \Gamma^\text{dir},\\ - u &= 0 \quad &&\text{ on } \Gamma^\text{no slip},\\ - p &= 0 \quad &&\text{ at } x^\text{pres}. - \end{alignedat} \right. - - -In contrast to the other demos, here we denote by :math:`u` the velocity of a fluid and by -:math:`p` its pressure, which are the two state variables. The control is now denoted by c and -acts as a volume source for the system. The tracking type cost functional again -aims at getting the velocity u close to some desired velocity :math:`u_d`. - -For this example, the geometry is again given by :math:`\Omega = (0,1)^2`, and we take a look at the setting of the well known -lid driven cavity benchmark here. In particular, the boundary conditions are classical -no slip boundary conditions at the left, right, and bottom sides of the square. On the -top (or the lid), a velocity :math:`u_\text{dir}` is prescribed, pointing into the positive x-direction. -Note, that since this problem has Dirichlet conditions on the entire boundary, the -pressure is only determined up to a constant, and hence we have to specify another -condition to ensure uniqueness. For this demo we choose another Dirichlet condition, -specifying the value of the pressure at a single point in the domain. Alternatively, -we could have also required that, e.g., the integral of the velocity :math:`u` over :math:`\Omega` -vanishes (the implementation would then only be slightly longer, but not as intuitive). -An example of how to treat such an additional constraint in FEniCS and cashocs -can be found in :ref:`demo_inverse_tomography`. - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_stokes.py `, -and the corresponding config can be found in :download:`config.ini `. - - -Initialization -************** - -The initialization is the same as in :ref:`demo_poisson`, i.e., :: - - from fenics import * - - import cashocs - - config = cashocs.load_config("./config.ini") - mesh, subdomains, boundaries, dx, ds, dS = cashocs.regular_mesh(30) - -For the solution of the Stokes (and adjoint Stokes) system, which have a saddle point -structure, we have to choose LBB stable elements or a suitable stabilization, see e.g. `Ern and Guermond, -Theory and Practice of Finite Elements `_. -For this demo, we use the classical Taylor-Hood elements of piecewise -quadratic Lagrange elements for the velocity, and piecewise linear ones for the pressure, -which are LBB-stable. These are defined as :: - - v_elem = VectorElement("CG", mesh.ufl_cell(), 2) - p_elem = FiniteElement("CG", mesh.ufl_cell(), 1) - V = FunctionSpace(mesh, MixedElement([v_elem, p_elem])) - U = VectorFunctionSpace(mesh, "CG", 1) - -Moreover, we have defined the control space ``U`` as :py:class:`fenics.FunctionSpace` with piecewise linear -Lagrange elements. - -Next, we set up the corresponding function objects, as follows :: - - up = Function(V) - u, p = split(up) - vq = Function(V) - v, q = split(vq) - c = Function(U) - -Here, ``up`` plays the role of the state variable, having components ``u`` and ``p``, which -are extracted using the :py:func:`fenics.split` command. The adjoint state ``vq`` is structured in -exactly the same fashion. See :ref:`demo_monolithic_problems` for more details. -Similarly to there, ``v`` will play the role of the adjoint -velocity, and ``q`` the one of the adjoint pressure. - -Next up is the definition of the Stokes system. This can be done via :: - - e = inner(grad(u), grad(v)) * dx - p * div(v) * dx - q * div(u) * dx - inner(c, v) * dx - -.. note:: - - Note, that we have chosen to consider the incompressibility condition with a negative - sign. This is used to make sure that the resulting system is symmetric (but indefinite) - which can simplify its solution. Using the positive sign for the divergence - constraint would instead lead to a non-symmetric but positive-definite system. - -The boundary conditions for this system can be defined as follows :: - - def pressure_point(x, on_boundary): - return near(x[0], 0) and near(x[1], 0) - - - no_slip_bcs = cashocs.create_dirichlet_bcs( - V.sub(0), Constant((0, 0)), boundaries, [1, 2, 3] - ) - lid_velocity = Expression(("4*x[0]*(1-x[0])", "0.0"), degree=2) - bc_lid = DirichletBC(V.sub(0), lid_velocity, boundaries, 4) - bc_pressure = DirichletBC(V.sub(1), Constant(0), pressure_point, method="pointwise") - bcs = no_slip_bcs + [bc_lid, bc_pressure] - -Here, we first define the point :math:`x^\text{pres}`, where the pressure is set to 0. -Afterwards, we use the cashocs function :py:func:`create_dirichlet_bcs ` -to quickly create the no slip conditions at the left, right, and bottom of the cavity. Next, we define the Dirichlet -velocity :math:`u_\text{dir}` for the lid of the cavity as a :py:class:`fenics.Expression`, and create a corresponding -boundary condition. Finally, the Dirichlet condition for the pressure is defined. Note, -that in order to make this work, one has to specify the keyword argument ``method='pointwise'``. - -Defintion of the optimization problem -************************************* - -The definition of the optimization problem is in complete analogy to the previous -ones we considered. The only difference is the fact that we now have to use :py:func:`fenics.inner` -to multiply the vector valued functions ``u``, ``u_d`` and ``c`` :: - - alpha = 1e-5 - u_d = Expression( - ( - "sqrt(pow(x[0], 2) + pow(x[1], 2))*cos(2*pi*x[1])", - "-sqrt(pow(x[0], 2) + pow(x[1], 2))*sin(2*pi*x[0])", - ), - degree=2, - ) - J = cashocs.IntegralFunctional( - Constant(0.5) * inner(u - u_d, u - u_d) * dx - + Constant(0.5 * alpha) * inner(c, c) * dx - ) - -As in :ref:`demo_monolithic_problems`, we then set up the optimization problem ``ocp`` and solve it -with the command :py:meth:`ocp.solve() ` :: - - ocp = cashocs.OptimalControlProblem(e, bcs, J, up, c, vq, config) - ocp.solve() - -For post processing, we then create deep copies of the single components of the state -and the adjoint variables with :: - - u, p = up.split(True) - v, q = vq.split(True) - -The result should look like this - -.. image:: /../../demos/documented/optimal_control/stokes/img_stokes.png diff --git a/docs/source/user/demos/optimal_control/index.rst b/docs/source/user/demos/optimal_control/index.rst new file mode 100644 index 00000000..385968b1 --- /dev/null +++ b/docs/source/user/demos/optimal_control/index.rst @@ -0,0 +1,28 @@ +Optimal Control Problems +======================== + +In this part of the tutorial, we investigate how optimal control problems can +be treated with cashocs. + +.. toctree:: + :maxdepth: 1 + :caption: List of all optimal control demos: + + demo_poisson.md + doc_config + demo_box_constraints.md + demo_neumann_control.md + demo_multiple_variables.md + demo_monolithic_problems.md + demo_picard_iteration.md + demo_stokes.md + demo_heat_equation.md + demo_nonlinear_pdes.md + demo_dirichlet_control.md + demo_iterative_solvers.md + demo_state_constraints.md + demo_sparse_control.md + demo_scalar_control_tracking.md + demo_constraints.md + demo_control_boundary_conditions.md + demo_pre_post_callbacks.md diff --git a/docs/source/user/demos/optimal_control/optimal_control_index.rst b/docs/source/user/demos/optimal_control/optimal_control_index.rst deleted file mode 100644 index 86e9be4d..00000000 --- a/docs/source/user/demos/optimal_control/optimal_control_index.rst +++ /dev/null @@ -1,28 +0,0 @@ -Optimal Control Problems -======================== - -In this part of the tutorial, we investigate how optimal control problems can -be treated with cashocs. - -.. toctree:: - :maxdepth: 1 - :caption: List of all optimal control demos: - - doc_poisson - doc_config - doc_box_constraints - doc_neumann_control - doc_multiple_variables - doc_monolithic_problems - doc_picard_iteration - doc_stokes - doc_heat_equation - doc_nonlinear_pdes - doc_dirichlet_control - doc_iterative_solvers - doc_state_constraints - doc_sparse_control - doc_scalar_control_tracking - doc_constraints - doc_control_boundary_conditions - doc_pre_post_hooks diff --git a/docs/source/user/demos/shape_optimization/doc_config.rst b/docs/source/user/demos/shape_optimization/doc_config.rst index 4543554b..c4cd4b30 100755 --- a/docs/source/user/demos/shape_optimization/doc_config.rst +++ b/docs/source/user/demos/shape_optimization/doc_config.rst @@ -42,7 +42,9 @@ The mesh section is, in contrast to the corresponding one for optimal control pr more important and also has more parameters. However, its primary importance is for remeshing, which we cover in :ref:`demo_remeshing`. -As first parameter, we have :: +As first parameter, we have + +.. code-block:: ini mesh_file = ./mesh/mesh.xdmf @@ -50,21 +52,25 @@ This specifies a path to a .xdmf file containing the discretized geometry. For a GMSH file using the command line command :py:func:`cashocs.convert`. Note, that the corresponding files for the boundaries and subdomains are generated -automatically with ``cashocs-convert``, and they will also be read by :py:func:`import_mesh ` +automatically with :py:func:`cashocs.convert`, and they will also be read by :py:func:`import_mesh ` if they are present. -The second parameter in the Mesh section, ``gmsh_file``, is defined via :: +The second parameter in the Mesh section, :ini:`gmsh_file`, is defined via + +.. code-block:: ini gmsh_file = ./mesh/mesh.msh This defines the path to the GMSH .msh file which was used to create the .xdmf file -specified in ``mesh_file``. As before, this parameter is only relevant for remeshing +specified in :ini:`mesh_file`. As before, this parameter is only relevant for remeshing purposes, and not needed otherwise. -The next parameter is ``geo_file``, which is the final file we need for remeshing ( +The next parameter is :ini:`geo_file`, which is the final file we need for remeshing ( and only there). It is also given by a path to a file, in this case to the GMSH .geo -file used to generate the ``gmsh_file``. It is specified, .e.g., as :: +file used to generate the :ini:`gmsh_file`. It is specified, .e.g., as + +.. code-block:: ini geo_file = ./mesh/mesh.geo @@ -72,21 +78,25 @@ file used to generate the ``gmsh_file``. It is specified, .e.g., as :: For a detailed discussion of how to use these parameters we refer to :ref:`demo_remeshing`. -Next up is a boolean flag that is used to indicate whether remeshing shall be performed :: +Next up is a boolean flag that is used to indicate whether remeshing shall be performed + +.. code-block:: ini remesh = False As the remeshing feature is experimental, we do advise to always try without -remeshing. Note, that by default this flag is set to ``False`` so that remeshing is disabled. +remeshing. Note, that by default this flag is set to :ini:`remehs = False` so that remeshing is disabled. + +Finally, we have the boolean flag :ini:`show_gmsh_output`, specified via -Finally, we have the boolean flag ``show_gmsh_output``, specified via :: +.. code-block:: ini show_gmsh_output = False This is used to toggle on / off the terminal output of GMSH when it performs a remeshing operation. This can be helpful for debugging purposes. By default, this -is set to ``False``. +is set to :ini:`show_gmsh_output = False`. As stated throughout the Mesh section, these parameters are optional most of the time, and are only really required for remeshing. You can safely leave them out of your config file, and you should not need them, unless you want to perform remeshing. @@ -100,53 +110,65 @@ Section StateSystem The StateSystem section is in complete analogy to :ref:`the corresponding one for optimal control problems `. For the sake of completeness, we briefly recall the parameters here, anyway. -The first parameter is ``is_linear``, and can be set as :: +The first parameter is :ini:`is_linear`, and can be set as + +.. code-block:: ini is_linear = True This is a boolean flag that indicates whether the state system is linear or not. -The default value for this parameter is ``False``, as every linear problem can also be +The default value for this parameter is :ini:`is_linear = False`, as every linear problem can also be interpreted as a nonlinear one. The next parameters are used to define the tolerances of the Newton solver, in -case a nonlinear state system has to be solved :: +case a nonlinear state system has to be solved + +.. code-block:: ini newton_rtol = 1e-11 newton_atol = 1e-13 -Here, ``newton_rtol`` sets the relative, and ``newton_atol`` the absolute tolerance -for Newton's method. Their default values are ``newton_rtol = 1e-11`` and -``newton_atol = 1e-13``. +Here, :ini:`newton_rtol` sets the relative, and :ini:`newton_atol` the absolute tolerance +for Newton's method. Their default values are :ini:`newton_rtol = 1e-11` and +:ini:`newton_atol = 1e-13`. The next parameter for the Newton iteration is the maximum number of iterations it -is allowed to perform before the iteration is cancelled. This is controlled via :: +is allowed to perform before the iteration is cancelled. This is controlled via + +.. code-block:: ini newton_iter = 50 -which defaults to ``newton_iter = 50``. +which defaults to :ini:`newton_iter = 50`. -The parameter ``newton_damped``, which is set via :: +The parameter :ini:`newton_damped`, which is set via + +.. code-block:: ini newton_damped = True is a boolean flag, indicating whether a damping strategy should be performed for the Newton method, or whether the classical Newton-Raphson iteration shall be used. This -defaults to ``False`` (as this is faster), but for some problems it might be beneficial to +defaults to :ini:`newton_damped = False` (as this is faster), but for some problems it might be beneficial to use damping in order to enhance the convergence of the nonlinear solver. -Additionally, we have the boolean parameter ``newton_inexact``, defined via :: +Additionally, we have the boolean parameter :ini:`newton_inexact`, defined via + +.. code-block:: ini newton_inexact = False -which sets up an inexact Newton method for solving nonlinear problems in case this is ``True``. The default is ``False``. +which sets up an inexact Newton method for solving nonlinear problems in case this is :ini:`newton_inexact = True`. The default is :ini:`newton_inexact = False`. -Next, we have the parameter :: +Next, we have the parameter + +.. code-block:: ini newton_verbose = False This is used to toggle the verbose output of the Newton method for the state system. -By default this is set to ``False`` so that there is not too much noise in the terminal. +By default this is set to :ini:`newton_verbose = False` so that there is not too much noise in the terminal. The upcoming parameters are used to define the behavior of a Picard iteration, that @@ -159,34 +181,42 @@ may be used if we have multiple variables. is written for optimal control problems, but the definition of the state system can be transferred analogously to shape optimization problems, too. -First, we have a boolean flag, set via :: +First, we have a boolean flag, set via + +.. code-block:: ini picard_iteration = False which determines whether the Picard iteration is enabled or not. This defaults -to ``picard_iteration = False``, so that the Picard solver is disabled by default. +to :ini:`picard_iteration = False`, so that the Picard solver is disabled by default. The following two parameters determine, analogously to above, the tolerances for the -Picard iteration :: +Picard iteration + +.. code-block:: ini picard_rtol = 1e-10 picard_atol = 1e-12 -The default values for these parameters are ``picard_rtol = 1e-10`` and -``picard_atol = 1e-12``. Moreover, note that the tolerances of the Newton solver are adjusted automatically in case +The default values for these parameters are :ini:`picard_rtol = 1e-10` and +:ini:`picard_atol = 1e-12`. Moreover, note that the tolerances of the Newton solver are adjusted automatically in case a Picard iteration is performedm, so that an inexact Picard iteration is used. -The maximum amout of iterations for the Picard iteration are set with :: +The maximum amout of iterations for the Picard iteration are set with + +.. code-block:: ini picard_iter = 10 -The default value for this is given by ``picard_iter = 50``. +The default value for this is given by :ini:`picard_iter = 50`. Finally, we can enable verbose output of the Picard iteration with the following -boolean flag :: +boolean flag + +.. code-block:: ini picard_verbose = False -which is set to ``False`` by default. +which is set to :ini:`picard_verbose = False` by default. .. _config_shape_optimization_routine: @@ -197,49 +227,61 @@ Section OptimizationRoutine The section OptimizationRoutine also closely resembles :ref:`the one for optimal control problems `. Again, we will take a brief look at all parameters here -The first parameter that can be controlled via the config file is ``algorithm``, which is -set via :: +The first parameter that can be controlled via the config file is :ini:`algorithm`, which is +set via + +.. code-block:: ini algorithm = lbfgs There are three possible choices for this parameter for shape optimization problems, namely -- ``gd`` or ``gradient_descent`` : A gradient descent method +- :ini:`algorithm = gd` or :ini:`algorithm = gradient_descent` : A gradient descent method + +- :ini:`algorithm = cg`, :ini:`algorithm = conjugate_gradient`, :ini:`algorithm = ncg`, :ini:`algorithm = nonlinear_cg` : Nonlinear CG methods -- ``cg``, ``conjugate_gradient``, ``ncg``, ``nonlinear_cg`` : Nonlinear CG methods +- :ini:`algorithm = lbfgs` or :ini:`algorithm = bfgs` : limited memory BFGS method. -- ``lbfgs`` or ``bfgs`` : limited memory BFGS method. +Thereafter, we specify the tolerances for the optimization algorithm with the parameters -Thereafter, we specify the tolerances for the optimization algorithm with the parameters :: +.. code-block:: ini rtol = 5e-3 atol = 0.0 -Again, ``rtol`` denotes the relative, and ``atol`` the absolute tolerance, and the -defaults for these parameters are given by ``rtol = 1e-3``, and ``atol = 0.0``. +Again, :ini:`rtol` denotes the relative, and :ini:`atol` the absolute tolerance, and the +defaults for these parameters are given by :ini:`rtol = 1e-3`, and :ini:`atol = 0.0`. The next parameter is used to control the maximum number of iterations performed by -the optimization algorithm. It is set via :: +the optimization algorithm. It is set via + +.. code-block:: ini maximum_iterations = 50 -and defaults to ``maximum_iterations = 100``. +and defaults to :ini:`maximum_iterations = 100`. -Next up, we have the initial guess for the step size, which can be determined via :: +Next up, we have the initial guess for the step size, which can be determined via + +.. code-block:: ini initial_stepsize = 1.0 -The default behavior is given by ``initial_stepsize = 1.0``. +The default behavior is given by :ini:`initial_stepsize = 1.0`. + +The next parameter is given by -The next parameter is given by :: +.. code-block:: ini safeguard_stepsize = True This parameter can be used to activate safeguarding of the initial stepsize for line search methods. This helps to choose an apropriate stepsize for the initial iteration even if the problem is poorly scaled. -The upcoming parameters are used for the Armijo rule :: +The upcoming parameters are used for the Armijo rule + +.. code-block:: ini epsilon_armijo = 1e-4 beta_armijo = 2 @@ -249,33 +291,39 @@ They are used to verify that the condition .. math:: J((I + t \mathcal{V})\Omega) \leq J(\Omega) + \varepsilon_{\text{Armijo}}\ t\ dJ(\Omega)[\mathcal{V}] holds, and if this is not satisfied, the stepsize is updated via :math:`t = \frac{t}{\beta_{\text{Armijo}}}`. -As default values for these parameters we use ``epsilon_armijo = 1e-4`` as well -as ``beta_armijo = 2``. +As default values for these parameters we use :ini:`epsilon_armijo = 1e-4` as well +as :ini:`beta_armijo = 2`. Next, we have a set of two parameters which detail the methods used for computing gradients in cashocs. -These parameters are :: +These parameters are + +.. code-block:: ini gradient_method = direct -as well as :: +as well as + +.. code-block:: ini gradient_tol = 1e-9 -The first parameter, ``gradient_method`` can be either ``direct`` or ``iterative``. In the former case, a +The first parameter, :ini:`gradient_method` can be either :ini:`gradient_method = direct` or :ini:`gradient_method = iterative`. In the former case, a direct solver is used to compute the gradient (using a Riesz projection) and in the latter case, an -iterative solver is used to do so. In case we have ``gradient_method = iterative``, the parameter -``gradient_tol`` is used to specify the (relative) tolerance for the iterative solver, in the other case +iterative solver is used to do so. In case we have :ini:`gradient_method = iterative`, the parameter +:ini:`gradient_tol` is used to specify the (relative) tolerance for the iterative solver, in the other case the parameter is not used. -The following parameter, ``soft_exit``, is a boolean flag which determines how -the optimization algorithm is terminated in case it does not converge. If ``soft_exit = True``, then an +The following parameter, :ini:`soft_exit`, is a boolean flag which determines how +the optimization algorithm is terminated in case it does not converge. If :ini:`soft_exit = True`, then an error message is printed, but code after the :py:meth:`solve ` call of the -optimization problem will still be executed. However, when ``soft_exit = False``, cashocs -raises an exception and terminates. This is set via :: +optimization problem will still be executed. However, when :ini:`soft_exit = False`, cashocs +raises an exception and terminates. This is set via + +.. code-block:: ini soft_exit = False -and is set to ``False`` by default. +and is set to :ini:`soft_exit = False` by default. .. _config_sop_linesearch: @@ -283,19 +331,25 @@ and is set to ``False`` by default. Section LineSearch ------------------ -In this section, parameters regarding the line search can be specified. The type of the line search can be chosen via the parameter :: +In this section, parameters regarding the line search can be specified. The type of the line search can be chosen via the parameter + +.. code-block:: ini method = armijo -Possible options are ``armijo``, which performs a simple backtracking line search based on the armijo rule with fixed steps (think of halving the stepsize in each iteration), and ``polynomial``, which uses polynomial models of the cost functional restricted to the line to generate "better" guesses for the stepsize. The default is ``armijo``. However, this will change in the future and users are encouraged to try the new polynomial line search models. +Possible options are :ini:`method = armijo`, which performs a simple backtracking line search based on the armijo rule with fixed steps (think of halving the stepsize in each iteration), and :ini:`method = polynomial`, which uses polynomial models of the cost functional restricted to the line to generate "better" guesses for the stepsize. The default is :ini:`method = armijo`. -The next parameter, ``polynomial_model``, specifies, which type of polynomials are used to generate new trial stepsizes. It is set via :: +The next parameter, :ini:`polynomial_model`, specifies, which type of polynomials are used to generate new trial stepsizes. It is set via + +.. code-block:: ini polynomial_model = cubic -The parameter can either be ``quadratic`` or ``cubic``. If this is ``quadratic``, a quadratic interpolation polynomial along the search direction is generated and this is minimized analytically to generate a new trial stepsize. Here, only the current function value, the direction derivative of the cost functional in direction of the search direction, and the most recent trial stepsize are used to generate the polynomial. In case that ``polynomial_model`` is chosen to be ``cubic``, the last two trial stepsizes (when available) are used in addition to the current cost functional value and the directional derivative, to generate a cubic model of the one-dimensional cost functional, which is then minimized to compute a new trial stepsize. +The parameter can either be :ini:`polynomial_model = quadratic` or :ini:`polynomial_model = cubic`. If this is :ini:`polynomial_model = quadratic`, a quadratic interpolation polynomial along the search direction is generated and this is minimized analytically to generate a new trial stepsize. Here, only the current function value, the direction derivative of the cost functional in direction of the search direction, and the most recent trial stepsize are used to generate the polynomial. In case that :ini:`polynomial_model = cubic`, the last two trial stepsizes (when available) are used in addition to the current cost functional value and the directional derivative, to generate a cubic model of the one-dimensional cost functional, which is then minimized to compute a new trial stepsize. + +For the polynomial models, we also have a safeguarding procedure, which ensures that trial stepsizes cannot be chosen too large or too small, and which can be configured with the following two parameters. The trial stepsizes generate by the polynomial models are projected to the interval :math:`[\beta_{low} \alpha, \beta_{high} \alpha]`, where :math:`\alpha` is the previous trial stepsize and :math:`\beta_{low}, \beta_{high}` are factors which can be set via the parameters :ini:`factor_low` and :ini:`factor_high`. In the config file, this can look like this -For the polynomial models, we also have a safeguarding procedure, which ensures that trial stepsizes cannot be chosen too large or too small, and which can be configured with the following two parameters. The trial stepsizes generate by the polynomial models are projected to the interval :math:`[\beta_{low} \alpha, \beta_{high} \alpha]`, where :math:`\alpha` is the previous trial stepsize and :math:`\beta_{low}, \beta_{high}` are factors which can be set via the parameters ``factor_low`` and ``factor_high``. In the config file, this can look like this :: +.. code-block:: ini factor_high = 0.5 factor_low = 0.1 @@ -311,15 +365,19 @@ Next, we discuss the parameters relevant for the limited memory BFGS method. For regarding this method, we refer to `Schulz, Siebenborn, and Welker, Efficient PDE Constrained Shape Optimization Based on Steklov-Poincaré-Type Metrics `_, where the methods are introduced. -The first parameter, ``bfgs_memory_size``, determines how large the storage of the BFGS method is. It is set via :: +The first parameter, :ini:`bfgs_memory_size`, determines how large the storage of the BFGS method is. It is set via + +.. code-block:: ini bfgs_memory_size = 3 Usually, a higher storage leads to a better Hessian approximation, and thus to faster convergence. However, this also leads to an increased memory usage. Typically, values -below 5 already work very well. The default is ``bfgs_memory_size = 5``. +below 5 already work very well. The default is :ini:`bfgs_memory_size = 5`. -The other parameter for the BFGS method is :: +The other parameter for the BFGS method is + +.. code-block:: ini use_bfgs_scaling = True @@ -327,11 +385,13 @@ This determines, whether one should use a scaling of the initial Hessian approxi (see `Nocedal and Wright, Numerical Optimization `_). This is usually very beneficial and should be kept enabled (which is the default). -Third, we have the parameter ``bfgs_periodic_restart``, which is set in the line :: +Third, we have the parameter :ini:`bfgs_periodic_restart`, which is set in the line + +.. code-block:: ini bfgs_periodic_restart = 0 -This is a non-negative integer value, which indicates the number of BFGS iterations, before a reinitialization takes place. In case that this is ``0`` (which is the default), no restarts are performed. +This is a non-negative integer value, which indicates the number of BFGS iterations, before a reinitialization takes place. In case that this is :ini:`bfgs_periodic_restart = 0` (which is the default), no restarts are performed. .. _config_shape_algocg: @@ -343,50 +403,60 @@ gradient methods for shape optimization. For more details on this, we refer to t preprint `Blauth, Nonlinear Conjugate Gradient Methods for PDE Constrained Shape Optimization Based on Steklov-Poincaré-Type Metrics `_. -First, we define which nonlinear CG method is used by :: +First, we define which nonlinear CG method is used by + +.. code-block:: ini cg_method = DY Available options are -- ``FR`` : The Fletcher-Reeves method +- :ini:`cg_method = FR` : The Fletcher-Reeves method + +- :ini:`cg_method = PR` : The Polak-Ribiere method -- ``PR`` : The Polak-Ribiere method +- :ini:`cg_method = HS` : The Hestenes-Stiefel method -- ``HS`` : The Hestenes-Stiefel method +- :ini:`cg_method = DY` : The Dai-Yuan method -- ``DY`` : The Dai-Yuan method +- :ini:`cg_method = HZ` : The Hager-Zhang method -- ``HZ`` : The Hager-Zhang method +The default value is :ini:`cg_method = FR`. As for optimal control problems, the subsequent parameters are used to define the +restart behavior of the nonlinear CG methods. First, we have -The default value is ``cg_method = FR``. As for optimal control problems, the subsequent parameters are used to define the -restart behavior of the nonlinear CG methods. First, we have :: +.. code-block:: ini cg_periodic_restart = False This boolean flag en- or disables that the NCG methods are restarted after a fixed -amount of iterations, which is specified via :: +amount of iterations, which is specified via + +.. code-block:: ini cg_periodic_its = 5 -i.e., if ``cg_periodic_restart = True`` and ``cg_periodic_its = n``, then the NCG method -is restarted every ``n`` iterations. The default behavior is given by -``cg_periodic_restart = False`` and ``cg_periodic_its = 10``. +i.e., if :ini:`cg_periodic_restart = True` and :ini:`cg_periodic_its = n`, then the NCG method +is restarted every :math:`n` iterations. The default behavior is given by +:ini:`cg_periodic_restart = False` and :ini:`cg_periodic_its = 10`. Alternatively, there also exists a relative restart criterion (see `Nocedal and Wright, Numerical Optimization `_), which can be enabled -via the boolean flag ``cg_relative_restart``, which is defined in the line :: +via the boolean flag :ini:`cg_relative_restart`, which is defined in the line + +.. code-block:: ini cg_relative_restart = False -and the corresponding restart tolerance is set in :: +and the corresponding restart tolerance is set in + +.. code-block:: ini cg_restart_tol = 0.5 -Note, that ``cg_restart_tol`` should be in :math:`(0, 1)`. If two subsequent +Note, that :ini:`cg_restart_tol` should be in :math:`(0, 1)`. If two subsequent gradients generated by the nonlinear CG method are not "sufficiently orthogonal", the method is restarted with a gradient step. The default behavior -is given by ``cg_relative_restart = False`` and ``cg_restart_tol = 0.25``. +is given by :ini:`cg_relative_restart = False` and :ini:`cg_restart_tol = 0.25`. .. _config_shape_shape_gradient: @@ -453,40 +523,50 @@ are fixed and deformable via their markers, which are either defined in the corresponding python script, or in the GMSH file, if such a mesh is imported. The config file for :ref:`demo_shape_poisson` defines the deformable boundaries -with the command :: +with the command + +.. code-block:: ini shape_bdry_def = [1] .. note:: - Remember, that in :ref:`demo_shape_poisson`, we defined ``boundaries`` with the commands :: + Remember, that in :ref:`demo_shape_poisson`, we defined :python:`boundaries` with the commands + + .. code-block:: ini boundary = CompiledSubDomain('on_boundary') boundaries = MeshFunction('size_t', mesh, dim=1) boundary.mark(boundaries, 1) - Hence, we see that the marker ``1`` corresponds to the entire boundary, so that this + Hence, we see that the marker :python:`1` corresponds to the entire boundary, so that this is set to being deformable through the config. As we do not have a fixed boundary for this problem, the corresponding list -for the fixed boundaries is empty :: +for the fixed boundaries is empty + +.. code-block:: ini shape_bdry_fix = [] Note, that cashocs also gives you the possibility of defining partially constrainted boundaries, where only one axial component is fixed, whereas the other two are -not. These are defined in :: +not. These are defined in + +.. code-block:: ini shape_bdry_fix_x = [] shape_bdry_fix_y = [] shape_bdry_fix_z = [] -For these, we have that ``shape_bdry_fix_x`` is a list of all markers whose corresponding +For these, we have that :ini:`shape_bdry_fix_x` is a list of all markers whose corresponding boundaries should not be deformable in x-direction, but can be deformed in the y- and z-directions. Of course you can constrain a boundary to be only variable in a single direction by adding the markers to the remaining lists. -The next parameter is specified via :: +The next parameter is specified via + +.. code-block:: ini use_pull_back = True @@ -496,7 +576,7 @@ enabled by default. .. warning:: - This parameter should always be set to ``True``, otherwise the shape derivative might + This parameter should always be set to :ini:`use_pull_back = True`, otherwise the shape derivative might be wrong. Only disable it when you are sure what you are doing. Furthermore, note that the material derivative computation is only correct, @@ -507,45 +587,55 @@ enabled by default. .. note:: See :ref:`demo_inverse_tomography` for a case, where we use - ``use_pull_back = False``. + :ini:`use_pull_back = False`. The next parameters determine the coefficients of the bilinear form :math:`a`. -First, we have the first Lamé parameter :math:`\lambda`, which is set via :: +First, we have the first Lamé parameter :math:`\lambda`, which is set via + +.. code-block:: ini lambda_lame = 1.428571428571429 -The default value for this is ``lambda_lame = 0.0``. +The default value for this is :ini:`lambda_lame = 0.0`. + +Next, we specify the damping parameter :math:`\delta` with the line -Next, we specify the damping parameter :math:`\delta` with the line :: +.. code-block:: ini damping_factor = 0.2 -The default for this is ``damping_factor = 0.0``. +The default for this is :ini:`damping_factor = 0.0`. .. note:: - As the default value for the damping factor is ``damping_factor = 0.0``, this + As the default value for the damping factor is :ini:`damping_factor = 0.0`, this should be set to a positive value in case the entire boundary of a problem is deformable. Otherwise, the Riesz identification problem for the shape gradient is not well-posed. Finally, we define the values for :math:`\mu_\text{def}` and :math:`\mu_\text{fix}` -via :: +via + +.. code-block:: ini mu_fix = 0.35714285714285715 mu_def = 0.35714285714285715 -The default behavior is given by ``mu_fix = 1.0`` and ``mu_def = 1.0``. +The default behavior is given by :ini:`mu_fix = 1.0` and :ini:`mu_def = 1.0`. -The parameter ``use_sqrt_mu`` is a boolean flag, which switches between using +The parameter :ini:`use_sqrt_mu` is a boolean flag, which switches between using :math:`\mu` and :math:`\sqrt{\mu}` as the stiffness for the linear elasticity -equations, as discussed above. This is set via :: +equations, as discussed above. This is set via + +.. code-block:: ini use_sqrt_mu = False -and the default value is ``use_sqrt_mu = False``. +and the default value is :ini:`use_sqrt_mu = False`. + +The next line in the config file is -The next line in the config file is :: +.. code-block:: ini inhomogeneous = False @@ -559,39 +649,47 @@ more details on this approach, we refer to the paper `Blauth, Leithäuser, and P Model Hierarchy for the Shape Optimization of a Microchannel Cooling System `_. -Moreover, the parameter :: +Moreover, the parameter + +.. code-block:: ini update_inhomogeneous = False -can be used to update the local mesh size after each mesh deformation, in case this is ``True``, so that elements which become smaller also obtain a higher stiffness and vice versa. The default is ``False``. +can be used to update the local mesh size after each mesh deformation, in case this is :ini:`update_inhomogeneous = True`, so that elements which become smaller also obtain a higher stiffness and vice versa. The default is :ini:`update_inhomogeneous = False`. There is also a different possibility to define the stiffness parameter :math:`\mu` using cashocs, namely to define :math:`\mu` in terms of how close a point of the computational domain is to a boundary. In the following we will explain this alternative way of defining :math:`\mu`. -To do so, we must first set the boolean parameter :: +To do so, we must first set the boolean parameter + +.. code-block:: ini use_distance_mu = True which enables this formulation and deactivates the previous one. Note that by default, -the value of ``use_distance_mu`` is ``False``. Next, we have the parameters ``dist_min``, ``dist_max``, -``mu_min`` and ``mu_max``. These do the following: If the distance to the boundary is -smaller than ``dist_min``, the value of :math:`\mu` is set to ``mu_min``, and if the distance -to the boundary is larger than ``dist_max``, :math:`\mu` is set to ``mu_max``. If the distance -to the boundary is between ``dist_min`` and ``dist_max``, the value of :math:`\mu` is -interpolated between ``mu_min`` and ``mu_max``. The type of this interpolation is -determined by the parameter :: +the value is :ini:`use_distance_mu = False`. Next, we have the parameters :ini:`dist_min`, :ini:`dist_max`, +:ini:`mu_min` and :ini:`mu_max`. These do the following: If the distance to the boundary is +smaller than :ini:`dist_min`, the value of :math:`\mu` is set to :ini:`mu_min`, and if the distance +to the boundary is larger than :ini:`dist_max`, :math:`\mu` is set to :ini:`mu_max`. If the distance +to the boundary is between :ini:`dist_min` and :ini:`dist_max`, the value of :math:`\mu` is +interpolated between :ini:`mu_min` and :ini:`mu_max`. The type of this interpolation is +determined by the parameter + +.. code-block:: ini smooth_mu = True -If this parameter is set to ``True``, then a smooth, cubic polynomial is used to -interplate between ``mu_min`` and ``mu_max``, which yields a continuously differentiable -:math:`\mu`. If this is set to ``False``, then a linear interpolation is used, which only yields -a continuous :math:`\mu`. The default for this parameter is ``False``. +If this parameter is set to :ini:`smooth_mu = True`, then a smooth, cubic polynomial is used to +interplate between :ini:`mu_min` and :ini:`mu_max`, which yields a continuously differentiable +:math:`\mu`. If this is set to :ini:`smooth_mu = False`, then a linear interpolation is used, which only yields +a continuous :math:`\mu`. The default for this parameter is :ini:`smooth_mu = False`. Finally, we can specify which boundaries we want to incorporate when computing the distance. To do so, we can specify a list of indices which contain the boundary -markers in the parameter :: +markers in the parameter + +.. code-block:: ini boundaries_dist = [1,2,3] @@ -599,29 +697,37 @@ This means, that only boundaries marked with 1, 2, and 3 are considered for comp the distance, and all others are ignored. The default behavior is that all (outer) boundaries are considered. -There is also another possibility to compute the shape gradient in cashocs, namely using the :math:`p`-Laplacian, as proposed by `Müller, Kühl, Siebenborn, Deckelnick, Hinze, and Rung `_. In order to do so, we have the following line :: +There is also another possibility to compute the shape gradient in cashocs, namely using the :math:`p`-Laplacian, as proposed by `Müller, Kühl, Siebenborn, Deckelnick, Hinze, and Rung `_. In order to do so, we have the following line + +.. code-block:: ini use_p_laplacian = False -If this is set to ``True``, the :math:`p`-Laplacian is used to compute the shape gradient, as explained in :ref:`demo_p_laplacian`. However, by default this is disabled. -The value of :math:`p` which is then used is defined in the next line :: +If this is set to :ini:`use_p_laplacian = True`, the :math:`p`-Laplacian is used to compute the shape gradient, as explained in :ref:`demo_p_laplacian`. However, by default this is disabled. +The value of :math:`p` which is then used is defined in the next line + +.. code-block:: ini p_laplacian_power = 6 -which defaults to ``2``, whenever the parameter is not defined. The higher :math:`p` is chosen, the better the numerical are expected to be, but the numerical solution of the problem becomes more involved. +which defaults to :ini:`p_laplacian_power = 2`, whenever the parameter is not defined. The higher :math:`p` is chosen, the better the numerical are expected to be, but the numerical solution of the problem becomes more involved. + +Finally, there is the possibility to use a stabilized weak form for the :math:`p`-Laplacian operator, where the stabilization parameter can be defined in the line -Finally, there is the possibility to use a stabilized weak form for the :math:`p`-Laplacian operator, where the stabilization parameter can be defined in the line :: +.. code-block:: ini p_laplacian_stabilization = 0.0 -The default value of this parameter is ``0.0``. Note, that the parameter should be chosen comparatively small, i.e., significantly smaller than ``1.0``. +The default value of this parameter is :ini:`p_laplacian_stabilization = 0.0`. Note, that the parameter should be chosen comparatively small, i.e., significantly smaller than 1. + +Furthermore, we have the parameter :ini:`fixed_dimensions`, which enables us to restrict the shape gradient to specific dimensions. It is set via -Furthermore, we have the parameter ``fixed_dimensions``, which enables us to restrict the shape gradient to specific dimensions. It is set via :: +.. code-block:: ini fixed_dimensions = [] -In case ``fixed_dimensions == []``, there is no restriction on the shape gradient. However, if ``fixed_dimensions == [i]``, then the ``i``-th component of the shape gradient is set to 0, so that we have no deformation in the ``i``-th coordinate direction. For example, if ``fixed_dimensions == [0, 2]``, we only have a deformation in the ``y``-component of the mesh. The default is ``fixed_dimensions = []``. +In case :ini:`fixed_dimensions = []`, there is no restriction on the shape gradient. However, if :ini:`fixed_dimensions = [i]`, then the i-th component of the shape gradient is set to 0, so that we have no deformation in the i-th coordinate direction. For example, if :ini:`fixed_dimensions = [0, 2]`, we only have a deformation in the :math:`y`-component of the mesh. The default is :ini:`fixed_dimensions = []`. .. _config_shape_regularization: @@ -631,8 +737,10 @@ Section Regularization In this section, the parameters for shape regularizations are specified. For a detailed discussion of their usage, we refer to :ref:`demo_regularization`. -First, we have the parameters ``factor_volume`` and ``target_volume``. These are set -via the lines :: +First, we have the parameters :ini:`factor_volume` and :ini:`target_volume`. These are set +via the lines + +.. code-block:: ini factor_volume = 0.0 target_volume = 3.14 @@ -643,22 +751,26 @@ They are used to implement the (target) volume regularization term \frac{\mu_\text{vol}}{2} \left( \int_{\Omega} 1 \text{ d}x - \text{vol}_\text{des} \right)^2 -Here, :math:`\mu_\text{vol}` is specified via ``factor_volume``, and :math:`\text{vol}_\text{des}` -is the target volume, specified via ``target_volume``. The default behavior is -``factor_volume = 0.0`` and ``target_volume = 0.0``, so that we do not have +Here, :math:`\mu_\text{vol}` is specified via :ini:`factor_volume`, and :math:`\text{vol}_\text{des}` +is the target volume, specified via :ini:`target_volume`. The default behavior is +:ini:`factor_volume = 0.0` and :ini:`target_volume = 0.0`, so that we do not have a volume regularization. -The next line, i.e., :: +The next line, i.e., + +.. code-block:: ini use_initial_volume = True -determines the boolean flag ``use_initial_volume``. If this is set to ``True``, -then not the value given in ``target_volume`` is used, but instead the +determines the boolean flag :ini:`use_initial_volume`. If this is set to :ini:`use_initial_volume = True`, +then not the value given in :ini:`target_volume` is used, but instead the volume of the initial geometry is used for :math:`\text{vol}_\text{des}`. For the next two _typing of regularization, namely the (target) surface and (target) barycenter regularization, the syntax for specifying the parameters is completely -analogous. For the (target) surface regularization we have :: +analogous. For the (target) surface regularization we have + +.. code-block:: ini factor_surface = 0.0 target_surface = 1.0 @@ -669,19 +781,23 @@ These parameter are used to implement the regularization term \frac{\mu_\text{surf}}{2} \left( \int_{\Gamma} 1 \text{ d}s - \text{surf}_\text{des} \right)^2 -Here, :math:`\mu_\text{surf}` is determined via ``factor_surface``, and -:math:`\text{surf}_\text{des}` is determined via ``target_surface``. The default -values are given by ``factor_surface = 0.0`` and ``target_surface = 0.0``. +Here, :math:`\mu_\text{surf}` is determined via :ini:`factor_surface`, and +:math:`\text{surf}_\text{des}` is determined via :ini:`target_surface`. The default +values are given by :ini:`factor_surface = 0.0` and :ini:`target_surface = 0.0`. + +As for the volume regularization, the parameter -As for the volume regularization, the parameter :: +.. code-block:: ini use_initial_surface = True -determines whether the target surface area is specified via ``target_surface`` +determines whether the target surface area is specified via :ini:`target_surface` or if the surface area of the initial geometry should be used instead. The default -behavior is given by ``use_initial_surface = False``. +behavior is given by :ini:`use_initial_surface = False`. + +Next, we have the curvature regularization, which is controlled by the parameter -Next, we have the curvature regularization, which is controlled by the parameter :: +.. code-block:: ini factor_curvature = 0.0 @@ -696,7 +812,9 @@ where :math:`\kappa` denotes the mean curvature. This regularization term can be used to generate more smooth boundaries and to prevent kinks from occurring. Finally, we have the (target) barycenter regularization. This is specified via -the parameters :: +the parameters + +.. code-block:: ini factor_barycenter = 0.0 target_barycenter = [0.0, 0.0, 0.0] @@ -707,32 +825,36 @@ and implements the term \frac{\mu_\text{bary}}{2} \left\lvert \frac{1}{\text{vol}(\Omega)} \int_\Omega x \text{ d}x - \text{bary}_\text{des} \right\rvert^2 -The default behavior is given by ``factor_barycenter = 0.0`` and ``target_barycenter = [0,0,0]``, +The default behavior is given by :ini:`factor_barycenter = 0.0` and :ini:`target_barycenter = [0,0,0]`, so that we do not have a barycenter regularization. -The flag :: +The flag + +.. code-block:: ini use_initial_barycenter = True -again determines, whether :math:`\text{bary}_\text{des}` is determined via ``target_barycenter`` +again determines, whether :math:`\text{bary}_\text{des}` is determined via :ini:`target_barycenter` or if the barycenter of the initial geometry should be used instead. The default behavior -is given by ``use_initial_barycenter = False``. +is given by :ini:`use_initial_barycenter = False`. .. hint:: - The object ``target_barycenter`` has to be a list. For 2D problems it is also + The object :ini:`target_barycenter` has to be a list. For 2D problems it is also sufficient, if the list only has two entries, for the :math:`x` and :math:`y` barycenters. -Finally, we have the parameter ``use_relative_scaling`` which is set in the line :: +Finally, we have the parameter :ini:`use_relative_scaling` which is set in the line + +.. code-block:: ini use_relative_scaling = False This boolean flag does the following. For some regularization term :math:`J_\text{reg}(\Omega)` with corresponding -factor :math:`\mu` (as defined above), the default behavior is given by ``use_relative_scaling = False`` +factor :math:`\mu` (as defined above), the default behavior is given by :ini:`use_relative_scaling = False` adds the term :math:`\mu J_\text{reg}(\Omega)` to the cost functional, so that the factor specified in the configuration file is actually used as the factor for the regularization term. -In case ``use_relative_scaling = True``, the behavior is different, and the following term is +In case :ini:`use_relative_scaling = True`, the behavior is different, and the following term is added to the cost functional: :math:`\frac{\mu}{\left\lvert J_\text{reg}(\Omega_0) \right\rvert} J_\text{reg}(\Omega)`, where :math:`\Omega_0` is the initial guess for the geometry. In particular, this means that the magnitude of the regularization term is equal to :math:`\mu` on the initial geometry. @@ -745,7 +867,9 @@ Section MeshQuality ------------------- This section details the parameters that influence the quality of the -computational mesh. First, we have the lines :: +computational mesh. First, we have the lines + +.. code-block:: ini volume_change = inf angle_change = inf @@ -761,15 +885,17 @@ First and Second Order Shape Optimization Based on Restricted Mesh Deformations \frac{1}{\alpha} &\leq \det\left( \text{id} + D\mathcal{V} \right) \leq \alpha \\ \left\lvert\left\lvert D\mathcal{V} \right\rvert\right\rvert_{F} &\leq \beta. -Here, :math:`\alpha` corresponds to ``volume_change`` and :math:`\beta` corresponds -to ``angle_change``, and :math:`\mathcal{V}` is the deformation. The default behavior -is given by ``volume_change = inf`` and ``angle_change = inf``, so that no restrictions +Here, :math:`\alpha` corresponds to :ini:`volume_change` and :math:`\beta` corresponds +to :ini:`angle_change`, and :math:`\mathcal{V}` is the deformation. The default behavior +is given by :ini:`volume_change = inf` and :ini:`angle_change = inf`, so that no restrictions are posed. Note, that, e.g., `Etling, Herzog, Loayza, Wachsmuth, First and Second Order Shape Optimization Based on Restricted Mesh Deformations -`_ use the values ``volume_change = 2.0`` and -``angle_change = 0.3``. +`_ use the values :ini:`volume_change = 2.0` and +:ini:`angle_change = 0.3`. + +The next two parameters are given byx -The next two parameters are given by :: +.. code-block:: ini tol_lower = 0.0 tol_upper = 1e-15 @@ -794,38 +920,42 @@ in :math:`[0,1]`): quality is assumed to be so poor, that even the solution of the state system is not possible anymore. In practice, this can only happen during the Armijo line search. Thanks to our previous considerations, we also know that the mesh, that is - to be deformed, has at least a quality of ``tol_lupper``, so that this quality + to be deformed, has at least a quality of :ini:`tol_lupper`, so that this quality might be reached again, if the step size is just decreased sufficiently often. This way, it is ensured that the state system is only solved when the mesh quality - is larger than ``tol_lower``, so that the corresponding cost functional value is + is larger than :ini:`tol_lower`, so that the corresponding cost functional value is reasonable. -The default behavior is given by ``tol_lower = 0.0`` and ``tol_upper = 1e-15``, +The default behavior is given by :ini:`tol_lower = 0.0` and :ini:`tol_upper = 1e-15`, so that there are basically no requirements on the mesh quality. Finally, the upcoming two parameters specify how exactly the mesh quality is measured. -The first one is :: +The first one is + +.. code-block:: ini measure = condition_number and determines one of the four mesh quality criteria, as defined in :py:class:`MeshQuality `. Available options are -- ``skewness`` -- ``maximum_angle`` -- ``radius_ratios`` -- ``condition_number`` +- :ini:`measure = skewness` +- :ini:`measure = maximum_angle` +- :ini:`measure = radius_ratios` +- :ini:`measure = condition_number` (see :py:class:`MeshQuality ` for a detailed description). -The default value is given by ``measure = skewness``. +The default value is given by :ini:`measure = skewness`. + +Finally, the parameter :ini:`type` determines, whether the minimum quality over all +elements (:ini:`type = min`) or the average quality over all elements (:ini:`type = avg`) +shall be used. This is set via -Finally, the parameter ``type`` determines, whether the minimum quality over all -elements (``type = min``) or the average quality over all elements (``type = avg``) -shall be used. This is set via :: +.. code-block:: ini type = min -and defaults to ``type = min``. +and defaults to :ini:`type = min`. .. _config_shape_output: @@ -833,73 +963,93 @@ Section Output -------------- In this section, the parameters for the output of the algorithm, either in the terminal -or as files, are specified. First, we have the parameter ``verbose``. This is used to toggle the output of the -optimization algorithm. It defaults to ``True`` and is controlled via :: +or as files, are specified. First, we have the parameter :ini:`verbose`. This is used to toggle the output of the +optimization algorithm. It defaults to :ini:`verbose = True` and is controlled via + +.. code-block:: ini verbose = True -The parameter ``save_results`` is a boolean flag, which determines whether a history +The parameter :ini:`save_results` is a boolean flag, which determines whether a history of the optimization algorithm, including cost functional value, gradient norm, accepted -step sizes, and mesh quality, shall be saved to a .json file. This defaults to ``True``, -and can be set with :: +step sizes, and mesh quality, shall be saved to a .json file. This defaults to :ini:`save_results = True`, +and can be set with + +.. code-block:: ini save_results = False -Moreover, we define the parameter ``save_txt`` :: +Moreover, we define the parameter :ini:`save_txt` + +.. code-block:: ini save_txt = False This saves the output of the optimization, which is usually shown in the terminal, to a .txt file, which is human-readable. -The next line in the config file is :: +The next line in the config file is + +.. code-block:: ini save_state = False -Here, the parameter ``save_state`` is set. This is a boolean flag, which can be set to -``True`` to enable that cashocs generates .xdmf files for the state variables for each iteration the optimization algorithm performs. These are great for visualizing the +Here, the parameter :ini:`save_state` is set. This is a boolean flag, which can be set to +:ini:`save_state = True` to enable that cashocs generates .xdmf files for the state variables for each iteration the optimization algorithm performs. These are great for visualizing the steps done by the optimization algorithm, but also need some disc space, so that they are disabled by default. Note, that for visualizing these files, you need `Paraview `_. -The next parameter, ``save_adjoint`` works analogously, and is given in the line :: +The next parameter, :ini:`save_adjoint` works analogously, and is given in the line + +.. code-block:: ini save_adjoint = False If this is set to True, cashocs generates .xdmf files for the adjoint variables in each iteration of the optimization algorithm. Its main purpose is for debugging. -The next parameter is given by ``save_gradient``, which is given in the line :: +The next parameter is given by :ini:`save_gradient`, which is given in the line + +.. code-block:: ini save_gradient = False This boolean flag ensures that a paraview with the computed shape gradient is saved in ``result_dir/xdmf``. The main purpose of this is for debugging. -Moreover, we also have the parameter ``save_mesh`` that is set via :: +Moreover, we also have the parameter :ini:`save_mesh` that is set via + +.. code-block:: ini save_mesh = False This is used to save the optimized geometry to a GMSH file. The default behavior -is given by ``save_mesh = False``. Note, that this is only +is given by :ini:`save_mesh = False`. Note, that this is only possible if the input mesh was already generated by GMSH, and specified in :ref:`the Mesh section of the config file `. For any other meshes, the underlying mesh is also saved in the .xdmf files, so that you can at least always visualize the optimized geometry. In the end, we also have, like for optimal control problems, a parameter that specifies -where the output is placed, again named ``result_dir``, which is given in the config file -in the line :: +where the output is placed, again named :ini:`result_dir`, which is given in the config file +in the line + +.. code-block:: ini result_dir = ./results As before, this is either a relative or absolute path to the directory where the results should be placed. -The parameter ``precision``, which is set via :: +The parameter :ini:`precision`, which is set via + +.. code-block:: ini precision = 3 is an integer parameter which determines how many significant digits are printed in the output to the console and / or the result file. -Moreover, we have the parameter ``time_suffix``, which adds a suffix to the result directory based on the current time. It is controlled by the line :: +Moreover, we have the parameter :ini:`time_suffix`, which adds a suffix to the result directory based on the current time. It is controlled by the line + +.. code-block:: ini time_suffix = False @@ -920,24 +1070,18 @@ in the following. .. list-table:: :header-rows: 1 - * - Parameters - - Default value + * - Parameter = Default value - Remarks - * - mesh_file - - + * - :ini:`mesh_file` - Only needed for remeshing - * - gmsh_file - - + * - :ini:`gmsh_file` - Only needed for remeshing - * - geo_file - - + * - :ini:`geo_file` - Only needed for remeshing - * - remesh - - ``False`` - - if ``True``, remeshing is enabled; this feature is experimental, use with care - * - show_gmsh_output - - ``False`` - - if ``True``, shows the output of GMSH during remeshing in the console + * - :ini:`remesh = False` + - if :ini:`remesh = True`, remeshing is enabled; this feature is experimental, use with care + * - :ini:`show_gmsh_output = False` + - if :ini:`show_gmsh_output = True`, shows the output of GMSH during remeshing in the console @@ -947,46 +1091,33 @@ in the following. .. list-table:: :header-rows: 1 - * - Parameter - - Default value + * - Parameter = Default value - Remarks - * - is_linear - - ``False`` - - using ``True`` gives an error for nonlinear problems - * - newton_rtol - - ``1e-11`` + * - :ini:`is_linear = False` + - using :ini:`is_linear = True` gives an error for nonlinear problems + * - :ini:`newton_rtol = 1e-11` - relative tolerance for Newton's method - * - newton_atol - - ``1e-13`` + * - :ini:`newton_atol = 1e-13` - absolute tolerance for Newton's method - * - newton_iter - - ``50`` + * - :ini:`newton_iter = 50` - maximum iterations for Newton's method - * - newton_damped - - ``False`` - - if ``True``, damping is enabled - * - newton_inexact - - ``False`` - - if ``True``, an inexact Newton's method is used - * - newton_verbose - - ``False`` - - ``True`` enables verbose output of Newton's method - * - picard_iteration - - ``False`` - - ``True`` enables Picard iteration; only has an effect for multiple + * - :ini:`newton_damped = False` + - if :ini:`newton_damped = True`, damping is enabled + * - :ini:`newton_inexact = False` + - if :ini:`newton_inexact = True`, an inexact Newton's method is used + * - :ini:`newton_verbose = False` + - :ini:`newton_verbose = True` enables verbose output of Newton's method + * - :ini:`picard_iteration = False` + - :ini:`picard_iteration = True` enables Picard iteration; only has an effect for multiple variables - * - picard_rtol - - ``1e-10`` + * - :ini:`picard_rtol = 1e-10` - relative tolerance for Picard iteration - * - picard_atol - - ``1e-12`` + * - :ini:`picard_atol = 1e-12` - absolute tolerance for Picard iteration - * - picard_iter - - ``50`` + * - :ini:`picard_iter = 50` - maximum iterations for Picard iteration - * - picard_verbose - - ``False`` - - ``True`` enables verbose output of Picard iteration + * - :ini:`picard_verbose = False` + - :ini:`picard_verbose = True` enables verbose output of Picard iteration @@ -996,39 +1127,22 @@ in the following. .. list-table:: :header-rows: 1 - * - Parameter - - Default value + * - Parameter = Default value - Remarks - * - algorithm - - + * - :ini:`algorithm` - has to be specified by the user; see :py:meth:`solve ` - * - rtol - - ``1e-3`` + * - :ini:`rtol = 1e-3` - relative tolerance for the optimization algorithm - * - atol - - ``0.0`` + * - :ini:`atol = 0.0` - absolute tolerance for the optimization algorithm - * - maximum iterations - - ``100`` + * - :ini:`maximum_iterations = 100` - maximum iterations for the optimization algorithm - * - initial_stepsize - - ``1.0`` - - initial stepsize for the first iteration in the Armijo rule - * - epsilon_armijo - - ``1e-4`` - - - * - beta_armijo - - ``2.0`` - - - * - gradient_method - - ``direct`` - - specifies the solver for computing the gradient, can be either ``direct`` or ``iterative`` - * - gradient_tol - - ``1e-9`` + * - :ini:`gradient_method = direct` + - specifies the solver for computing the gradient, can be either :ini:`gradient_method = direct` or :ini:`gradient_method = iterative` + * - :ini:`gradient_tol = 1e-9` - the relative tolerance in case an iterative solver is used to compute the gradient. - * - soft_exit - - ``False`` - - if ``True``, the optimization algorithm does not raise an exception if + * - :ini:`soft_exit = False` + - if :ini:`soft_exit = True`, the optimization algorithm does not raise an exception if it did not converge @@ -1038,32 +1152,23 @@ in the following. .. list-table:: :header-rows: 1 - * - Parameter - - Default value + * - Parameter = Default value - Remarks - * - method - - ``armijo`` - - ``armijo`` is a simple backtracking line search, whereas ``polynomial`` uses polynomial models to compute trial stepsizes. - * - initial_stepsize - - ``1.0`` + * - :ini:`method = armijo` + - :ini:`method = armijo` is a simple backtracking line search, whereas :ini:`method = polynomial` uses polynomial models to compute trial stepsizes. + * - :ini:`initial_stepsize = 1.0` - initial stepsize for the first iteration in the Armijo rule - * - epsilon_armijo - - ``1e-4`` + * - :ini:`epsilon_armijo = 1e-4` - - * - beta_armijo - - ``2.0`` + * - :ini:`beta_armijo = 2.0` - - * - safeguard_stepsize - - ``True`` + * - :ini:`safeguard_stepsize = True` - De(-activates) a safeguard against poor scaling - * - polynomial_model - - ``cubic`` - - This specifies, whether a ``cubic`` or ``quadratic`` model is used for computing trial stepsizes - * - factor_high - - ``0.5`` + * - :ini:`polynomial_model = cubic` + - This specifies, whether a cubic or quadratic model is used for computing trial stepsizes + * - :ini:`factor_high = 0.5` - Safeguard for stepsize, upper bound - * - factor_low - - ``0.1`` + * - :ini:`factor_low = 0.1` - Safeguard for stepsize, lower bound @@ -1073,17 +1178,13 @@ in the following. .. list-table:: :header-rows: 1 - * - Parameter - - Default value + * - Parameter = Default value - Remarks - * - bfgs_memory_size - - ``5`` + * - :ini:`bfgs_memory_size = 5` - memory size of the L-BFGS method - * - use_bfgs_scaling - - ``True`` - - if ``True``, uses a scaled identity mapping as initial guess for the inverse Hessian - * - bfgs_periodic_restart - - ``0`` + * - :ini:`use_bfgs_scaling = True` + - if :ini:`use_bfgs_scaling = True`, uses a scaled identity mapping as initial guess for the inverse Hessian + * - :ini:`bfgs_periodic_restart = 0` - specifies, after how many iterations the method is restarted. If this is 0, no restarting is done. [AlgoCG] @@ -1092,23 +1193,17 @@ in the following. .. list-table:: :header-rows: 1 - * - Parameter - - Default value + * - Parameter = Default value - Remarks - * - cg_method - - ``FR`` + * - :ini:`cg_method = FR` - specifies which nonlinear CG method is used - * - cg_periodic_restart - - ``False`` - - if ``True``, enables periodic restart of NCG method - * - cg_periodic_its - - ``10`` + * - :ini:`cg_periodic_restart = False` + - if :ini:`cg_periodic_restart = True`, enables periodic restart of NCG method + * - :ini:`cg_periodic_its = 10` - specifies, after how many iterations the NCG method is restarted, if applicable - * - cg_relative_restart - - ``False`` - - if ``True``, enables restart of NCG method based on a relative criterion - * - cg_restart_tol - - ``0.25`` + * - :ini:`cg_relative_restart = False` + - if :ini:`cg_relative_restart = True`, enables restart of NCG method based on a relative criterion + * - :ini:`cg_restart_tol = 0.25` - the tolerance of the relative restart criterion, if applicable @@ -1119,83 +1214,57 @@ in the following. .. list-table:: :header-rows: 1 - * - Parameter - - Default value + * - Parameter = Default value - Remarks - * - shape_bdry_def - - ``[]`` + * - :ini:`shape_bdry_def = []` - list of indices for the deformable boundaries - * - shape_bdry_fix - - ``[]`` + * - :ini:`shape_bdry_fix = []` - list of indices for the fixed boundaries - * - shape_bdry_fix_x - - ``[]`` + * - :ini:`shape_bdry_fix_x = []` - list of indices for boundaries with fixed x values - * - shape_bdry_fix_y - - ``[]`` + * - :ini:`shape_bdry_fix_y = []` - list of indices for boundaries with fixed y values - * - shape_bdry_fix_z - - ``[]`` + * - :ini:`shape_bdry_fix_z = []` - list of indices for boundaries with fixed z values - * - fixed_dimensions - - ``[]`` + * - :ini:`fixed_dimensions = []` - a list of coordinates which should be fixed during the shape optimization (x=0, y=1, etc.) - * - use_pull_back - - ``True`` - - if ``False``, shape derivative might be wrong; no pull-back for the material derivative is performed; + * - :ini:`use_pull_back = True` + - if :ini:`use_pull_back = False`, shape derivative might be wrong; no pull-back for the material derivative is performed; only use with caution - * - lambda_lame - - ``0.0`` + * - :ini:`lambda_lame = 0.0` - value of the first Lamé parameter for the elasticity equations - * - damping_factor - - ``0.0`` + * - :ini:`damping_factor = 0.0` - value of the damping parameter for the elasticity equations - * - mu_def - - ``1.0`` + * - :ini:`mu_def = 1.0` - value of the second Lamé parameter on the deformable boundaries - * - mu_fix - - ``1.0`` + * - :ini:`mu_fix = 1.0` - value of the second Lamé parameter on the fixed boundaries - * - use_sqrt_mu - - ``False`` - - if ``True``, uses the square root of the computed ``mu_lame``; might be good for 3D problems - * - inhomogeneous - - ``False`` - - if ``True``, uses inhomogeneous elasticity equations, weighted by the local mesh size - * - update_inhomogeneous - - ``False`` - - if ``True`` and ``inhomogeneous=True``, then the weighting with the local mesh size is updated as the mesh is deformed. - - * - use_distance_mu - - ``False`` - - if ``True``, the value of the second Lamé parameter is computed via the distance to the boundary - * - dist_min - - 1.0 - - Specifies the distance to the boundary, until which :math:`\mu` is given by ``mu_min`` - * - dist_max - - 1.0 - - Specifies the distance to the boundary, until which :math:`\mu` is given by ``mu_max`` - * - mu_min - - 1.0 - - The value of :math:`\mu` for a boundary distance smaller than ``dist_min`` - * - mu_max - - 1.0 - - The value of :math:`\mu` for a boundary distance larger than ``dist_max`` - * - boundaries_dist - - [] - - The indices of the boundaries, which shall be used to compute the distance, ``[]`` means that all boundaries are considered - * - smooth_mu - - ``False`` - - If false, a linear (continuous) interpolation between ``mu_min`` and ``mu_max`` is used, otherwise a cubic :math:`C^1` interpolant is used - * - use_p_laplacian - - ``False`` - - If ``True``, then the :math:`p`-Laplacian is used to compute the shape gradient - * - p_laplacian_power - - 2 + * - :ini:`use_sqrt_mu = False` + - if :ini:`use_sqrt_mu = True`, uses the square root of the computed ``mu_lame``; might be good for 3D problems + * - :ini:`inhomogeneous = False` + - if :ini:`inhomogeneous = True`, uses inhomogeneous elasticity equations, weighted by the local mesh size + * - :ini:`update_inhomogeneous = False` + - if :ini:`update_inhomogeneous = True` and :ini:`inhomogeneous=True`, then the weighting with the local mesh size is updated as the mesh is deformed. + * - :ini:`use_distance_mu = False` + - if :ini:`use_distance_mu = True`, the value of the second Lamé parameter is computed via the distance to the boundary + * - :ini:`dist_min = 1.0` + - Specifies the distance to the boundary, until which :math:`\mu` is given by :ini:`mu_min` + * - :ini:`dist_max = 1.0` + - Specifies the distance to the boundary, until which :math:`\mu` is given by :ini:`mu_max` + * - :ini:`mu_min = 1.0` + - The value of :math:`\mu` for a boundary distance smaller than :ini:`dist_min` + * - :ini:`mu_max = 1.0` + - The value of :math:`\mu` for a boundary distance larger than :ini:`dist_max` + * - :ini:`boundaries_dist = []` + - The indices of the boundaries, which shall be used to compute the distance, :ini:`boundaries_dist = []` means that all boundaries are considered + * - :ini:`smooth_mu = False` + - If false, a linear (continuous) interpolation between :ini:`mu_min` and :ini:`mu_max` is used, otherwise a cubic :math:`C^1` interpolant is used + * - :ini:`use_p_laplacian = False` + - If :ini:`use_p_laplacian = True`, then the :math:`p`-Laplacian is used to compute the shape gradient + * - :ini:`p_laplacian_power = 2` - The parameter :math:`p` of the :math:`p`-Laplacian - * - p_laplacian_stabilization - - 0.0 - - The stabilization parameter for the :math:`p`-Laplacian problem. No stabilization is used when this is ``0.0``. + * - :ini:`p_laplacian_stabilization = 0.0` + - The stabilization parameter for the :math:`p`-Laplacian problem. No stabilization is used when this is :ini:`p_laplacian_stabilization = 0.0`. [Regularization] @@ -1204,42 +1273,30 @@ in the following. .. list-table:: :header-rows: 1 - * - Parameter - - Default value + * - Parameter = Default value - Remarks - * - factor_volume - - ``0.0`` + * - :ini:`factor_volume = 0.0` - value of the regularization parameter for volume regularization; needs to be non-negative - * - target_volume - - ``0.0`` + * - :ini:`target_volume = 0.0` - prescribed volume for the volume regularization - * - use_initial_volume - - ``False`` - - if ``True`` uses the volume of the initial geometry as prescribed volume - * - factor_surface - - ``0.0`` + * - :ini:`use_initial_volume = False` + - if :ini:`use_initial_volume = True` uses the volume of the initial geometry as prescribed volume + * - :ini:`factor_surface = 0.0` - value of the regularization parameter for surface regularization; needs to be non-negative - * - target_surface - - ``0.0`` + * - :ini:`target_surface = 0.0` - prescribed surface for the surface regularization - * - use_initial_surface - - ``False`` - - if ``True`` uses the surface area of the initial geometry as prescribed surface - * - factor_curvature - - ``0.0`` + * - :ini:`use_initial_surface = False` + - if :ini:`use_initial_surface = True` uses the surface area of the initial geometry as prescribed surface + * - :ini:`factor_curvature = 0.0` - value of the regularization parameter for curvature regularization; needs to be non-negative - * - factor_barycenter - - ``0.0`` + * - :ini:`factor_barycenter = 0.0` - value of the regularization parameter for barycenter regularization; needs to be non-negative - * - target_barycenter - - ``[0.0, 0.0, 0.0]`` + * - :ini:`target_barycenter = [0.0, 0.0, 0.0]` - prescribed barycenter for the barycenter regularization - * - use_initial_barycenter - - ``False`` - - if ``True`` uses the barycenter of the initial geometry as prescribed barycenter - * - use_relative_scaling - - ``False`` - - if ``True``, the regularization terms are scaling so that they have the magnitude specified in the respective factor for the initial iteration. + * - :ini:`use_initial_barycenter = False` + - if :ini:`use_initial_barycenter = True` uses the barycenter of the initial geometry as prescribed barycenter + * - :ini:`use_relative_scaling = False` + - if :ini:`use_relative_scaling = True`, the regularization terms are scaling so that they have the magnitude specified in the respective factor for the initial iteration. @@ -1249,29 +1306,22 @@ in the following. .. list-table:: :header-rows: 1 - * - Parameter - - Default value + * - Parameter = Default value - Remarks - * - volume_change - - ``inf`` + * - :ini:`volume_change = inf` - determines by what factor the volume of a cell is allowed to change within a single deformation - * - angle_change - - ``inf`` + * - :ini:`angle_change = inf` - determines how much the angles of a cell are allowed to change within a single deformation - * - tol_lower - - ``0.0`` + * - :ini:`tol_lower = 0.0` - if the mesh quality is lower than this tolerance, the state system is not solved for the Armijo rule, instead step size is decreased - * - tol_upper - - ``1e-15`` - - if the mesh quality is between ``tol_lower`` and ``tol_upper``, the state + * - :ini:`tol_upper = 1e-15` + - if the mesh quality is between :ini:`tol_lower` and :ini:`tol_upper`, the state system will still be solved for the Armijo rule. If the accepted step yields a quality lower than this, algorithm is terminated (or remeshing is initiated) - * - measure - - ``skewness`` + * - :ini:`measure = skewness` - determines which quality measure is used - * - type - - ``min`` + * - :ini:`type = min` - determines if minimal or average quality is considered @@ -1283,38 +1333,27 @@ in the following. .. list-table:: :header-rows: 1 - * - Parameter - - Default value + * - Parameter = Default value - Remarks - * - verbose - - ``True`` - - if ``True``, the history of the optimization is printed to the console - * - save_results - - ``True`` - - if ``True``, the history of the optimization is saved to a .json file - * - save_txt - - ``True`` - - if ``True``, the history of the optimization is saved to a human readable .txt file - * - save_state - - ``False`` - - if ``True``, the history of the state variables over the optimization is + * - :ini:`verbose = True` + - if :ini:`verbose = True`, the history of the optimization is printed to the console + * - :ini:`save_results = True` + - if :ini:`save_results = True`, the history of the optimization is saved to a .json file + * - :ini:`save_txt = True` + - if :ini:`save_txt = True`, the history of the optimization is saved to a human readable .txt file + * - :ini:`save_state = False` + - if :ini:`save_state = True`, the history of the state variables over the optimization is saved in .xdmf files - * - save_adjoint - - ``False`` - - if ``True``, the history of the adjoint variables over the optimization is + * - :ini:`save_adjoint = False` + - if :ini:`save_adjoint = True`, the history of the adjoint variables over the optimization is saved in .xdmf files - * - save_gradient - - ``False`` - - if ``True``, the history of the shape gradient over the optimization is saved in .xdmf files - * - save_mesh - - ``False`` - - if ``True``, saves the mesh for the optimized geometry; only available for GMSH input - * - result_dir - - ``./results`` + * - :ini:`save_gradient = False` + - if :ini:`save_gradient = True`, the history of the shape gradient over the optimization is saved in .xdmf files + * - :ini:`save_mesh = False` + - if :ini:`save_mesh = True`, saves the mesh for the optimized geometry; only available for GMSH input + * - :ini:`result_dir = ./results` - path to the directory, where the output should be placed - * - precision - - 3 + * - :ini:`precision = 3` - number of significant digits to be printed - * - time_suffix - - ``False`` - - Boolean flag, which adds a suffix to ``result_dir`` based on the current time + * - :ini:`time_suffix = False` + - Boolean flag, which adds a suffix to :ini:`result_dir` based on the current time diff --git a/docs/source/user/demos/shape_optimization/doc_custom_scalar_product.rst b/docs/source/user/demos/shape_optimization/doc_custom_scalar_product.rst deleted file mode 100755 index ad48f1cd..00000000 --- a/docs/source/user/demos/shape_optimization/doc_custom_scalar_product.rst +++ /dev/null @@ -1,101 +0,0 @@ -.. _demo_custom_scalar_product: - -Custom Scalar Products for Shape Gradient Computation -===================================================== - -Problem Formulation -------------------- - -In this demo, we show how to supply a custom bilinear form for the computation -of the shape gradient with cashocs. For the sake of simplicity, we again consider -our model problem from :ref:`demo_shape_poisson`, given by - -.. math:: - - &\min_\Omega J(u, \Omega) = \int_\Omega u \text{ d}x \\ - &\text{subject to} \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta u &= f \quad &&\text{ in } \Omega,\\ - u &= 0 \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - - -For the initial domain, we use the unit disc :math:`\Omega = \{ x \in \mathbb{R}^2 \,\mid\, \lvert\lvert x \rvert\rvert_2 < 1 \}`, and the right-hand side :math:`f` is given by - -.. math:: f(x) = 2.5 \left( x_1 + 0.4 - x_2^2 \right)^2 + x_1^2 + x_2^2 - 1. - - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_custom_scalar_product.py `, -and the corresponding config can be found in :download:`config.ini `. - - -Initialization -************** - -The demo program closely follows the one from :ref:`demo_shape_poisson`, so that up -to the definition of the :py:class:`ShapeOptimizationProblem `, -the code is identical to the one in :ref:`demo_shape_poisson`, and given by :: - - from fenics import * - - import cashocs - - config = cashocs.load_config("./config.ini") - - mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh("./mesh/mesh.xdmf") - - V = FunctionSpace(mesh, "CG", 1) - u = Function(V) - p = Function(V) - - x = SpatialCoordinate(mesh) - f = 2.5 * pow(x[0] + 0.4 - pow(x[1], 2), 2) + pow(x[0], 2) + pow(x[1], 2) - 1 - - e = inner(grad(u), grad(p)) * dx - f * p * dx - bcs = DirichletBC(V, Constant(0), boundaries, 1) - - J = cashocs.IntegralFunctional(u * dx) - - -Definition of the scalar product -******************************** - -To define the scalar product that shall be used for the shape optimization, we can -proceed analogously to :ref:`demo_neumann_control` and define the corresponding bilinear form -in FEniCS. However, note that one has to use a :py:class:`fenics.VectorFunctionSpace` with -piecewise linear Lagrange elements, i.e., one has to define the corresponding function space as :: - - VCG = VectorFunctionSpace(mesh, "CG", 1) - -With this, we can now define the bilinear form as follows :: - - shape_scalar_product = ( - inner((grad(TrialFunction(VCG))), (grad(TestFunction(VCG)))) * dx - + inner(TrialFunction(VCG), TestFunction(VCG)) * dx - ) - -.. note:: - - Note, that we cannot use the formulation :: - - shape_scalar_product = inner((grad(TrialFunction(VCG))), (grad(TestFunction(VCG))))*dx - - as this would not yield a coercive bilinear form for this problem. This is due to - the fact that the entire boundary of :math:`\Omega` is variable. Hence, we actually - need this second term. - -Finally, we can set up the :py:class:`ShapeOptimizationProblem ` -and solve it with the lines :: - - sop = cashocs.ShapeOptimizationProblem( - e, bcs, J, u, p, boundaries, config, shape_scalar_product=shape_scalar_product - ) - sop.solve() - - -The result of the optimization looks like this - -.. image:: /../../demos/documented/shape_optimization/custom_scalar_product/img_custom_scalar_product.png diff --git a/docs/source/user/demos/shape_optimization/doc_eikonal_stiffness.rst b/docs/source/user/demos/shape_optimization/doc_eikonal_stiffness.rst deleted file mode 100644 index e9c78525..00000000 --- a/docs/source/user/demos/shape_optimization/doc_eikonal_stiffness.rst +++ /dev/null @@ -1,99 +0,0 @@ -.. _demo_eikonal_stiffness: - -Computing the Shape Stiffness via Distance to the Boundaries -============================================================ - -Problem Formulation -------------------- - -We are solving the same problem as in :ref:`demo_shape_stokes`, but now use -a different approach for computing the stiffness of the shape gradient. -Recall, that the corresponding (regularized) shape optimization problem is given by - -.. math:: - - \min_\Omega J(u, \Omega) = &\int_{\Omega^\text{flow}} Du : Du\ \text{ d}x + - \frac{\mu_\text{vol}}{2} \left( \int_\Omega 1 \text{ d}x - \text{vol}(\Omega_0) \right)^2 \\ - &+ \frac{\mu_\text{bary}}{2} \left\lvert \frac{1}{\text{vol}(\Omega)} \int_\Omega x \text{ d}x - \text{bary}(\Omega_0) \right\rvert^2 \\ - &\text{subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - - \Delta u + \nabla p &= 0 \quad &&\text{ in } \Omega, \\ - \text{div}(u) &= 0 \quad &&\text{ in } \Omega, \\ - u &= u^\text{in} \quad &&\text{ on } \Gamma^\text{in}, \\ - u &= 0 \quad &&\text{ on } \Gamma^\text{wall} \cup \Gamma^\text{obs}, \\ - \partial_n u - p n &= 0 \quad &&\text{ on } \Gamma^\text{out}. - \end{alignedat} - \right. - -For a background on the stiffness of the shape gradient, we refer to :ref:`config_shape_shape_gradient`, -where it is defined as the parameter :math:`\mu` used in the computation of -the shape gradient. Note, that the distance computation is done via an eikonal equation, -hence the name of the demo. - - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_eikonal_stiffness.py `, -and the corresponding config can be found in :download:`config.ini `. - - -Changes in the config file --------------------------- - -In order to compute the stiffness :math:`\mu` based on the distance to selected boundaries, -we only have to change the configuration file we are using, the python code -for solving the shape optimization problem with cashocs stays exactly as -it was in :ref:`demo_shape_stokes`. - -To use the stiffness computation based on the distance to the boundary, we add the -following lines to the config file :: - - use_distance_mu = True - dist_min = 0.05 - dist_max = 1.25 - mu_min = 5e2 - mu_max = 1.0 - smooth_mu = false - boundaries_dist = [4] - -The first line :: - - use_distance_mu = True - -ensures that the stiffness will be computed based on the distance to the boundary. - -The next four lines then specify the behavior of this computation. In particular, -we have the following behavior for :math:`\mu` - -.. math:: - - \mu = \begin{cases} - \mu_\mathrm{min} \quad \text{ if } \delta \leq \delta_\mathrm{min},\\ - \mu_\mathrm{max} \quad \text{ if } \delta \geq \delta_\mathrm{max} - \end{cases} - -where :math:`\delta` denotes the distance to the boundary and :math:`\delta_\mathrm{min}` -and :math:`\delta_\mathrm{max}` correspond to ``dist_min`` and ``dist_max``, -respectively. - -The values in-between are given by interpolation. Either a linear, continuous interpolation -is used, or a smooth :math:`C^1` interpolation given by a third order polynomial. -These can be selected with the option :: - - smooth_mu = false - -where ``smooth_mu = True`` uses the third order polynomial, and ``smooth_mu = False`` uses -the linear function. - -Finally, the line :: - - boundaries_dist = [4] - -specifies, which boundaries are considered for the distance computation. These are -again specified using the boundary markers, as it was previously explained in -:ref:`config_shape_shape_gradient`. - -The results should look like this - -.. image:: /../../demos/documented/shape_optimization/shape_stokes/img_shape_stokes.png diff --git a/docs/source/user/demos/shape_optimization/doc_inverse_tomography.rst b/docs/source/user/demos/shape_optimization/doc_inverse_tomography.rst deleted file mode 100755 index ad1fed90..00000000 --- a/docs/source/user/demos/shape_optimization/doc_inverse_tomography.rst +++ /dev/null @@ -1,286 +0,0 @@ -.. _demo_inverse_tomography: - -Inverse Problem in Electric Impedance Tomography -================================================ - -Problem Formulation -------------------- - -For this demo, we investigate an inverse problem in the setting of electric -impedance tomography. It is based on the one used in the preprint -`Blauth, Nonlinear Conjugate Gradient Methods for PDE Constrained Shape Optimization -Based on Steklov-Poincaré-Type Metrics `_. The -problem reads - -.. math:: - - &\min_{\Omega} J(u, \Omega) = \sum_{i=1}^{M} \frac{\nu_i}{2} - \int_{\partial D} \left( u_i - m_i \right)^2 \text{ d}s \\ - &\text{subject to} \quad \left\lbrace \quad - \begin{alignedat}{2} - -\kappa^\text{in} \Delta u_i^\text{in} &= 0 \quad &&\text{ in } \Omega,\\ - -\kappa^\text{out} \Delta u_i^\text{out} &= 0 \quad &&\text{ in } D \setminus \Omega, \\ - \kappa^\text{out} \partial_n u^\text{out}_i &= f_i \quad &&\text{ on } \partial D, \\ - u^\text{out}_i &= u^\text{in}_i \quad &&\text{ on } \Gamma, \\ - \kappa^\text{out} \partial_{n^\text{in}} u^\text{out}_i &= \kappa^\text{in} \partial_{n^\text{in}} u^\text{in}_i \quad &&\text{ on } \Gamma, \\ - \int_{\partial D} u_i^\text{out} \text{ d}s &= 0. - \end{alignedat} - \right. - -The setting is as follows. -We have an object :math:`\Omega` that is located inside another one, :math:`D \setminus \Omega`. -Our goal is to identify the shape of the interior from measurements of the electric -potential, denoted by :math:`u`, on the outer boundary of :math:`D`, to which we have -access. In particular, we consider the case of having several measurements -at our disposal, as indicated by the index :math:`i`. The PDE constraint now models -the electric potential in experiment :math:`i`, namely :math:`u_i`, which results -from an application of the electric current :math:`f_i` on the outer boundary :math:`\partial D`. -Our goal of identifying the interior body :math:`\Omega` is modeled by the -tracking type cost functional, which measures the :math:`L^2(\Gamma)` distance -between the simulated electric potential :math:`u_i` and the measured one, :math:`m_i`. -In particular, note that the outer boundaries, i.e. :math:`\partial D` are fixed, and -only the internal boundary :math:`\Gamma = \partial \Omega` is deformable. -For a detailed description of this problem as well as its physical interpretation, -we refer to the preprint `Blauth, Nonlinear Conjugate Gradient Methods for PDE Constrained Shape Optimization -Based on Steklov-Poincaré-Type Metrics `_. - -For our demo, we use as domain the unit square :math:`D = (0,1)^2`, and the initial -geometry of :math:`\Omega` is a square inside :math:`D`. We consider the case of -three measurements, so that :math:`M = 3`, given by - -.. math:: - - f_1 = 1 \quad \text{ on } \Gamma^l \cup \Gamma^r \qquad \text{ and } \qquad f_1 = -1 \quad \text{ on } \Gamma^t \cup \Gamma^b,\\ - f_2 = 1 \quad \text{ on } \Gamma^l \cup \Gamma^t \qquad \text{ and } \qquad f_2 = -1 \quad \text{ on } \Gamma^r \cup \Gamma^b,\\ - f_3 = 1 \quad \text{ on } \Gamma^l \cup \Gamma^b \qquad \text{ and } \qquad f_3 = -1 \quad \text{ on } \Gamma^r \cup \Gamma^t, - -where :math:`\Gamma^l, \Gamma^r, \Gamma^t, \Gamma^b` are the left, right, top, and -bottom sides of the unit square. - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_inverse_tomography.py `, -and the corresponding config can be found in :download:`config.ini `. - - -Initialization and generation of synthetic measurements -******************************************************* - -We start our code by importing FEniCS and cashocs :: - - from fenics import * - - import cashocs - -Next, we directly define the sample values of :math:`\kappa^\text{in}` and -:math:`\kappa^\text{out}` since these are needed later on :: - - kappa_out = 1e0 - kappa_in = 1e1 - -In the next part, we generate synthetic measurements, which correspond to :math:`m_i`. -To do this, we define a function :py:func:`generate_measurements()` as follows :: - - def generate_measurements(): - mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh( - "./mesh/reference.xdmf" - ) - - cg_elem = FiniteElement("CG", mesh.ufl_cell(), 1) - r_elem = FiniteElement("R", mesh.ufl_cell(), 0) - V = FunctionSpace(mesh, MixedElement([cg_elem, r_elem])) - - u, c = TrialFunctions(V) - v, d = TestFunctions(V) - - a = ( - kappa_out * inner(grad(u), grad(v)) * dx(1) - + kappa_in * inner(grad(u), grad(v)) * dx(2) - + u * d * ds - + v * c * ds - ) - L1 = Constant(1) * v * (ds(3) + ds(4)) + Constant(-1) * v * (ds(1) + ds(2)) - L2 = Constant(1) * v * (ds(3) + ds(2)) + Constant(-1) * v * (ds(1) + ds(4)) - L3 = Constant(1) * v * (ds(3) + ds(1)) + Constant(-1) * v * (ds(2) + ds(4)) - - meas1 = Function(V) - meas2 = Function(V) - meas3 = Function(V) - solve(a == L1, meas1) - solve(a == L2, meas2) - solve(a == L3, meas3) - - m1, _ = meas1.split(True) - m2, _ = meas2.split(True) - m3, _ = meas3.split(True) - - return [m1, m2, m3] - -.. note:: - - The code executed in :py:func:`generate_measurements()` is used to solve the - state problem on a reference domain, given by the mesh ``./mesh/reference.xdmf``. - This mesh has the domain :math:`\Omega` as a circle in the center of the unit - square. To distinguish between these two, we note that :math:`D \setminus \Omega` - has the index / marker 1 and that :math:`\Omega` has the index / marker 2 in - the corresponding GMSH file, which is then imported into ``subdomains``. - - Note, that we have to use a mixed finite element method to incorporate the - integral constraint on the electric potential. The second component of the - corresponding :py:class:`fenics.FunctionSpace` ``V`` is just a scalar, one-dimensional, - real element. The actual PDE constraint is then given by the part :: - - kappa_out*inner(grad(u), grad(v))*dx(1) + kappa_in*inner(grad(u), grad(v))*dx(2) - - and the integral constraint is realized with the saddle point formulation :: - - u*d*ds + v*c*ds - - The right hand sides ``L1``, ``L2``, and ``L3`` are just given by the Neumann - boundary conditions as specified above. - - Finally, these PDEs are then solved via the :py:func:`fenics.solve` command, - and then only the actual solution of the PDE (and not the Lagrange multiplier - for the integral constraint) is returned. - -As usual, we load the config into cashocs with the line :: - - config = cashocs.load_config("./config.ini") - -Afterwards, we import the mesh into cashocs :: - - mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh("./mesh/mesh.xdmf") - -Next, we define the :py:class:`fenics.FunctionSpace` object, which consists of -CG1 elements together with a scalar, real element, which acts as a Lagrange multiplier -for the integral constraint :: - - cg_elem = FiniteElement("CG", mesh.ufl_cell(), 1) - r_elem = FiniteElement("R", mesh.ufl_cell(), 0) - V = FunctionSpace(mesh, MixedElement([cg_elem, r_elem])) - -Next, we compute the synthetic measurements via :: - - measurements = generate_measurements() - - -The PDE constraint -****************** - -Let us now investigate how the PDE constraint is defined. As we have a mixed -finite element problem due to the integral constraint, we proceed similarly to :ref:`demo_monolithic_problems` -and define the first state equation with the following lines :: - - uc1 = Function(V) - u1, c1 = split(uc1) - pd1 = Function(V) - p1, d1 = split(pd1) - e1 = ( - kappa_out * inner(grad(u1), grad(p1)) * dx(1) - + kappa_in * inner(grad(u1), grad(p1)) * dx(2) - + u1 * d1 * ds - + p1 * c1 * ds - - Constant(1) * p1 * (ds(3) + ds(4)) - - Constant(-1) * p1 * (ds(1) + ds(2)) - ) - -The remaining two experiments are defined completely analogously:: - - uc2 = Function(V) - u2, c2 = split(uc2) - pd2 = Function(V) - p2, d2 = split(pd2) - e2 = ( - kappa_out * inner(grad(u2), grad(p2)) * dx(1) - + kappa_in * inner(grad(u2), grad(p2)) * dx(2) - + u2 * d2 * ds - + p2 * c2 * ds - - Constant(1) * p2 * (ds(3) + ds(2)) - - Constant(-1) * p2 * (ds(1) + ds(4)) - ) - - uc3 = Function(V) - u3, c3 = split(uc3) - pd3 = Function(V) - p3, d3 = split(pd3) - e3 = ( - kappa_out * inner(grad(u3), grad(p3)) * dx(1) - + kappa_in * inner(grad(u3), grad(p3)) * dx(2) - + u3 * d3 * ds - + p3 * c3 * ds - - Constant(1) * p3 * (ds(3) + ds(1)) - - Constant(-1) * p3 * (ds(2) + ds(4)) - ) - -Finally, we group together the state equations as well as the state and adjoint variables -to (ordered) lists, as in :ref:`demo_multiple_variables` :: - - e = [e1, e2, e3] - u = [uc1, uc2, uc3] - p = [pd1, pd2, pd3] - -Since the problem only has Neumann boundary conditions, we use :: - - bcs = [[], [], []] - -to specify this. - - -The shape optimization problem -****************************** - -The cost functional is then defined by first creating the individual summands, -and then adding them to a list:: - - J1 = cashocs.IntegralFunctional(Constant(0.5) * pow(u1 - measurements[0], 2) * ds) - J2 = cashocs.IntegralFunctional(Constant(0.5) * pow(u2 - measurements[1], 2) * ds) - J3 = cashocs.IntegralFunctional(Constant(0.5) * pow(u3 - measurements[2], 2) * ds) - - J = [J1, J2, J3] - -where we use a coefficient of :math:`\nu_i = 1` for all cases. - -Before we can define the shape optimization properly, we have to take a look at the -config file to specify which boundaries are fixed, and which are deformable. There, -we have the following lines :: - - [ShapeGradient] - shape_bdry_def = [] - shape_bdry_fix = [1, 2, 3, 4] - -Note, that the boundaries ``1, 2, 3, 4`` are the sides of the unit square, as defined -in the .geo file for the geometry (located in the ``./mesh/`` directory), and they -are fixed due to the problem definition (recall that :math:`\partial D` is fixed). -However, at the first glance it seems that there is no deformable boundary. This -is, however, wrong. In fact, there is still an internal boundary, namely :math:`\Gamma`, -which is not specified here, and which is, thus, deformable (this is the default behavior). - -.. warning:: - - As stated in :ref:`config_shape_optimization`, we have to use the config file - setting :: - - use_pull_back = False - - This is due to the fact that the measurements are defined / computed on a different - mesh / geometry than the remaining objects, and FEniCS is not able to do some - computations in this case. However, note that the cost functional is posed on - :math:`\partial D` only, which is fixed anyway. Hence, the deformation field - vanishes there, and the corresponding diffeomorphism, which maps between the - deformed and original domain, is just the identity mapping. In particular, - no material derivatives are needed for the measurements, which is why it - is safe to disable ``use_pull_back`` for this particular problem. - -The shape optimization problem can now be created as in :ref:`demo_shape_poisson` -and can be solved as easily, with the commands :: - - sop = cashocs.ShapeOptimizationProblem(e, bcs, J, u, p, boundaries, config) - sop.solve() - -The results should look like this - -.. image:: /../../demos/documented/shape_optimization/inverse_tomography/img_inverse_tomography.png - -and we observe that we are indeed able to identify the shape of the circle which -was used to create the measurements. diff --git a/docs/source/user/demos/shape_optimization/doc_p_laplacian.rst b/docs/source/user/demos/shape_optimization/doc_p_laplacian.rst deleted file mode 100755 index 790a8b08..00000000 --- a/docs/source/user/demos/shape_optimization/doc_p_laplacian.rst +++ /dev/null @@ -1,69 +0,0 @@ -.. _demo_p_laplacian: - -Shape Optimization with the p-Laplacian -======================================= - -Problem Formulation -------------------- - - -In this demo, we take a look at yet another possibility to compute the shape gradient and to use this method for solving shape optimization problems. Here, we investigate the approach of `Müller, Kühl, Siebenborn, Deckelnick, Hinze, and Rung `_ and use the :math:`p`-Laplacian in order to compute the shape gradient. -As a model problem, we consider the following one, as in :ref:`demo_shape_poisson`: - -.. math:: - - &\min_\Omega J(u, \Omega) = \int_\Omega u \text{ d}x \\ - &\text{subject to} \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta u &= f \quad &&\text{ in } \Omega,\\ - u &= 0 \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - - -For the initial domain, we use the unit disc :math:`\Omega = \{ x \in \mathbb{R}^2 \,\mid\, \lvert\lvert x \rvert\rvert_2 < 1 \}`, and the right-hand side :math:`f` is given by - -.. math:: f(x) = 2.5 \left( x_1 + 0.4 - x_2^2 \right)^2 + x_1^2 + x_2^2 - 1. - - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_p_laplacian.py `, -and the corresponding config can be found in :download:`config.ini `. - - -Source Code -*********** - -The python source code for this example is completely identical to the one in :ref:`demo_shape_poisson`, so we do not cover this here again. The only changes occur in -the configuration file, which we cover below. - -Configuration File -****************** - -All the relevant changes appear in the ShapeGradient Section of the config file, where we now add the following three lines :: - - use_p_laplacian = True - p_laplacian_power = 10 - p_laplacian_stabilization = 0.0 - -Here, ``use_p_laplacian`` is a boolean flag which indicates that we want to override the default behavior and use the :math:`p` Laplacian to compute the shape gradient instead of linear elasticity. In particular, this means that we solve the following equation to determine the shape gradient :math:`\mathcal{G}` - -.. math:: - - \begin{aligned} - &\text{Find } \mathcal{G} \text{ such that } \\ - &\qquad \int_\Omega \mu \left( \nabla \mathcal{G} : \nabla \mathcal{G} \right)^{\frac{p-2}{2}} \nabla \mathcal{G} : \nabla \mathcal{V} + \delta \mathcal{G} \cdot \mathcal{V} \text{ d}x = dJ(\Omega)[\mathcal{V}] \\ - &\text{for all } \mathcal{V}. - \end{aligned} - -Here, :math:`dJ(\Omega)[\mathcal{V}]` is the shape derivative. The parameter :math:`p` is defined via the config file parameter ``p_laplacian_power``, and is 10 for this example. Finally, it is possible to use a stabilized formulation of the :math:`p`-Laplacian equation shown above, where the stabilization parameter is determined via the config line parameter ``p_laplacian_stabilization``, which should be small (e.g. in the order of ``1e-3``). Moreover, :math:`\mu` is the stiffness parameter, which can be specified via the config file parameters ``mu_def`` and ``mu_fixed`` and works as usually (cf. :ref:`demo_shape_poisson`:). Finally, we have added the possibility to use the damping parameter :math:`\delta`, which is specified via the config file parameter ``damping_factor``, also in the Section ShapeGradient. - -.. note:: - - Note, that the :math:`p`-Laplace methods are only meant to work with the gradient descent method. Other methods, such as BFGS or NCG methods, might be able to work on certain problems, but you might encounter strange behavior of the methods. - -Finally, we show the result of the optimization, which looks similar to the one obtained in :ref:`demo_shape_poisson`: - -.. image:: /../../demos/documented/shape_optimization/shape_poisson/img_shape_poisson.png - diff --git a/docs/source/user/demos/shape_optimization/doc_regularization.rst b/docs/source/user/demos/shape_optimization/doc_regularization.rst deleted file mode 100755 index 1f3392d7..00000000 --- a/docs/source/user/demos/shape_optimization/doc_regularization.rst +++ /dev/null @@ -1,116 +0,0 @@ -.. _demo_regularization: - -Regularization for Shape Optimization Problems -============================================== - -Problem Formulation -------------------- - -In this demo, we investigate how we can use regularizations for shape optimization -problems in cashocs. For our model problem, we use one similar to the one in :ref:`demo_shape_poisson`, -but which has additional regularization terms, i.e., - -.. math:: - - \min_\Omega J(u, \Omega) = &\int_\Omega u \text{ d}x + - \alpha_\text{vol} \int_\Omega 1 \text{ d}x + - \alpha_\text{surf} \int_\Gamma 1 \text{ d}s \\ - &+ - \frac{\mu_\text{vol}}{2} \left( \int_\Omega 1 \text{ d}x - \text{vol}_\text{des} \right)^2 + - \frac{\mu_\text{surf}}{2} \left( \int_\Gamma 1 \text{ d}s - \text{surf}_\text{des} \right)^2 \\ - &+ \frac{\mu_\text{curv}}{2} \int_\Gamma \kappa^2 \text{ d}s \\ - &\text{subject to} \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta u &= f \quad &&\text{ in } \Omega,\\ - u &= 0 \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - - -Here, :math:`\kappa` is the mean curvature. -For the initial domain, we use the unit disc :math:`\Omega = \{ x \in \mathbb{R}^2 \,\mid\, \lvert\lvert x \rvert\rvert_2 < 1 \}`, and the right-hand side :math:`f` is given by - -.. math:: f(x) = 2.5 \left( x_1 + 0.4 - x_2^2 \right)^2 + x_1^2 + x_2^2 - 1, - -as in :ref:`demo_shape_poisson`. - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_regularization.py `, -and the corresponding config can be found in :download:`config.ini `. - - -Initialization -************** - -The initial code, including the defition of the PDE constraint, is identical to -:ref:`demo_shape_poisson`, and uses the following code :: - - from fenics import * - - import cashocs - - config = cashocs.load_config("./config.ini") - - mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh("./mesh/mesh.xdmf") - - V = FunctionSpace(mesh, "CG", 1) - u = Function(V) - p = Function(V) - - x = SpatialCoordinate(mesh) - f = 2.5 * pow(x[0] + 0.4 - pow(x[1], 2), 2) + pow(x[0], 2) + pow(x[1], 2) - 1 - - e = inner(grad(u), grad(p)) * dx - f * p * dx - bcs = DirichletBC(V, Constant(0), boundaries, 1) - -Cost functional and regularization -********************************** - -The only difference to :ref:`demo_shape_poisson` comes now, in the definition -of the cost functional which includes the additional regularization terms. - -.. note:: - - cashocs cannot treat the last two regularization terms directly by a user - implementation. Instead, these regularization terms can be realized by setting - the appropriate parameters in the config files, see the :ref:`Section Regularization `. - -The first three summands of the cost functional can then be defined as :: - - alpha_vol = 1e-1 - alpha_surf = 1e-1 - - J = cashocs.IntegralFunctional( - u * dx + Constant(alpha_vol) * dx + Constant(alpha_surf) * ds - ) - -The remaining two parts are specified via :download:`config.ini -`, where -the following lines are relevant :: - - [Regularization] - factor_volume = 1.0 - target_volume = 1.5 - use_initial_volume = False - factor_surface = 1.0 - target_surface = 4.5 - use_initial_surface = False - factor_curvature = 1e-4 - -This sets the factor :math:`\mu_\text{vol}` to ``1.0``, :math:`\text{vol}_\text{des}` -to ``1.5``, :math:`\mu_\text{surf}` to ``1.0``, :math:`\text{surf}_\text{des}` -to ``4.5``, and :math:`\mu_\text{curv}` to ``1e-4``. Note, that ``use_initial_volume`` and ``use_initial_surface`` -have to be set to ``False``, otherwise the corresponding quantities of the initial -geometry would be used instead of the ones prescribed in the config file. -The resulting regularization terms are then treated by cashocs, but are, except -for these definitions in the config file, invisible for the user. - -Finally, we solve the problem as in :ref:`demo_shape_poisson` with the lines :: - - sop = cashocs.ShapeOptimizationProblem(e, bcs, J, u, p, boundaries, config) - sop.solve() - -The results should look like this - -.. image:: /../../demos/documented/shape_optimization/regularization/img_regularization.png diff --git a/docs/source/user/demos/shape_optimization/doc_remeshing.rst b/docs/source/user/demos/shape_optimization/doc_remeshing.rst deleted file mode 100755 index 2fc83fce..00000000 --- a/docs/source/user/demos/shape_optimization/doc_remeshing.rst +++ /dev/null @@ -1,189 +0,0 @@ -.. _demo_remeshing: - -Remeshing with cashocs -====================== - -Problem Formulation -------------------- - -In this tutorial, we take a close look at how remeshing works in cashocs. To keep -this discussion simple, we take a look at the model problem already investigated -in :ref:`demo_shape_poisson`, i.e., - -.. math:: - - &\min_\Omega J(u, \Omega) = \int_\Omega u \text{ d}x \\ - &\text{subject to} \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta u &= f \quad &&\text{ in } \Omega,\\ - u &= 0 \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - -As before, we use the unit disc :math:`\Omega = \{ x \in \mathbb{R}^2 \,\mid\, \lvert\lvert x \rvert\rvert_2 < 1 \}`, -as initial geometry, and the right-hand side :math:`f` is given by - -.. math:: f(x) = 2.5 \left( x_1 + 0.4 - x_2^2 \right)^2 + x_1^2 + x_2^2 - 1. - - - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_remeshing.py `, -and the corresponding config can be found in :download:`config.ini `. -The corresponding mesh files are :download:`./mesh/mesh.geo ` and -:download:`./mesh/mesh.msh `. - -Pre-Processing with GMSH -************************ - -Before we can start with the actual cashocs implementation of remeshing, we have -to take a closer look at how we can define a geometry with GMSH. For this, .geo -files are used. - -.. hint:: - - A detailed documentation and tutorials regarding the generation of geometries - and meshes with GMSH can be found `here `_. - -The file :download:`./mesh/mesh.geo ` -describes our geometry. - -.. important:: - - Any user defined variables that should be also kept for the remeshing, such - as the characteristic lengths, must be lower case, so that cashocs can distinguish them - from the other GMSH commands. Any user defined variable starting with an upper - case letter is not considered for the .geo file created for remeshing and will, - thus, probably cause an error. - - In our case of the .geo file, the characteristic length is defined as ``lc``, - and this is used to specify the (local) size of the discretization via so-called - size fields. Note, that this variable is indeed taken into consideration for - the remeshing as it starts with a lower case letter. - -The resulting mesh file was created over the command line -with the command :: - - gmsh ./mesh/mesh.geo -o ./mesh/mesh.msh -2 - -.. note:: - - For the purpose of this tutorial it is recommended to leave the ``./mesh/mesh.msh`` - file as it is. In particular, carrying out the above command will overwrite - the file and is, thus, not recommended. The command just highlights, how one - would / could use GMSH to define their own geometries and meshes for cashocs - or FEniCS. - -The resulting file is :download:`./mesh/mesh.msh `. -This .msh file can be converted to the .xdmf format by using :py:func:`cashocs.convert` or alternatively, via the command line :: - - cashocs-convert ./mesh/mesh.msh ./mesh/mesh.xdmf - - -.. hint:: - - As :py:func:`cashocs.convert` merely **converts** the .msh - file to .xdmf, the user may very well use this command. - -To ensure that cashocs also finds these files, we have to specify them in the file -:download:`config.ini `. -For this, we have the following lines :: - - [Mesh] - mesh_file = ./mesh/mesh.xdmf - gmsh_file = ./mesh/mesh.msh - geo_file = ./mesh/mesh.geo - remesh = True - show_gmsh_output = True - -With this, we have specified the paths to the mesh files and also enabled the remeshing -as well as the verbose output of GMSH to the terminal, as explained in :ref:`the -corresponding documentation of the config files `. - -.. note:: - - Note, that the paths given in the config file can be either absolute or relative. - In the latter case, they have to be relative to the location of the cashocs script - which is used to solve the problem. - -With this, we can now focus on the implementation in python. - -Initialization -************** - -The program starts as :ref:`demo_shape_poisson`, with the following lines :: - - from fenics import * - - import cashocs - - config = cashocs.load_config("./config.ini") - -with which we import FEniCS and cashocs, and read the config file. The mesh and -all other related objects are created with the command :: - - mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh(config) - - -Note, that in contrast to :ref:`demo_shape_poisson`, we cannot use a built-in mesh for this -tutorial since remeshing is only available for meshes generated by GMSH. - -.. important:: - - It is important to note that we have to pass the config as argument to - :py:func:`import_mesh `. The alternative syntax :: - - mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh(./mesh/mesh.xdmf) - - is **NOT** equivalent for remeshing, even though the definition in the config - file points to the same object, where the corresponding line reads :: - - mesh_file = ./mesh/mesh.xdmf - -Definition of the state system -****************************** - -The definition of the state system is now completely analogous to the one in -:ref:`demo_shape_poisson`. Here, we just repeat the code for the sake of -completeness :: - - V = FunctionSpace(mesh, "CG", 1) - u = Function(V) - p = Function(V) - - x = SpatialCoordinate(mesh) - f = 2.5 * pow(x[0] + 0.4 - pow(x[1], 2), 2) + pow(x[0], 2) + pow(x[1], 2) - 1 - - e = inner(grad(u), grad(p)) * dx - f * p * dx - bcs = DirichletBC(V, Constant(0), boundaries, 1) - - -The shape optimization problem -****************************** - -The definition of the :py:class:`ShapeOptimizationProblem ` -as well as its solution is now also completely analogous to :ref:`demo_shape_poisson`, -and is done with the lines :: - - J = cashocs.IntegralFunctional(u * dx) - - sop = cashocs.ShapeOptimizationProblem(e, bcs, J, u, p, boundaries, config) - sop.solve() - - -The results should look like the one of :ref:`demo_shape_poisson`: - -.. image:: /../../demos/documented/shape_optimization/remeshing/img_remeshing.png - -.. note:: - - The example for remeshing is somewhat artificial, as the problem does not - actually need remeshing. Therefore, the tolerances used in the config file, i.e., :: - - tol_lower = 0.1 - tol_upper = 0.25 - - are comparatively large. However, this problem still shows all relevant - aspects of remeshing in cashocs and can, thus, be transferred to "harder" - problems that require remeshing. diff --git a/docs/source/user/demos/shape_optimization/doc_scaling.rst b/docs/source/user/demos/shape_optimization/doc_scaling.rst deleted file mode 100755 index 4d24086f..00000000 --- a/docs/source/user/demos/shape_optimization/doc_scaling.rst +++ /dev/null @@ -1,133 +0,0 @@ -.. _demo_scaling: - -Scaling of the Cost Functional -============================== - -Problem Formulation -------------------- - -In this demo, we take a look at how cashocs can be used to scale cost functionals, -which is particularly useful in case one uses multiple terms to define the cost functional -and wants to weight them appropriately, e.g., if there are multiple competing objectives. -This demo investigates this problem by considering a slightly modified version of -the model shape optimization problem from :ref:`demo_shape_poisson`, i.e., - -.. math:: - - &\min_\Omega J(u, \Omega) = \alpha \int_\Omega u \text{ d}x + \beta \int_\Omega 1 \text{ d}x \\ - &\text{subject to} \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta u &= f \quad &&\text{ in } \Omega,\\ - u &= 0 \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - - -For the initial domain, we use the unit disc :math:`\Omega = \{ x \in \mathbb{R}^2 \,\mid\, \lvert\lvert x \rvert\rvert_2 < 1 \}`, and the right-hand side :math:`f` is given by - -.. math:: f(x) = 2.5 \left( x_1 + 0.4 - x_2^2 \right)^2 + x_1^2 + x_2^2 - 1. - -Our goal is to choose the parameters :math:`\alpha` and :math:`\beta` in such a way -that the magnitude of the second term is twice as big as the value of the first term for -the initial geometry. - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_scaling.py `, -and the corresponding config can be found in :download:`config.ini `. - - -Initialization -************** - -The definition of the PDE constraint is completely identical to the one described in -:ref:`demo_shape_poisson`, which we recall here for the sake of completeness :: - - from fenics import * - - import cashocs - - config = cashocs.load_config("./config.ini") - - mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh("./mesh/mesh.xdmf") - - V = FunctionSpace(mesh, "CG", 1) - u = Function(V) - p = Function(V) - - x = SpatialCoordinate(mesh) - f = 2.5 * pow(x[0] + 0.4 - pow(x[1], 2), 2) + pow(x[0], 2) + pow(x[1], 2) - 1 - - e = inner(grad(u), grad(p)) * dx - f * p * dx - bcs = DirichletBC(V, Constant(0), boundaries, 1) - - -Definition of the Cost Functional -********************************* - -Our goal is to choose :math:`\alpha` and :math:`\beta` in such a way that - -.. math:: - \begin{aligned} - \left\lvert \alpha \int_{\Omega_0} u \text{ d}x \right\rvert &= C_1,\\ - \left\lvert \beta \int_{\Omega_0} 1 \text{ d}x \right\rvert &= C_2, - \end{aligned} - -where :math:`\Omega_0` is the intial geometry. Here, we choose the value :math:`C_1 = 1` and :math:`C_2 = 2` without loss of generality. -This would then achieve our goal of having the second term (a volume regularization) -being twice as large as the first term in magnitude. - -To implement this in cashocs, we do not specify a single UFL form for the cost functional -as in all previous demos, instead we supply a list of UFL forms into which we -put every single term of the cost functional. These are then scaled automatically -by cashocs and then added to obtain the actual cost functional. - -Hence, let us first define the individual terms of the cost functional we consider, -and place them into a list :: - - J_1 = cashocs.IntegralFunctional(u * dx) - J_2 = cashocs.IntegralFunctional(Constant(1) * dx) - J_list = [J_1, J_2] - -Afterwards, we have to create a second list which includes the values that the -terms of ``J_list`` should have on the initial geometry :: - - desired_weights = [1, 2] - -Finally, these are supplied to the shape optimization problem as follows: ``J_list`` -is passed instead of the usual ``cost_functional_form`` parameter, and ``desired_weights`` -enters the optimization problem as keyword argument of the same name, i.e., :: - - sop = cashocs.ShapeOptimizationProblem( - e, bcs, J_list, u, p, boundaries, config, desired_weights=desired_weights - ) - sop.solve() - -.. note:: - - Since the first term of the cost functional, i.e., :math:`\int_\Omega u \text{ d}x`, - is negative, the intial function value for our choice of scaling is :math:`-1 + 2 = 1`. - -.. note:: - - If the keyword argument ``desired_weights`` is not given or ``None``, this is - equivalent to not using the scaling described above, i.e., one could also just pass - ``J = J_1 + J_2`` as ``cost_functional_form``. - -.. note:: - - If a cost functional is close to zero for the initial domain, the scaling is - disabled for this term, and instead the respective term is just multiplied - by the corresponding factor in ``desired_weights``. cashocs issues an info message - in this case. - -.. note:: - - The scaling of the cost functional works completely analogous for optimal control problems: - There, one also has to supply a list of the individual terms of the cost functional - and use the keyword argument ``desired_weights`` in order to define and supply the - desired magnitude of the terms for the initial iteration. - -The result of the optimization looks like this - -.. image:: /../../demos/documented/shape_optimization/scaling/img_scaling.png diff --git a/docs/source/user/demos/shape_optimization/doc_shape_poisson.rst b/docs/source/user/demos/shape_optimization/doc_shape_poisson.rst deleted file mode 100755 index b375b533..00000000 --- a/docs/source/user/demos/shape_optimization/doc_shape_poisson.rst +++ /dev/null @@ -1,148 +0,0 @@ -.. _demo_shape_poisson: - -Shape Optimization with a Poisson Problem -========================================= - -Problem Formulation -------------------- - -In this demo, we investigate the basics of cashocs for shape optimization problems. -As a model problem, we investigate the following one from -`Etling, Herzog, Loayza, Wachsmuth, First and Second Order Shape Optimization Based on Restricted Mesh Deformations `_ - -.. math:: - - &\min_\Omega J(u, \Omega) = \int_\Omega u \text{ d}x \\ - &\text{subject to} \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta u &= f \quad &&\text{ in } \Omega,\\ - u &= 0 \quad &&\text{ on } \Gamma. - \end{alignedat} \right. - - -For the initial domain, we use the unit disc :math:`\Omega = \{ x \in \mathbb{R}^2 \,\mid\, \lvert\lvert x \rvert\rvert_2 < 1 \}`, and the right-hand side :math:`f` is given by - -.. math:: f(x) = 2.5 \left( x_1 + 0.4 - x_2^2 \right)^2 + x_1^2 + x_2^2 - 1. - - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_shape_poisson.py `, -and the corresponding config can be found in :download:`config.ini `. - - -Initialization -************** - -We start the problem by using a wildcard import for FEniCS, and by importing cashocs :: - - from fenics import * - - import cashocs - -As for the case of optimal control problems, we can specify the verbosity of cashocs with -the line :: - - cashocs.set_log_level(cashocs.LogLevel.INFO) - -which is documented at :py:func:`cashocs.set_log_level` (cf. :ref:`demo_poisson`). - -Similarly to the optimal control case, we also require config files for shape -optimization problems in cashocs. A detailed discussion of the config files -for shape optimization is given in :ref:`config_shape_optimization`. -We read the config file with the :py:func:`load_config ` command :: - - config = cashocs.load_config('./config.ini') - -Next, we have to define the mesh. We load the mesh (which was previously generated with Gmsh and converted to xdmf with :py:func:`cashocs.convert` :: - - mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh("./mesh/mesh.xdmf") - - -.. note:: - - In :download:`config.ini `, - in the section :ref:`ShapeGradient `, there is - the line :: - - shape_bdry_def = [1] - - which specifies that the boundary marked with 1 is deformable. For our - example this is exactly what we want, as this means that the entire boundary - is variable, due to the fact that the entire boundary is marked by 1 in the Gmsh file. For a detailed documentation we - refer to :ref:`the corresponding documentation of the ShapeGradient section - `. - - -After having defined the initial geometry, we define a :py:class:`fenics.FunctionSpace` consisting of -piecewise linear Lagrange elements via :: - - V = FunctionSpace(mesh, "CG", 1) - u = Function(V) - p = Function(V) - -This also defines our state variable :math:`u` as ``u``, and the adjoint state :math:`p` is given by -``p``. - -.. note:: - - As remarked in :ref:`demo_poisson`, in - classical FEniCS syntax we would use a :py:class:`fenics.TrialFunction` for ``u`` - and a :py:class:`fenics.TestFunction` for ``p``. However, for cashocs this must not - be the case. Instead, the state and adjoint variables have to be :py:class:`fenics.Function` objects. - -The right-hand side of the PDE constraint is then defined as :: - - x = SpatialCoordinate(mesh) - f = 2.5 * pow(x[0] + 0.4 - pow(x[1], 2), 2) + pow(x[0], 2) + pow(x[1], 2) - 1 - -which allows us to define the weak form of the state equation via :: - - e = inner(grad(u), grad(p)) * dx - f * p * dx - bcs = DirichletBC(V, Constant(0), boundaries, 1) - -The optimization problem and its solution -***************************************** - -We are now almost done, the only thing left to do is to define the cost functional :: - - J = cashocs.IntegralFunctional(u * dx) - - -and the shape optimization problem :: - - sop = cashocs.ShapeOptimizationProblem(e, bcs, J, u, p, boundaries, config) - - -This can then be solved in complete analogy to :ref:`demo_poisson` with -the :py:meth:`sop.solve() ` command :: - - sop.solve() - -The result of the optimization looks like this - - -.. image:: /../../demos/documented/shape_optimization/shape_poisson/img_shape_poisson.png - -.. note:: - - As in :ref:`demo_poisson` we can specify some keyword - arguments for the :py:meth:`solve ` command. - If none are given, then the settings from the config file are used, but if - some are given, they override the parameters specified - in the config file. In particular, these arguments are - - - ``algorithm`` : Specifies which solution algorithm shall be used. - - ``rtol`` : The relative tolerance for the optimization algorithm. - - ``atol`` : The absolute tolerance for the optimization algorithm. - - ``max_iter`` : The maximum amount of iterations that can be carried out. - - The possible choices for these parameters are discussed in detail in - :ref:`config_shape_optimization_routine` and the documentation of the :py:func:`solve ` - method. - - As before, it is not strictly necessary to supply config files to cashocs, but - it is very strongly recommended to do so. In case one does not supply a config - file, one has to at least specify the solution algorithm in the call to - the :py:meth:`solve ` method. diff --git a/docs/source/user/demos/shape_optimization/doc_shape_stokes.rst b/docs/source/user/demos/shape_optimization/doc_shape_stokes.rst deleted file mode 100755 index 52934776..00000000 --- a/docs/source/user/demos/shape_optimization/doc_shape_stokes.rst +++ /dev/null @@ -1,220 +0,0 @@ -.. _demo_shape_stokes: - -Optimization of an Obstacle in Stokes Flow -========================================== - -Problem Formulation -------------------- - -For this tutorial, we investigate a problem similarly to the ones considered in, e.g., -`Blauth, Nonlinear Conjugate Gradient Methods for PDE Constrained Shape Optimization -Based on Steklov-Poincaré-Type Metrics `_ and -`Schulz and Siebenborn, Computational Comparison of Surface Metric for PDE Constrained Shape Optimization -`_, which reads: - -.. math:: - - &\min_\Omega J(u, \Omega) = \int_{\Omega^\text{flow}} Du : Du\ \text{ d}x \\ - &\text{subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - - \Delta u + \nabla p &= 0 \quad &&\text{ in } \Omega, \\ - \text{div}(u) &= 0 \quad &&\text{ in } \Omega, \\ - u &= u^\text{in} \quad &&\text{ on } \Gamma^\text{in}, \\ - u &= 0 \quad &&\text{ on } \Gamma^\text{wall} \cup \Gamma^\text{obs}, \\ - \partial_n u - p n &= 0 \quad &&\text{ on } \Gamma^\text{out}. - \end{alignedat} - \right. - -Here, we have an inflow boundary condition on the inlet :math:`\Gamma^\text{in}`, -a no-slip boundary condition on the wall boundary :math:`\Gamma^\text{wall}` as well -as the obstacle boundary :math:`\Gamma^\text{obs}`, and a do-nothing condition -on the outlet :math:`\Gamma^\text{out}`. - -This problem is supplemented with the geometrical constraints - -.. math:: - - \text{vol}(\Omega) = \int_\Omega 1 \text{ d}x &= \int_{\Omega_0} 1 \text{ d}x = \text{vol}(\Omega_0), \\ - \text{bary}(\Omega) = \frac{1}{\text{vol}(\Omega)} \int_\Omega x \text{ d}x &= \frac{1}{\text{vol}(\Omega_0)} \int_{\Omega_0} x \text{ d}x = \text{bary}(\Omega_0), - -where :math:`\Omega_0` denotes the initial geometry. This models that both the volume -and the barycenter of the geometry should remain fixed during the optimization. -To treat this problem with cashocs, we regularize the constraints in the sense -of a quadratic penalty function. See :ref:`the corresponding section in the documentation -of the config files for shape optimization ` as well -as :ref:`demo_regularization`. In particular, the regularized problem reads - -.. math:: - - \min_\Omega J(u, \Omega) = &\int_{\Omega^\text{flow}} Du : Du\ \text{ d}x + - \frac{\mu_\text{vol}}{2} \left( \int_\Omega 1 \text{ d}x - \text{vol}(\Omega_0) \right)^2 \\ - &+ \frac{\mu_\text{bary}}{2} \left\lvert \frac{1}{\text{vol}(\Omega)} \int_\Omega x \text{ d}x - \text{bary}(\Omega_0) \right\rvert^2 \\ - &\text{subject to } \quad \left\lbrace \quad - \begin{alignedat}{2} - - \Delta u + \nabla p &= 0 \quad &&\text{ in } \Omega, \\ - \text{div}(u) &= 0 \quad &&\text{ in } \Omega, \\ - u &= u^\text{in} \quad &&\text{ on } \Gamma^\text{in}, \\ - u &= 0 \quad &&\text{ on } \Gamma^\text{wall} \cup \Gamma^\text{obs}, \\ - \partial_n u - p n &= 0 \quad &&\text{ on } \Gamma^\text{out}. - \end{alignedat} - \right. - -where we have no additional geometrical constraints. However, the parameters -:math:`\mu_\text{vol}` and :math:`\mu_\text{bary}` have to be sufficiently large, -so that the regularized constraints are satisfied approximately. - -Note, that the domain :math:`\Omega` denotes the domain of the obstacle, but that -the cost functional involves an integral over the flow domain :math:`\Omega^\text{flow}`. -Therefore, we switch our point of view and optimize the geometry of the flow instead -(which of course includes the optimization of the "hole" generated by the obstacle). -In particular, only the boundary of the obstacle :math:`\Gamma^\text{obs}` is deformable, all remaining boundaries -are fixed. Hence, it is equivalent to pose the geometrical constraints on -:math:`\Omega` and :math:`\Omega^\text{flow}`, so that we can work completely with the -flow domain. - -As (initial) domain, we consider the rectangle :math:`(-2, 2) \times (-3, 3)` -without a circle centered in the origin with radius ``0.5``. The correspondig -geometry is created with GMSH, and can be found in the ``./mesh/`` directory. - - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_shape_stokes.py `, -and the corresponding config can be found in :download:`config.ini `. - -Initialization -************** - -As for the previous tutorial problems, we start by importing FEniCS and cashocs:: - - from fenics import * - - import cashocs - - config = cashocs.load_config("./config.ini") - -Afterwards we import the (initial) geometry with the :py:func:`import_mesh ` -command :: - - mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh("./mesh/mesh.xdmf") - - -.. note:: - - As defined in ``./mesh/mesh.geo``, we have the following boundaries - - - The inlet boundary :math:`\Gamma^\text{in}`, which has index ``1`` - - The wall boundary :math:`\Gamma^\text{wall}`, which have index ``2`` - - The outlet boundary :math:`\Gamma^\text{out}`, which has index ``3`` - - The boundary of the obstacle :math:`\Gamma^\text{obs}`, which has index ``4`` - - To include the fact that only the obstacle boundary, with index 4, is deformable, - we have the following lines in the config file :: - - [ShapeGradient] - shape_bdry_def = [4] - shape_bdry_fix = [1,2,3] - - which ensures that only :math:`\Gamma^\text{obs}` is deformable. - -State system -************ - -The definition of the state system is analogous to the one we considered in -:ref:`demo_stokes`. Here, we, too, use LBB stable Taylor-Hood elements, which are -defined as :: - - v_elem = VectorElement("CG", mesh.ufl_cell(), 2) - p_elem = FiniteElement("CG", mesh.ufl_cell(), 1) - V = FunctionSpace(mesh, MixedElement([v_elem, p_elem])) - -For the weak form of the PDE, we have the same code as in :ref:`demo_stokes` :: - - up = Function(V) - u, p = split(up) - vq = Function(V) - v, q = split(vq) - - e = inner(grad(u), grad(v)) * dx - p * div(v) * dx - q * div(u) * dx - -The Dirichlet boundary conditions are slightly different, though. For the inlet -velocity :math:`u^\text{in}` we use a parabolic profile :: - - u_in = Expression(("-1.0/4.0*(x[1] - 2.0)*(x[1] + 2.0)", "0.0"), degree=2) - bc_in = DirichletBC(V.sub(0), u_in, boundaries, 1) - -The wall and obstacle boundaries get a no-slip boundary condition, each, with the -line :: - - bc_no_slip = cashocs.create_dirichlet_bcs( - V.sub(0), Constant((0, 0)), boundaries, [2, 4] - ) - -Finally, all Dirichlet boundary conditions are gathered into the list ``bcs`` :: - - bcs = [bc_in] + bc_no_slip - -.. note:: - - The outflow boundary condition is of Neumann type and already included in the - weak form of the problem. - -Cost functional and optimization problem -**************************************** - -The cost functional is easily defined with the line :: - - J = cashocs.IntegralFunctional(inner(grad(u), grad(u)) * dx) - - -.. note:: - - The additional regularization terms are defined similarly to :ref:`demo_regularization`. - In the config file, we have the following (relevant) lines:: - - [Regularization] - factor_volume = 1e4 - use_initial_volume = True - factor_barycenter = 1e5 - use_initial_barycenter = True - - This ensures that we use :math:`\mu_\text{vol} = 1e4` and :math:`\mu_\text{bary} = 1e5`, - which are comparatively large parameters, so that the geometrical constraints are satisfied - with high accuracy. - Moreover, the boolean flags ``use_initial_volume`` and ``use_initial_barycenter``, - which are both set to ``True``, ensure that we actually use the volume and - barycenter of the initial geometry. - -Finally, we solve the shape optimization problem as previously with the commands :: - - sop = cashocs.ShapeOptimizationProblem(e, bcs, J, up, vq, boundaries, config) - sop.solve() - -.. note:: - - For the definition of the shape gradient, we use the same parameters for the - linear elasticity equations as in `Blauth, Nonlinear Conjugate Gradient Methods for PDE Constrained Shape Optimization - Based on Steklov-Poincaré-Type Metrics `_ and - `Schulz and Siebenborn, Computational Comparison of Surface Metric for PDE Constrained Shape Optimization - `_. These are defined in the config file - in the :ref:`ShapeGradient section ` :: - - lambda_lame = 0.0 - damping_factor = 0.0 - mu_fix = 1 - mu_def = 5e2 - - so that we use a rather high stiffness for the elements at the deformable boundary, - which is also discretized finely, and a rather low stiffness for the fixed - boundaries. - -.. note:: - - This demo might take a little longer than the others, depending on the machine - it is run on. This is normal, and caused by the finer discretization of the geometry - compared to the previous problems. - -The results should look like this - -.. image:: /../../demos/documented/shape_optimization/shape_stokes/img_shape_stokes.png diff --git a/docs/source/user/demos/shape_optimization/doc_space_mapping_semilinear_transmission.rst b/docs/source/user/demos/shape_optimization/doc_space_mapping_semilinear_transmission.rst deleted file mode 100755 index a8e985c6..00000000 --- a/docs/source/user/demos/shape_optimization/doc_space_mapping_semilinear_transmission.rst +++ /dev/null @@ -1,314 +0,0 @@ -.. _demo_space_mapping_semilinear_transmission: - -Space Mapping Shape Optimization - Semilinear Transmission Problem -================================================================== - -Problem Formulation -------------------- - -In this demo we detail the space mapping capabilities of cashocs. The space mapping technique is used to optimize a fine (detailed, costly, expensive) model with the help of a coarse (cheaper, approximate) one. In particular, only coarse model optimizations are required, whereas for the fine model we only require simulations. This makes the technique very attractive when using commercial solvers for the fine model, while using, e.g., cashocs, as solver for the coarse model optimization problem. -For an overview over the space mapping technique, we refer the reader, e.g., to `Bakr, Bandler, Madsen, Sondergaard - An Introduction to the Space Mapping Technique `_ and `Echeverria and Hemker - Space mapping and defect correction `_. For a detailed description of the methods in the context of shape optimization, we refer to `Blauth - Space Mapping for PDE Constrained Shape Optimization `_. - -For this example, we consider the one investigated numerically in Section 4.2 of `Blauth - Space Mapping for PDE Constrained Shape Optimization `_. -Let us now state the semi linear transmission which we want to solve. It is given by - -.. math:: - - &\min_\Omega J(u_f, \Omega) = \int_D (u_f - u_\mathrm{des})^2 \text{ d}x \\ - &\text{subject to} \quad \left\lbrace \quad - \begin{alignedat}{2} - -\nabla (\alpha \nabla u_f) + \beta u_f^3 &= f \quad &&\text{ in } D,\\ - u_f &= 0 \quad &&\text{ on } \partial D,\\ - [\![ u_f ]\!]_{\Gamma} &= 0,\\ - [\![ \alpha \partial_n u_f ]\!]_{\Gamma} &= 0. - \end{alignedat} \right. - -Here, :math:`\alpha` is given by :math:`\alpha(x) = \chi_\Omega(x) \alpha_1 + (1 - \chi_\Omega(x)) \alpha_2` and :math:`f` is given by :math:`f(x) = \chi_\Omega(x) f_1 + (1 - \chi_\Omega(x)) f_2`. -The above optimization problem plays the role of the fine model. Note that the difficulty of this problem comes from the nonlinear term in the state equation, which makes the PDE constraint harder to solver than a linear equation. Therefore, we want to use an approximate model which is easier to solve. Our coarse model for this problem is given by - -.. math:: - - &\min_\Omega J(u_c, \Omega) = \int_D (u_c - u_\mathrm{des})^2 \text{ d}x \\ - &\text{subject to} \quad \left\lbrace \quad - \begin{alignedat}{2} - -\nabla (\alpha \nabla u_c) &= f \quad &&\text{ in } D,\\ - u_c &= 0 \quad &&\text{ on } \partial D,\\ - [\![ u_c ]\!]_{\Gamma} &= 0,\\ - [\![ \alpha \partial_n u_c ]\!]_{\Gamma} &= 0. - \end{alignedat} \right. - -The difference between the fine and the coarse model optimization problems is now only the difference in the PDE constraints, i.e., the coarse model problem is the same as the fine model with :math:`\beta = 0`. This can also be interpreted as linearization of the fine model around :math:`u = 0`. - -.. note:: - Note that we also need a misalignment function in order to match the fine and coarse model outputs. For this problem, a natural choice is to use the misalignment function - - .. math:: - - r(u, v) = \int_D (u - v)^2 \text{ d}x. - - This leads to a parameter extraction step in which the following subproblem has to be solved - - .. math:: - - &\min_\Omega J(u_p, \Omega) = \int_D (u_p - u_f)^2 \text{ d}x \\ - &\text{subject to} \quad \left\lbrace \quad - \begin{alignedat}{2} - - \nabla (\alpha \nabla u_p) &= f \quad &&\text{ in } D,\\ - u_p &= 0 \quad &&\text{ on } \partial D,\\ - [\![ u_p ]\!]_{\Gamma} &= 0,\\ - [\![ \alpha \partial_n u_p ]\!]_{\Gamma} &= 0. - \end{alignedat} \right. - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_space_mapping_semilinear_transmission.py `, -and the corresponding config can be found in :download:`config.ini `. - - -The coarse model -**************** - -We start our description of the space mapping technqiue with the implementation of the coarse model. For this, we can use standard cashocs syntax, with the difference that we will not put the cost functional, etc., into a :py:class:`cashocs.ShapeOptimizationProblem`, but we will use a :py:class:`CoarseModel ` for the coarse model. - -As usual, we begin the script with importing cashocs and fenics :: - - import os - - from fenics import * - - import cashocs - - -Next, we define the space mapping module we are using in the line :: - - space_mapping = cashocs.space_mapping.shape_optimization - -and, further, we decrease the verbosity of cashocs so that we can see only the output of the space mapping routine :: - - cashocs.set_log_level(cashocs.LogLevel.ERROR) - -Finally, we define the path to the current directory (where the script is located) via :: - - dir = os.path.dirname(os.path.realpath(__file__)) - - -Next, we define some model parameters and load the configuration file for the problem :: - - alpha_1 = 1.0 - alpha_2 = 10.0 - f_1 = 1.0 - f_2 = 10.0 - beta = 100.0 - cfg = cashocs.load_config("config.ini") - -In the next step, we define our desired state :math:`u_\mathrm{des}` as solution of the fine model state constraint with a given geometry :math:`\Omega` :: - - def create_desired_state(alpha_1, alpha_2, beta, f_1, f_2): - mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh( - "./mesh/reference.xdmf" - ) - V = FunctionSpace(mesh, "CG", 1) - u = Function(V) - v = TestFunction(V) - F = ( - Constant(alpha_1) * dot(grad(u), grad(v)) * dx(1) - + Constant(alpha_2) * dot(grad(u), grad(v)) * dx(2) - + Constant(beta) * pow(u, 3) * v * dx - - Constant(f_1) * v * dx(1) - - Constant(f_2) * v * dx(2) - ) - bcs = cashocs.create_dirichlet_bcs(V, Constant(0.0), boundaries, [1, 2, 3, 4]) - cashocs.newton_solve(F, u, bcs, verbose=False) - - return u - - - u_des_fixed = create_desired_state(alpha_1, alpha_2, beta, f_1, f_2) - -Here, ``u_des_fixed`` plays the role of the fixed desired state, which is given on a different mesh than the one we consider for the optimization later on. - -.. note:: - - As ``u_des_fixed`` is given on another mesh and represents a fixed state, it has to be re-interpolated during each iteration of the optimization algorithms. This is due to the fact that, otherwise, it would be moved along with the mesh / geometry that is to be optimized and then become distorted. Therefore, a re-interpolation has to happen. - -Now, we can define the coarse model, in analogy to :ref:`demo_shape_poisson` :: - - mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh("./mesh/mesh.xdmf") - V = FunctionSpace(mesh, "CG", 1) - - u = Function(V) - p = Function(V) - u_des = Function(V) - F = ( - Constant(alpha_1) * dot(grad(u), grad(p)) * dx(1) - + Constant(alpha_2) * dot(grad(u), grad(p)) * dx(2) - - Constant(f_1) * p * dx(1) - - Constant(f_2) * p * dx(2) - ) - bcs = cashocs.create_dirichlet_bcs(V, Constant(0.0), boundaries, [1, 2, 3, 4]) - J = cashocs.IntegralFunctional(Constant(0.5) * pow(u - u_des, 2) * dx) - coarse_model = space_mapping.CoarseModel(F, bcs, J, u, p, boundaries, config=cfg) - -As mentioned earlier, we now use the :py:class:`CoarseModel ` instead of :py:class:`ShapeOptimizationProblem `. - -The fine model -************** - -After defining the coarse model, we can now define the fine model by overloading the :py:class:`FineModel ` class :: - - class FineModel(space_mapping.FineModel): - def __init__(self, mesh, alpha_1, alpha_2, beta, f_1, f_2, u_des_fixed): - super().__init__(mesh) - self.u = Constant(0.0) - self.iter = 0 - - self.alpha_1 = alpha_1 - self.alpha_2 = alpha_2 - self.beta = beta - self.f_1 = f_1 - self.f_2 = f_2 - self.u_des_fixed = u_des_fixed - - def solve_and_evaluate(self) -> None: - self.iter += 1 - cashocs.io.write_out_mesh( - self.mesh, "./mesh/mesh.msh", f"./mesh/fine/mesh_{self.iter}.msh" - ) - cashocs.convert( - f"{dir}/mesh/fine/mesh_{self.iter}.msh", f"{dir}/mesh/fine/mesh.xdmf" - ) - mesh, self.subdomains, boundaries, dx, ds, dS = cashocs.import_mesh( - "./mesh/fine/mesh.xdmf" - ) - - V = FunctionSpace(mesh, "CG", 1) - u = Function(V) - u_des = Function(V) - v = TestFunction(V) - F = ( - Constant(self.alpha_1) * dot(grad(u), grad(v)) * dx(1) - + Constant(self.alpha_2) * dot(grad(u), grad(v)) * dx(2) - + Constant(self.beta) * pow(u, 3) * v * dx - - Constant(self.f_1) * v * dx(1) - - Constant(self.f_2) * v * dx(2) - ) - bcs = cashocs.create_dirichlet_bcs(V, Constant(0.0), boundaries, [1, 2, 3, 4]) - cashocs.newton_solve(F, u, bcs, verbose=False) - - LagrangeInterpolator.interpolate(u_des, self.u_des_fixed) - - self.cost_functional_value = assemble(Constant(0.5) * pow(u - u_des, 2) * dx) - self.u = u - -.. note:: - - The ``__init__`` method of the fine model initializes the model and saves the parameters to make them accessible to the class. - Users have to overload the ``solve_and_evaluate`` method of the :py:class:`FineModel ` class so that the fine model is actually solved and the cost function value is computed during the call to this method. - -.. note:: - Let us go over some implementation details of the fine model's ``solve_and_evaluate`` method, as defined here. First, an iteration counter is incremented with the line :: - - self.iter += 1 - - Next, the fine model mesh is saved to a file. This is done in order to be able to re-import it with the correct physical tags as defined with Gmsh. This is done with the lines :: - - cashocs.io.write_out_mesh( - self.mesh, "./mesh/mesh.msh", f"./mesh/fine/mesh_{self.iter}.msh" - ) - cashocs.convert( - f"{dir}/mesh/fine/mesh_{self.iter}.msh", f"{dir}/mesh/fine/mesh.xdmf" - ) - mesh, self.subdomains, boundaries, dx, ds, dS = cashocs.import_mesh( - "./mesh/fine/mesh.xdmf" - ) - - In the following lines, the fine model state constraint is defined and then solved :: - - V = FunctionSpace(mesh, "CG", 1) - u = Function(V) - u_des = Function(V) - v = TestFunction(V) - F = ( - Constant(self.alpha_1) * dot(grad(u), grad(v)) * dx(1) - + Constant(self.alpha_2) * dot(grad(u), grad(v)) * dx(2) - + Constant(self.beta) * pow(u, 3) * v * dx - - Constant(self.f_1) * v * dx(1) - - Constant(self.f_2) * v * dx(2) - ) - bcs = cashocs.create_dirichlet_bcs(V, Constant(0.0), boundaries, [1, 2, 3, 4]) - cashocs.newton_solve(F, u, bcs, verbose=False) - - - After solving the fine model PDE constraint, we re-interpolate the desired state to the current mesh with the line :: - - LagrangeInterpolator.interpolate(u_des, self.u_des_fixed) - - Here, ``u_des`` is the desired state on the fine model mesh. Finally, we evaluate the cost functional and store the solution of the PDE constraint with the lines :: - - self.cost_functional_value = assemble(Constant(0.5) * pow(u - u_des, 2) * dx) - self.u = u - -.. attention:: - - Users have to overwrite the attribute ``cost_functional_value`` of the fine model class since the space mapping algorithm makes usage of this attribute. - -.. note:: - - In the ``solve_and_evaluate`` method, we do not have to use the same discretization of the geometry for the coarse and fine models. In particular, we could remesh the geometry with a finer discretization. For an overview of how this can be done, we refer to :ref:`demo_space_mapping_uniform_flow_distribution`. - -After having define the fine model class, we instantiate it and define a placeholder function for the solution of the fine model :: - - fine_model = FineModel(mesh, alpha_1, alpha_2, beta, f_1, f_2, u_des_fixed) - u_fine = Function(V) - -As mentioned earlier, due to the fact that the geometry changes during the optimization, the desired state has to be re-interpolated to the changing mesh in each iteration. We do so by using a callback function which is defined as :: - - def callback(): - LagrangeInterpolator.interpolate(u_des, u_des_fixed) - LagrangeInterpolator.interpolate(u_fine, fine_model.u) - -Parameter Extraction -******************** - -As mentioned in the beginning, in order to perform the space mapping, we have to establish a connection between the coarse and the fine models. This is done via the parameter extraction step, which we now detail. For this, a new cost functional (the misalignment function) has to be defined, and the corresponding optimization problem (constrained by the coarse model) is solved in each space mapping iteration. For our problem, this is done via :: - - u_param = Function(V) - J_param = cashocs.IntegralFunctional(Constant(0.5) * pow(u_param - u_fine, 2) * dx) - parameter_extraction = space_mapping.ParameterExtraction( - coarse_model, J_param, u_param, config=cfg, mode="initial" - ) - -but of course other approaches are possible. - - -Space Mapping Problem and Solution -********************************** - -Finally, we have all ingredients available to define the space mapping problem and solve it. This is done with the lines :: - - problem = space_mapping.SpaceMappingProblem( - fine_model, - coarse_model, - parameter_extraction, - method="broyden", - max_iter=25, - tol=1e-2, - use_backtracking_line_search=False, - broyden_type="good", - memory_size=5, - verbose=True, - save_history=True, - ) - problem.inject_pre_callback(callback) - problem.solve() - -There, we first define the problem, then inject the callback function we defined above so that the required re-interpolation takes place, and solve the problem with the call of it's :py:meth:`solve ` method. - -The result of the optimization looks like this - -.. image:: /../../demos/documented/shape_optimization/space_mapping_semilinear_transmission/img_space_mapping_semilinear_transmission.png - -.. note:: - - The left image shows the optimized geometry with the coarse model, the middle image shows the optimized geometry with the fine model (with the space mapping technique), and the right image shows the reference geometry, which we were trying to reconstruct. We can see that using the coarse model alone as approximation of the original problem does not work sufficiently well as we recover some kind of rotated peanut shape, instead of an rotated ellipse. However, we see that the space mapping approach works very well for recovering the desired ellipse. diff --git a/docs/source/user/demos/shape_optimization/doc_space_mapping_uniform_flow_distribution.rst b/docs/source/user/demos/shape_optimization/doc_space_mapping_uniform_flow_distribution.rst deleted file mode 100755 index 92dde112..00000000 --- a/docs/source/user/demos/shape_optimization/doc_space_mapping_uniform_flow_distribution.rst +++ /dev/null @@ -1,405 +0,0 @@ -.. _demo_space_mapping_uniform_flow_distribution: - -Space Mapping Shape Optimization - Uniform Flow Distribution -============================================================ - -Problem Formulation -------------------- - -Let us consider another example of the space mapping technique, this time with an application to -fluid dynamics. For an introduction to the space mapping technique, we refer the reader, e.g., to `Bakr, Bandler, Madsen, Sondergaard - An Introduction to the Space Mapping Technique `_ and `Echeverria and Hemker - Space mapping and defect correction `_. For a detailed description of the methods in the context of shape optimization, we refer to `Blauth - Space Mapping for PDE Constrained Shape Optimization `_. - -The example is taken from `Blauth - Space Mapping for PDE Constrained Shape Optimization `_ and we refer the reader to this publication for a detailed discussion of the problem setup. - -Assume that we have a pipe system consisting of one inlet :math:`\Gamma_\mathrm{in}` and three outlets :math:`\Gamma_\mathrm{out}^i` for :math:`i=1,2,3`. The geometry of the pipe is denoted by :math:`\Omega` and the wall of the pipe is denoted by :math:`\Gamma_\mathrm{wall}`. A sketch of this problem is shown below - -.. image:: /../../demos/documented/shape_optimization/space_mapping_uniform_flow_distribution/reference_pipe.png - -We consider the optimization of the pipe system so that the flow of some fluid becomes uniform over all three outlets. Therefore, let :math:`u` denote the fluid's velocity. The outlet flow rate :math:`q_\mathrm{out}^i(u)` over pipe :math:`i` is defined as - -.. math:: q_\mathrm{out}^i(u) = \int_{\Gamma_\mathrm{out}^i} u \cdot n \text{ d}s - -Therefore, we can define our optimization problem as follows - -.. math:: - - &\min_\Omega J(u_f, \Omega) = \frac{1}{2} \sum_{i=1}^{3} \left( q_\mathrm{out}^i(u_f) - q_\mathrm{des} \right)^2 \\ - &\text{subject to} \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta u_f + \mathrm{Re} (u_f \cdot \nabla) u_f + \nabla p_f &= 0 \quad &&\text{ in } \Omega,\\ - \text{div}(u_f) &= 0 \quad &&\text{ in } \Omega,\\ - u_f &= u_\mathrm{in} \quad &&\text{ on } \Gamma_\mathrm{in},\\ - u_f &= 0 \quad &&\text{ on } \Gamma_\mathrm{wall},\\ - p_f &= 0 \quad &&\text{ on } \Gamma_\mathrm{out},\\ - u_f \times n &= 0 \quad &&\text{ on } \Gamma_\mathrm{out}. - \end{alignedat} \right. - -Here, we use the incompressible Navier-Stokes equations as PDE constraint. Note that the above model plays the role of the fine model, which is the problem we are interested in solving. - -However, as solving the nonlinear Navier-Stokes equations can be difficult and time consuming, we use a coarse model consisting of the linear Stokes system, so that the coarse model optimization problem is given by - -.. math:: - - &\min_\Omega J(u_c, \Omega) = \frac{1}{2} \sum_{i=1}^{3} \left( q_\mathrm{out}^i(u_c) - q_\mathrm{des} \right)^2 \\ - &\text{subject to} \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta u_c + \nabla p_c &= 0 \quad &&\text{ in } \Omega,\\ - \text{div}(u_c) &= 0 \quad &&\text{ in } \Omega,\\ - u_c &= u_\mathrm{in} \quad &&\text{ on } \Gamma_\mathrm{in},\\ - u_c &= 0 \quad &&\text{ on } \Gamma_\mathrm{wall},\\ - p_c &= 0 \quad &&\text{ on } \Gamma_\mathrm{out},\\ - u_c \times n &= 0 \quad &&\text{ on } \Gamma_\mathrm{out}. - \end{alignedat} \right. - -.. note:: - - Again, we need a misalignment function to match the fine and coarse model responses, see :ref:`demo_space_mapping_semilinear_transmission`. For this problem, we use the misalignment function - - .. math:: - - r(u,v) = \frac{1}{2} \sum_{i=1}^3 \left( q_\mathrm{out}^i(u) - q_\mathrm{out}^i(v) \right)^2 - - so that the parameter extraction problem which has to be solved in each iteration of the space mapping method reads - - .. math:: - - &\min_\Omega J(u_p, \Omega) = \frac{1}{2} \sum_{i=1}^{3} \left( q_\mathrm{out}^i(u_p) - q_\mathrm{out}^i(u_f) \right)^2 \\ - &\text{subject to} \quad \left\lbrace \quad - \begin{alignedat}{2} - -\Delta u_p + \nabla p_p &= 0 \quad &&\text{ in } \Omega,\\ - \text{div}(u_p) &= 0 \quad &&\text{ in } \Omega,\\ - u_p &= u_\mathrm{in} \quad &&\text{ on } \Gamma_\mathrm{in},\\ - u_p &= 0 \quad &&\text{ on } \Gamma_\mathrm{wall},\\ - p_p &= 0 \quad &&\text{ on } \Gamma_\mathrm{out},\\ - u_p \times n &= 0 \quad &&\text{ on } \Gamma_\mathrm{out}. - \end{alignedat} \right. - - -Implementation --------------- - -The complete python code can be found in the file :download:`demo_space_mapping_uniform_flow_distribution.py `, -and the corresponding config can be found in :download:`config.ini `. - - -The coarse model -**************** - -As in :ref:`demo_space_mapping_semilinear_transmission`, we start with the implementation of the coarse model. We start by importing the required packages :: - - import os - import ctypes - import subprocess - - from fenics import * - - import cashocs - -and we will detail why we require the packages when they are used. Next, we define the space mapping module, set up the log level, and define the current working directory :: - - space_mapping = cashocs.space_mapping.shape_optimization - cashocs.set_log_level(cashocs.LogLevel.ERROR) - dir = os.path.dirname(os.path.realpath(__file__)) - -We then define the Reynolds number :math:`\mathrm{Re} = 100` and load the configuration file for the problem :: - - Re = 100.0 - cfg = cashocs.load_config("./config.ini") - -In the next steps, we define the coarse model, in analogy to all of the previous demos (see, e.g., :ref:`demo_shape_stokes` :: - - mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh("./mesh/mesh.xdmf") - n = FacetNormal(mesh) - - u_in = Expression(("6.0*(0.0 - x[1])*(x[1] + 1.0)", "0.0"), degree=2) - q_in = -assemble(dot(u_in, n) * ds(1)) - output_list = [] - - v_elem = VectorElement("CG", mesh.ufl_cell(), 2) - p_elem = FiniteElement("CG", mesh.ufl_cell(), 1) - V = FunctionSpace(mesh, v_elem * p_elem) - - up = Function(V) - u, p = split(up) - vq = Function(V) - v, q = split(vq) - - F = inner(grad(u), grad(v)) * dx - p * div(v) * dx - q * div(u) * dx - bc_in = DirichletBC(V.sub(0), u_in, boundaries, 1) - bcs_wall = cashocs.create_dirichlet_bcs( - V.sub(0), Constant((0.0, 0.0)), boundaries, [2, 3, 4] - ) - bc_out = DirichletBC(V.sub(0).sub(0), Constant(0.0), boundaries, 5) - bc_pressure = DirichletBC(V.sub(1), Constant(0.0), boundaries, 5) - bcs = [bc_in] + bcs_wall + [bc_out] + [bc_pressure] - - J = [cashocs.ScalarTrackingFunctional(dot(u, n) * ds(i), q_in / 3) for i in range(5, 8)] - - coarse_model = space_mapping.CoarseModel(F, bcs, J, up, vq, boundaries, config=cfg) - -.. note:: - - The difference between the standard cashocs syntax and the syntax for space mapping is that we now use :py:class:`CoarseModel ` instead of the usual :py:class:`ShapeOptimizationProblem `. - -.. note:: - - The boundary conditions consist of a prescribed flow at the inlet. In order to be compatible with the incompressibility condition, we need to have that the outlet flow rates sum up to the inlet flow rate, so that mass is neither created nor destroyed. Of course, one could use a target output flow rate which does not satisfy this condition, but this would be unphysical. Therefore, the boundary condition on the inlet is defined as :: - - u_in = Expression(("6.0*(0.0 - x[1])*(x[1] + 1.0)", "0.0"), degree=2) - bc_in = DirichletBC(V.sub(0), u_in, boundaries, 1) - q_in = -assemble(dot(u_in, n) * ds(1)) - - and the cost functional is given by :: - - J = [cashocs.ScalarTrackingFunctional(dot(u, n) * ds(i), q_in / 3) for i in range(5, 8)] - - so that the target outlet flow rate is given by ``q_in / 3``, which means that we have a uniform flow distribution. For more details regrading the usage of :py:class:`ScalarTrackingFunctional `, we refer to :ref:`demo_scalar_control_tracking`. - -The fine model -************** - -As next step, we define the fine model optimization problem as follows :: - - class FineModel(space_mapping.FineModel): - def __init__(self, mesh, Re, q_in, output_list): - super().__init__(mesh) - - self.tracking_goals = [ctypes.c_double(0.0) for _ in range(5, 8)] - - self.iter = 0 - self.Re = Re - self.q_in = q_in - self.output_list = output_list - - def solve_and_evaluate(self): - self.iter += 1 - - # write out the mesh - cashocs.io.write_out_mesh( - self.mesh, "./mesh/mesh.msh", f"./mesh/fine/mesh_{self.iter}.msh" - ) - cashocs.io.write_out_mesh(self.mesh, "./mesh/mesh.msh", f"./mesh/fine/mesh.msh") - - subprocess.run( - ["gmsh", "./mesh/fine.geo", "-2", "-o", "./mesh/fine/fine.msh"], - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - cashocs.convert("./mesh/fine/fine.msh", "./mesh/fine/fine.xdmf") - - mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh( - "./mesh/fine/fine.xdmf" - ) - n = FacetNormal(mesh) - v_elem = VectorElement("CG", mesh.ufl_cell(), 2) - p_elem = FiniteElement("CG", mesh.ufl_cell(), 1) - V = FunctionSpace(mesh, v_elem * p_elem) - R = FunctionSpace(mesh, "R", 0) - - up = Function(V) - u, p = split(up) - v, q = TestFunctions(V) - - F = ( - inner(grad(u), grad(v)) * dx - + Constant(self.Re) * inner(grad(u) * u, v) * dx - - p * div(v) * dx - - q * div(u) * dx - ) - - u_in = Expression(("6.0*(0.0 - x[1])*(x[1] + 1.0)", "0.0"), degree=2) - bc_in = DirichletBC(V.sub(0), u_in, boundaries, 1) - bcs_wall = cashocs.create_dirichlet_bcs( - V.sub(0), Constant((0.0, 0.0)), boundaries, [2, 3, 4] - ) - bc_out = DirichletBC(V.sub(0).sub(0), Constant(0.0), boundaries, 5) - bc_pressure = DirichletBC(V.sub(1), Constant(0.0), boundaries, 5) - bcs = [bc_in] + bcs_wall + [bc_out] + [bc_pressure] - - cashocs.newton_solve(F, up, bcs, verbose=False) - self.u, p = up.split(True) - - file = File(f"./pvd/u_{self.iter}.pvd") - file.write(self.u) - - J_list = [ - cashocs.ScalarTrackingFunctional(dot(self.u, n) * ds(i), self.q_in / 3) - for i in range(5, 8) - ] - self.cost_functional_value = cashocs._utils.summation( - [J.evaluate() for J in J_list] - ) - - self.flow_values = [assemble(dot(self.u, n) * ds(i)) for i in range(5, 8)] - self.output_list.append(self.flow_values) - - for idx in range(len(self.tracking_goals)): - self.tracking_goals[idx].value = self.flow_values[idx] - - - fine_model = FineModel(mesh, Re, q_in, output_list) - -Again, the fine model problem is defined by subclassing :py:class:`FineModel ` and overwriting its :py:meth:`solve_and_evaluate ` method. - -.. note:: - - Let us investigate the fine model in more details in the following. The fine models initialization starts with a call to its parent's ``__init__`` method, where the mesh is passed :: - - super().__init__(mesh) - - Next, a list of tracking goals is defined using the ``ctypes`` module :: - - self.tracking_goals = [ctypes.c_double(0.0) for _ in range(5, 8)] - - This module allows us to make floats in python mutable, i.e., to behave like pointers. This is needed for the parameter extraction step, where we want to find a coarse model "optimum" which achieves the same flow rates as the current iterate of the fine model. In particular, the list ``self.tracking_goals`` will be used later as input for the parameter extraction. - Afterwards, we have standard initializations of an iteration counter, the Reynolds number, the inlet flow rate, and an output list, which will be used to save the progress of the space mapping method :: - - self.iter = 0 - self.Re = Re - self.q_in = q_in - self.output_list = output_list - - Let us now take a look at the core of the fine model, its ``solve_and_evaluate`` method. It starts by incrementing the iteration counter :: - - self.iter += 1 - - Next, the current mesh is exported to two Gmsh .msh files. The first is used for a possible post processing (so that the evolution of the geometries is saved) whereas the second is used to define the fine model mesh :: - - cashocs.io.write_out_mesh( - self.mesh, "./mesh/mesh.msh", f"./mesh/fine/mesh_{self.iter}.msh" - ) - cashocs.io.write_out_mesh(self.mesh, "./mesh/mesh.msh", f"./mesh/fine/mesh.msh") - - Note that we do not use the same discretization for the fine and coarse model in this problem, but we remesh the geometry of the fine model using a higher resolution. To do so, the following Gmsh command is used, which is invoked via the ``subprocess`` module :: - - subprocess.run( - ["gmsh", "./mesh/fine.geo", "-2", "-o", "./mesh/fine/fine.msh"], - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - - Finally, the mesh generated with the above command is converted to XML with :py:func:`` :: - - cashocs.convert("./mesh/fine/fine.msh", "./mesh/fine/fine.xdmf") - - Now that we have the geometry of the problem, it is loaded into python and we define the Taylor-Hood Function Space for the Navier-Stokes system :: - - mesh, subdomains, boundaries, dx, ds, dS = cashocs.import_mesh( - "./mesh/fine/fine.xdmf" - ) - n = FacetNormal(mesh) - v_elem = VectorElement("CG", mesh.ufl_cell(), 2) - p_elem = FiniteElement("CG", mesh.ufl_cell(), 1) - V = FunctionSpace(mesh, v_elem * p_elem) - - up = Function(V) - u, p = split(up) - v, q = TestFunctions(V) - - Next, we define the weak form of the problem and its boundary conditions :: - - F = ( - inner(grad(u), grad(v)) * dx - + Constant(self.Re) * inner(grad(u) * u, v) * dx - - p * div(v) * dx - - q * div(u) * dx - ) - - u_in = Expression(("6.0*(0.0 - x[1])*(x[1] + 1.0)", "0.0"), degree=2) - bc_in = DirichletBC(V.sub(0), u_in, boundaries, 1) - bcs_wall = cashocs.create_dirichlet_bcs( - V.sub(0), Constant((0.0, 0.0)), boundaries, [2, 3, 4] - ) - bc_out = DirichletBC(V.sub(0).sub(0), Constant(0.0), boundaries, 5) - bc_pressure = DirichletBC(V.sub(1), Constant(0.0), boundaries, 5) - bcs = [bc_in] + bcs_wall + [bc_out] + [bc_pressure] - - The problem is then solved with :py:func:`` :: - - cashocs.newton_solve(F, up, bcs, verbose=False) - - Finally, after having solved the problem, we first save the solution for later visualization by :: - - self.u, p = up.split(True) - - file = File(f"./pvd/u_{self.iter}.pvd") - file.write(self.u) - - Next, we evaluate the cost functional with the lines :: - - J_list = [ - cashocs.ScalarTrackingFunctional(dot(self.u, n) * ds(i), self.q_in / 3) - for i in range(5, 8) - ] - self.cost_functional_value = cashocs._utils.summation( - [J.evaluate() for J in J_list] - ) - - Finally, we save the values of the outlet flow rate first to our list ``self.output_list`` and second to the list ``self.tracking_goals``, so that the parameter extraction can see the updated flow rates :: - - self.flow_values = [assemble(dot(self.u, n) * ds(i)) for i in range(5, 8)] - self.output_list.append(self.flow_values) - - for idx in range(len(self.tracking_goals)): - self.tracking_goals[idx].value = self.flow_values[idx] - - Note that we have to overwrite ``self.tracking_goals[idx].value`` as ``self.tracking_goals[idx]`` is a ``ctypes.double`` object. - -.. attention:: - - As already mentioned in :ref:`demo_space_mapping_semilinear_transmission`, users have to update the attribute ``cost_functional_value`` of the fine model in order for the space mapping method to be able to use the value. - -Parameter Extraction -******************** - -Now, we are finally ready to define the parameter extraction. This is done via :: - - up_param = Function(V) - u_param, p_param = split(up_param) - J_param = [ - cashocs.ScalarTrackingFunctional( - dot(u_param, n) * ds(i), fine_model.tracking_goals[idx] - ) - for idx, i in enumerate(range(5, 8)) - ] - parameter_extraction = space_mapping.ParameterExtraction( - coarse_model, J_param, up_param, config=cfg - ) - -.. note:: - - The parameter extraction uses the previously defined list ``fine_model.tracking_goals`` of type ``ctypes.double`` for the definition of its cost functional. - - -Space Mapping Problem and Solution -********************************** - -In the end, we can now define the space mapping problem and solve it with the lines :: - - problem = space_mapping.SpaceMappingProblem( - fine_model, - coarse_model, - parameter_extraction, - method="broyden", - max_iter=25, - tol=1e-4, - use_backtracking_line_search=False, - broyden_type="good", - memory_size=5, - save_history=True, - ) - problem.solve() - -The result of the optimization looks like this - -.. image:: /../../demos/documented/shape_optimization/space_mapping_uniform_flow_distribution/img_space_mapping_uniform_flow_distribution.png - -.. note:: - - On the left side of the above image the results for the coarse model are shown, whereas the results for the fine model are shown on the left side. On the top of the figure we see the geometries used for the models, and on the bottom the velocity magnitude is shown. - - We observe that the coarse model uses a much coarser discretization in comparison with the fine model. Moreover, we can also nicely see that the coarse model optimal geometry works fine for optimizing the Stokes system, but that the behavior of the fine model is vastly different due to the inertial effects of the Navier-Stokes system. However, the space mapping technique still allows us to solve the fine model problem with the help of the approximate coarse model, where we only require simulations of the fine model. - - For a more thorough discussion of the results, we refer the reader to `Blauth - Space Mapping for PDE Constrained Shape Optimization `_. diff --git a/docs/source/user/demos/shape_optimization/index.rst b/docs/source/user/demos/shape_optimization/index.rst new file mode 100644 index 00000000..a1cd8bf6 --- /dev/null +++ b/docs/source/user/demos/shape_optimization/index.rst @@ -0,0 +1,22 @@ +Shape Optimization Problems +=========================== + +In this part of the tutorial, we investigate how shape optimization problems can +be treated with cashocs. + +.. toctree:: + :maxdepth: 1 + :caption: List of all shape optimization problems: + + demo_shape_poisson.md + doc_config + demo_regularization.md + demo_inverse_tomography.md + demo_shape_stokes.md + demo_remeshing.md + demo_custom_scalar_product.md + demo_scaling.md + demo_eikonal_stiffness.md + demo_p_laplacian.md + demo_space_mapping_semilinear_transmission.md + demo_space_mapping_uniform_flow_distribution.md diff --git a/docs/source/user/demos/shape_optimization/shape_optimization_index.rst b/docs/source/user/demos/shape_optimization/shape_optimization_index.rst deleted file mode 100644 index eaf7bbe2..00000000 --- a/docs/source/user/demos/shape_optimization/shape_optimization_index.rst +++ /dev/null @@ -1,22 +0,0 @@ -Shape Optimization Problems -=========================== - -In this part of the tutorial, we investigate how shape optimization problems can -be treated with cashocs. - -.. toctree:: - :maxdepth: 1 - :caption: List of all shape optimization problems: - - doc_shape_poisson - doc_config - doc_regularization - doc_inverse_tomography - doc_shape_stokes - doc_remeshing - doc_custom_scalar_product - doc_scaling - doc_eikonal_stiffness - doc_p_laplacian - doc_space_mapping_semilinear_transmission - doc_space_mapping_uniform_flow_distribution diff --git a/docs/source/user/index.rst b/docs/source/user/index.rst index 1e519aea..eb9b7bab 100644 --- a/docs/source/user/index.rst +++ b/docs/source/user/index.rst @@ -23,9 +23,9 @@ Shape Optimization and Optimal Control Software