├── .circleci
└── config.yml
├── .flake8
├── .gitignore
├── .pre-commit-config.yaml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── NOTICE
├── README.md
├── docker
└── Dockerfile
├── letop
├── __about__.py
├── __init__.py
├── levelset
│ ├── __init__.py
│ ├── level_set_functional.py
│ └── regularization_solver.py
├── optimization
│ ├── __init__.py
│ ├── cg_hj_solver.py
│ ├── cg_reinit_solver.py
│ ├── hj_context.py
│ ├── interface.py
│ ├── nullspace_shape.py
│ └── utils.py
└── physics
│ ├── __init__.py
│ ├── advection_diffusion.py
│ ├── navier_stokes_brinkman.py
│ └── utils.py
├── letop_examples
├── __init__.py
├── bridge
│ ├── bridge.py
│ └── solver_parameters.py
├── cantilever
│ ├── __init__.py
│ ├── cantilever.py
│ ├── mesh_cantilever.geo
│ └── mesh_cantilever.geo.opt
├── heat_exchanger
│ ├── 2D_mesh.geo
│ ├── 2D_mesh.geo.opt
│ ├── 2D_mesh.py
│ ├── README.md
│ ├── __init__.py
│ ├── heat_exchanger_nls.py
│ └── parameters.py
├── heat_exchanger_3D
│ ├── box_heat_exch.geo
│ ├── box_heat_exch.geo.opt
│ ├── heat_exchanger.py
│ ├── parameters_box.py
│ ├── physics_solvers.py
│ ├── qois.py
│ └── solver_parameters.py
├── heat_exchanger_ns
│ ├── 2D_mesh.geo
│ ├── 2D_mesh.py
│ ├── heat_exchanger.py
│ └── params.py
└── navier_stokes
│ ├── mesh_stokes.geo
│ └── navier_stokes.py
├── pyproject.toml
├── setup.py
└── test
├── 2D_mesh.geo
├── 3D_mesh.geo
├── build_example_meshes.sh
├── conftest.py
├── nullspace
└── test_line_search.py
├── physics
└── test_navier_stokes.py
├── test_examples.py
├── test_hj_solver.py
├── test_regularization_solver.py
├── test_reinit_solver.py
├── unstructured_rectangle.geo
└── unstructured_rectangle.geo.opt
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | jobs:
3 | build:
4 | docker:
5 | - image: miguelsalazartroya/miguelsalazartroya-letop:latest
6 | steps:
7 | - checkout # check out the code in the project directory
8 | - run:
9 | name: install dependencies
10 | command: |
11 | source /home/firedrake/firedrake/bin/activate
12 | pip3 install roltrilinos ROL
13 | pip3 install protobuf==3.8.0
14 | pip3 install --upgrade numpy
15 | pip3 install pytest-cov
16 |
17 | # run tests!
18 | - run:
19 | name: Run tests
20 | no_output_timeout: 30m
21 | working_directory: test/
22 | command: |
23 | source /home/firedrake/firedrake/bin/activate
24 | pip3 install -e ../
25 | gmsh -2 2D_mesh.geo
26 | gmsh -3 3D_mesh.geo
27 | gmsh -2 -option unstructured_rectangle.geo.opt unstructured_rectangle.geo
28 | ./build_example_meshes.sh ${VIRTUAL_ENV}
29 | pytest --cov=../letop/ .
30 | bash <(curl -s https://codecov.io/bash)
31 | workflows:
32 | version: 2
33 | all:
34 | jobs:
35 | - build
36 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | ignore = E203, E266, E501, W503, F403, F401, E722
3 | max-line-length = 79
4 | max-complexity = 18
5 | select = B,C,E,F,W,T4,B9
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.vtu
2 | *.pvtu
3 | *.pvd
4 | *.vtk
5 | *.msh
6 | .cache
7 | .vim
8 | __pycache__
9 | build/
10 | dist/
11 | old_scripts/
12 | gallery/
13 | .vscode/
14 | .devcontainer/
15 | lestofire.*
16 | examples/heat_exchanger/2D_mesh.geo
17 |
18 | # TS history data
19 | **/TS*
20 |
21 | .eggs/
22 |
23 | *.txt
24 |
25 | **/.coverage
26 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | # See https://pre-commit.com for more information
2 | # See https://pre-commit.com/hooks.html for more hooks
3 | repos:
4 | - repo: https://github.com/pre-commit/pre-commit-hooks
5 | rev: v4.0.1
6 | hooks:
7 | - id: trailing-whitespace
8 | - id: end-of-file-fixer
9 | - id: check-yaml
10 | - id: check-added-large-files
11 | - repo: https://github.com/ambv/black
12 | rev: 21.5b2
13 | hooks:
14 | - id: black
15 | language_version: python3.7
16 | - repo: https://gitlab.com/pycqa/flake8
17 | rev: 3.7.9
18 | hooks:
19 | - id: flake8
20 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Code of Conduct"
3 | ---
4 |
5 | ## Community Code of Conduct
6 |
7 | ### Our Pledge
8 |
9 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
10 |
11 | ### Our Standards
12 |
13 | Examples of behavior that contributes to creating a positive environment include:
14 |
15 | * Using welcoming and inclusive language
16 | * Being respectful of differing viewpoints and experiences
17 | * Gracefully accepting constructive criticism
18 | * Focusing on what is best for the community
19 | * Showing empathy towards other community members
20 |
21 | Examples of unacceptable behavior by participants include:
22 |
23 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
24 | * Trolling, insulting/derogatory comments, and personal or political attacks
25 | * Public or private harassment
26 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
27 | * Other conduct which could reasonably be considered inappropriate in a professional setting
28 |
29 | ### Our Responsibilities
30 |
31 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
32 |
33 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
34 |
35 | ### Scope
36 |
37 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the LeToP project or its community. Examples of representing the project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of the project may be further defined and clarified by LeToP maintainers.
38 |
39 | ### Enforcement
40 |
41 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at LeToP or the LLNL GitHub Admins at [github-admin@llnl.gov](mailto:github-admin@llnl.gov) . The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
42 |
43 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project or organization's leadership.
44 |
45 | ### Attribution
46 |
47 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/) ([version 1.4](http://contributor-covenant.org/version/1/4)).
48 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to LeToP
2 |
3 | We welcome contributions to LeToP. To do so please submit a pull request through our
4 | LeToP github page at https://github.com/LLNL/letop.
5 |
6 | All contributions to LeToP must be made under the MIT License.
7 |
8 | Any questions can be sent to salazardetro1@llnl.gov.
9 |
10 | # Attribution
11 |
12 | The LeToP project uses git's commit history to track contributions from individual developers.
13 |
14 | Since we want everyone to feel they are getting the proper attribution for their contributions, please add your name to
15 | the list below as part of your commit.
16 |
17 | # Contributors (In Alphabetical Order)
18 |
19 | * Miguel Salazar, LLNL
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018, Lawrence Livermore National Security, LLC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | This work was produced under the auspices of the U.S. Department of Energy by
2 | Lawrence Livermore National Laboratory under Contract DE-AC52-07NA27344.
3 |
4 | This work was prepared as an account of work sponsored by an agency of the
5 | United States Government. Neither the United States Government nor Lawrence
6 | Livermore National Security, LLC, nor any of their employees makes any warranty,
7 | expressed or implied, or assumes any legal liability or responsibility for the
8 | accuracy, completeness, or usefulness of any information, apparatus, product, or
9 | process disclosed, or represents that its use would not infringe privately owned
10 | rights. Reference herein to any specific commercial product, process, or service
11 | by trade name, trademark, manufacturer, or otherwise does not necessarily
12 | constitute or imply its endorsement, recommendation, or favoring by the United
13 | States Government or Lawrence Livermore National Security, LLC. The views and
14 | opinions of authors expressed herein do not necessarily state or reflect those
15 | of the United States Government or Lawrence Livermore National Security, LLC,
16 | and shall not be used for advertising or product endorsement purposes.
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | [](https://codecov.io/gh/LLNL/letop)
3 |
4 |
5 |
6 |
7 |

8 |
9 |
10 | ## Level set topology optimization in Firedrake 🚧 🚧 (in progress)
11 |
12 | LeToP implements the level set method in topology optimization.
13 | It combines the automated calculation of shape derivatives in [Firedrake](https://gitlab.com/florian.feppon/null-space-optimizer) and [pyadjoint](https://github.com/dolfin-adjoint/pyadjoint) with the [Null space optimizer](https://gitlab.com/florian.feppon/null-space-optimizer) to find the optimal design. The level set in advected with a Hamilton-Jacobi equation and properly reinitialized to maintain the signed distance property.
14 |
15 | The user interface is very close to pyadjoint to allow for easy compatibility.
16 |
17 | # Installation
18 |
19 | Install with
20 |
21 | ```python
22 | pip3 install .
23 | ```
24 | at the project's root directory and with the Firedrake's virtual environment activated.
25 | LeTop depends on the [10.5281/zenodo.7017917](https://zenodo.org/record/7017917#.Y4YXbLLMKK0), which can be simply installed by passing the flag `--doi 10.5281/zenodo.7017917` to `firedrake-install`.
26 |
27 | ## Installation issues
28 | ```
29 | src/C/umfpack.c:23:10: fatal error: 'umfpack.h' file not found
30 | ```
31 | The package `cvxopt` cannot find the suitesparse library, which should be within PETSc (check the option `--download-suitesparse` was passed). Create the following environment variables before installing `letop`:
32 | ```
33 | export CVXOPT_SUITESPARSE_LIB_DIR=$PETSC_DIR/$PETSC_ARCH/lib
34 | export CVXOPT_SUITESPARSE_INC_DIR=$PETSC_DIR/$PETSC_ARCH/include
35 | ```
36 |
37 | # Examples
38 | ## Cantilever
39 |
40 | 
41 |
42 | ## Heat exchanger
43 |
44 | 
45 |
46 | ## Bridge
47 |
48 | 
49 |
50 |
51 |
52 | LLNL Release Number: LLNL-CODE-817098
53 |
54 | ## Considerations when using LeToP
55 |
56 | Make use of the pyadjoint context manager `stop_annotating()` and the decorator `no_annotations` for:
57 |
58 | - When using `interpolate()` or `project` from Firedrake as they might annotate unnecessary operations and result in wrong the shape derivatives.
59 | - Similarly, when extending LeToP routines, make sure you are not annotating additional operations as Firedrake annotates everything by default when importing `firedrake_adjoint`.
60 | - The shape velocities should be zeroed on the Dirichlet and Neumann boundaries. Use the `RegularizationSolver` boundary conditions to this effect.
61 | - Add fd.parameters["form_compiler"]["quadrature_degree"] = 4 to ensure quadrature does not go too high due to the heaviside function (used to mark the subdomains)
62 | - The isocontours of the level set must have enough mesh resolution, otherwise the reinitialization solver might fail.
63 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM firedrakeproject/firedrake-env:latest
2 |
3 | # This DockerFile is looked after by
4 | MAINTAINER Miguel Salazar
5 | USER root
6 |
7 | RUN apt-get update && apt-get install -y wget libglu1 libxcursor-dev libxinerama1
8 | RUN pip3 install meshio[all] pygmsh
9 | USER firedrake
10 | WORKDIR /home/firedrake
11 |
12 | # Now install Firedrake.
13 | RUN curl -O https://raw.githubusercontent.com/firedrakeproject/firedrake/master/scripts/firedrake-install
14 | RUN bash -c "python3 firedrake-install --no-package-manager --disable-ssh --remove-build-files --pip-install scipy --doi 10.5281/zenodo.5526481"
15 |
--------------------------------------------------------------------------------
/letop/__about__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 |
4 | __author__ = u"Miguel Salazar de Troya"
5 | __email__ = "salazardetro1@llnl.gov"
6 | __copyright__ = u"Copyright (c) 2019 {} <{}>".format(__author__, __email__)
7 | __license__ = "License :: OSI Approved :: MIT License"
8 | __version__ = "0.0.1"
9 | __status__ = "Development Status :: 4 - Beta"
10 |
--------------------------------------------------------------------------------
/letop/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | from .__about__ import (
4 | __author__,
5 | __email__,
6 | __license__,
7 | __status__,
8 | __version__,
9 | )
10 |
11 | __all__ = [
12 | "__author__",
13 | "__email__",
14 | "__license__",
15 | "__version__",
16 | "__status__",
17 | ]
18 |
--------------------------------------------------------------------------------
/letop/levelset/__init__.py:
--------------------------------------------------------------------------------
1 | from .level_set_functional import LevelSetFunctional
2 | from .regularization_solver import RegularizationSolver
3 |
--------------------------------------------------------------------------------
/letop/levelset/level_set_functional.py:
--------------------------------------------------------------------------------
1 | from pyadjoint.drivers import compute_gradient, compute_hessian
2 | from pyadjoint.enlisting import Enlist
3 | from pyadjoint.tape import get_working_tape, stop_annotating, no_annotations
4 | from pyadjoint import Control, AdjFloat
5 |
6 |
7 | class LevelSetFunctional(object):
8 | """Class representing a Lagrangian that depends on a level set
9 |
10 | This class is based of pyadjoint.ReducedFunctional and shares many functionalities.
11 | The motivation is to calculate shape derivatives when we evolve a level set.
12 |
13 | Args:
14 | functional (:obj:`OverloadedType`): An instance of an OverloadedType,
15 | usually :class:`AdjFloat`. This should be the return value of the
16 | functional you want to reduce.
17 | controls (list[Control]): A list of Control instances, which you want
18 | to map to the functional. It is also possible to supply a single Control
19 | instance instead of a list.
20 | """
21 |
22 | def __init__(
23 | self,
24 | functional,
25 | controls,
26 | level_set,
27 | scale=1.0,
28 | tape=None,
29 | eval_cb_pre=lambda *args: None,
30 | eval_cb_post=lambda *args: None,
31 | derivative_cb_pre=lambda *args: None,
32 | derivative_cb_post=lambda *args: None,
33 | hessian_cb_pre=lambda *args: None,
34 | hessian_cb_post=lambda *args: None,
35 | ):
36 | self.functional = functional
37 | self.cost_function = self.functional
38 | self.tape = get_working_tape() if tape is None else tape
39 | self.controls = Enlist(controls)
40 | self.level_set = Enlist(level_set)
41 | self.scale = scale
42 | self.eval_cb_pre = eval_cb_pre
43 | self.eval_cb_post = eval_cb_post
44 | self.derivative_cb_pre = derivative_cb_pre
45 | self.derivative_cb_post = derivative_cb_post
46 | self.hessian_cb_pre = hessian_cb_pre
47 | self.hessian_cb_post = hessian_cb_post
48 |
49 | # TODO Check that the level set is in the tape.
50 | # Actually, not even pyadjoint checks if the given Control is in the
51 | # tape.
52 |
53 | def derivative(self, options={}):
54 | """Returns the derivative of the functional w.r.t. the control.
55 |
56 | Using the adjoint method, the derivative of the functional with
57 | respect to the control, around the last supplied value of the control,
58 | is computed and returned.
59 |
60 | Args:
61 | options (dict): A dictionary of options. To find a list of available options
62 | have a look at the specific control type.
63 |
64 | Returns:
65 | OverloadedType: The derivative with respect to the control.
66 | Should be an instance of the same type as the control.
67 |
68 | """
69 | # Call callback
70 | self.derivative_cb_pre(self.level_set)
71 |
72 | derivatives = compute_gradient(
73 | self.functional,
74 | self.controls,
75 | options=options,
76 | tape=self.tape,
77 | adj_value=self.scale,
78 | )
79 |
80 | # Call callback
81 | self.derivative_cb_post(
82 | self.functional.block_variable.checkpoint,
83 | self.level_set.delist(derivatives),
84 | self.level_set,
85 | )
86 |
87 | return self.level_set.delist(derivatives)
88 |
89 | @no_annotations
90 | def hessian(self, m_dot, options={}):
91 | """Returns the action of the Hessian of the functional w.r.t. the control on a vector m_dot.
92 |
93 | Using the second-order adjoint method, the action of the Hessian of the
94 | functional with respect to the control, around the last supplied value
95 | of the control, is computed and returned.
96 |
97 | Args:
98 | m_dot ([OverloadedType]): The direction in which to compute the
99 | action of the Hessian.
100 | options (dict): A dictionary of options. To find a list of
101 | available options have a look at the specific control type.
102 |
103 | Returns:
104 | OverloadedType: The action of the Hessian in the direction m_dot.
105 | Should be an instance of the same type as the control.
106 | """
107 | # Call callback
108 | self.hessian_cb_pre(self.level_set)
109 |
110 | r = compute_hessian(
111 | self.functional,
112 | self.controls,
113 | m_dot,
114 | options=options,
115 | tape=self.tape,
116 | )
117 |
118 | # Call callback
119 | self.hessian_cb_post(
120 | self.functional.block_variable.checkpoint,
121 | self.level_set.delist(r),
122 | self.level_set,
123 | )
124 |
125 | return self.level_set.delist(r)
126 |
127 | @no_annotations
128 | def __call__(self, values):
129 | """Computes the reduced functional with supplied control value.
130 |
131 | Args:
132 | values ([OverloadedType]): If you have multiple controls this should be a list of
133 | new values for each control in the order you listed the controls to the constructor.
134 | If you have a single control it can either be a list or a single object.
135 | Each new value should have the same type as the corresponding control.
136 |
137 | If values has a len(ufl_shape) > 0, we are in a Taylor test and we are updating
138 | self.controls
139 | If values has ufl_shape = (), it is a level set.
140 |
141 | Returns:
142 | :obj:`OverloadedType`: The computed value. Typically of instance
143 | of :class:`AdjFloat`.
144 |
145 | """
146 | values = Enlist(values)
147 | if len(values) != len(self.level_set):
148 | raise ValueError(
149 | "values should be a list of same length as level sets."
150 | )
151 |
152 | # Call callback.
153 | self.eval_cb_pre(self.level_set.delist(values))
154 |
155 | # TODO Is there a better way to do this?
156 | if len(values[0].ufl_shape) > 0:
157 | for i, value in enumerate(values):
158 | self.controls[i].update(value)
159 | else:
160 | for i, value in enumerate(values):
161 | self.level_set[i].block_variable.checkpoint = value
162 |
163 | self.tape.reset_blocks()
164 | blocks = self.tape.get_blocks()
165 | with self.marked_controls():
166 | with stop_annotating():
167 | for i in range(len(blocks)):
168 | blocks[i].recompute()
169 |
170 | func_value = self.scale * self.functional.block_variable.checkpoint
171 |
172 | # Call callback
173 | self.eval_cb_post(func_value, self.level_set.delist(values))
174 |
175 | return func_value
176 |
177 | # TODO fix this to avoid deleting the level set
178 | def optimize_tape(self):
179 | self.tape.optimize(
180 | controls=self.controls + self.level_set,
181 | functionals=[self.functional],
182 | )
183 |
184 | def marked_controls(self):
185 | return marked_controls(self)
186 |
187 |
188 | class marked_controls(object):
189 | def __init__(self, rf):
190 | self.rf = rf
191 |
192 | def __enter__(self):
193 | for control in self.rf.controls:
194 | control.mark_as_control()
195 |
196 | def __exit__(self, *args):
197 | for control in self.rf.controls:
198 | control.unmark_as_control()
199 |
--------------------------------------------------------------------------------
/letop/levelset/regularization_solver.py:
--------------------------------------------------------------------------------
1 | from firedrake import (
2 | LinearVariationalSolver,
3 | LinearVariationalProblem,
4 | FacetNormal,
5 | VectorElement,
6 | FunctionSpace,
7 | Function,
8 | Constant,
9 | TrialFunction,
10 | TestFunction,
11 | assemble,
12 | solve,
13 | par_loop,
14 | DirichletBC,
15 | WRITE,
16 | READ,
17 | RW,
18 | File,
19 | warning,
20 | )
21 | from pyadjoint.enlisting import Enlist
22 | from pyadjoint import no_annotations
23 | from firedrake.mesh import ExtrudedMeshTopology
24 | from letop.physics import min_mesh_size
25 |
26 | from ufl import grad, inner, dot, dx, ds
27 | from ufl import ds_b, ds_t, ds_tb, ds_v
28 |
29 |
30 | direct_parameters = {
31 | "mat_type": "aij",
32 | "ksp_type": "preonly",
33 | "pc_type": "lu",
34 | "pc_factor_mat_solver_type": "mumps",
35 | }
36 |
37 | iterative_parameters = {
38 | "mat_type": "aij",
39 | "ksp_type": "cg",
40 | "pc_type": "hypre",
41 | "pc_hypre_type": "boomeramg",
42 | "pc_hypre_boomeramg_max_iter": 200,
43 | "pc_hypre_boomeramg_coarsen_type": "HMIS",
44 | "pc_hypre_boomeramg_agg_nl": 1,
45 | "pc_hypre_boomeramg_strong_threshold": 0.7,
46 | "pc_hypre_boomeramg_interp_type": "ext+i",
47 | "pc_hypre_boomeramg_P_max": 4,
48 | "pc_hypre_boomeramg_relax_type_all": "sequential-Gauss-Seidel",
49 | "pc_hypre_boomeramg_grid_sweeps_all": 1,
50 | "pc_hypre_boomeramg_max_levels": 15,
51 | "ksp_monitor_true_residual": None,
52 | }
53 |
54 |
55 | class RegularizationSolver(object):
56 | @no_annotations
57 | def __init__(
58 | self,
59 | S,
60 | mesh,
61 | beta=1,
62 | gamma=1.0e4,
63 | bcs=None,
64 | dx=dx,
65 | design_domain=None,
66 | solver_parameters=direct_parameters,
67 | output_dir="./",
68 | ):
69 | """
70 | Solver class to regularize the shape derivatives as explained in
71 | Frédéric de Gournay
72 | Velocity Extension for the Level-set Method and Multiple Eigenvalues in Shape Optimization
73 | SIAM J. Control Optim., 45(1), 343–367. (25 pages)
74 | Args:
75 | S ([type]): Function space of the mesh coordinates
76 | mesh ([type]): Mesh
77 | beta ([type], optional): Regularization parameter.
78 | It should be finite multiple of the mesh size.
79 | Defaults to 1.
80 | gamma ([type], optional): Penalty parameter for the penalization of the normal components
81 | of the regularized shape derivatives on the boundary. Defaults to 1.0e4.
82 | bcs ([type], optional): Dirichlet Boundary conditions.
83 | They should be setting the regularized shape derivatives to zero
84 | wherever there are boundary conditions on the original PDE.
85 | Defaults to None.
86 | dx ([type], optional): [description]. Defaults to dx.
87 | design_domain ([type], optional): If we're interested in setting the shape derivatives to
88 | zero outside of the design_domain,
89 | we pass design_domain marker.
90 | This is convenient when we want to fix certain regions in the domain.
91 | Defaults to None.
92 | solver_parameters ([type], optional): Solver options. Defaults to direct_parameters.
93 | output_dir (str, optional): Plot the output somewhere. Defaults to "./".
94 | """
95 | n = FacetNormal(mesh)
96 | theta, xi = [TrialFunction(S), TestFunction(S)]
97 | self.xi = xi
98 |
99 | hmin = min_mesh_size(mesh)
100 | if beta > 20.0 * hmin:
101 | warning(
102 | f"Length scale parameter beta ({beta}) is much larger than the mesh size {hmin}"
103 | )
104 |
105 | self.beta_param = Constant(beta)
106 |
107 | self.a = (
108 | self.beta_param * inner(grad(theta), grad(xi)) + inner(theta, xi)
109 | ) * (dx)
110 | if isinstance(mesh.topology, ExtrudedMeshTopology):
111 | ds_reg = ds_b + ds_v + ds_tb + ds_t
112 | else:
113 | ds_reg = ds
114 | self.a += Constant(gamma) * (inner(dot(theta, n), dot(xi, n))) * ds_reg
115 |
116 | # Dirichlet boundary conditions equal to zero for regions where we want
117 | # the domain to be static, i.e. zero velocities
118 |
119 | if bcs is None:
120 | self.bcs = []
121 | else:
122 | self.bcs = Enlist(bcs)
123 | if design_domain is not None:
124 | # Heaviside step function in domain of interest
125 | V_DG0_B = FunctionSpace(mesh, "DG", 0)
126 | I_B = Function(V_DG0_B)
127 | I_B.assign(1.0)
128 | # Set to zero all the cells within sim_domain
129 | par_loop(
130 | ("{[i] : 0 <= i < f.dofs}", "f[i, 0] = 0.0"),
131 | dx(design_domain),
132 | {"f": (I_B, WRITE)},
133 | is_loopy_kernel=True,
134 | )
135 |
136 | I_cg_B = Function(S)
137 | dim = S.mesh().geometric_dimension()
138 | # Assume that `A` is a :class:`.Function` in CG1 and `B` is a
139 | # `.Function` in DG0. Then the following code sets each DoF in
140 | # `A` to the maximum value that `B` attains in the cells adjacent to
141 | # that DoF::
142 | par_loop(
143 | (
144 | "{{[i, j] : 0 <= i < A.dofs and 0 <= j < {0} }}".format(
145 | dim
146 | ),
147 | "A[i, j] = fmax(A[i, j], B[0, 0])",
148 | ),
149 | dx,
150 | {"A": (I_cg_B, RW), "B": (I_B, READ)},
151 | is_loopy_kernel=True,
152 | )
153 |
154 | import numpy as np
155 |
156 | class MyBC(DirichletBC):
157 | def __init__(self, V, value, markers):
158 | # Call superclass init
159 | # We provide a dummy subdomain id.
160 | super(MyBC, self).__init__(V, value, 0)
161 | # Override the "nodes" property which says where the boundary
162 | # condition is to be applied.
163 | self.nodes = np.unique(
164 | np.where(markers.dat.data_ro_with_halos > 0)[0]
165 | )
166 |
167 | self.bcs.append(MyBC(S, 0, I_cg_B))
168 |
169 | self.Av = assemble(self.a, bcs=self.bcs)
170 |
171 | self.solver_parameters = solver_parameters
172 |
173 | @no_annotations
174 | def solve(self, velocity, dJ):
175 | """Solve the problem
176 |
177 | Args:
178 | velocity ([type]): Solution. velocity means regularized shape derivatives
179 | dJ ([type]): Shape derivatives to be regularized
180 | """
181 | for bc in self.bcs:
182 | bc.apply(dJ)
183 |
184 | solve(
185 | self.Av,
186 | velocity.vector(),
187 | dJ,
188 | options_prefix="reg_solver",
189 | solver_parameters=self.solver_parameters,
190 | )
191 |
--------------------------------------------------------------------------------
/letop/optimization/__init__.py:
--------------------------------------------------------------------------------
1 | from .cg_hj_solver import HamiltonJacobiCGSolver
2 | from .cg_reinit_solver import ReinitSolverCG
3 | from .interface import InfDimProblem, Constraint
4 | from .nullspace_shape import nlspace_solve
5 | from .utils import read_checkpoint, is_checkpoint
6 |
--------------------------------------------------------------------------------
/letop/optimization/cg_hj_solver.py:
--------------------------------------------------------------------------------
1 | import firedrake as fd
2 | import firedrake_ts as fdts
3 | from letop.physics import AdvectionDiffusionGLS
4 | from typing import Union, List, Callable
5 |
6 |
7 | def HamiltonJacobiCGSolver(
8 | V: fd.FunctionSpace,
9 | theta: fd.Function,
10 | phi: fd.Function,
11 | t_end: float = 5000.0,
12 | bcs: Union[fd.DirichletBC, List[fd.DirichletBC]] = None,
13 | monitor_callback: Callable = None,
14 | solver_parameters=None,
15 | pre_jacobian_callback=None,
16 | post_jacobian_callback=None,
17 | pre_function_callback=None,
18 | post_function_callback=None,
19 | ) -> fdts.DAESolver:
20 | """Builds the solver for the advection-diffusion equation (Hamilton-Jacobi in the context of
21 | topology optimization) which is used to advect the level set)
22 |
23 | Args:
24 | V (fd.FunctionSpace): Function space of the level set
25 | theta (fd.Function): Velocity function
26 | phi (fd.Function): Level set
27 | t_end (float, optional): Max time of simulation. Defaults to 5000.0.
28 | bcs (Union[fd.DirichletBC, List[fd.DirichletBC]], optional): BC for the equation. Defaults to None.
29 | monitor_callback (Callable, optional): Monitor function called each time step. Defaults to None.
30 | solver_parameters ([type], optional): Solver options. Defaults to None.
31 | :kwarg pre_jacobian_callback: A user-defined function that will
32 | be called immediately before Jacobian assembly. This can
33 | be used, for example, to update a coefficient function
34 | that has a complicated dependence on the unknown solution.
35 | :kwarg pre_function_callback: As above, but called immediately
36 | before residual assembly.
37 | :kwarg post_jacobian_callback: As above, but called after the
38 | Jacobian has been assembled.
39 | :kwarg post_function_callback: As above, but called immediately
40 | after residual assembly.
41 |
42 |
43 | Returns:
44 | fdts.DAESolver: DAESolver configured to solve the advection-diffusion equation
45 | """
46 |
47 | default_peclet_inv = 1e-4
48 | if solver_parameters:
49 | PeInv = solver_parameters.get("peclet_number", default_peclet_inv)
50 | else:
51 | PeInv = default_peclet_inv
52 |
53 | phi_t = fd.Function(V)
54 | # Galerkin residual
55 | F = AdvectionDiffusionGLS(V, theta, phi, PeInv=PeInv, phi_t=phi_t)
56 |
57 | problem = fdts.DAEProblem(F, phi, phi_t, (0.0, t_end), bcs=bcs)
58 | parameters = {
59 | "ts_type": "rosw",
60 | "ts_rows_type": "2m",
61 | "ts_adapt_type": "dsp",
62 | "ts_exact_final_time": "matchstep",
63 | "ts_atol": 1e-7,
64 | "ts_rtol": 1e-7,
65 | "ksp_type": "preonly",
66 | "pc_type": "lu",
67 | "pc_factor_mat_solver_type": "mumps",
68 | }
69 | if solver_parameters:
70 | parameters.update(solver_parameters)
71 |
72 | return fdts.DAESolver(
73 | problem,
74 | solver_parameters=parameters,
75 | monitor_callback=monitor_callback,
76 | pre_function_callback=pre_function_callback,
77 | post_function_callback=post_function_callback,
78 | pre_jacobian_callback=pre_jacobian_callback,
79 | post_jacobian_callback=post_jacobian_callback,
80 | )
81 |
--------------------------------------------------------------------------------
/letop/optimization/cg_reinit_solver.py:
--------------------------------------------------------------------------------
1 | import firedrake as fd
2 | from firedrake import inner, sqrt, grad, dx, conditional, le
3 | from pyop2 import READ, RW
4 | import numpy as np
5 | from pyadjoint import no_annotations
6 |
7 |
8 | class BCOut(fd.DirichletBC):
9 | def __init__(self, V, value, markers):
10 | super(BCOut, self).__init__(V, value, 0)
11 | self.nodes = np.unique(
12 | np.where(markers.dat.data_ro_with_halos == 0)[0]
13 | )
14 |
15 |
16 | class BCInt(fd.DirichletBC):
17 | def __init__(self, V, value, markers):
18 | super(BCInt, self).__init__(V, value, 0)
19 | self.nodes = np.unique(
20 | np.where(markers.dat.data_ro_with_halos != 0)[0]
21 | )
22 |
23 |
24 | class ReinitSolverCG:
25 | def __init__(self, phi: fd.Function, solver_parameters=None) -> None:
26 | """Reinitialization solver. Returns the level set
27 | to a signed distance function
28 |
29 | Args:
30 | V (fd.FunctionSpace): Function space of the level set
31 | """
32 | V = phi.function_space()
33 | self.V = V
34 | self.phi = phi
35 | self.mesh = V.ufl_domain()
36 | self.DG0 = fd.FunctionSpace(self.mesh, "DG", 0)
37 |
38 | rho, sigma = fd.TrialFunction(V), fd.TestFunction(V)
39 | a = rho * sigma * dx
40 | self.phi_int = fd.Function(V)
41 | self.A_proj = fd.assemble(a)
42 |
43 | self.solver_parameters = {
44 | "ksp_type": "preonly",
45 | "pc_type": "lu",
46 | "pc_factor_mat_solver_type": "mumps",
47 | }
48 | if solver_parameters:
49 | self.solver_parameters.update(solver_parameters)
50 |
51 | @no_annotations
52 | def solve(self, phi: fd.Function, iters: int = 5) -> fd.Function:
53 |
54 | marking = fd.Function(self.DG0)
55 | marking_bc_nodes = fd.Function(self.V)
56 | # Mark cells cut by phi(x) = 0
57 | domain = "{[i, j]: 0 <= i < b.dofs}"
58 | instructions = """
59 | min_value = 1e20
60 | max_value = -1e20
61 | for i
62 | min_value = fmin(min_value, b[i, 0])
63 | max_value = fmax(max_value, b[i, 0])
64 | end
65 | a[0, 0] = 1.0 if (min_value < 0 and max_value > 0) else 0.0
66 | """
67 | fd.par_loop(
68 | (domain, instructions),
69 | dx,
70 | {"a": (marking, RW), "b": (phi, READ)},
71 | is_loopy_kernel=True,
72 | )
73 | # Mark the nodes in the marked cells
74 | fd.par_loop(
75 | ("{[i] : 0 <= i < A.dofs}", "A[i, 0] = fmax(A[i, 0], B[0, 0])"),
76 | dx,
77 | {"A": (marking_bc_nodes, RW), "B": (marking, READ)},
78 | is_loopy_kernel=True,
79 | )
80 | # Mark the nodes in the marked cells
81 | # Project the gradient of phi on the cut cells
82 | self.phi.assign(phi)
83 | V = self.V
84 | rho, sigma = fd.TrialFunction(V), fd.TestFunction(V)
85 | a = rho * sigma * marking * dx
86 | L_proj = (
87 | self.phi
88 | / sqrt(inner(grad(self.phi), grad(self.phi)))
89 | * marking
90 | * sigma
91 | * dx
92 | )
93 | bc_proj = BCOut(V, fd.Constant(0.0), marking_bc_nodes)
94 | self.A_proj = fd.assemble(a, tensor=self.A_proj, bcs=bc_proj)
95 | b_proj = fd.assemble(L_proj, bcs=bc_proj)
96 | solver_proj = fd.LinearSolver(
97 | self.A_proj, solver_parameters=self.solver_parameters
98 | )
99 | solver_proj.solve(self.phi_int, b_proj)
100 |
101 | def nabla_phi_bar(phi):
102 | return sqrt(inner(grad(phi), grad(phi)))
103 |
104 | def d1(s):
105 | return fd.Constant(1.0) - fd.Constant(1.0) / s
106 |
107 | def d2(s):
108 | return conditional(
109 | le(s, fd.Constant(1.0)), s - fd.Constant(1.0), d1(s)
110 | )
111 |
112 | def residual_phi(phi):
113 | return fd.norm(
114 | fd.assemble(
115 | d2(nabla_phi_bar(phi)) * inner(grad(phi), grad(sigma)) * dx
116 | )
117 | )
118 |
119 | a = inner(grad(rho), grad(sigma)) * dx
120 | L = (
121 | inner(
122 | (-d2(nabla_phi_bar(self.phi)) + fd.Constant(1.0))
123 | * grad(self.phi),
124 | grad(sigma),
125 | )
126 | * dx
127 | )
128 | bc = BCInt(V, self.phi_int, marking_bc_nodes)
129 | phi_sol = fd.Function(V)
130 | A = fd.assemble(a, bcs=bc)
131 | b = fd.assemble(L, bcs=bc)
132 | solver = fd.LinearSolver(A, solver_parameters=self.solver_parameters)
133 |
134 | # Solve the Signed distance equation with Picard iteration
135 | bc.apply(phi_sol)
136 | init_res = residual_phi(phi_sol)
137 | res = 1e10
138 | it = 0
139 | while res > init_res or it < iters:
140 | solver.solve(phi_sol, b)
141 | self.phi.assign(phi_sol)
142 | b = fd.assemble(L, bcs=bc, tensor=b)
143 | it += 1
144 | res = residual_phi(phi_sol)
145 | if res > init_res:
146 | fd.warning(
147 | f"Residual in signed distance function increased: {res}, before: {init_res}"
148 | )
149 | return self.phi
150 |
--------------------------------------------------------------------------------
/letop/optimization/hj_context.py:
--------------------------------------------------------------------------------
1 | from firedrake import (
2 | assemble,
3 | dmhooks,
4 | function,
5 | )
6 | from firedrake.exceptions import ConvergenceError
7 | from firedrake.petsc import PETSc
8 | from firedrake.utils import cached_property
9 |
10 |
11 | def _make_reasons(reasons):
12 | return dict(
13 | [
14 | (getattr(reasons, r), r)
15 | for r in dir(reasons)
16 | if not r.startswith("_")
17 | ]
18 | )
19 |
20 |
21 | TSReasons = _make_reasons(PETSc.TS.ConvergedReason())
22 |
23 |
24 | def check_ts_convergence(ts):
25 | r = ts.getConvergedReason()
26 | # TODO: submit PR to petsc4py to add the following reasons
27 | # TSFORWARD_DIVERGED_LINEAR_SOLVE = -3,
28 | # TSADJOINT_DIVERGED_LINEAR_SOLVE = -4
29 | if r == -3:
30 | raise ConvergenceError(
31 | "TS solve failed to converge. Reason: TSFORWARD_DIVERGED_LINEAR_SOLVE"
32 | )
33 | if r == -4:
34 | raise ConvergenceError(
35 | "TS solve failed to converge. Reason: TSADJOINT_DIVERGED_LINEAR_SOLVE"
36 | )
37 | reason = TSReasons[r]
38 | if r < 0:
39 | raise ConvergenceError(
40 | f"TS solve failed to converge after {ts.getStepNumber()} iterations. Reason: {reason}"
41 | )
42 |
43 |
44 | class _HJContext(object):
45 | r"""
46 | Context holding information for TS callbacks.
47 |
48 | :arg problem: a :class:`HamiltonJacobiProlbem`.
49 | :arg appctx: Any extra information used in the assembler. For the
50 | matrix-free case this will contain the Newton state in
51 | ``"state"``.
52 | :arg options_prefix: The options prefix of the TS.
53 | """
54 |
55 | def __init__(
56 | self,
57 | problem,
58 | appctx=None,
59 | options_prefix=None,
60 | ):
61 | from firedrake.bcs import DirichletBC
62 |
63 | self.options_prefix = options_prefix
64 |
65 | self._problem = problem
66 |
67 | self.fcp = problem.form_compiler_parameters
68 |
69 | if appctx is None:
70 | appctx = {}
71 | appctx.setdefault("state", self._problem.phi_n)
72 | appctx.setdefault("form_compiler_parameters", self.fcp)
73 |
74 | self.appctx = appctx
75 |
76 | self.bcs_F = [
77 | bc if isinstance(bc, DirichletBC) else bc._F for bc in problem.bcs
78 | ]
79 |
80 | def set_rhsfunction(self, ts):
81 | r"""Set the function to compute F(t,U,U_t) where F() = 0 is the DAE to be solved."""
82 | with self._F.dat.vec_wo as v:
83 | ts.setRHSFunction(self.form_function, f=v)
84 |
85 | @staticmethod
86 | def form_function(ts, t, X, F):
87 | r"""Form the residual for this problem
88 |
89 | :arg ts: a PETSc TS object
90 | :arg t: the time at step/stage being solved
91 | :arg X: state vector
92 | :arg F: function vector
93 | """
94 | dm = ts.getDM()
95 | ctx = dmhooks.get_appctx(dm)
96 | # X may not be the same vector as the vec behind self._problem.phi_n, so
97 | # copy guess in from X.
98 | with ctx._problem.phi_n.dat.vec_wo as v:
99 | X.copy(v)
100 |
101 | b1 = assemble(ctx._problem.L1)
102 | ctx._problem.solver1.solve(ctx._problem.p1, b1)
103 |
104 | b2 = assemble(ctx._problem.L2)
105 | ctx._problem.solver2.solve(ctx._problem.p2, b2)
106 |
107 | b3 = assemble(ctx._problem.Lb)
108 | ctx._problem.solver_b.solve(ctx._F, b3)
109 |
110 | # F may not be the same vector as self._F, so copy
111 | # residual out to F.
112 | with ctx._F.dat.vec_ro as v:
113 | v.copy(F)
114 |
115 | @cached_property
116 | def _F(self):
117 | return function.Function(self._problem.phi_n.function_space())
118 |
--------------------------------------------------------------------------------
/letop/optimization/interface.py:
--------------------------------------------------------------------------------
1 | from letop.physics.utils import max_mesh_dimension
2 | import firedrake as fd
3 | from firedrake import inner, dx
4 | from pyadjoint import Control, no_annotations
5 | from pyadjoint.enlisting import Enlist
6 | from letop.levelset import (
7 | LevelSetFunctional,
8 | RegularizationSolver,
9 | )
10 | from letop.optimization import (
11 | HamiltonJacobiCGSolver,
12 | ReinitSolverCG,
13 | )
14 | from letop.physics import calculate_max_vel, InteriorBC
15 | from pyop2.profiling import timed_function
16 | from ufl.algebra import Abs
17 | from petsc4py.PETSc import TS
18 |
19 |
20 |
21 | class Constraint(object):
22 | @no_annotations
23 | def __init__(self, rfnl, constraint_value, scalar_control):
24 | """Docstring for __init__.
25 |
26 | :rfnl: LevelSetFunctional
27 | :constraint_value: Scalar
28 | :scalar_control: Control
29 |
30 | """
31 | if not isinstance(rfnl, LevelSetFunctional):
32 | raise TypeError(
33 | f"Provided '{type(rfnl).__name__}', not a LevelSetFunctional"
34 | )
35 | if not isinstance(scalar_control, Control):
36 | raise TypeError(
37 | f"Provided '{type(scalar_control).__name__}', not a Control"
38 | )
39 | if not isinstance(constraint_value, float):
40 | raise TypeError(
41 | f"Provided '{type(constraint_value).__name__}', not a float"
42 | )
43 |
44 | self.rfnl = rfnl
45 | self.constraint_value = constraint_value
46 | self.scalar_control = scalar_control
47 |
48 | def evaluate(self, x):
49 | # TODO: Any way to check that the value saved in self.scalar_control corresponds to `x`?
50 | """Evaluate the constraint as G(x) - Gval <= 0
51 |
52 | Args:
53 | x ([type]): [description]
54 |
55 | Returns:
56 | [float]: [Constraint value. Violation if it is > 0]
57 | """
58 |
59 | return self.scalar_control.tape_value() - self.constraint_value
60 |
61 | def derivative(self):
62 | return self.rfnl.derivative()
63 |
64 |
65 | class InfDimProblem(object):
66 | def __init__(
67 | self,
68 | cost_function,
69 | reg_solver,
70 | eqconstraints=None,
71 | ineqconstraints=None,
72 | reinit_distance=0.05,
73 | solver_parameters=None,
74 | output_dir=None,
75 | ):
76 | """Problem interface for the null-space solver
77 |
78 | Args:
79 | cost_function ([type]): [description]
80 | reg_solver ([type]): [description]
81 | eqconstraints ([type], optional): [description]. Defaults to None.
82 | ineqconstraints ([type], optional): [description]. Defaults to None.
83 | reinit_distance (int, optional): The reinitialization solver is activated
84 | after the level set is shifted reinit_distance * D,
85 | where D is the max dimensions of a mesh
86 | Defaults to 0.1
87 | solver_parameters ([type], optional): [description]. Defaults to None.
88 |
89 | Raises:
90 | TypeError: [description]
91 | TypeError: [description]
92 | TypeError: [description]
93 |
94 | Returns:
95 | [type]: [description]
96 | """
97 | if not isinstance(reg_solver, RegularizationSolver):
98 | raise TypeError(
99 | f"Provided regularization solver '{type(reg_solver).__name__}',\
100 | is not a RegularizationSolver"
101 | )
102 | self.reg_solver = reg_solver
103 | assert len(cost_function.controls) < 2, "Only one control for now"
104 | self.phi = cost_function.level_set[0]
105 | # Copy for the HamiltonJacobiCGSolver
106 | self.phi_hj = fd.Function(cost_function.level_set[0], name="hamilton-jacobi")
107 | # Copy for the Line search
108 | self.phi_ls = fd.Function(cost_function.level_set[0], name="line-search")
109 | # Copy for the Regularization solver
110 | self.phi_rs = fd.Function(cost_function.level_set[0], name='reinit')
111 | self.V = self.phi.function_space()
112 | self.Vvec = cost_function.controls[0].control.function_space()
113 | self.delta_x = fd.Function(self.Vvec)
114 | self.max_distance = reinit_distance * max_mesh_dimension(
115 | self.V.ufl_domain()
116 | )
117 | self.current_max_distance = self.max_distance
118 | self.current_max_distance_at_t0 = self.current_max_distance
119 | self.accum_distance = 0.0
120 | self.last_distance = 0.0
121 | self.output_dir = output_dir
122 | self.accept_iteration = False
123 | self.termination_event = None
124 |
125 | V_elem = self.V.ufl_element()
126 | if V_elem.family() in ["TensorProductElement", "Lagrange"]:
127 | if V_elem.family() == "TensorProductElement":
128 | assert (
129 | V_elem.sub_elements()[0].family() == "Q"
130 | and V_elem.sub_elements()[1].family() == "Lagrange"
131 | ), "Only Lagrange basis"
132 | self.build_cg_solvers(
133 | solver_parameters,
134 | )
135 | else:
136 | raise RuntimeError(
137 | f"Level set function element {self.V.ufl_element()} not supported."
138 | )
139 |
140 | def event(ts, t, X, fvalue):
141 | max_vel = calculate_max_vel(self.delta_x)
142 | fvalue[0] = (
143 | self.accum_distance + max_vel * t
144 | ) - self.current_max_distance
145 |
146 | def postevent(ts, events, t, X, forward):
147 | with self.phi_hj.dat.vec_wo as v:
148 | X.copy(v)
149 | self.phi_hj.assign(self.reinit_solver.solve(self.phi_hj))
150 | with self.phi_hj.dat.vec_wo as v:
151 | v.copy(X)
152 | self.current_max_distance += self.max_distance
153 |
154 | direction = [1]
155 | terminate = [False]
156 |
157 | self.hj_solver.ts.setEventHandler(
158 | direction, terminate, event, postevent
159 | )
160 | self.hj_solver.ts.setEventTolerances(1e-4, vtol=[1e-4])
161 |
162 | if eqconstraints:
163 | self.eqconstraints = Enlist(eqconstraints)
164 | for constr in self.eqconstraints:
165 | if not isinstance(constr, Constraint):
166 | raise TypeError(
167 | f"Provided equality constraint '{type(constr).__name__}', not a Constraint"
168 | )
169 | else:
170 | self.eqconstraints = []
171 |
172 | if ineqconstraints:
173 | self.ineqconstraints = Enlist(ineqconstraints)
174 | for ineqconstr in self.ineqconstraints:
175 | if not isinstance(ineqconstr, Constraint):
176 | raise TypeError(
177 | f"Provided inequality constraint '{type(ineqconstr).__name__}',\
178 | not a Constraint"
179 | )
180 | else:
181 | self.ineqconstraints = []
182 |
183 | self.n_eqconstraints = len(self.eqconstraints)
184 | self.n_ineqconstraints = len(self.ineqconstraints)
185 |
186 | self.gradJ = fd.Function(self.Vvec)
187 |
188 | self.gradH = [fd.Function(self.Vvec) for _ in self.ineqconstraints]
189 | self.gradG = [fd.Function(self.Vvec) for _ in self.eqconstraints]
190 |
191 | self.cost_function = cost_function
192 |
193 | self.i = 0 # iteration count
194 |
195 | self.beta_param = reg_solver.beta_param.values()[0]
196 |
197 |
198 | def set_termination_event(
199 | self, termination_event, termination_tolerance=1e-2
200 | ):
201 |
202 | if termination_event and not isinstance(termination_event(), float):
203 | raise TypeError(f"termination_event must return a float")
204 |
205 | self.termination_event = termination_event
206 | self.termination_tolerance = termination_tolerance
207 |
208 | def build_cg_solvers(self, solver_parameters=None):
209 | hj_solver_parameters = None
210 | reinit_solver_parameters = None
211 | if solver_parameters:
212 | if solver_parameters.get("hj_solver"):
213 | hj_solver_parameters = solver_parameters["hj_solver"]
214 | if solver_parameters.get("reinit_solver"):
215 | reinit_solver_parameters = solver_parameters["reinit_solver"]
216 |
217 | self.hj_solver = HamiltonJacobiCGSolver(
218 | self.V,
219 | self.delta_x,
220 | self.phi_hj,
221 | solver_parameters=hj_solver_parameters,
222 | )
223 | self.reinit_solver = ReinitSolverCG(
224 | self.phi_rs, solver_parameters=reinit_solver_parameters
225 | )
226 |
227 | def fespace(self):
228 | return self.Vvec
229 |
230 | def eval(self, x):
231 | """Returns the triplet (J(x),G(x),H(x))"""
232 | return (self.J(x), self.G(x), self.H(x))
233 |
234 | @timed_function("Cost function")
235 | def J(self, x):
236 | return self.cost_function(x)
237 |
238 | @timed_function("Cost function gradient")
239 | def dJ(self, x):
240 | return self.cost_function.derivative()
241 |
242 | @timed_function("Equality constraint function")
243 | def G(self, x):
244 | return [eqconstr.evaluate(x) for eqconstr in self.eqconstraints]
245 |
246 | @timed_function("Equality constraint function gradient")
247 | def dG(self, x):
248 | return [eqconstr.derivative() for eqconstr in self.eqconstraints]
249 |
250 | @timed_function("Inequality constraint function")
251 | def H(self, x):
252 | return [ineqconstr.evaluate(x) for ineqconstr in self.ineqconstraints]
253 |
254 | @timed_function("Inequality constraint function gradient")
255 | def dH(self, x):
256 | return [ineqconstr.derivative() for ineqconstr in self.ineqconstraints]
257 |
258 | @no_annotations
259 | def reinit(self, x):
260 | pass
261 |
262 | @no_annotations
263 | def eval_gradients(self, x):
264 | """Returns the triplet (gradJ(x), gradG(x), gradH(x))"""
265 | self.accum_distance += self.last_distance
266 | self.i += 1
267 | self.phi.assign(x)
268 |
269 | if self.termination_event:
270 | event_value = self.termination_event()
271 | if event_value < self.termination_tolerance:
272 | self.accept_iteration = True
273 |
274 | dJ = self.dJ(x)
275 | dG = self.dG(x)
276 | dH = self.dH(x)
277 |
278 | # Regularize all gradients
279 | self.reg_solver.solve(self.gradJ, dJ)
280 |
281 | for gradHi, dHi in zip(self.gradH, dH):
282 | self.reg_solver.solve(gradHi, dHi)
283 | for gradGi, dGi in zip(self.gradG, dG):
284 | self.reg_solver.solve(gradGi, dGi)
285 |
286 | return (self.gradJ, self.gradG, self.gradH)
287 |
288 | def reset_distance(self):
289 | """Necessary in cases where we have performed a reinitialization
290 | but the new level set was scraped by the line search
291 | """
292 | self.current_max_distance = self.current_max_distance_at_t0
293 |
294 | def velocity_scale(self, delta_x):
295 | return calculate_max_vel(delta_x)
296 |
297 | @no_annotations
298 | def retract(self, input_phi, delta_x, scaling=1):
299 | """input_phi is not modified,
300 | output_phi refers to problem.phi
301 | Args:
302 | input_phi ([type]): [description]
303 | delta_x ([type]): [description]
304 | scaling (int, optional): [description]. Defaults to 1.
305 |
306 | Returns:
307 | [type]: [description]
308 | """
309 | self.current_max_distance_at_t0 = self.current_max_distance
310 | self.hj_solver.ts.setMaxTime(scaling)
311 | self.hj_solver.ts.setTimeStep(1e-4)
312 | self.hj_solver.ts.setTime(0.0)
313 | self.hj_solver.ts.setStepNumber(0)
314 | try:
315 | self.phi_hj.assign(input_phi)
316 | self.hj_solver.solve()
317 | except:
318 | reason = self.hj_solver.ts.getConvergedReason()
319 | print(f"Time stepping of Hamilton Jacobi failed. Reason: {reason}")
320 | if self.output_dir:
321 | print(f"Printing last solution to {self.output_dir}")
322 | fd.File(f"{self.output_dir}/failed_hj.pvd").write(input_phi)
323 |
324 | max_vel = calculate_max_vel(delta_x)
325 | self.last_distance = max_vel * scaling
326 |
327 | conv = self.hj_solver.ts.getConvergedReason()
328 | rtol, atol = (
329 | self.hj_solver.parameters["ts_rtol"],
330 | self.hj_solver.parameters["ts_atol"],
331 | )
332 | max_steps = self.hj_solver.parameters.get("ts_max_steps", 800)
333 | current_time = self.hj_solver.ts.getTime()
334 | total_time = self.hj_solver.ts.getMaxTime()
335 | if conv == TS.ConvergedReason.CONVERGED_ITS:
336 | warning = (
337 | f"Maximum number of time steps {self.hj_solver.ts.getStepNumber()} reached."
338 | f"Current time is only: {current_time} for total time: {total_time}."
339 | "Consider making the optimization time step dt shorter."
340 | "Restarting this step with more time steps and tighter tolerances if applicable."
341 | )
342 | fd.warning(warning)
343 |
344 | # Tighten tolerances if we are really far
345 | if current_time / total_time < 0.2:
346 | current_rtol, current_atol = self.hj_solver.ts.getTolerances()
347 | new_rtol, new_atol = max(rtol / 200, current_rtol / 10), max(
348 | atol / 200, current_atol / 10
349 | )
350 | self.hj_solver.ts.setTolerances(rtol=new_rtol, atol=new_atol)
351 |
352 | # Relax max time steps
353 | current_max_steps = self.hj_solver.ts.getMaxSteps()
354 | new_max_steps = min(current_max_steps * 1.5, max_steps * 3)
355 | self.hj_solver.ts.setMaxSteps(new_max_steps)
356 |
357 | elif conv == TS.ConvergedReason.CONVERGED_TIME:
358 | self.hj_solver.ts.setTolerances(rtol=rtol, atol=atol)
359 |
360 | def restore(self):
361 | pass
362 |
363 | @no_annotations
364 | def inner_product(self, x, y):
365 | return fd.assemble(inner(x, y) * dx)
366 |
367 | def accept(self):
368 | return self.accept_iteration
369 |
--------------------------------------------------------------------------------
/letop/optimization/nullspace_shape.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import cvxopt as cvx
3 | import time
4 | from mpi4py import MPI
5 |
6 | import firedrake as fd
7 | from firedrake import inner, Function, dx, Constant, File
8 | from numpy.core.numeric import indices
9 | from sympy import solve
10 | from letop.optimization import InfDimProblem
11 | from pyadjoint import no_annotations
12 | from firedrake.petsc import PETSc
13 | from functools import partial
14 |
15 |
16 | def print(x):
17 | return PETSc.Sys.Print(x)
18 |
19 |
20 | class MPITimer:
21 | def __init__(self, comm) -> None:
22 | self.comm = comm
23 |
24 | def __enter__(self):
25 | self.start = time.process_time()
26 | return self
27 |
28 | def __exit__(self, *args):
29 | self.end = time.process_time()
30 | total_time = self.end - self.start
31 | self.min_time = self.comm.allreduce(total_time, op=MPI.MIN)
32 | self.max_time = self.comm.allreduce(total_time, op=MPI.MAX)
33 |
34 |
35 | def set_parameters(params):
36 |
37 | params_default = {
38 | "alphaJ": 1,
39 | "alphaC": 1,
40 | "maxit": 4000,
41 | "maxtrials": 3,
42 | "debug": 0,
43 | "normalisation_norm": np.inf,
44 | "tol_merit": 0,
45 | "K": 0.1,
46 | "tol_qp": 1e-20,
47 | "show_progress_qp": False,
48 | "monitor_time": False,
49 | "dt": 1.0,
50 | "tol": 1e-5,
51 | "itnormalisation": 10,
52 | }
53 | if params is not None:
54 | for key in params.keys():
55 | if key not in params_default.keys():
56 | raise ValueError(
57 | "Don't know how to deal with parameter %s (a %s)"
58 | % (key, params[key].__class__)
59 | )
60 |
61 | for (prop, default) in params_default.items():
62 | params[prop] = params.get(prop, default)
63 | else:
64 | params = params_default
65 |
66 | return params
67 |
68 |
69 | def display(message, debug, level=0, color=None):
70 | if color:
71 | try:
72 | import colored as col
73 |
74 | message = col.stylize(message, col.fg(color))
75 | except Exception:
76 | pass
77 | if debug >= level:
78 | print(message)
79 |
80 |
81 | def inner_product(x, y):
82 | return fd.assemble(inner(x, y) * dx, annotate=False)
83 |
84 |
85 | def compute_norm(f, norm_type=np.inf):
86 | if norm_type == np.inf:
87 | with f.dat.vec_ro as vu:
88 | return vu.norm(PETSc.NormType.NORM_INFINITY)
89 | elif norm_type == 2:
90 | return fd.norm(f)
91 |
92 |
93 | def getTilde(C, p, eps=0):
94 | """
95 | Obtain the set of violated inequality constraints
96 | """
97 | tildeEps = C[p:] >= -eps
98 | tildeEps = np.asarray(np.concatenate(([True] * p, tildeEps)), dtype=bool)
99 | return tildeEps
100 |
101 |
102 | def getEps(dC, p, dt, K, norm_type=np.inf):
103 | if len(dC) == 0:
104 | return np.array([])
105 | if norm_type == np.inf:
106 | eps = []
107 | for dCi in dC[p:]:
108 | with dCi.dat.vec_ro as dCv:
109 | norm_inf = dCv.norm(PETSc.NormType.NORM_INFINITY)
110 | eps.append(norm_inf)
111 | eps = np.array(eps) * dt * K
112 | elif norm_type == 2:
113 | eps = np.array([fd.norm(dCi) * dt * K for dCi in dC[p:]])
114 | return eps
115 |
116 |
117 | def p_matrix_eval(dC, tildeEps):
118 | p_matrix = np.zeros((sum(tildeEps), sum(tildeEps)))
119 | ii = 0
120 | for i, tildei in enumerate(tildeEps):
121 | jj = 0
122 | for j, tildej in enumerate(tildeEps):
123 | if tildej and tildei:
124 | p_matrix[ii, jj] = inner_product(dC[i], dC[j])
125 | jj += 1
126 | if tildei:
127 | ii += 1
128 | return p_matrix
129 |
130 |
131 | def q_vector_eval(dJ, dC, tildeEps):
132 | q_vector = np.zeros((sum(tildeEps), 1))
133 | ii = 0
134 | for i, tildei in enumerate(tildeEps):
135 | if tildei:
136 | q_vector[ii] = inner_product(dJ, dC[i])
137 | ii += 1
138 | return q_vector
139 |
140 |
141 | def invert_dCdCT(dCdCT, debug):
142 | try:
143 | dCtdCtTinv = np.linalg.inv(dCdCT)
144 | except Exception:
145 | display(
146 | "Warning, constraints are not qualified. "
147 | + "Using pseudo-inverse.",
148 | debug,
149 | 1,
150 | color="orange_4a",
151 | )
152 | dCtdCtTinv = np.linalg.pinv(dCdCT)
153 |
154 | return dCtdCtTinv
155 |
156 |
157 | def line_search(
158 | problem,
159 | merit_eval_new,
160 | merit,
161 | AJ,
162 | AC,
163 | dt=1.0,
164 | maxtrials=10,
165 | tol_merit=1e-3,
166 | debug=1.0,
167 | ):
168 |
169 | vel_scale = problem.velocity_scale(problem.delta_x)
170 | problem.delta_x /= vel_scale
171 | success = 0
172 | for k in range(maxtrials):
173 | problem.retract(problem.phi, problem.delta_x, scaling=(dt * 0.5 ** k))
174 | problem.phi_ls.assign(problem.phi_hj)
175 | (newJ, newG, newH) = problem.eval(problem.phi_ls)
176 | newC = np.concatenate((newG, newH))
177 | new_merit = merit_eval_new(
178 | AJ,
179 | newJ,
180 | AC,
181 | newC,
182 | )
183 | print(f"newJ={newJ}, newC={newC}")
184 | print(f"merit={merit}, new_merit={new_merit}")
185 | if new_merit < (1 + np.sign(merit) * tol_merit) * merit:
186 | success = 1
187 | break
188 | else:
189 | display(
190 | "Warning, merit function did not decrease "
191 | + f"(merit={merit}, new_merit={new_merit})"
192 | + f"-> Trial {k+1}",
193 | debug,
194 | 0,
195 | "red",
196 | )
197 | # Last attempt is accepted, forgo reset the distance.
198 | if k == maxtrials - 1:
199 | break
200 | problem.reset_distance()
201 | if not success:
202 | display(
203 | "All trials have failed, passing to the next iteration.",
204 | debug,
205 | color="red",
206 | )
207 |
208 | return newJ, newG, newH
209 |
210 |
211 | def solve_dual_problem(
212 | p_matrix, q_vector, tildeEps, p, show_progress_qp=None, tol_qp=None
213 | ):
214 | # Solve the dual problem to obtain the new set of active constraints
215 | # Solve it using a quadratic solver:
216 | # minimize (1/2)*x'*P*x + q'*x
217 | # subject to G*x <= h
218 | # A*x = b.
219 | # A and b are optionals and not given here.
220 | # sum(tildeEps) = p + qtildeEps
221 |
222 | qtildeEps = sum(tildeEps) - p
223 |
224 | Pcvx = cvx.matrix(p_matrix)
225 | qcvx = cvx.matrix(q_vector)
226 | Gcvx = cvx.matrix(
227 | np.concatenate((np.zeros((qtildeEps, p)), -np.eye(qtildeEps)), axis=1)
228 | )
229 | hcvx = cvx.matrix(np.zeros((qtildeEps, 1)))
230 | if p + qtildeEps > 0:
231 | return cvx.solvers.qp(
232 | Pcvx,
233 | qcvx,
234 | Gcvx,
235 | hcvx,
236 | options={
237 | "show_progress": show_progress_qp,
238 | "reltol": 1e-20,
239 | "abstol": 1e-20,
240 | "feastol": tol_qp,
241 | },
242 | )
243 | else:
244 | return None
245 |
246 |
247 | def dCdCT_eval(dC, hat):
248 | dCdCT = np.zeros((sum(hat), sum(hat)))
249 | ii = 0
250 | for i, tildei in enumerate(hat):
251 | jj = 0
252 | for j, tildej in enumerate(hat):
253 | if tildej and tildei:
254 | dCdCT[ii, jj] = inner_product(dC[i], dC[j])
255 | jj += 1
256 | if tildei:
257 | ii += 1
258 | return dCdCT
259 |
260 |
261 | def dCdCT_eval_tilde(dC, indicesEps):
262 | dCdCT = np.zeros((sum(indicesEps), sum(indicesEps)))
263 | ii = 0
264 | for i, indi in enumerate(indicesEps):
265 | jj = 0
266 | for j, indj in enumerate(indicesEps):
267 | if indi and indj:
268 | dCdCT[ii, jj] = inner_product(dC[i], dC[j])
269 | jj += 1
270 | if indi:
271 | ii += 1
272 | return dCdCT
273 |
274 |
275 | def dCdJ_eval(dJ, dC, hat):
276 | dCdJ = hat.copy()
277 | ii = 0
278 | for i, hati in enumerate(hat):
279 | if hati:
280 | dCdJ[ii] = inner_product(dC[i], dJ)
281 | ii += 1
282 | return dCdJ
283 |
284 |
285 | def xiJ_eval(dJ, dC, muls, hat):
286 | xiJ = Function(dJ.function_space())
287 | if hat.any():
288 | list_func = []
289 | for i, hati in enumerate(hat):
290 | if hati:
291 | list_func.append(Constant(muls[i]) * dC[i])
292 | else:
293 | # Still add a function to be able to call Function.assign with
294 | # objects of same shape.
295 | list_func.append(Constant(0.0) * dC[i])
296 | xiJ.assign(dJ + sum(list_func))
297 | else:
298 | xiJ.assign(dJ)
299 |
300 | return xiJ
301 |
302 |
303 | def xiC_eval(C, dC, dCtdCtTinv, alphas, indicesEps):
304 | if len(C) == 0:
305 | return None
306 | # Sum of functions over indicesEps
307 | xiC = Function(dC[0].function_space())
308 | if indicesEps.any():
309 | list_func = []
310 | dCdTinvCalpha = dCtdCtTinv.dot(C[indicesEps] * alphas[indicesEps])
311 | ii = 0
312 | for i, indi in enumerate(indicesEps):
313 | if indi:
314 | list_func.append(Constant(dCdTinvCalpha[ii]) * dC[i])
315 | ii += 1
316 | xiC.assign(sum(list_func))
317 | return xiC
318 |
319 |
320 | def merit_eval(muls, indicesEps, dCtdCtTinv, AJ, J, AC, C):
321 | return AJ * (J + muls.dot(C)) + 0.5 * AC * C[indicesEps].dot(
322 | dCtdCtTinv.dot(C[indicesEps])
323 | )
324 |
325 |
326 | @no_annotations
327 | def nlspace_solve(
328 | problem: InfDimProblem, params=None, results=None, descent_output_dir=None
329 | ):
330 | """
331 | Solve the optimization problem
332 | min J(phi)
333 | phi in V
334 | under the constraints
335 | g_i(phi)=0 for all i=0..p-1
336 | h_i(phi)<=0 for all i=0..q-1
337 |
338 | Usage
339 | -----
340 | results=nlspace_solve(problem: InfDimProblem, params: dict, results:dict)
341 |
342 | Inputs
343 | ------
344 | problem : an `~InfDimProblem` object corresponding to the optimization
345 | problem above.
346 |
347 | params : (optional) a dictionary containing algorithm parameters
348 | (see below).
349 |
350 | results : (optional) a previous output of the nlspace_solve` function.
351 | The optimization will keep going from the last input of
352 | the dictionary `results['phi'][-1]`.
353 | Useful to restart an optimization after an interruption.
354 | descent_output_dir : Plot the descent direction in the given directory
355 |
356 | Output
357 | ------
358 | results : dictionary containing
359 | results['J'] : values of the objective function along the path
360 | (J(phi_0),...,J(phi_n))
361 | results['G'] : equality constraint values
362 | (G(phi_0),...,G(phi_n))
363 | results['H'] : inequality constraints values
364 | (H(phi_0),...,H(phi_n))
365 | results['muls'] : lagrange multiplier values
366 | (mu(phi_0),...,mu(phi_n))
367 |
368 |
369 | Optional algorithm parameters
370 | -----------------------------
371 |
372 | params['alphaJ'] : (default 1) scaling coefficient for the null space
373 | step xiJ decreasing the objective function
374 |
375 | params['alphaC'] : (default 1) scaling coefficient for the Gauss Newton
376 | step xiC decreasing the violation of the constraints
377 |
378 | params['alphas'] : (optional) vector of dimension
379 | problem.nconstraints + problem.nineqconstraints containing
380 | proportionality coefficients scaling the Gauss Newton direction xiC for
381 | each of the constraints
382 |
383 | params['debug'] : Tune the verbosity of the output (default 0)
384 | Set param['debug']=-1 to display only the final result
385 | Set param['debug']=-2 to remove any output
386 |
387 | params['dt'] : (default : `1.0`). Pseudo time-step expressed in a time unit.
388 | Used to modulate the optimization convergence/oscillatory behavior.
389 |
390 | params['hmin'] : (default : `1.0`). Mesh minimum length. TODO Replace this for dt
391 | in the calculation of the tolerances `eps`
392 |
393 | params['K']: tunes the distance at which inactive inequality constraints
394 | are felt. Constraints are felt from a distance K*params['dt']
395 |
396 | params['maxit'] : Maximal number of iterations (default : 4000)
397 |
398 | params['maxtrials']: (default 3) number of trials in between time steps
399 | until the merit function decreases
400 |
401 | params['tol'] : (default 1e-7) Algorithm stops when
402 | ||phi_{n+1}-phi_n|| params["tol"] and len(results["J"]) < params["maxit"]:
444 | with MPITimer(problem.phi.comm) as timings:
445 |
446 | results["J"].append(J)
447 | results["G"].append(G)
448 | results["H"].append(H)
449 |
450 | if problem.accept():
451 | break
452 |
453 | it = len(results["J"]) - 1
454 | display("\n", params["debug"], 1)
455 | display(
456 | f"{it}. J="
457 | + format(J, ".4g")
458 | + " "
459 | + "G=["
460 | + ",".join(format(constraint, ".4g") for constraint in G[:10])
461 | + "] "
462 | + "H=["
463 | + ",".join(format(constraint, ".4g") for constraint in H[:10])
464 | + "] "
465 | + " ||dx||_V="
466 | + format(normdx, ".4g"),
467 | params["debug"],
468 | 0,
469 | )
470 |
471 | # Returns the gradients (in the primal space). They are
472 | # firedrake.Function's
473 | (dJ, dG, dH) = problem.eval_gradients(problem.phi)
474 | dC = dG + dH
475 |
476 | H = np.asarray(H)
477 | G = np.asarray(G)
478 | C = np.concatenate((G, H))
479 |
480 | # Obtain the tolerances for the inequality constraints and the indices
481 | # for the violated constraints
482 | eps = getEps(
483 | dC,
484 | problem.n_eqconstraints,
485 | params["dt"],
486 | params["K"],
487 | norm_type=params["normalisation_norm"],
488 | )
489 | tildeEps = getTilde(C, problem.n_eqconstraints, eps=eps)
490 | print(f"eps: {eps}")
491 | # Obtain the violated contraints
492 | tilde = getTilde(C, problem.n_eqconstraints)
493 |
494 | p_matrix = p_matrix_eval(dC, tildeEps)
495 | q_vector = q_vector_eval(dJ, dC, tildeEps)
496 | qp_results = solve_dual_problem(
497 | p_matrix,
498 | q_vector,
499 | tildeEps,
500 | problem.n_eqconstraints,
501 | show_progress_qp=params["show_progress_qp"],
502 | tol_qp=params["tol_qp"],
503 | )
504 | muls = np.zeros(len(C))
505 | oldmuls = np.zeros(len(C))
506 | hat = np.asarray([False] * len(C))
507 |
508 | if qp_results:
509 | muls[tildeEps] = np.asarray(qp_results["x"]).flatten()
510 | oldmuls = muls.copy()
511 | hat = np.asarray([True] * len(C))
512 | hat[problem.n_eqconstraints :] = (
513 | muls[problem.n_eqconstraints :] > 30 * params["tol_qp"]
514 | )
515 | if params.get("disable_dual", False):
516 | hat = tildeEps
517 |
518 | dCdCT = dCdCT_eval(dC, hat)
519 | dCdCTinv = invert_dCdCT(dCdCT, params["debug"])
520 | muls = np.zeros(len(C))
521 |
522 | dCdJ = dCdJ_eval(dJ, dC, hat)
523 | muls[hat] = -dCdCTinv.dot(dCdJ[hat])
524 |
525 | if not np.all(muls[problem.n_eqconstraints :] >= 0):
526 | display(
527 | "Warning, the active set has not been predicted "
528 | + "correctly Using old lagrange multipliers",
529 | params["debug"],
530 | level=1,
531 | color="orange_4a",
532 | )
533 | hat = np.asarray([True] * len(C))
534 | muls = oldmuls.copy()
535 |
536 | results["muls"].append(muls)
537 | display(
538 | f"Lagrange multipliers: {muls[:10]}", params["debug"], level=5
539 | )
540 | xiJ = xiJ_eval(dJ, dC, muls, hat)
541 |
542 | # Set of constraints union of active and new violated constraints.
543 | indicesEps = np.logical_or(tilde, hat)
544 | dCdCT = dCdCT_eval(dC, indicesEps)
545 | dCtdCtTinv = invert_dCdCT(dCdCT, params["debug"])
546 |
547 | xiC = xiC_eval(C, dC, dCtdCtTinv, alphas, indicesEps)
548 |
549 | # TODO Consider this? AC = min(0.9, alphaC * dt / max(compute_norm(xiC), 1e-9))
550 | AJ = params["alphaJ"]
551 | AC = params["alphaC"]
552 |
553 | # Make updates with merit function
554 | if xiC:
555 | problem.delta_x.assign(
556 | Constant(-AJ) * xiJ - Constant(AC) * xiC
557 | )
558 | else:
559 | problem.delta_x.assign(Constant(-AJ) * xiJ)
560 | normdx = fd.norm(problem.delta_x)
561 |
562 | merit_eval_new = partial(merit_eval, muls, indicesEps, dCtdCtTinv)
563 | merit = merit_eval_new(AJ, J, AC, C)
564 | results["merit"].append(merit)
565 | if len(results["merit"]) > 3:
566 | print(
567 | f"Merit oscillation: {(results['merit'][-1] - results['merit'][-2]) * (results['merit'][-2] - results['merit'][-3])}"
568 | )
569 |
570 | if descent_output_dir:
571 | descent_pvd.write(problem.delta_x)
572 |
573 | newJ, newG, newH = line_search(
574 | problem,
575 | merit_eval_new,
576 | merit,
577 | AJ,
578 | AC,
579 | dt=params["dt"],
580 | maxtrials=params["maxtrials"],
581 | tol_merit=params["tol_merit"],
582 | debug=params["debug"],
583 | )
584 | problem.phi.assign(problem.phi_ls)
585 | (J, G, H) = (newJ, newG, newH)
586 |
587 | if params["monitor_time"]:
588 | print(
589 | f"Max time per iteration: {timings.max_time}, min time per iteration: {timings.min_time}"
590 | )
591 |
592 | results["J"].append(J)
593 | results["G"].append(G)
594 | results["H"].append(H)
595 |
596 | display("\n", params["debug"], -1)
597 | display("Optimization completed.", params["debug"], -1)
598 | return results
599 |
--------------------------------------------------------------------------------
/letop/optimization/utils.py:
--------------------------------------------------------------------------------
1 | import firedrake_adjoint as fda
2 | from pyadjoint import no_annotations
3 | import re
4 | import glob
5 | import firedrake as fd
6 |
7 |
8 | @no_annotations
9 | def is_checkpoint(output_dir):
10 | checkpoints = glob.glob(f"{output_dir}/checkpoint*")
11 | checkpoints_sorted = sorted(
12 | checkpoints,
13 | key=lambda L: list(map(int, re.findall(r"iter_(\d+)\.h5", L)))[0],
14 | )
15 | return checkpoints_sorted
16 |
17 |
18 | @no_annotations
19 | def read_checkpoint(checkpoints, phi):
20 | last_file = checkpoints[-1]
21 | current_iter = int(re.findall(r"iter_(\d+)\.h5", last_file)[0])
22 | with fd.HDF5File(last_file, "r") as checkpoint:
23 | checkpoint.read(phi, "/checkpoint")
24 | print(f"Restarting simulation at {current_iter}")
25 | return current_iter
26 |
--------------------------------------------------------------------------------
/letop/physics/__init__.py:
--------------------------------------------------------------------------------
1 | from .navier_stokes_brinkman import (
2 | NavierStokesBrinkmannForm,
3 | NavierStokesBrinkmannSolver,
4 | mark_no_flow_regions,
5 | InteriorBC,
6 | )
7 | from .advection_diffusion import AdvectionDiffusionGLS
8 | from .utils import hs, min_mesh_size, calculate_max_vel, max_mesh_dimension
9 |
--------------------------------------------------------------------------------
/letop/physics/advection_diffusion.py:
--------------------------------------------------------------------------------
1 | import firedrake as fd
2 | from firedrake import inner, dot, grad, div, dx
3 |
4 | def AdvectionDiffusionGLS(
5 | V: fd.FunctionSpace,
6 | theta: fd.Function,
7 | phi: fd.Function,
8 | PeInv: float = 1e-4,
9 | phi_t: fd.Function = None,
10 | ):
11 | PeInv_ct = fd.Constant(PeInv)
12 | rho = fd.TestFunction(V)
13 | F = (
14 | inner(theta, grad(phi)) * rho + PeInv_ct * inner(grad(phi), grad(rho))
15 | ) * dx
16 |
17 | if phi_t:
18 | F += phi_t * rho * dx
19 |
20 | h = fd.CellDiameter(V.ufl_domain())
21 | R_U = dot(theta, grad(phi)) - PeInv_ct * div(grad(phi))
22 |
23 | if phi_t:
24 | R_U += phi_t
25 |
26 | beta_gls = 0.9
27 | tau_gls = beta_gls * (
28 | (4.0 * dot(theta, theta) / h ** 2)
29 | + 9.0 * (4.0 * PeInv_ct / h ** 2) ** 2
30 | ) ** (-0.5)
31 |
32 | theta_U = dot(theta, grad(rho)) - PeInv_ct * div(grad(rho))
33 | F += tau_gls * inner(R_U, theta_U) * dx()
34 |
35 | return F
36 |
--------------------------------------------------------------------------------
/letop/physics/navier_stokes_brinkman.py:
--------------------------------------------------------------------------------
1 | from cmath import tau
2 | import firedrake as fd
3 | from firedrake import (
4 | inner,
5 | dot,
6 | grad,
7 | div,
8 | dx,
9 | )
10 | from pyadjoint.enlisting import Enlist
11 | import ufl
12 | from .utils import hs
13 | from typing import Callable, Union
14 | from ufl.algebra import Product
15 | from functools import partial
16 | from firedrake.cython.dmcommon import FACE_SETS_LABEL, CELL_SETS_LABEL
17 | from pyop2.utils import as_tuple
18 | from firedrake.utils import cached_property
19 | from pyop2.datatypes import IntType
20 | import numpy as np
21 | from typing import List
22 |
23 |
24 | def mark_no_flow_regions(mesh: fd.Mesh, regions: List, regions_marker: List):
25 | dm = mesh.topology_dm
26 | dm.createLabel(FACE_SETS_LABEL)
27 | dm.markBoundaryFaces("boundary_faces")
28 | for region, marker in zip(regions, regions_marker):
29 | cells = dm.getStratumIS(CELL_SETS_LABEL, region)
30 | for cell in cells.array:
31 | faces = dm.getCone(cell)
32 | for face in faces:
33 | if dm.getLabelValue("boundary_faces", face) == 1:
34 | continue
35 | dm.setLabelValue(FACE_SETS_LABEL, face, marker)
36 | dm.removeLabel("boundary_faces")
37 | return mesh
38 |
39 |
40 | class InteriorBC(fd.DirichletBC):
41 | @cached_property
42 | def nodes(self):
43 | dm = self.function_space().mesh().topology_dm
44 | section = self.function_space().dm.getDefaultSection()
45 | nodes = []
46 | for sd in as_tuple(self.sub_domain):
47 | nfaces = dm.getStratumSize(FACE_SETS_LABEL, sd)
48 | faces = dm.getStratumIS(FACE_SETS_LABEL, sd)
49 | if nfaces == 0:
50 | continue
51 | for face in faces.indices:
52 | # if dm.getLabelValue("interior_facets", face) < 0:
53 | # continue
54 | closure, _ = dm.getTransitiveClosure(face)
55 | for p in closure:
56 | dof = section.getDof(p)
57 | offset = section.getOffset(p)
58 | nodes.extend((offset + d) for d in range(dof))
59 | return np.unique(np.asarray(nodes, dtype=IntType))
60 |
61 |
62 | def NavierStokesBrinkmannForm(
63 | W: fd.FunctionSpace,
64 | w: fd.Function,
65 | nu,
66 | phi: Union[fd.Function, Product] = None,
67 | brinkmann_penalty: fd.Constant = None,
68 | brinkmann_min=0.0,
69 | design_domain=None,
70 | hs: Callable = hs,
71 | beta_gls=0.9,
72 | ) -> ufl.form:
73 | """Returns the Galerkin Least Squares formulation for the Navier-Stokes problem with a Brinkmann term
74 |
75 | Args:
76 | W (fd.FunctionSpace): [description]
77 | w (fd.Function): [description]
78 | phi (fd.Function): [description]
79 | nu ([type]): [description]
80 | brinkmann_penalty ([type], optional): [description]. Defaults to None.
81 | design_domain ([type], optional): Region where the level set is defined. Defaults to None.
82 |
83 | Returns:
84 | ufl.form: Nonlinear form
85 | """
86 | mesh = w.ufl_domain()
87 |
88 | W_elem = W.ufl_element()
89 | assert isinstance(W_elem, fd.MixedElement)
90 | if brinkmann_penalty:
91 | assert isinstance(brinkmann_penalty, fd.Constant)
92 | assert W_elem.num_sub_elements() == 2
93 |
94 | for W_sub_elem in W_elem.sub_elements():
95 | assert W_sub_elem.family() == "Lagrange"
96 | assert W_sub_elem.degree() == 1
97 | assert isinstance(W_elem.sub_elements()[0], fd.VectorElement)
98 |
99 | v, q = fd.TestFunctions(W)
100 | u, p = fd.split(w)
101 |
102 | # Main NS form
103 | F = (
104 | nu * inner(grad(u), grad(v)) * dx
105 | + inner(dot(grad(u), u), v) * dx
106 | - p * div(v) * dx
107 | + div(u) * q * dx
108 | )
109 |
110 | # Brinkmann terms for design
111 | def add_measures(list_dd, **kwargs):
112 | return sum((dx(dd, kwargs) for dd in list_dd[1::]), dx(list_dd[0]))
113 |
114 | def alpha(phi):
115 | return brinkmann_penalty * hs(phi) + fd.Constant(brinkmann_min)
116 |
117 | if brinkmann_penalty and phi is not None:
118 | if design_domain is not None:
119 | dx_brinkmann = partial(add_measures, Enlist(design_domain))
120 | else:
121 | dx_brinkmann = dx
122 |
123 | F = F + alpha(phi) * inner(u, v) * dx_brinkmann()
124 |
125 | # GLS stabilization
126 | R_U = dot(u, grad(u)) - nu * div(grad(u)) + grad(p)
127 | if isinstance(beta_gls, (float, int)):
128 | beta_gls = fd.Constant(beta_gls)
129 | h = fd.CellSize(mesh)
130 | tau_gls = beta_gls * (
131 | (4.0 * dot(u, u) / h ** 2) + 9.0 * (4.0 * nu / h ** 2) ** 2
132 | ) ** (-0.5)
133 |
134 | theta_U = dot(u, grad(v)) - nu * div(grad(v)) + grad(q)
135 | F = F + tau_gls * inner(R_U, theta_U) * dx()
136 |
137 | if brinkmann_penalty and phi is not None:
138 | tau_gls_alpha = beta_gls * (
139 | (4.0 * dot(u, u) / h ** 2)
140 | + 9.0 * (4.0 * nu / h ** 2) ** 2
141 | + (alpha(phi) / 1.0) ** 2
142 | ) ** (-0.5)
143 | R_U_alpha = R_U + alpha(phi) * u
144 | theta_alpha = theta_U + alpha(phi) * v
145 |
146 | F = F + tau_gls_alpha * inner(R_U_alpha, theta_alpha) * dx_brinkmann()
147 | if (
148 | design_domain is not None
149 | ): # Substract this domain from the original integral
150 | F = F - tau_gls * inner(R_U, theta_U) * dx_brinkmann()
151 |
152 | return F
153 |
154 |
155 | class NavierStokesBrinkmannSolver(object):
156 | def __init__(
157 | self, problem: fd.NonlinearVariationalProblem, **kwargs
158 | ) -> None:
159 | """Same than NonlinearVariationalSolver, but with just the SIMPLE preconditioner by default
160 | Args:
161 | problem ([type]): [description]
162 | nullspace ([type], optional): [description]. Defaults to None.
163 | solver_parameters ([type], optional): [description]. Defaults to None.
164 | """
165 | solver_parameters_default = {
166 | # "snes_type": "ksponly",
167 | # "snes_no_convergence_test" : None,
168 | # "snes_max_it": 1,
169 | "snes_type": "newtonls",
170 | "snes_linesearch_type": "l2",
171 | "snes_linesearch_maxstep": 1.0,
172 | # "snes_monitor": None,
173 | # "snes_linesearch_monitor": None,
174 | "snes_rtol": 1.0e-4,
175 | "snes_atol": 1.0e-4,
176 | "snes_stol": 0.0,
177 | "snes_max_linear_solve_fail": 10,
178 | "snes_converged_reason": None,
179 | "ksp_type": "fgmres",
180 | "mat_type": "aij",
181 | # "default_sub_matrix_type": "aij",
182 | "ksp_rtol": 1.0e-4,
183 | "ksp_atol": 1.0e-4,
184 | "ksp_max_it": 2000,
185 | # "ksp_monitor": None,
186 | "ksp_converged_reason": None,
187 | "pc_type": "fieldsplit",
188 | "pc_fieldsplit_type": "schur",
189 | "pc_fieldsplit_schur_factorization_type": "full",
190 | "pc_fieldsplit_schur_precondition": "selfp",
191 | "fieldsplit_0": {
192 | "ksp_type": "richardson",
193 | "ksp_richardson_self_scale": False,
194 | "ksp_max_it": 1,
195 | "pc_type": "ml",
196 | "ksp_atol": 1e-2,
197 | "pc_mg_cycle_type": "v",
198 | "pc_mg_type": "full",
199 | # "ksp_converged_reason": None,
200 | # "ksp_monitor": None,
201 | },
202 | "fieldsplit_1": {
203 | "ksp_type": "preonly",
204 | "pc_type": "ml",
205 | # "ksp_monitor": None,
206 | # "ksp_converged_reason": None,
207 | },
208 | "fieldsplit_1_upper_ksp_type": "preonly",
209 | "fieldsplit_1_upper_pc_type": "jacobi",
210 | }
211 | solver_parameters = kwargs.pop("solver_parameters", None)
212 | if solver_parameters:
213 | solver_parameters_default.update(solver_parameters)
214 |
215 | self.solver = fd.NonlinearVariationalSolver(
216 | problem, solver_parameters=solver_parameters_default, **kwargs
217 | )
218 |
219 | def solve(self, **kwargs):
220 | self.solver.solve(**kwargs)
221 |
--------------------------------------------------------------------------------
/letop/physics/utils.py:
--------------------------------------------------------------------------------
1 | import firedrake as fd
2 | from functools import lru_cache
3 | import numpy as np
4 | import ufl
5 | import math
6 | from mpi4py import MPI
7 |
8 |
9 | @lru_cache(1)
10 | def min_mesh_size(mesh):
11 | """Calculate minimum cell diameter in mesh
12 |
13 | Args:
14 | mesh ([type]): [description]
15 |
16 | Returns:
17 | float: [description]
18 | """
19 | DG0 = fd.FunctionSpace(mesh, "DG", 0)
20 | h_sizes = fd.assemble(
21 | fd.CellDiameter(mesh)
22 | / fd.CellVolume(mesh)
23 | * fd.TestFunction(DG0)
24 | * fd.dx
25 | ).dat.data_ro
26 | local_min_size = np.max(h_sizes)
27 | min_size = mesh.comm.allreduce(local_min_size, op=MPI.MAX)
28 | return min_size
29 |
30 |
31 | def hs(
32 | phi: fd.Function,
33 | epsilon: fd.Constant = fd.Constant(10000.0),
34 | width_h: float = None,
35 | shift: float = 0.0,
36 | min_value: fd.Function = fd.Constant(0.0),
37 | ):
38 | """Heaviside approximation
39 |
40 | Args:
41 | phi (fd.Function): Level set
42 | epsilon ([type], optional): Parameter to approximate the Heaviside.
43 | Defaults to Constant(10000.0).
44 | width_h (float): Width of the Heaviside approximation transition in
45 | terms of multiple of the mesh element size
46 | shift: (float): Shift the level set value to define the interface.
47 |
48 | Returns:
49 | [type]: [description]
50 | """
51 | if width_h:
52 | if epsilon:
53 | fd.warning(
54 | "Epsilon and width_h are both defined, pick one or the other. \
55 | Overriding epsilon choice"
56 | )
57 | mesh = phi.ufl_domain()
58 | hmin = min_mesh_size(mesh)
59 | epsilon = fd.Constant(
60 | math.log(0.99 ** 2 / 0.01 ** 2) / (width_h * hmin)
61 | )
62 |
63 | return (
64 | fd.Constant(1.0)
65 | / (fd.Constant(1.0) + ufl.exp(-epsilon * (phi - fd.Constant(shift))))
66 | + min_value
67 | )
68 |
69 |
70 | def dirac_delta(phi: fd.Function, epsilon=fd.Constant(10000.0), width_h=None):
71 | """Dirac delta approximation
72 |
73 | Args:
74 | phi (fd.Function): Level set
75 | epsilon ([type], optional): Parameter to approximate the Heaviside.
76 | Defaults to Constant(10000.0).
77 | width_h (float): Width of the Heaviside approximation transition in
78 | terms of multiple of the mesh element size
79 |
80 | Returns:
81 | [type]: [description]
82 | """
83 | if width_h:
84 | if epsilon:
85 | fd.warning(
86 | "Epsilon and width_h are both defined, pick one or the other. \
87 | Overriding epsilon choice"
88 | )
89 | mesh = phi.ufl_domain()
90 | hmin = min_mesh_size(mesh)
91 | epsilon = fd.Constant(
92 | math.log(0.95 ** 2 / 0.05 ** 2) / (width_h * hmin)
93 | )
94 |
95 | return (
96 | fd.Constant(epsilon)
97 | * ufl.exp(-epsilon * phi)
98 | / (fd.Constant(1.0) + ufl.exp(-epsilon * phi)) ** 2
99 | )
100 |
101 |
102 | def calculate_max_vel(velocity: fd.Function):
103 | mesh = velocity.ufl_domain()
104 | MAXSP = fd.FunctionSpace(mesh, "R", 0)
105 | maxv = fd.Function(MAXSP)
106 | domain = "{[i, j] : 0 <= i < u.dofs}"
107 | instruction = """
108 | maxv[0] = abs(u[i, 0]) + abs(u[i, 1])
109 | """
110 | fd.par_loop(
111 | (domain, instruction),
112 | fd.dx,
113 | {"u": (velocity, fd.READ), "maxv": (maxv, fd.MAX)},
114 | is_loopy_kernel=True,
115 | )
116 | maxval = maxv.dat.data[0]
117 | return maxval
118 |
119 |
120 | @lru_cache(1)
121 | def max_mesh_dimension(mesh: fd.Mesh):
122 | coords = mesh.coordinates
123 | MAXSP = fd.FunctionSpace(mesh, "R", 0)
124 | max_y = fd.Function(MAXSP)
125 | min_y = fd.Function(MAXSP)
126 | max_x = fd.Function(MAXSP)
127 | min_x = fd.Function(MAXSP)
128 | domain = "{[i, j] : 0 <= i < u.dofs}"
129 |
130 | def extract_comp(mode, comp, result):
131 | instruction = f"""
132 | component[0] = abs(u[i, {comp}])
133 | """
134 | fd.par_loop(
135 | (domain, instruction),
136 | fd.dx,
137 | {"u": (coords, fd.READ), "component": (result, mode)},
138 | is_loopy_kernel=True,
139 | )
140 | return result
141 |
142 | max_y_comp = extract_comp(fd.MAX, 1, max_y).dat.data[0]
143 | min_y_comp = extract_comp(fd.MIN, 1, min_y).dat.data[0]
144 | max_x_comp = extract_comp(fd.MAX, 0, max_x).dat.data[0]
145 | min_x_comp = extract_comp(fd.MIN, 0, min_x).dat.data[0]
146 | max_dim_x = abs(max_x_comp - min_x_comp)
147 | max_dim_y = abs(max_y_comp - min_y_comp)
148 |
149 | max_dim = max(max_dim_x, max_dim_y)
150 | if mesh.geometric_dimension() == 3:
151 | max_z = fd.Function(MAXSP)
152 | min_z = fd.Function(MAXSP)
153 | max_z_comp = extract_comp(fd.MAX, 2, max_z).dat.data[0]
154 | min_z_comp = extract_comp(fd.MIN, 2, min_z).dat.data[0]
155 | max_dim_z = abs(max_z_comp - min_z_comp)
156 | max_dim = max(max_dim, max_dim_z)
157 |
158 | return max_dim
159 |
--------------------------------------------------------------------------------
/letop_examples/__init__.py:
--------------------------------------------------------------------------------
1 | from .heat_exchanger import heat_exchanger_optimization
2 | from .cantilever import compliance_optimization
3 |
--------------------------------------------------------------------------------
/letop_examples/bridge/bridge.py:
--------------------------------------------------------------------------------
1 | import firedrake as fd
2 | from firedrake import (
3 | cos,
4 | pi,
5 | inner,
6 | grad,
7 | dx,
8 | ds,
9 | sym,
10 | nabla_grad,
11 | tr,
12 | Identity,
13 | ds_t,
14 | )
15 | import firedrake_adjoint as fda
16 | import numpy as np
17 |
18 | from letop.levelset import LevelSetFunctional, RegularizationSolver
19 | from letop.optimization import InfDimProblem, Constraint
20 | from letop.physics import hs
21 | from letop.optimization import nlspace_solve
22 | import itertools
23 | from firedrake import PETSc
24 | from solver_parameters import (
25 | gamg_parameters,
26 | hj_solver_parameters,
27 | reinit_solver_parameters,
28 | )
29 |
30 |
31 | import argparse
32 |
33 | fd.parameters["pyop2_options"]["block_sparsity"] = False
34 |
35 |
36 | def print(x):
37 | PETSc.Sys.Print(x)
38 |
39 |
40 | class MyBC(fd.DirichletBC):
41 | def __init__(self, V, value, markers):
42 | # Call superclass init
43 | # We provide a dummy subdomain id.
44 | super(MyBC, self).__init__(V, value, 0)
45 | # Override the "nodes" property which says where the boundary
46 | # condition is to be applied.
47 | self.nodes = np.unique(np.where(markers.dat.data_ro_with_halos > 0)[0])
48 |
49 |
50 | def create_function_marker(PHI, W, xlimits, ylimits):
51 | x, y, z = fd.SpatialCoordinate(PHI.ufl_domain())
52 | x_func, y_func, z_func = (
53 | fd.Function(PHI),
54 | fd.Function(PHI),
55 | fd.Function(PHI),
56 | )
57 | with fda.stop_annotating():
58 | x_func.interpolate(x)
59 | y_func.interpolate(y)
60 | z_func.interpolate(z)
61 |
62 | domain = "{[i, j]: 0 <= i < f.dofs and 0<= j <= 3}"
63 | instruction = f"""
64 | f[i, j] = 1.0 if (x[i, 0] < {xlimits[1]} and x[i, 0] > {xlimits[0]}) and (y[i, 0] < {ylimits[0]} or y[i, 0] > {ylimits[1]}) and z[i, 0] < 1e-7 else 0.0
65 | """
66 | I_BC = fd.Function(W)
67 | fd.par_loop(
68 | (domain, instruction),
69 | dx,
70 | {
71 | "f": (I_BC, fd.RW),
72 | "x": (x_func, fd.READ),
73 | "y": (y_func, fd.READ),
74 | "z": (z_func, fd.READ),
75 | },
76 | is_loopy_kernel=True,
77 | )
78 |
79 | return I_BC
80 |
81 |
82 | def compliance_bridge():
83 |
84 | parser = argparse.ArgumentParser(description="Heat exchanger")
85 | parser.add_argument(
86 | "--n_iters",
87 | dest="n_iters",
88 | type=int,
89 | action="store",
90 | default=1000,
91 | help="Number of optimization iterations",
92 | )
93 | parser.add_argument(
94 | "--output_dir",
95 | dest="output_dir",
96 | type=str,
97 | action="store",
98 | default="./",
99 | help="Output directory",
100 | )
101 | opts = parser.parse_args()
102 | output_dir = opts.output_dir
103 | # Elasticity parameters
104 | E, nu = 1.0, 0.3
105 | rho_min = fd.Constant(1e-4) # Min Vol fraction
106 | eps = fd.Constant(100.0) # Heaviside parameter
107 | mu, lmbda = fd.Constant(E / (2 * (1 + nu))), fd.Constant(
108 | E * nu / ((1 + nu) * (1 - 2 * nu))
109 | )
110 |
111 | mesh = fd.RectangleMesh(20, 40, 0.5, 1, quadrilateral=True)
112 | mh = fd.MeshHierarchy(mesh, 1)
113 | m = fd.ExtrudedMeshHierarchy(mh, height=1, base_layer=40)
114 | mesh = m[-1]
115 |
116 | S = fd.VectorFunctionSpace(mesh, "CG", 1)
117 | s = fd.Function(S, name="deform")
118 | mesh.coordinates.assign(mesh.coordinates + s)
119 |
120 | x, y, z = fd.SpatialCoordinate(mesh)
121 | PHI = fd.FunctionSpace(mesh, "CG", 1)
122 | lx = 1.0
123 | ly = 2.0
124 | lz = ly
125 | phi_expr = (
126 | -cos(4.0 / lx * pi * x)
127 | * cos(4.0 * pi / ly * y)
128 | * cos(4.0 / lz * pi * z)
129 | - 0.6
130 | )
131 | with fda.stop_annotating():
132 | phi = fd.interpolate(-phi_expr, PHI)
133 | phi.rename("LevelSet")
134 |
135 | H1 = fd.VectorElement("CG", mesh.ufl_cell(), 1)
136 | W = fd.FunctionSpace(mesh, H1)
137 | print(f"DOFS: {W.dim()}")
138 |
139 | modes = [fd.Function(W) for _ in range(6)]
140 | modes[0].interpolate(fd.Constant([1, 0, 0]))
141 | modes[1].interpolate(fd.Constant([0, 1, 0]))
142 | modes[2].interpolate(fd.Constant([0, 0, 1]))
143 | modes[3].interpolate(fd.as_vector([0, z, -y]))
144 | modes[4].interpolate(fd.as_vector([-z, 0, x]))
145 | modes[5].interpolate(fd.as_vector([y, -x, 0]))
146 | nullmodes = fd.VectorSpaceBasis(modes)
147 | # Make sure they're orthonormal.
148 | nullmodes.orthonormalize()
149 |
150 | u = fd.TrialFunction(W)
151 | v = fd.TestFunction(W)
152 |
153 | def epsilon(u):
154 | return sym(nabla_grad(u))
155 |
156 | def sigma(v):
157 | return 2.0 * mu * epsilon(v) + lmbda * tr(epsilon(v)) * Identity(3)
158 |
159 | # Variational forms
160 | a = inner(hs(phi, eps, min_value=rho_min) * sigma(u), nabla_grad(v)) * dx(
161 | degree=2
162 | )
163 | t = fd.Constant((0.0, 0.0, -1.0e-1))
164 | L = inner(t, v) * ds_t
165 |
166 | # Dirichlet BCs
167 | ylimits = (0.2, 1.8)
168 | xlimits = (0.4, 0.6)
169 | I_BC = create_function_marker(PHI, W, xlimits, ylimits)
170 | bc1 = MyBC(W, 0, I_BC)
171 | bc2 = fd.DirichletBC(W.sub(0), fd.Constant(0.0), 2)
172 | bc3 = fd.DirichletBC(W.sub(1), fd.Constant(0.0), 4)
173 |
174 | u_sol = fd.Function(W)
175 | fd.solve(
176 | a == L,
177 | u_sol,
178 | bcs=[bc1, bc2, bc3],
179 | solver_parameters=gamg_parameters,
180 | near_nullspace=nullmodes,
181 | )
182 |
183 | # Cost function
184 | Jform = fd.assemble(
185 | inner(hs(phi, eps, min_value=rho_min) * sigma(u_sol), epsilon(u_sol))
186 | * dx(degree=2)
187 | )
188 | # Constraint
189 | VolPen = fd.assemble(hs(phi, eps, min_value=rho_min) * dx(degree=2))
190 | total_vol = fd.assemble(fd.Constant(1.0) * dx(domain=mesh), annotate=False)
191 | VolControl = fda.Control(VolPen)
192 | Vval = 0.15 * total_vol
193 |
194 | # Plotting
195 | global_counter1 = itertools.count()
196 | phi_pvd = fd.File(f"{output_dir}/level_set_evolution.pvd")
197 |
198 | def deriv_cb(phi):
199 | iter = next(global_counter1)
200 | if iter % 10 == 0:
201 | phi_pvd.write(phi[0])
202 |
203 | c = fda.Control(s)
204 | Jhat = LevelSetFunctional(Jform, c, phi, derivative_cb_pre=deriv_cb)
205 | Vhat = LevelSetFunctional(VolPen, c, phi)
206 |
207 | # Regularization solver. Zero on the BCs boundaries
208 | beta_param = 0.005
209 | bcs_vel_1 = MyBC(S, 0, I_BC)
210 | bcs_vel_2 = fd.DirichletBC(S, fd.Constant((0.0, 0.0, 0.0)), "top")
211 | bcs_vel = [bcs_vel_1, bcs_vel_2]
212 | reg_solver = RegularizationSolver(
213 | S,
214 | mesh,
215 | beta=beta_param,
216 | gamma=1.0e4,
217 | dx=dx,
218 | bcs=bcs_vel,
219 | output_dir=None,
220 | solver_parameters=gamg_parameters,
221 | )
222 | dt = 0.05
223 | tol = 1e-5
224 |
225 | params = {
226 | "alphaC": 1.0,
227 | "K": 0.0001,
228 | "debug": 5,
229 | "maxit": opts.n_iters,
230 | "alphaJ": 2.0,
231 | "dt": dt,
232 | "maxtrials": 500,
233 | "tol_merit": 5e-2, # new merit can be within 0.5% of the previous merit
234 | "itnormalisation": 50,
235 | "tol": tol,
236 | }
237 | hj_solver_parameters["ts_dt"] = dt / 50.0
238 | solver_parameters = {
239 | "hj_solver": hj_solver_parameters,
240 | "reinit_solver": reinit_solver_parameters,
241 | }
242 |
243 | vol_constraint = Constraint(Vhat, Vval, VolControl)
244 | problem = InfDimProblem(
245 | Jhat,
246 | reg_solver,
247 | ineqconstraints=vol_constraint,
248 | solver_parameters=solver_parameters,
249 | )
250 | _ = nlspace_solve(problem, params)
251 |
252 |
253 | if __name__ == "__main__":
254 | compliance_bridge()
255 |
--------------------------------------------------------------------------------
/letop_examples/bridge/solver_parameters.py:
--------------------------------------------------------------------------------
1 | gamg_parameters = {
2 | "ksp_type": "cg",
3 | "ksp_max_it": 200,
4 | "pc_type": "gamg",
5 | "mat_type": "aij",
6 | "ksp_converged_reason": None,
7 | "mg_levels_esteig_ksp_type": "cg",
8 | "mg_levels_ksp_chebyshev_esteig_steps": 50,
9 | "mg_levels_ksp_type": "chebyshev",
10 | "mg_levels_pc_type": "sor",
11 | "pc_gamg_type": "agg",
12 | "pc_gamg_agg_nsmooths": 1,
13 | "pc_gamg_threshold": 0.01,
14 | }
15 |
16 | hj_solver_parameters = {
17 | "peclet_number": 1e-3,
18 | "ksp_type": "gmres",
19 | "ksp_converged_maxits": None,
20 | "snes_type": "ksponly",
21 | "snes_no_convergence_test": None,
22 | "snes_atol": 1e-5,
23 | "ksp_atol": 1e-5,
24 | "ksp_rtol": 1e-5,
25 | "ts_type": "beuler",
26 | "pc_type": "bjacobi",
27 | "sub_pc_type": "ilu",
28 | "sub_pc_factor_levels": 1,
29 | "ksp_max_it": 5000,
30 | "ksp_gmres_restart": 200,
31 | "ts_atol": 1e-5,
32 | "ts_rtol": 1e-5,
33 | "ts_max_steps": 400,
34 | }
35 |
36 | reinit_solver_parameters = {
37 | "ksp_type": "cg",
38 | "ksp_rtol": 1e-6,
39 | "pc_type": "hypre",
40 | "pc_hypre_type": "boomeramg",
41 | "pc_hypre_boomeramg_max_iter": 5,
42 | "pc_hypre_boomeramg_coarsen_type": "PMIS",
43 | "pc_hypre_boomeramg_agg_nl": 2,
44 | "pc_hypre_boomeramg_strong_threshold": 0.95,
45 | "pc_hypre_boomeramg_interp_type": "ext+i",
46 | "pc_hypre_boomeramg_P_max": 2,
47 | "pc_hypre_boomeramg_relax_type_all": "sequential-Gauss-Seidel",
48 | "pc_hypre_boomeramg_grid_sweeps_all": 1,
49 | "pc_hypre_boomeramg_truncfactor": 0.3,
50 | "pc_hypre_boomeramg_max_levels": 6,
51 | "ksp_max_it": 200,
52 | "ksp_converged_maxits": None,
53 | }
54 |
--------------------------------------------------------------------------------
/letop_examples/cantilever/__init__.py:
--------------------------------------------------------------------------------
1 | from .cantilever import compliance_optimization
2 |
--------------------------------------------------------------------------------
/letop_examples/cantilever/cantilever.py:
--------------------------------------------------------------------------------
1 | import firedrake as fd
2 | import firedrake_adjoint as fda
3 | from firedrake import (
4 | inner,
5 | dx,
6 | ds,
7 | cos,
8 | pi,
9 | exp,
10 | max_value,
11 | sym,
12 | nabla_grad,
13 | tr,
14 | Identity,
15 | )
16 |
17 | from letop.levelset import LevelSetFunctional, RegularizationSolver
18 | from letop.optimization import InfDimProblem, Constraint
19 | from letop.optimization import nlspace_solve
20 |
21 | import os
22 |
23 |
24 | def compliance_optimization(n_iters=200):
25 |
26 | output_dir = "cantilever/"
27 |
28 | path = os.path.abspath(__file__)
29 | dir_path = os.path.dirname(path)
30 | m = fd.Mesh(f"{dir_path}/mesh_cantilever.msh")
31 | mesh = fd.MeshHierarchy(m, 0)[-1]
32 |
33 | # Perturb the mesh coordinates. Necessary to calculate shape derivatives
34 | S = fd.VectorFunctionSpace(mesh, "CG", 1)
35 | s = fd.Function(S, name="deform")
36 | mesh.coordinates.assign(mesh.coordinates + s)
37 |
38 | # Initial level set function
39 | x, y = fd.SpatialCoordinate(mesh)
40 | PHI = fd.FunctionSpace(mesh, "CG", 1)
41 | lx = 2.0
42 | ly = 1.0
43 | phi_expr = (
44 | -cos(6.0 / lx * pi * x) * cos(4.0 * pi * y)
45 | - 0.6
46 | + max_value(200.0 * (0.01 - x ** 2 - (y - ly / 2) ** 2), 0.0)
47 | + max_value(100.0 * (x + y - lx - ly + 0.1), 0.0)
48 | + max_value(100.0 * (x - y - lx + 0.1), 0.0)
49 | )
50 | # Avoid recording the operation interpolate into the tape.
51 | # Otherwise, the shape derivatives will not be correct
52 | with fda.stop_annotating():
53 | phi = fd.interpolate(phi_expr, PHI)
54 | phi.rename("LevelSet")
55 | fd.File(output_dir + "phi_initial.pvd").write(phi)
56 |
57 | # Physics. Elasticity
58 | rho_min = 1e-5
59 | beta = fd.Constant(200.0)
60 |
61 | def hs(phi, beta):
62 | return fd.Constant(1.0) / (
63 | fd.Constant(1.0) + exp(-beta * phi)
64 | ) + fd.Constant(rho_min)
65 |
66 | H1_elem = fd.VectorElement("CG", mesh.ufl_cell(), 1)
67 | W = fd.FunctionSpace(mesh, H1_elem)
68 |
69 | u = fd.TrialFunction(W)
70 | v = fd.TestFunction(W)
71 |
72 | # Elasticity parameters
73 | E, nu = 1.0, 0.3
74 | mu, lmbda = fd.Constant(E / (2 * (1 + nu))), fd.Constant(
75 | E * nu / ((1 + nu) * (1 - 2 * nu))
76 | )
77 |
78 | def epsilon(u):
79 | return sym(nabla_grad(u))
80 |
81 | def sigma(v):
82 | return 2.0 * mu * epsilon(v) + lmbda * tr(epsilon(v)) * Identity(2)
83 |
84 | a = inner(hs(-phi, beta) * sigma(u), nabla_grad(v)) * dx
85 | t = fd.Constant((0.0, -75.0))
86 | L = inner(t, v) * ds(2)
87 |
88 | bc = fd.DirichletBC(W, fd.Constant((0.0, 0.0)), 1)
89 | parameters = {
90 | "ksp_type": "preonly",
91 | "pc_type": "lu",
92 | "mat_type": "aij",
93 | "ksp_converged_reason": None,
94 | "pc_factor_mat_solver_type": "mumps",
95 | }
96 | u_sol = fd.Function(W)
97 | F = fd.action(a, u_sol) - L
98 | problem = fd.NonlinearVariationalProblem(F, u_sol, bcs=bc)
99 | solver = fd.NonlinearVariationalSolver(
100 | problem, solver_parameters=parameters
101 | )
102 | solver.solve()
103 | # fd.solve(
104 | # a == L, u_sol, bcs=[bc], solver_parameters=parameters
105 | # ) # , nullspace=nullspace)
106 | with fda.stop_annotating():
107 | fd.File("u_sol.pvd").write(u_sol)
108 |
109 | # Cost function: Compliance
110 | J = fd.assemble(
111 | fd.Constant(1e-2)
112 | * inner(hs(-phi, beta) * sigma(u_sol), epsilon(u_sol))
113 | * dx
114 | )
115 |
116 | # Constraint: Volume
117 | with fda.stop_annotating():
118 | total_volume = fd.assemble(fd.Constant(1.0) * dx(domain=mesh))
119 | VolPen = fd.assemble(hs(-phi, beta) * dx)
120 | # Needed to track the value of the volume
121 | VolControl = fda.Control(VolPen)
122 | Vval = total_volume / 2.0
123 |
124 | phi_pvd = fd.File("phi_evolution.pvd", target_continuity=fd.H1)
125 |
126 | def deriv_cb(phi):
127 | with fda.stop_annotating():
128 | phi_pvd.write(phi[0])
129 |
130 | c = fda.Control(s)
131 | Jhat = LevelSetFunctional(J, c, phi, derivative_cb_pre=deriv_cb)
132 | Vhat = LevelSetFunctional(VolPen, c, phi)
133 | beta_param = 0.1
134 | # Boundary conditions for the shape derivatives.
135 | # They must be zero at the boundary conditions.
136 | bcs_vel = fd.DirichletBC(S, fd.Constant((0.0, 0.0)), (1, 2))
137 | # Regularize the shape derivatives
138 | reg_solver = RegularizationSolver(
139 | S,
140 | mesh,
141 | beta=beta_param,
142 | gamma=1.0e5,
143 | dx=dx,
144 | bcs=bcs_vel,
145 | output_dir=None,
146 | )
147 | # Hamilton-Jacobi equation to advect the level set
148 | dt = 0.05
149 | tol = 1e-5
150 |
151 | # Optimization problem
152 | vol_constraint = Constraint(Vhat, Vval, VolControl)
153 | problem = InfDimProblem(Jhat, reg_solver, ineqconstraints=vol_constraint)
154 |
155 | parameters = {
156 | "ksp_type": "preonly",
157 | "pc_type": "lu",
158 | "mat_type": "aij",
159 | "ksp_converged_reason": None,
160 | "pc_factor_mat_solver_type": "mumps",
161 | }
162 |
163 | params = {
164 | "alphaC": 3.0,
165 | "K": 0.1,
166 | "debug": 5,
167 | "alphaJ": 1.0,
168 | "dt": dt,
169 | "maxtrials": 10,
170 | "maxit": n_iters,
171 | "itnormalisation": 50,
172 | "tol": tol,
173 | }
174 | results = nlspace_solve(problem, params)
175 |
176 | return results
177 |
178 |
179 | if __name__ == "__main__":
180 | compliance_optimization()
181 |
--------------------------------------------------------------------------------
/letop_examples/cantilever/mesh_cantilever.geo:
--------------------------------------------------------------------------------
1 | size = 0.02;
2 | width = 2.0;
3 | height = 1.0;
4 | p0 = newp;
5 | Point(p0) = {0.0, 0.0, 0.0, size};
6 | p1 = newp;
7 | Point(p1) = {width, 0.0, 0.0, size};
8 | p2 = newp;
9 | Point(p2) = {width, 15.0/32.0 * height, 0.0, size};
10 | p3 = newp;
11 | Point(p3) = {width, 17.0/32.0 * height, 0.0, size};
12 | p4 = newp;
13 | Point(p4) = {width, height, 0.0, size};
14 | p5 = newp;
15 | Point(p5) = {0.0, height, 0.0, size};
16 |
17 | l0 = newl;
18 | Line(l0) = {p0, p1};
19 | l1 = newl;
20 | Line(l1) = {p1, p2};
21 | l2 = newl;
22 | Line(l2) = {p2, p3};
23 | l3 = newl;
24 | Line(l3) = {p3, p4};
25 | l4 = newl;
26 | Line(l4) = {p4, p5};
27 | l5 = newl;
28 | Line(l5) = {p5, p0};
29 |
30 | ll0 = newll;
31 | Line Loop(ll0) = {l0, l1, l2, l3, l4, l5};
32 | s0 = news;
33 | Plane Surface(s0) = {ll0};
34 | Physical Line(1) = {l5};
35 | Physical Line(2) = {l2};
36 | Physical Surface(0) = {s0};
37 |
--------------------------------------------------------------------------------
/letop_examples/heat_exchanger/2D_mesh.geo:
--------------------------------------------------------------------------------
1 | // This code was created by pygmsh vunknown.
2 | SetFactory("OpenCASCADE");
3 | Mesh.CharacteristicLengthMin = 0.02;
4 | Mesh.CharacteristicLengthMax = 0.02;
5 | s0 = news;
6 | Rectangle(s0) = {0.0, 0.0, 0.0, 1.2, 1.5};
7 | s1 = news;
8 | Rectangle(s1) = {-0.2, -2.7755575615628914e-17, 0.0, 0.2, 0.2};
9 | s2 = news;
10 | Rectangle(s2) = {-0.2, 0.26, 0.0, 0.2, 0.2};
11 | s3 = news;
12 | Rectangle(s3) = {1.2, -2.7755575615628914e-17, 0.0, 0.2, 0.2};
13 | s4 = news;
14 | Rectangle(s4) = {1.2, 0.26, 0.0, 0.2, 0.2};
15 | Physical Surface(2) = {s1};
16 | Physical Surface(3) = {s2};
17 | Physical Surface(4) = {s3};
18 | Physical Surface(5) = {s4};
19 | Physical Surface(0) = {s0};
20 | bo1[] = BooleanFragments{ Surface{s0}; Delete; } { Surface{s1};Surface{s2};Surface{s3};Surface{s4}; Delete;};
21 | vb1[] = Boundary{Surface{ s1 };};
22 | vb2[] = Boundary{Surface{ s2 };};
23 | vb3[] = Boundary{Surface{ s3 };};
24 | vb4[] = Boundary{Surface{ s4 };};
25 | vb0[] = Boundary{Surface{ s0 };};
26 | Physical Curve(5) = {vb0[],
27 | vb1[0], vb1[2],
28 | vb2[0], vb2[2],
29 | vb3[0], vb3[2],
30 | vb4[0], vb4[2]};
31 | Physical Curve(5) -= {-vb1[1], -vb2[1], -vb3[3], -vb4[3]};
32 | Physical Curve(1) = {vb1[3]};
33 | Physical Curve(2) = {vb3[1]};
34 | Physical Curve(3) = {vb2[3]};
35 | Physical Curve(4) = {vb4[1]};
36 |
--------------------------------------------------------------------------------
/letop_examples/heat_exchanger/2D_mesh.py:
--------------------------------------------------------------------------------
1 | import pygmsh as pg
2 | from heat_exchanger_nls import (
3 | height,
4 | width,
5 | dist_center,
6 | inlet_width,
7 | inlet_depth,
8 | line_sep,
9 | ymin1,
10 | ymin2,
11 | )
12 | from heat_exchanger_nls import (
13 | INMOUTH1,
14 | INMOUTH2,
15 | OUTMOUTH1,
16 | OUTMOUTH2,
17 | INLET1,
18 | INLET2,
19 | OUTLET1,
20 | OUTLET2,
21 | WALLS,
22 | DOMAIN,
23 | )
24 |
25 |
26 | def main():
27 | size = 0.02
28 | geom = pg.opencascade.Geometry(
29 | characteristic_length_min=size, characteristic_length_max=size
30 | )
31 |
32 | main_rect = geom.add_rectangle([0.0, 0.0, 0.0], width, height)
33 | mouth_inlet1 = geom.add_rectangle(
34 | [-inlet_depth, ymin1, 0.0], inlet_depth, inlet_width
35 | )
36 | mouth_inlet2 = geom.add_rectangle(
37 | [-inlet_depth, ymin2, 0.0], inlet_depth, inlet_width
38 | )
39 |
40 | mouth_outlet1 = geom.add_rectangle(
41 | [width, ymin1, 0.0], inlet_depth, inlet_width
42 | )
43 | mouth_outlet2 = geom.add_rectangle(
44 | [width, ymin2, 0.0], inlet_depth, inlet_width
45 | )
46 |
47 | print("ymin1 :{}".format(ymin1))
48 | print("ymin2 :{}".format(ymin2))
49 |
50 | geom.add_physical(mouth_inlet1, INMOUTH1)
51 | geom.add_physical(mouth_inlet2, INMOUTH2)
52 | geom.add_physical(mouth_outlet1, OUTMOUTH1)
53 | geom.add_physical(mouth_outlet2, OUTMOUTH2)
54 | geom.add_physical([main_rect], DOMAIN)
55 |
56 | _ = geom.boolean_fragments(
57 | [main_rect], [mouth_inlet1, mouth_inlet2, mouth_outlet1, mouth_outlet2]
58 | )
59 |
60 | geom.add_raw_code(
61 | """vb1[] = Boundary{{Surface{{ {0} }};}};
62 | vb2[] = Boundary{{Surface{{ {1} }};}};
63 | vb3[] = Boundary{{Surface{{ {2} }};}};
64 | vb4[] = Boundary{{Surface{{ {3} }};}};
65 | vb0[] = Boundary{{Surface{{ {4} }};}};""".format(
66 | mouth_inlet1.id,
67 | mouth_inlet2.id,
68 | mouth_outlet1.id,
69 | mouth_outlet2.id,
70 | main_rect.id,
71 | )
72 | )
73 | geom.add_raw_code(
74 | """Physical Curve({0}) = {{vb0[],
75 | vb1[0], vb1[2],
76 | vb2[0], vb2[2],
77 | vb3[0], vb3[2],
78 | vb4[0], vb4[2]}};""".format(
79 | WALLS
80 | )
81 | )
82 |
83 | geom.add_raw_code(
84 | "Physical Curve({0}) -= {{-vb1[1], -vb2[1], -vb3[3], -vb4[3]}};\n \
85 | Physical Curve({1}) = {{vb1[3]}};\n \
86 | Physical Curve({2}) = {{vb3[1]}};\n \
87 | Physical Curve({3}) = {{vb2[3]}};\n \
88 | Physical Curve({4}) = {{vb4[1]}};".format(
89 | WALLS, INLET1, OUTLET1, INLET2, OUTLET2
90 | )
91 | )
92 |
93 | mesh = pg.generate_mesh(geom, geo_filename="2D_mesh.geo")
94 | import meshio
95 |
96 | meshio.write("2D_mesh_heat_exchanger.vtk", mesh)
97 |
98 |
99 | if __name__ == "__main__":
100 | main()
101 |
--------------------------------------------------------------------------------
/letop_examples/heat_exchanger/README.md:
--------------------------------------------------------------------------------
1 | Example code used in the paper "Two dimensional topology optimization of heat exchangers with the density and level-set methods", Miguel Salazar et al.
2 | Run with
3 | ```python
4 | python3 heat_exchanger_nls.py --mu 0.02
5 | ```
6 | for a simulation with dynamic viscosity equal to 0.02.
7 |
8 | 
9 |
--------------------------------------------------------------------------------
/letop_examples/heat_exchanger/__init__.py:
--------------------------------------------------------------------------------
1 | from .heat_exchanger_nls import heat_exchanger_optimization
2 |
--------------------------------------------------------------------------------
/letop_examples/heat_exchanger/heat_exchanger_nls.py:
--------------------------------------------------------------------------------
1 | import firedrake as fd
2 | import firedrake_adjoint as fda
3 | from firedrake import (
4 | inner,
5 | derivative,
6 | grad,
7 | div,
8 | dx,
9 | ds,
10 | dot,
11 | dS,
12 | jump,
13 | avg,
14 | sin,
15 | cos,
16 | pi,
17 | exp,
18 | )
19 |
20 | from letop.levelset import LevelSetFunctional, RegularizationSolver
21 | from letop.optimization import InfDimProblem, Constraint
22 | from letop.optimization import nlspace_solve
23 |
24 | from pyadjoint import stop_annotating
25 | import os
26 |
27 | # Parameters
28 | height = 1.5
29 | shift_center = 0.52
30 | dist_center = 0.03
31 | inlet_width = 0.2
32 | inlet_depth = 0.2
33 | width = 1.2
34 | line_sep = height / 2.0 - shift_center
35 | # Line separating both inlets
36 | DOMAIN = 0
37 | INMOUTH1 = 2
38 | INMOUTH2 = 3
39 | OUTMOUTH1 = 4
40 | OUTMOUTH2 = 5
41 | INLET1, OUTLET1, INLET2, OUTLET2, WALLS, DESIGNBC = 1, 2, 3, 4, 5, 6
42 | ymin1 = line_sep - (dist_center + inlet_width)
43 | ymin2 = line_sep + dist_center
44 |
45 |
46 | def heat_exchanger_optimization(mu=0.03, n_iters=1000):
47 |
48 | output_dir = "2D/"
49 |
50 | path = os.path.abspath(__file__)
51 | dir_path = os.path.dirname(path)
52 | mesh = fd.Mesh(f"{dir_path}/2D_mesh.msh")
53 | # Perturb the mesh coordinates. Necessary to calculate shape derivatives
54 | S = fd.VectorFunctionSpace(mesh, "CG", 1)
55 | s = fd.Function(S, name="deform")
56 | mesh.coordinates.assign(mesh.coordinates + s)
57 |
58 | # Initial level set function
59 | x, y = fd.SpatialCoordinate(mesh)
60 | PHI = fd.FunctionSpace(mesh, "CG", 1)
61 | phi_expr = sin(y * pi / 0.2) * cos(x * pi / 0.2) - fd.Constant(0.8)
62 | # Avoid recording the operation interpolate into the tape.
63 | # Otherwise, the shape derivatives will not be correct
64 | with fda.stop_annotating():
65 | phi = fd.interpolate(phi_expr, PHI)
66 | phi.rename("LevelSet")
67 | fd.File(output_dir + "phi_initial.pvd").write(phi)
68 |
69 | # Physics
70 | mu = fd.Constant(mu) # viscosity
71 | alphamin = 1e-12
72 | alphamax = 2.5 / (2e-4)
73 | parameters = {
74 | "mat_type": "aij",
75 | "ksp_type": "preonly",
76 | "ksp_converged_reason": None,
77 | "pc_type": "lu",
78 | "pc_factor_mat_solver_type": "mumps",
79 | }
80 | stokes_parameters = parameters
81 | temperature_parameters = parameters
82 | u_inflow = 2e-3
83 | tin1 = fd.Constant(10.0)
84 | tin2 = fd.Constant(100.0)
85 |
86 | P2 = fd.VectorElement("CG", mesh.ufl_cell(), 2)
87 | P1 = fd.FiniteElement("CG", mesh.ufl_cell(), 1)
88 | TH = P2 * P1
89 | W = fd.FunctionSpace(mesh, TH)
90 |
91 | U = fd.TrialFunction(W)
92 | u, p = fd.split(U)
93 | V = fd.TestFunction(W)
94 | v, q = fd.split(V)
95 |
96 | epsilon = fd.Constant(10000.0)
97 |
98 | def hs(phi, epsilon):
99 | return fd.Constant(alphamax) * fd.Constant(1.0) / (
100 | fd.Constant(1.0) + exp(-epsilon * phi)
101 | ) + fd.Constant(alphamin)
102 |
103 | def stokes(phi, BLOCK_INLET_MOUTH, BLOCK_OUTLET_MOUTH):
104 | a_fluid = mu * inner(grad(u), grad(v)) - div(v) * p - q * div(u)
105 | darcy_term = inner(u, v)
106 | return (
107 | a_fluid * dx
108 | + hs(phi, epsilon) * darcy_term * dx(0)
109 | + alphamax
110 | * darcy_term
111 | * (dx(BLOCK_INLET_MOUTH) + dx(BLOCK_OUTLET_MOUTH))
112 | )
113 |
114 | # Dirichlet boundary conditions
115 | inflow1 = fd.as_vector(
116 | [
117 | u_inflow
118 | * sin(
119 | ((y - (line_sep - (dist_center + inlet_width))) * pi)
120 | / inlet_width
121 | ),
122 | 0.0,
123 | ]
124 | )
125 | inflow2 = fd.as_vector(
126 | [
127 | u_inflow
128 | * sin(((y - (line_sep + dist_center)) * pi) / inlet_width),
129 | 0.0,
130 | ]
131 | )
132 |
133 | noslip = fd.Constant((0.0, 0.0))
134 |
135 | # Stokes 1
136 | bcs1_1 = fd.DirichletBC(W.sub(0), noslip, WALLS)
137 | bcs1_2 = fd.DirichletBC(W.sub(0), inflow1, INLET1)
138 | bcs1_3 = fd.DirichletBC(W.sub(1), fd.Constant(0.0), OUTLET1)
139 | bcs1_4 = fd.DirichletBC(W.sub(0), noslip, INLET2)
140 | bcs1_5 = fd.DirichletBC(W.sub(0), noslip, OUTLET2)
141 | bcs1 = [bcs1_1, bcs1_2, bcs1_3, bcs1_4, bcs1_5]
142 |
143 | # Stokes 2
144 | bcs2_1 = fd.DirichletBC(W.sub(0), noslip, WALLS)
145 | bcs2_2 = fd.DirichletBC(W.sub(0), inflow2, INLET2)
146 | bcs2_3 = fd.DirichletBC(W.sub(1), fd.Constant(0.0), OUTLET2)
147 | bcs2_4 = fd.DirichletBC(W.sub(0), noslip, INLET1)
148 | bcs2_5 = fd.DirichletBC(W.sub(0), noslip, OUTLET1)
149 | bcs2 = [bcs2_1, bcs2_2, bcs2_3, bcs2_4, bcs2_5]
150 |
151 | # Forward problems
152 | U1, U2 = fd.Function(W), fd.Function(W)
153 | L = inner(fd.Constant((0.0, 0.0, 0.0)), V) * dx
154 | problem = fd.LinearVariationalProblem(
155 | stokes(-phi, INMOUTH2, OUTMOUTH2), L, U1, bcs=bcs1
156 | )
157 | solver_stokes1 = fd.LinearVariationalSolver(
158 | problem, solver_parameters=stokes_parameters, options_prefix="stokes_1"
159 | )
160 | solver_stokes1.solve()
161 | problem = fd.LinearVariationalProblem(
162 | stokes(phi, INMOUTH1, OUTMOUTH1), L, U2, bcs=bcs2
163 | )
164 | solver_stokes2 = fd.LinearVariationalSolver(
165 | problem, solver_parameters=stokes_parameters, options_prefix="stokes_2"
166 | )
167 | solver_stokes2.solve()
168 |
169 | # Convection difussion equation
170 | ks = fd.Constant(1e0)
171 | cp_value = 5.0e5
172 | cp = fd.Constant(cp_value)
173 | T = fd.FunctionSpace(mesh, "DG", 1)
174 | t = fd.Function(T, name="Temperature")
175 | w = fd.TestFunction(T)
176 |
177 | # Mesh-related functions
178 | n = fd.FacetNormal(mesh)
179 | h = fd.CellDiameter(mesh)
180 | u1, p1 = fd.split(U1)
181 | u2, p2 = fd.split(U2)
182 |
183 | def upwind(u):
184 | return (dot(u, n) + abs(dot(u, n))) / 2.0
185 |
186 | u1n = upwind(u1)
187 | u2n = upwind(u2)
188 |
189 | # Penalty term
190 | alpha = fd.Constant(500.0)
191 | # Bilinear form
192 | a_int = dot(grad(w), ks * grad(t) - cp * (u1 + u2) * t) * dx
193 |
194 | a_fac = (
195 | fd.Constant(-1.0) * ks * dot(avg(grad(w)), jump(t, n)) * dS
196 | + fd.Constant(-1.0) * ks * dot(jump(w, n), avg(grad(t))) * dS
197 | + ks("+") * (alpha("+") / avg(h)) * dot(jump(w, n), jump(t, n)) * dS
198 | )
199 |
200 | a_vel = (
201 | dot(
202 | jump(w),
203 | cp * (u1n("+") + u2n("+")) * t("+")
204 | - cp * (u1n("-") + u2n("-")) * t("-"),
205 | )
206 | * dS
207 | + dot(w, cp * (u1n + u2n) * t) * ds
208 | )
209 |
210 | a_bnd = (
211 | dot(w, cp * dot(u1 + u2, n) * t) * (ds(INLET1) + ds(INLET2))
212 | + w * t * (ds(INLET1) + ds(INLET2))
213 | - w * tin1 * ds(INLET1)
214 | - w * tin2 * ds(INLET2)
215 | + alpha / h * ks * w * t * (ds(INLET1) + ds(INLET2))
216 | - ks * dot(grad(w), t * n) * (ds(INLET1) + ds(INLET2))
217 | - ks * dot(grad(t), w * n) * (ds(INLET1) + ds(INLET2))
218 | )
219 |
220 | aT = a_int + a_fac + a_vel + a_bnd
221 |
222 | LT_bnd = (
223 | alpha / h * ks * tin1 * w * ds(INLET1)
224 | + alpha / h * ks * tin2 * w * ds(INLET2)
225 | - tin1 * ks * dot(grad(w), n) * ds(INLET1)
226 | - tin2 * ks * dot(grad(w), n) * ds(INLET2)
227 | )
228 |
229 | problem = fd.LinearVariationalProblem(derivative(aT, t), LT_bnd, t)
230 | solver_temp = fd.LinearVariationalSolver(
231 | problem,
232 | solver_parameters=temperature_parameters,
233 | options_prefix="temperature",
234 | )
235 | solver_temp.solve()
236 | # fd.solve(eT == 0, t, solver_parameters=temperature_parameters)
237 |
238 | # Cost function: Flux at the cold outlet
239 | scale_factor = 4e-4
240 | Jform = fd.assemble(
241 | fd.Constant(-scale_factor * cp_value) * inner(t * u1, n) * ds(OUTLET1)
242 | )
243 | # Constraints: Pressure drop on each fluid
244 | power_drop = 1e-2
245 | Power1 = fd.assemble(p1 / power_drop * ds(INLET1))
246 | Power2 = fd.assemble(p2 / power_drop * ds(INLET2))
247 |
248 | phi_pvd = fd.File("phi_evolution.pvd")
249 |
250 | def deriv_cb(phi):
251 | with stop_annotating():
252 | phi_pvd.write(phi[0])
253 |
254 | c = fda.Control(s)
255 |
256 | # Reduced Functionals
257 | Jhat = LevelSetFunctional(Jform, c, phi, derivative_cb_pre=deriv_cb)
258 | P1hat = LevelSetFunctional(Power1, c, phi)
259 | P1control = fda.Control(Power1)
260 |
261 | P2hat = LevelSetFunctional(Power2, c, phi)
262 | P2control = fda.Control(Power2)
263 |
264 | Jhat_v = Jhat(phi)
265 | print("Initial cost function value {:.5f}".format(Jhat_v), flush=True)
266 | print("Power drop 1 {:.5f}".format(Power1), flush=True)
267 | print("Power drop 2 {:.5f}".format(Power2), flush=True)
268 |
269 | beta_param = 0.08
270 | # Regularize the shape derivatives only in the domain marked with 0
271 | reg_solver = RegularizationSolver(
272 | S, mesh, beta=beta_param, gamma=1e5, dx=dx, design_domain=0
273 | )
274 |
275 | tol = 1e-5
276 | dt = 0.05
277 | params = {
278 | "alphaC": 1.0,
279 | "debug": 5,
280 | "alphaJ": 1.0,
281 | "dt": dt,
282 | "K": 1e-3,
283 | "maxit": n_iters,
284 | "maxtrials": 5,
285 | "itnormalisation": 10,
286 | "tol_merit": 5e-3, # new merit can be within 0.5% of the previous merit
287 | # "normalize_tol" : -1,
288 | "tol": tol,
289 | }
290 |
291 | solver_parameters = {
292 | "reinit_solver": {
293 | "h_factor": 2.0,
294 | }
295 | }
296 | # Optimization problem
297 | problem = InfDimProblem(
298 | Jhat,
299 | reg_solver,
300 | ineqconstraints=[
301 | Constraint(P1hat, 1.0, P1control),
302 | Constraint(P2hat, 1.0, P2control),
303 | ],
304 | solver_parameters=solver_parameters,
305 | )
306 | results = nlspace_solve(problem, params)
307 |
308 | return results
309 |
310 |
311 | if __name__ == "__main__":
312 | heat_exchanger_optimization()
313 |
--------------------------------------------------------------------------------
/letop_examples/heat_exchanger/parameters.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LLNL/letop/71d532b2f7f0caf0d50ba90a01419906570260dd/letop_examples/heat_exchanger/parameters.py
--------------------------------------------------------------------------------
/letop_examples/heat_exchanger_3D/box_heat_exch.geo:
--------------------------------------------------------------------------------
1 | //+
2 | SetFactory("OpenCASCADE");
3 | //+
4 | Box(1) = {0, 0, 0, 10, 10, 3};
5 | //+
6 | Cylinder(2) = {0, 2, 1.5, -2, 0, 0, 1.0, 2*Pi};
7 | //+
8 | Cylinder(3) = {0, 8, 1.5, -2, 0, 0, 1.0, 2*Pi};
9 | //+
10 | Cylinder(4) = {10, 8, 1.5, 2, 0, 0, 1.0, 2*Pi};
11 | //+
12 | Cylinder(5) = {10, 2, 1.5, 2, 0, 0, 1.0, 2*Pi};
13 | //+
14 | BooleanFragments{ Volume{1}; Delete; }{ Volume{4}; Volume{5}; Volume{2}; Volume{3}; Delete; }
15 | Dilate {{0, 0, 0}, {0.1, 0.1, 0.1}} {
16 | Volume{3}; Volume{1}; Volume{2}; Volume{4}; Volume{5};
17 | }
18 | //+
19 | Physical Surface(1) = {26};
20 | //+
21 | Physical Surface(2) = {40};
22 | //+
23 | Physical Surface(3) = {38};
24 | //+
25 | Physical Surface(4) = {42};
26 | //+
27 | Physical Surface(5) = {34, 35, 36, 28, 33, 30, 39, 41, 37, 25};
28 | //+
29 | Physical Volume(0) = {1};
30 | mesh_size = 0.02;
31 | MeshSize{ PointsOf{ Volume{1, 2, 3, 4, 5}; } } = mesh_size;
32 | //+
33 | Physical Volume(6) = {3};
34 | //+
35 | Physical Volume(7) = {2};
36 | //+
37 | Physical Volume(8) = {4};
38 | //+
39 | Physical Volume(9) = {5};
40 |
--------------------------------------------------------------------------------
/letop_examples/heat_exchanger_3D/box_heat_exch.geo.opt:
--------------------------------------------------------------------------------
1 | General.AxesFormatX = "%.3g";
2 | General.AxesFormatY = "%.3g";
3 | General.AxesFormatZ = "%.3g";
4 | General.AxesLabelX = "";
5 | General.AxesLabelY = "";
6 | General.AxesLabelZ = "";
7 | General.BackgroundImageFileName = "";
8 | General.BuildInfo = "Version: 4.7.1; License: GNU General Public License; Build OS: MacOSX; Build date: 20201116; Build host: gmsh.info; Build options: 64Bit ALGLIB ANN Bamg Blas[petsc] Blossom Cgns DIntegration Dlopen DomHex Eigen Fltk Gmm Hxt Jpeg[fltk] Kbipack Lapack[petsc] MathEx Med Mesh Metis Mmg Mpeg Netgen ONELAB ONELABMetamodel OpenCASCADE OpenCASCADE-CAF OpenGL OptHom PETSc Parser Plugins Png[fltk] Post QuadTri Solver TetGen/BR Voro++ Zlib[fltk]; FLTK version: 1.4.0; PETSc version: 3.9.3 (real arithmtic); OCC version: 7.4.0; MED version: 4.1.0; Packaged by: geuzaine; Web site: https://gmsh.info; Issue tracker: https://gitlab.onelab.info/gmsh/gmsh/issues";
9 | General.BuildOptions = "64Bit ALGLIB ANN Bamg Blas[petsc] Blossom Cgns DIntegration Dlopen DomHex Eigen Fltk Gmm Hxt Jpeg[fltk] Kbipack Lapack[petsc] MathEx Med Mesh Metis Mmg Mpeg Netgen ONELAB ONELABMetamodel OpenCASCADE OpenCASCADE-CAF OpenGL OptHom PETSc Parser Plugins Png[fltk] Post QuadTri Solver TetGen/BR Voro++ Zlib[fltk]";
10 | General.DefaultFileName = "untitled.geo";
11 | General.Display = "";
12 | General.ErrorFileName = ".gmsh-errors";
13 | General.ExecutableFileName = "/Applications/Gmsh.app/Contents/MacOS/gmsh";
14 | General.FileName = "box_heat_exch.geo";
15 | General.FltkTheme = "";
16 | General.GraphicsFont = "Helvetica";
17 | General.GraphicsFontEngine = "Native";
18 | General.GraphicsFontTitle = "Helvetica";
19 | General.OptionsFileName = ".gmsh-options";
20 | General.RecentFile0 = "box_heat_exch.geo";
21 | General.TextEditor = "open -t '%s'";
22 | General.TmpFileName = ".gmsh-tmp";
23 | General.Version = "4.7.1";
24 | General.WatchFilePattern = "";
25 | General.AbortOnError = 0;
26 | General.AlphaBlending = 1;
27 | General.Antialiasing = 0;
28 | General.ArrowHeadRadius = 0.12;
29 | General.ArrowStemLength = 0.5600000000000001;
30 | General.ArrowStemRadius = 0.02;
31 | General.Axes = 0;
32 | General.AxesMikado = 0;
33 | General.AxesAutoPosition = 1;
34 | General.AxesForceValue = 0;
35 | General.AxesMaxX = 1;
36 | General.AxesMaxY = 1;
37 | General.AxesMaxZ = 1;
38 | General.AxesMinX = 0;
39 | General.AxesMinY = 0;
40 | General.AxesMinZ = 0;
41 | General.AxesTicsX = 5;
42 | General.AxesTicsY = 5;
43 | General.AxesTicsZ = 5;
44 | General.AxesValueMaxX = 1;
45 | General.AxesValueMaxY = 1;
46 | General.AxesValueMaxZ = 1;
47 | General.AxesValueMinX = 0;
48 | General.AxesValueMinY = 0;
49 | General.AxesValueMinZ = 0;
50 | General.BackgroundGradient = 1;
51 | General.BackgroundImage3D = 0;
52 | General.BackgroundImagePage = 0;
53 | General.BackgroundImagePositionX = 0;
54 | General.BackgroundImagePositionY = 0;
55 | General.BackgroundImageWidth = -1;
56 | General.BackgroundImageHeight = -1;
57 | General.BoundingBoxSize = 1.766352490303145;
58 | General.Camera = 0;
59 | General.CameraAperture = 40;
60 | General.CameraEyeSeparationRatio = 1.5;
61 | General.CameraFocalLengthRatio = 1;
62 | General.Clip0A = 1;
63 | General.Clip0B = 0;
64 | General.Clip0C = 0;
65 | General.Clip0D = 0;
66 | General.Clip1A = 0;
67 | General.Clip1B = 1;
68 | General.Clip1C = 0;
69 | General.Clip1D = 0;
70 | General.Clip2A = 0;
71 | General.Clip2B = 0;
72 | General.Clip2C = 1;
73 | General.Clip2D = 0;
74 | General.Clip3A = -1;
75 | General.Clip3B = 0;
76 | General.Clip3C = 0;
77 | General.Clip3D = 1;
78 | General.Clip4A = 0;
79 | General.Clip4B = -1;
80 | General.Clip4C = 0;
81 | General.Clip4D = 1;
82 | General.Clip5A = 0;
83 | General.Clip5B = 0;
84 | General.Clip5C = -1;
85 | General.Clip5D = 1;
86 | General.ClipFactor = 5;
87 | General.ClipOnlyDrawIntersectingVolume = 0;
88 | General.ClipOnlyVolume = 0;
89 | General.ClipPositionX = 2064;
90 | General.ClipPositionY = 207;
91 | General.ClipWholeElements = 0;
92 | General.ColorScheme = 1;
93 | General.ConfirmOverwrite = 1;
94 | General.ContextPositionX = 2084;
95 | General.ContextPositionY = 204;
96 | General.DetachedMenu = 0;
97 | General.DisplayBorderFactor = 0.2;
98 | General.DoubleBuffer = 1;
99 | General.DrawBoundingBoxes = 0;
100 | General.ExpertMode = 0;
101 | General.ExtraPositionX = 650;
102 | General.ExtraPositionY = 350;
103 | General.ExtraHeight = 100;
104 | General.ExtraWidth = 100;
105 | General.FastRedraw = 0;
106 | General.FieldPositionX = 1469;
107 | General.FieldPositionY = 511;
108 | General.FieldHeight = 488;
109 | General.FieldWidth = 651;
110 | General.FileChooserPositionX = 200;
111 | General.FileChooserPositionY = 200;
112 | General.FltkColorScheme = 0;
113 | General.FltkRefreshRate = 5;
114 | General.FontSize = -1;
115 | General.GraphicsFontSize = 15;
116 | General.GraphicsFontSizeTitle = 18;
117 | General.GraphicsHeight = 1310;
118 | General.GraphicsPositionX = 0;
119 | General.GraphicsPositionY = 45;
120 | General.GraphicsWidth = 2560;
121 | General.HighOrderToolsPositionX = 650;
122 | General.HighOrderToolsPositionY = 150;
123 | General.HighResolutionGraphics = 1;
124 | General.HighResolutionPointSizeFactor = 2;
125 | General.InitialModule = 0;
126 | General.InputScrolling = 1;
127 | General.Light0 = 1;
128 | General.Light0X = 0.65;
129 | General.Light0Y = 0.65;
130 | General.Light0Z = 1;
131 | General.Light0W = 0;
132 | General.Light1 = 0;
133 | General.Light1X = 0.5;
134 | General.Light1Y = 0.3;
135 | General.Light1Z = 1;
136 | General.Light1W = 0;
137 | General.Light2 = 0;
138 | General.Light2X = 0.5;
139 | General.Light2Y = 0.3;
140 | General.Light2Z = 1;
141 | General.Light2W = 0;
142 | General.Light3 = 0;
143 | General.Light3X = 0.5;
144 | General.Light3Y = 0.3;
145 | General.Light3Z = 1;
146 | General.Light3W = 0;
147 | General.Light4 = 0;
148 | General.Light4X = 0.5;
149 | General.Light4Y = 0.3;
150 | General.Light4Z = 1;
151 | General.Light4W = 0;
152 | General.Light5 = 0;
153 | General.Light5X = 0.5;
154 | General.Light5Y = 0.3;
155 | General.Light5Z = 1;
156 | General.Light5W = 0;
157 | General.LineWidth = 1;
158 | General.ManipulatorPositionX = 650;
159 | General.ManipulatorPositionY = 150;
160 | General.MaxX = 1.2000001;
161 | General.MaxY = 1.0000001;
162 | General.MaxZ = 0.3500001;
163 | General.MenuWidth = 219;
164 | General.MenuHeight = 200;
165 | General.MenuPositionX = 400;
166 | General.MenuPositionY = 400;
167 | General.MessageFontSize = -1;
168 | General.MessageHeight = 300;
169 | General.MinX = -0.2000001;
170 | General.MinY = -1e-07;
171 | General.MinZ = -0.05000009999999996;
172 | General.MouseHoverMeshes = 0;
173 | General.MouseSelection = 1;
174 | General.MouseInvertZoom = 0;
175 | General.NativeFileChooser = 1;
176 | General.NonModalWindows = 1;
177 | General.NoPopup = 0;
178 | General.NumThreads = 1;
179 | General.OptionsPositionX = 1591;
180 | General.OptionsPositionY = 212;
181 | General.Orthographic = 1;
182 | General.PluginPositionX = 649;
183 | General.PluginPositionY = 549;
184 | General.PluginHeight = 488;
185 | General.PluginWidth = 708;
186 | General.PointSize = 3;
187 | General.PolygonOffsetAlwaysOn = 0;
188 | General.PolygonOffsetFactor = 0.5;
189 | General.PolygonOffsetUnits = 1;
190 | General.ProgressMeterStep = 10;
191 | General.QuadricSubdivisions = 6;
192 | General.RotationX = -0;
193 | General.RotationY = 0;
194 | General.RotationZ = -0;
195 | General.RotationCenterGravity = 1;
196 | General.RotationCenterX = 0;
197 | General.RotationCenterY = 0;
198 | General.RotationCenterZ = 0;
199 | General.SaveOptions = 0;
200 | General.SaveSession = 1;
201 | General.ScaleX = 1;
202 | General.ScaleY = 1;
203 | General.ScaleZ = 1;
204 | General.Shininess = 0.4;
205 | General.ShininessExponent = 40;
206 | General.ShowModuleMenu = 1;
207 | General.ShowOptionsOnStartup = 0;
208 | General.ShowMessagesOnStartup = 0;
209 | General.SmallAxes = 1;
210 | General.SmallAxesPositionX = -60;
211 | General.SmallAxesPositionY = -40;
212 | General.SmallAxesSize = 30;
213 | General.StatisticsPositionX = 650;
214 | General.StatisticsPositionY = 150;
215 | General.Stereo = 0;
216 | General.SystemMenuBar = 1;
217 | General.Terminal = 0;
218 | General.Tooltips = 1;
219 | General.Trackball = 1;
220 | General.TrackballHyperbolicSheet = 1;
221 | General.TrackballQuaternion0 = 0;
222 | General.TrackballQuaternion1 = 0;
223 | General.TrackballQuaternion2 = 0;
224 | General.TrackballQuaternion3 = 1;
225 | General.TranslationX = 0;
226 | General.TranslationY = 0;
227 | General.TranslationZ = 0;
228 | General.VectorType = 4;
229 | General.Verbosity = 5;
230 | General.VisibilityPositionX = 1854;
231 | General.VisibilityPositionY = 406;
232 | General.ZoomFactor = 4;
233 | General.Color.Background = {255,255,255};
234 | General.Color.BackgroundGradient = {208,215,255};
235 | General.Color.Foreground = {85,85,85};
236 | General.Color.Text = {0,0,0};
237 | General.Color.Axes = {0,0,0};
238 | General.Color.SmallAxes = {0,0,0};
239 | General.Color.AmbientLight = {25,25,25};
240 | General.Color.DiffuseLight = {255,255,255};
241 | General.Color.SpecularLight = {255,255,255};
242 | Geometry.DoubleClickedPointCommand = "";
243 | Geometry.DoubleClickedCurveCommand = "";
244 | Geometry.DoubleClickedSurfaceCommand = "";
245 | Geometry.DoubleClickedVolumeCommand = "";
246 | Geometry.OCCTargetUnit = "";
247 | Geometry.AutoCoherence = 1;
248 | Geometry.Clip = 0;
249 | Geometry.CopyMeshingMethod = 0;
250 | Geometry.Curves = 1;
251 | Geometry.CurveNumbers = 0;
252 | Geometry.CurveSelectWidth = 3;
253 | Geometry.CurveType = 0;
254 | Geometry.CurveWidth = 2;
255 | Geometry.DoubleClickedEntityTag = 0;
256 | Geometry.ExactExtrusion = 1;
257 | Geometry.ExtrudeReturnLateralEntities = 1;
258 | Geometry.ExtrudeSplinePoints = 5;
259 | Geometry.HighlightOrphans = 0;
260 | Geometry.LabelType = 0;
261 | Geometry.Light = 1;
262 | Geometry.LightTwoSide = 1;
263 | Geometry.MatchGeomAndMesh = 0;
264 | Geometry.MatchMeshScaleFactor = 1;
265 | Geometry.MatchMeshTolerance = 1e-06;
266 | Geometry.Normals = 0;
267 | Geometry.NumSubEdges = 40;
268 | Geometry.OCCAutoFix = 1;
269 | Geometry.OCCBooleanPreserveNumbering = 1;
270 | Geometry.OCCBoundsUseStl = 0;
271 | Geometry.OCCDisableStl = 0;
272 | Geometry.OCCFixDegenerated = 0;
273 | Geometry.OCCFixSmallEdges = 0;
274 | Geometry.OCCFixSmallFaces = 0;
275 | Geometry.OCCImportLabels = 1;
276 | Geometry.OCCMakeSolids = 0;
277 | Geometry.OCCParallel = 0;
278 | Geometry.OCCScaling = 1;
279 | Geometry.OCCSewFaces = 0;
280 | Geometry.OCCThruSectionsDegree = -1;
281 | Geometry.OCCUnionUnify = 1;
282 | Geometry.OffsetX = 0;
283 | Geometry.OffsetY = 0;
284 | Geometry.OffsetZ = 0;
285 | Geometry.OldCircle = 0;
286 | Geometry.OldRuledSurface = 0;
287 | Geometry.OldNewReg = 1;
288 | Geometry.Points = 1;
289 | Geometry.PointNumbers = 0;
290 | Geometry.PointSelectSize = 6;
291 | Geometry.PointSize = 4;
292 | Geometry.PointType = 0;
293 | Geometry.ReparamOnFaceRobust = 0;
294 | Geometry.ScalingFactor = 1;
295 | Geometry.OrientedPhysicals = 1;
296 | Geometry.SnapX = 0.1;
297 | Geometry.SnapY = 0.1;
298 | Geometry.SnapZ = 0.1;
299 | Geometry.Surfaces = 0;
300 | Geometry.SurfaceNumbers = 0;
301 | Geometry.SurfaceType = 0;
302 | Geometry.Tangents = 0;
303 | Geometry.Tolerance = 1e-08;
304 | Geometry.ToleranceBoolean = 0;
305 | Geometry.Transform = 0;
306 | Geometry.TransformXX = 1;
307 | Geometry.TransformXY = 0;
308 | Geometry.TransformXZ = 0;
309 | Geometry.TransformYX = 0;
310 | Geometry.TransformYY = 1;
311 | Geometry.TransformYZ = 0;
312 | Geometry.TransformZX = 0;
313 | Geometry.TransformZY = 0;
314 | Geometry.TransformZZ = 1;
315 | Geometry.Volumes = 0;
316 | Geometry.VolumeNumbers = 0;
317 | Geometry.Color.Points = {90,90,90};
318 | Geometry.Color.Curves = {0,0,255};
319 | Geometry.Color.Surfaces = {128,128,128};
320 | Geometry.Color.Volumes = {255,255,0};
321 | Geometry.Color.Selection = {255,0,0};
322 | Geometry.Color.HighlightZero = {255,0,0};
323 | Geometry.Color.HighlightOne = {255,150,0};
324 | Geometry.Color.HighlightTwo = {255,255,0};
325 | Geometry.Color.Tangents = {255,255,0};
326 | Geometry.Color.Normals = {255,0,0};
327 | Geometry.Color.Projection = {0,255,0};
328 | Mesh.Algorithm = 2;
329 | Mesh.Algorithm3D = 1;
330 | Mesh.AlgorithmSwitchOnFailure = 1;
331 | Mesh.AngleSmoothNormals = 30;
332 | Mesh.AngleToleranceFacetOverlap = 0.1;
333 | Mesh.AnisoMax = 9.999999999999999e+32;
334 | Mesh.AllowSwapAngle = 10;
335 | Mesh.BdfFieldFormat = 1;
336 | Mesh.Binary = 0;
337 | Mesh.BoundaryLayerFanPoints = 5;
338 | Mesh.CgnsImportOrder = 1;
339 | Mesh.CgnsImportIgnoreBC = 0;
340 | Mesh.CgnsImportIgnoreSolution = 0;
341 | Mesh.CgnsConstructTopology = 0;
342 | Mesh.CgnsExportCPEX0045 = 0;
343 | Mesh.CgnsExportStructured = 0;
344 | Mesh.Clip = 0;
345 | Mesh.ColorCarousel = 1;
346 | Mesh.CompoundClassify = 1;
347 | Mesh.CompoundMeshSizeFactor = 0.5;
348 | Mesh.CpuTime = 0;
349 | Mesh.DrawSkinOnly = 0;
350 | Mesh.Dual = 0;
351 | Mesh.ElementOrder = 1;
352 | Mesh.Explode = 1;
353 | Mesh.FirstElementTag = 1;
354 | Mesh.FirstNodeTag = 1;
355 | Mesh.FlexibleTransfinite = 0;
356 | Mesh.Format = 10;
357 | Mesh.Hexahedra = 1;
358 | Mesh.HighOrderDistCAD = 0;
359 | Mesh.HighOrderIterMax = 100;
360 | Mesh.HighOrderNumLayers = 6;
361 | Mesh.HighOrderOptimize = 0;
362 | Mesh.HighOrderPassMax = 25;
363 | Mesh.HighOrderPeriodic = 0;
364 | Mesh.HighOrderPoissonRatio = 0.33;
365 | Mesh.HighOrderSavePeriodic = 0;
366 | Mesh.HighOrderPrimSurfMesh = 0;
367 | Mesh.HighOrderThresholdMin = 0.1;
368 | Mesh.HighOrderThresholdMax = 2;
369 | Mesh.LabelSampling = 1;
370 | Mesh.LabelType = 0;
371 | Mesh.LcIntegrationPrecision = 1e-09;
372 | Mesh.Light = 1;
373 | Mesh.LightLines = 2;
374 | Mesh.LightTwoSide = 1;
375 | Mesh.Lines = 0;
376 | Mesh.LineNumbers = 0;
377 | Mesh.LineWidth = 1;
378 | Mesh.MaxIterDelaunay3D = 0;
379 | Mesh.MaxNumThreads1D = 0;
380 | Mesh.MaxNumThreads2D = 0;
381 | Mesh.MaxNumThreads3D = 0;
382 | Mesh.MaxRetries = 10;
383 | Mesh.MeshOnlyVisible = 0;
384 | Mesh.MeshOnlyEmpty = 0;
385 | Mesh.MeshSizeExtendFromBoundary = 1;
386 | Mesh.MeshSizeFactor = 1;
387 | Mesh.MeshSizeMin = 0;
388 | Mesh.MeshSizeMax = 1e+22;
389 | Mesh.MeshSizeFromCurvature = 0;
390 | Mesh.MeshSizeFromPoints = 1;
391 | Mesh.MeshSizeFromParametricPoints = 0;
392 | Mesh.MetisAlgorithm = 1;
393 | Mesh.MetisEdgeMatching = 2;
394 | Mesh.MetisMaxLoadImbalance = -1;
395 | Mesh.MetisObjective = 1;
396 | Mesh.MetisMinConn = -1;
397 | Mesh.MetisRefinementAlgorithm = 2;
398 | Mesh.MinimumCirclePoints = 7;
399 | Mesh.MinimumCurvePoints = 3;
400 | Mesh.MinimumElementsPerTwoPi = 6;
401 | Mesh.MshFileVersion = 4.1;
402 | Mesh.MedFileMinorVersion = -1;
403 | Mesh.MedImportGroupsOfNodes = 0;
404 | Mesh.MedSingleModel = 0;
405 | Mesh.PartitionHexWeight = -1;
406 | Mesh.PartitionLineWeight = -1;
407 | Mesh.PartitionPrismWeight = -1;
408 | Mesh.PartitionPyramidWeight = -1;
409 | Mesh.PartitionQuadWeight = -1;
410 | Mesh.PartitionTrihedronWeight = 0;
411 | Mesh.PartitionTetWeight = -1;
412 | Mesh.PartitionTriWeight = -1;
413 | Mesh.PartitionCreateTopology = 1;
414 | Mesh.PartitionCreatePhysicals = 1;
415 | Mesh.PartitionCreateGhostCells = 0;
416 | Mesh.PartitionSplitMeshFiles = 0;
417 | Mesh.PartitionTopologyFile = 0;
418 | Mesh.PartitionOldStyleMsh2 = 1;
419 | Mesh.ReparamMaxTriangles = 250000;
420 | Mesh.NbHexahedra = 0;
421 | Mesh.NbNodes = 0;
422 | Mesh.NbPartitions = 0;
423 | Mesh.NbPrisms = 0;
424 | Mesh.NbPyramids = 0;
425 | Mesh.NbTrihedra = 0;
426 | Mesh.NbQuadrangles = 0;
427 | Mesh.NbTetrahedra = 0;
428 | Mesh.NbTriangles = 0;
429 | Mesh.NewtonConvergenceTestXYZ = 0;
430 | Mesh.Normals = 0;
431 | Mesh.NumSubEdges = 2;
432 | Mesh.Optimize = 1;
433 | Mesh.OptimizeThreshold = 0.3;
434 | Mesh.OptimizeNetgen = 0;
435 | Mesh.Points = 0;
436 | Mesh.PointNumbers = 0;
437 | Mesh.PointSize = 4;
438 | Mesh.PointType = 0;
439 | Mesh.Prisms = 1;
440 | Mesh.Pyramids = 1;
441 | Mesh.Trihedra = 1;
442 | Mesh.Quadrangles = 1;
443 | Mesh.QualityInf = 0;
444 | Mesh.QualitySup = 0;
445 | Mesh.QualityType = 2;
446 | Mesh.RadiusInf = 0;
447 | Mesh.RadiusSup = 0;
448 | Mesh.RandomFactor = 1e-09;
449 | Mesh.RandomFactor3D = 1e-12;
450 | Mesh.RandomSeed = 1;
451 | Mesh.PreserveNumberingMsh2 = 0;
452 | Mesh.IgnoreParametrization = 0;
453 | Mesh.IgnorePeriodicity = 1;
454 | Mesh.RecombinationAlgorithm = 0;
455 | Mesh.RecombineAll = 0;
456 | Mesh.RecombineOptimizeTopology = 5;
457 | Mesh.Recombine3DAll = 0;
458 | Mesh.Recombine3DLevel = 0;
459 | Mesh.Recombine3DConformity = 0;
460 | Mesh.RefineSteps = 10;
461 | Mesh.Renumber = 1;
462 | Mesh.SaveAll = 0;
463 | Mesh.SaveElementTagType = 1;
464 | Mesh.SaveTopology = 0;
465 | Mesh.SaveParametric = 0;
466 | Mesh.SaveGroupsOfElements = 1;
467 | Mesh.SaveGroupsOfNodes = 0;
468 | Mesh.ScalingFactor = 1;
469 | Mesh.SecondOrderIncomplete = 0;
470 | Mesh.SecondOrderLinear = 0;
471 | Mesh.Smoothing = 1;
472 | Mesh.SmoothCrossField = 0;
473 | Mesh.CrossFieldClosestPoint = 1;
474 | Mesh.SmoothNormals = 0;
475 | Mesh.SmoothRatio = 1.8;
476 | Mesh.StlAngularDeflection = 0.35;
477 | Mesh.StlLinearDeflection = 0.01;
478 | Mesh.StlOneSolidPerSurface = 0;
479 | Mesh.StlRemoveDuplicateTriangles = 0;
480 | Mesh.SubdivisionAlgorithm = 0;
481 | Mesh.SurfaceEdges = 1;
482 | Mesh.SurfaceFaces = 0;
483 | Mesh.SurfaceNumbers = 0;
484 | Mesh.SwitchElementTags = 0;
485 | Mesh.Tangents = 0;
486 | Mesh.Tetrahedra = 1;
487 | Mesh.ToleranceEdgeLength = 0;
488 | Mesh.ToleranceInitialDelaunay = 1e-08;
489 | Mesh.Triangles = 1;
490 | Mesh.UnvStrictFormat = 1;
491 | Mesh.VolumeEdges = 1;
492 | Mesh.VolumeFaces = 0;
493 | Mesh.VolumeNumbers = 0;
494 | Mesh.Voronoi = 0;
495 | Mesh.ZoneDefinition = 0;
496 | Mesh.Color.Points = {0,0,255};
497 | Mesh.Color.PointsSup = {255,0,255};
498 | Mesh.Color.Lines = {0,0,0};
499 | Mesh.Color.Triangles = {160,150,255};
500 | Mesh.Color.Quadrangles = {130,120,225};
501 | Mesh.Color.Tetrahedra = {160,150,255};
502 | Mesh.Color.Hexahedra = {130,120,225};
503 | Mesh.Color.Prisms = {232,210,23};
504 | Mesh.Color.Pyramids = {217,113,38};
505 | Mesh.Color.Trihedra = {20,255,0};
506 | Mesh.Color.Tangents = {255,255,0};
507 | Mesh.Color.Normals = {255,0,0};
508 | Mesh.Color.Zero = {255,120,0};
509 | Mesh.Color.One = {0,255,132};
510 | Mesh.Color.Two = {255,160,0};
511 | Mesh.Color.Three = {0,255,192};
512 | Mesh.Color.Four = {255,200,0};
513 | Mesh.Color.Five = {0,216,255};
514 | Mesh.Color.Six = {255,240,0};
515 | Mesh.Color.Seven = {0,176,255};
516 | Mesh.Color.Eight = {228,255,0};
517 | Mesh.Color.Nine = {0,116,255};
518 | Mesh.Color.Ten = {188,255,0};
519 | Mesh.Color.Eleven = {0,76,255};
520 | Mesh.Color.Twelve = {148,255,0};
521 | Mesh.Color.Thirteen = {24,0,255};
522 | Mesh.Color.Fourteen = {108,255,0};
523 | Mesh.Color.Fifteen = {84,0,255};
524 | Mesh.Color.Sixteen = {68,255,0};
525 | Mesh.Color.Seventeen = {104,0,255};
526 | Mesh.Color.Eighteen = {0,255,52};
527 | Mesh.Color.Nineteen = {184,0,255};
528 | Solver.Executable0 = "";
529 | Solver.Executable1 = "";
530 | Solver.Executable2 = "";
531 | Solver.Executable3 = "";
532 | Solver.Executable4 = "";
533 | Solver.Executable5 = "";
534 | Solver.Executable6 = "";
535 | Solver.Executable7 = "";
536 | Solver.Executable8 = "";
537 | Solver.Executable9 = "";
538 | Solver.Name0 = "GetDP";
539 | Solver.Name1 = "";
540 | Solver.Name2 = "";
541 | Solver.Name3 = "";
542 | Solver.Name4 = "";
543 | Solver.Name5 = "";
544 | Solver.Name6 = "";
545 | Solver.Name7 = "";
546 | Solver.Name8 = "";
547 | Solver.Name9 = "";
548 | Solver.Extension0 = ".pro";
549 | Solver.Extension1 = "";
550 | Solver.Extension2 = "";
551 | Solver.Extension3 = "";
552 | Solver.Extension4 = "";
553 | Solver.Extension5 = "";
554 | Solver.Extension6 = "";
555 | Solver.Extension7 = "";
556 | Solver.Extension8 = "";
557 | Solver.Extension9 = "";
558 | Solver.OctaveInterpreter = "octave";
559 | Solver.PythonInterpreter = "python";
560 | Solver.RemoteLogin0 = "";
561 | Solver.RemoteLogin1 = "";
562 | Solver.RemoteLogin2 = "";
563 | Solver.RemoteLogin3 = "";
564 | Solver.RemoteLogin4 = "";
565 | Solver.RemoteLogin5 = "";
566 | Solver.RemoteLogin6 = "";
567 | Solver.RemoteLogin7 = "";
568 | Solver.RemoteLogin8 = "";
569 | Solver.RemoteLogin9 = "";
570 | Solver.SocketName = ".gmshsock";
571 | Solver.AlwaysListen = 0;
572 | Solver.AutoArchiveOutputFiles = 0;
573 | Solver.AutoCheck = 1;
574 | Solver.AutoLoadDatabase = 0;
575 | Solver.AutoSaveDatabase = 1;
576 | Solver.AutoMesh = 2;
577 | Solver.AutoMergeFile = 1;
578 | Solver.AutoShowViews = 2;
579 | Solver.AutoShowLastStep = 1;
580 | Solver.Plugins = 0;
581 | Solver.ShowInvisibleParameters = 0;
582 | Solver.Timeout = 5;
583 | PostProcessing.DoubleClickedGraphPointCommand = "";
584 | PostProcessing.GraphPointCommand = "";
585 | PostProcessing.AnimationDelay = 0.1;
586 | PostProcessing.AnimationCycle = 0;
587 | PostProcessing.AnimationStep = 1;
588 | PostProcessing.CombineRemoveOriginal = 1;
589 | PostProcessing.CombineCopyOptions = 1;
590 | PostProcessing.DoubleClickedGraphPointX = 0;
591 | PostProcessing.DoubleClickedGraphPointY = 0;
592 | PostProcessing.DoubleClickedView = 0;
593 | PostProcessing.ForceElementData = 0;
594 | PostProcessing.ForceNodeData = 0;
595 | PostProcessing.Format = 10;
596 | PostProcessing.GraphPointX = 0;
597 | PostProcessing.GraphPointY = 0;
598 | PostProcessing.HorizontalScales = 1;
599 | PostProcessing.Link = 0;
600 | PostProcessing.NbViews = 0;
601 | PostProcessing.Plugins = 1;
602 | PostProcessing.SaveInterpolationMatrices = 1;
603 | PostProcessing.SaveMesh = 1;
604 | PostProcessing.Smoothing = 0;
605 | Print.ParameterCommand = "Mesh.Clip=1; View.Clip=1; General.ClipWholeElements=1; General.Clip0D=Print.Parameter; SetChanged;";
606 | Print.Parameter = 0;
607 | Print.ParameterFirst = -1;
608 | Print.ParameterLast = 1;
609 | Print.ParameterSteps = 10;
610 | Print.Background = 0;
611 | Print.CompositeWindows = 0;
612 | Print.DeleteTemporaryFiles = 1;
613 | Print.EpsBestRoot = 1;
614 | Print.EpsCompress = 0;
615 | Print.EpsLineWidthFactor = 1;
616 | Print.EpsOcclusionCulling = 1;
617 | Print.EpsPointSizeFactor = 1;
618 | Print.EpsPS3Shading = 0;
619 | Print.EpsQuality = 1;
620 | Print.Format = 10;
621 | Print.GeoLabels = 1;
622 | Print.GeoOnlyPhysicals = 0;
623 | Print.GifDither = 0;
624 | Print.GifInterlace = 0;
625 | Print.GifSort = 1;
626 | Print.GifTransparent = 0;
627 | Print.Height = -1;
628 | Print.JpegQuality = 100;
629 | Print.JpegSmoothing = 0;
630 | Print.PgfTwoDim = 1;
631 | Print.PgfExportAxis = 0;
632 | Print.PgfHorizontalBar = 0;
633 | Print.PostElementary = 1;
634 | Print.PostElement = 0;
635 | Print.PostGamma = 0;
636 | Print.PostEta = 0;
637 | Print.PostSICN = 0;
638 | Print.PostSIGE = 0;
639 | Print.PostDisto = 0;
640 | Print.TexAsEquation = 0;
641 | Print.TexForceFontSize = 0;
642 | Print.TexWidthInMm = 150;
643 | Print.Text = 1;
644 | Print.X3dCompatibility = 0;
645 | Print.X3dPrecision = 1e-09;
646 | Print.X3dRemoveInnerBorders = 0;
647 | Print.X3dTransparency = 0;
648 | Print.X3dSurfaces = 1;
649 | Print.X3dEdges = 0;
650 | Print.X3dVertices = 0;
651 | Print.Width = -1;
652 |
--------------------------------------------------------------------------------
/letop_examples/heat_exchanger_3D/heat_exchanger.py:
--------------------------------------------------------------------------------
1 | import firedrake as fd
2 | from firedrake import inner, dot, grad, div, dx, ds, sin, pi, as_vector, cos
3 | import firedrake_adjoint as fda
4 |
5 | from letop.levelset import LevelSetFunctional, RegularizationSolver
6 | from letop.optimization import (
7 | InfDimProblem,
8 | Constraint,
9 | nlspace_solve,
10 | read_checkpoint,
11 | is_checkpoint,
12 | )
13 | from letop.physics import (
14 | mark_no_flow_regions,
15 | )
16 | from physics_solvers import (
17 | temperature_solver,
18 | flow_solver,
19 | bc_velocity_profile,
20 | )
21 | import signal
22 | from qois import pressure_drop, heat_flux
23 | from itertools import count
24 | import petsc4py
25 | from pyadjoint import stop_annotating
26 | import argparse
27 | from copy import copy
28 | from solver_parameters import (
29 | reinit_solver_parameters,
30 | hj_solver_parameters,
31 | regularization_solver_parameters,
32 | temperature_solver_parameters,
33 | ns_solver_parameters,
34 | )
35 | from parameters_box import WALLS, DESIGN_DOMAIN
36 |
37 | petsc4py.PETSc.Sys.popErrorHandler()
38 |
39 |
40 | def print(x):
41 | return fd.PETSc.Sys.Print(x)
42 |
43 |
44 | def forward(
45 | W,
46 | T,
47 | phi,
48 | opts,
49 | brinkmann_penalty,
50 | *,
51 | no_flow_domain_1,
52 | no_flow_domain_2,
53 | markers,
54 | ):
55 | # Parameters
56 | nu = fd.Constant(opts.nu) # viscosity
57 | beta_gls = 0.9
58 | PeInv_val = 2e-4
59 | PeInv = fd.Constant(PeInv_val)
60 | t1 = fd.Constant(10.0)
61 | t2 = fd.Constant(1.0)
62 |
63 | x, y, z = fd.SpatialCoordinate(W.ufl_domain())
64 |
65 | inflow1, inflow2 = bc_velocity_profile(opts.type_he, (x, y, z))
66 |
67 | # Navier-Stokes 1
68 | w_sol1 = fd.Function(W)
69 |
70 | solver1 = flow_solver(
71 | W,
72 | w_sol1,
73 | no_flow=no_flow_domain_1,
74 | phi=phi,
75 | beta_gls=beta_gls,
76 | solver_parameters=ns_solver_parameters,
77 | brinkmann_penalty=brinkmann_penalty,
78 | design_domain=DESIGN_DOMAIN,
79 | inflow=inflow1,
80 | inlet=markers["INLET1"],
81 | walls=(markers["WALLS"], markers["INLET2"], markers["OUTLET2"]),
82 | nu=nu,
83 | )
84 | solver1.solve()
85 | u_sol1, _ = fd.split(w_sol1)
86 |
87 | # Navier-Stokes 2
88 | w_sol2 = fd.Function(W)
89 | solver2 = flow_solver(
90 | W,
91 | w_sol2,
92 | no_flow=no_flow_domain_2,
93 | phi=-phi,
94 | beta_gls=beta_gls,
95 | solver_parameters=ns_solver_parameters,
96 | brinkmann_penalty=brinkmann_penalty,
97 | design_domain=DESIGN_DOMAIN,
98 | inflow=inflow2,
99 | inlet=markers["INLET2"],
100 | walls=(markers["WALLS"], markers["INLET1"], markers["OUTLET1"]),
101 | nu=nu,
102 | )
103 | solver2.solve()
104 | u_sol2, _ = fd.split(w_sol2)
105 |
106 | beta = u_sol1 + u_sol2
107 | t = fd.Function(T, name="Temperature")
108 | solver_T = temperature_solver(
109 | T,
110 | t,
111 | beta,
112 | PeInv,
113 | solver_parameters=temperature_solver_parameters,
114 | t1=t1,
115 | INLET1=markers["INLET1"],
116 | t2=t2,
117 | INLET2=markers["INLET2"],
118 | )
119 | solver_T.solve()
120 |
121 | return w_sol1, w_sol2, t
122 |
123 |
124 | def heat_exchanger_3D():
125 | parser = argparse.ArgumentParser(description="Level set method parameters")
126 | parser.add_argument(
127 | "--nu",
128 | action="store",
129 | dest="nu",
130 | type=float,
131 | help="Kinematic Viscosity",
132 | default=1.0,
133 | )
134 | parser.add_argument(
135 | "--brinkmann_penalty",
136 | action="store",
137 | dest="brinkmann_penalty",
138 | type=float,
139 | help="Brinkmann term",
140 | default=1e5,
141 | )
142 | parser.add_argument(
143 | "--refinement",
144 | action="store",
145 | dest="refinement",
146 | type=int,
147 | help="Level of refinement",
148 | default=0,
149 | )
150 | parser.add_argument(
151 | "--pressure_drop_constraint",
152 | action="store",
153 | dest="pressure_drop_constraint",
154 | type=float,
155 | help="Pressure drop constraint",
156 | default=10.0,
157 | )
158 | parser.add_argument(
159 | "--n_iters",
160 | dest="n_iters",
161 | type=int,
162 | action="store",
163 | default=1000,
164 | help="Number of optimization iterations",
165 | )
166 | parser.add_argument(
167 | "--output_dir",
168 | dest="output_dir",
169 | type=str,
170 | action="store",
171 | default="./",
172 | help="Output folder",
173 | )
174 | parser.add_argument(
175 | "--type",
176 | dest="type_he",
177 | type=str,
178 | action="store",
179 | default="parallel",
180 | help="Type of heat exchanger: parallel or counter",
181 | )
182 | opts = parser.parse_args()
183 | print(f"Parameters used: {opts}")
184 |
185 | beta_param = 0.4
186 | mesh = fd.Mesh("./box_heat_exch.msh")
187 |
188 | from parameters_box import (
189 | INLET2,
190 | INLET1,
191 | OUTLET1,
192 | OUTLET2,
193 | INMOUTH1,
194 | INMOUTH2,
195 | OUTMOUTH1,
196 | OUTMOUTH2,
197 | )
198 |
199 | if opts.type_he == "counter":
200 | INLET1, OUTLET1 = OUTLET1, INLET1
201 | elif opts.type_he == "u_flow":
202 | INLET2, OUTLET1 = OUTLET1, INLET2
203 | OUTMOUTH1, INMOUTH2 = INMOUTH2, OUTMOUTH1
204 |
205 | markers = {
206 | "WALLS": WALLS,
207 | "INLET1": INLET1,
208 | "INLET2": INLET2,
209 | "OUTLET1": OUTLET1,
210 | "OUTLET2": OUTLET2,
211 | }
212 |
213 | no_flow_domain_1 = [INMOUTH2, OUTMOUTH2]
214 | no_flow_domain_2 = [INMOUTH1, OUTMOUTH1]
215 | no_flow = no_flow_domain_1.copy()
216 | no_flow.extend(no_flow_domain_2)
217 | mesh = mark_no_flow_regions(mesh, no_flow, no_flow)
218 |
219 | mh = fd.MeshHierarchy(mesh, opts.refinement)
220 | mesh = mh[-1]
221 |
222 | pressure_drop_constraint = opts.pressure_drop_constraint
223 | pressure_drop_1 = pressure_drop_constraint
224 | pressure_drop_2 = pressure_drop_constraint
225 |
226 | S = fd.VectorFunctionSpace(mesh, "CG", 1)
227 |
228 | PHI = fd.FunctionSpace(mesh, "CG", 1)
229 | phi = fd.Function(PHI, name="LevelSet")
230 | x, y, z = fd.SpatialCoordinate(mesh)
231 |
232 | ω = 0.25
233 | phi_expr = sin(y * pi / ω) * cos(x * pi / ω) * sin(
234 | z * pi / ω
235 | ) - fd.Constant(0.2)
236 |
237 | checkpoints = is_checkpoint(opts.output_dir)
238 | if checkpoints:
239 | current_iter = read_checkpoint(checkpoints, phi)
240 | with open(
241 | f"{opts.output_dir}/brinkmann_penalty.txt", "r"
242 | ) as txt_brinkmann:
243 | brinkmann_penalty_initial = fd.Constant(txt_brinkmann.read())
244 | print(
245 | f"Current brinkmann term: {brinkmann_penalty_initial.values()[0]}"
246 | )
247 | else:
248 | with stop_annotating():
249 | phi.interpolate(phi_expr)
250 | current_iter = 0
251 | brinkmann_penalty_initial = fd.Constant(opts.brinkmann_penalty)
252 |
253 | P2 = fd.VectorElement("CG", mesh.ufl_cell(), 1)
254 | P1 = fd.FiniteElement("CG", mesh.ufl_cell(), 1)
255 | TH = P2 * P1
256 | W = fd.FunctionSpace(mesh, TH)
257 | print(f"DOFS: {W.dim()}")
258 |
259 | T = fd.FunctionSpace(mesh, "CG", 1)
260 |
261 | global_counter = count(current_iter)
262 |
263 | def receive_signal(signum, stack):
264 | iter_current = next(copy(global_counter))
265 | print(f"Received: {signum}, iter: {iter_current}")
266 | with fd.HDF5File(
267 | f"{opts.output_dir}/checkpoint_iter_{iter_current}.h5", "w"
268 | ) as checkpoint:
269 | checkpoint.write(phi, "/checkpoint")
270 | with open(
271 | f"{opts.output_dir}/brinkmann_penalty.txt", "w"
272 | ) as txt_brinkmann:
273 | txt_brinkmann.write(str(brinkmann_penalty.values()[0]))
274 |
275 | signal.signal(signal.SIGHUP, receive_signal)
276 |
277 | phi_pvd = fd.File(
278 | f"{opts.output_dir}/phi_evolution.pvd",
279 | target_degree=1,
280 | target_continuity=fd.H1,
281 | mode="a",
282 | )
283 | ns1 = fd.File(f"{opts.output_dir}/navier_stokes_1.pvd", mode="a")
284 | ns2 = fd.File(f"{opts.output_dir}/navier_stokes_2.pvd", mode="a")
285 | temperature = fd.File(f"{opts.output_dir}/temperature.pvd", mode="a")
286 | temperature_pvd = fd.Function(T)
287 |
288 | def termination_event_1():
289 | p1_constraint = P1control.tape_value() - 1
290 | p2_constraint = P2control.tape_value() - 1
291 | event_value = max(p1_constraint, p2_constraint)
292 | print(f"Value event: {event_value}")
293 | return event_value
294 |
295 | def termination_event_2():
296 | iter_current = next(copy(global_counter))
297 | print(f"Value event iter count: {iter_current}")
298 | return float(iter_current % 500)
299 |
300 | brinkmann_penalty_initial_value = brinkmann_penalty_initial.values()[0]
301 | if brinkmann_penalty_initial_value > opts.brinkmann_penalty:
302 | termination_events = [termination_event_2, termination_event_2]
303 | brinkmann_pen_terms = [
304 | brinkmann_penalty_initial,
305 | fd.Constant(brinkmann_penalty_initial_value * 10),
306 | ]
307 | else:
308 | termination_events = [termination_event_1, None]
309 | brinkmann_pen_terms = [
310 | brinkmann_penalty_initial,
311 | fd.Constant(brinkmann_penalty_initial_value * 5),
312 | ]
313 |
314 | for termination_event, brinkmann_penalty in zip(
315 | termination_events, brinkmann_pen_terms
316 | ):
317 |
318 | s = fd.Function(S, name="deform")
319 | mesh.coordinates.assign(mesh.coordinates + s)
320 | # w_sol1, w_sol2, t = forward(brinkmann_penalty)
321 |
322 | w_sol1, w_sol2, t = forward(
323 | W,
324 | T,
325 | phi,
326 | opts,
327 | brinkmann_penalty,
328 | no_flow_domain_1=no_flow_domain_1,
329 | no_flow_domain_2=no_flow_domain_2,
330 | markers=markers,
331 | )
332 |
333 | w_sol1_control = fda.Control(w_sol1)
334 | w_sol2_control = fda.Control(w_sol2)
335 | t_control = fda.Control(t)
336 |
337 | J = heat_flux(w_sol2, t, OUTLET2)
338 | J_hot = heat_flux(w_sol1, t, OUTLET1)
339 | Pdrop1 = pressure_drop(w_sol1, INLET1, OUTLET1, pressure_drop_1)
340 | Pdrop2 = pressure_drop(w_sol2, INLET2, OUTLET2, pressure_drop_2)
341 | print(f"Cold flux: {J}, hot flux: {J_hot}")
342 |
343 | c = fda.Control(s)
344 | J_hot_control = fda.Control(J_hot)
345 |
346 | def deriv_cb(phi):
347 | with stop_annotating():
348 | iter = next(global_counter)
349 | print(f"Hot flux: {J_hot_control.tape_value()}")
350 | if iter % 15 == 0:
351 | u_sol1, p_sol1 = w_sol1_control.tape_value().split()
352 | u_sol2, p_sol2 = w_sol2_control.tape_value().split()
353 |
354 | u_sol1.rename("Velocity1")
355 | p_sol1.rename("Pressure1")
356 |
357 | u_sol2.rename("Velocity2")
358 | p_sol2.rename("Pressure2")
359 |
360 | ns1.write(u_sol1, p_sol1, time=iter)
361 | ns2.write(u_sol2, p_sol2, time=iter)
362 | phi_pvd.write(phi[0], time=iter)
363 |
364 | temperature_pvd.assign(t_control.tape_value())
365 | temperature_pvd.rename("temperature")
366 | temperature.write(temperature_pvd, time=iter)
367 |
368 | # Reduced Functionals
369 | Jhat = LevelSetFunctional(J, c, phi, derivative_cb_pre=deriv_cb)
370 | P1hat = LevelSetFunctional(Pdrop1, c, phi)
371 | P1control = fda.Control(Pdrop1)
372 |
373 | P2hat = LevelSetFunctional(Pdrop2, c, phi)
374 | P2control = fda.Control(Pdrop2)
375 | print("Pressure drop 1 {:.5f}".format(Pdrop1))
376 | print("Pressure drop 2 {:.5f}".format(Pdrop2))
377 |
378 | reg_solver = RegularizationSolver(
379 | S,
380 | mesh,
381 | beta=beta_param,
382 | gamma=1e6,
383 | dx=dx,
384 | design_domain=DESIGN_DOMAIN,
385 | solver_parameters=regularization_solver_parameters,
386 | )
387 |
388 | tol = 1e-7
389 | dt = 0.02
390 | params = {
391 | "alphaC": 0.1,
392 | "debug": 5,
393 | "alphaJ": 0.1,
394 | "dt": dt,
395 | "K": 1e-3,
396 | "maxit": opts.n_iters,
397 | "maxtrials": 10,
398 | "itnormalisation": 10,
399 | "tol_merit": 1e-2, # new merit can be within 1% of the previous merit
400 | # "normalize_tol" : -1,
401 | "tol": tol,
402 | }
403 |
404 | hj_solver_parameters["ts_dt"] = dt / 50.0
405 | solver_parameters = {
406 | "hj_solver": hj_solver_parameters,
407 | "reinit_solver": reinit_solver_parameters,
408 | }
409 |
410 | problem = InfDimProblem(
411 | Jhat,
412 | reg_solver,
413 | ineqconstraints=[
414 | Constraint(P1hat, 1.0, P1control),
415 | Constraint(P2hat, 1.0, P2control),
416 | ],
417 | reinit_distance=0.08,
418 | solver_parameters=solver_parameters,
419 | )
420 | problem.set_termination_event(
421 | termination_event, termination_tolerance=1e-1
422 | )
423 |
424 | _ = nlspace_solve(problem, params)
425 | fda.get_working_tape().clear_tape()
426 |
427 |
428 | if __name__ == "__main__":
429 | fd.parameters["form_compiler"]["quadrature_degree"] = 4
430 | heat_exchanger_3D()
431 |
--------------------------------------------------------------------------------
/letop_examples/heat_exchanger_3D/parameters_box.py:
--------------------------------------------------------------------------------
1 | WALLS = 5
2 | INLET1 = 1
3 | INLET2 = 3
4 | OUTLET1 = 2
5 | OUTLET2 = 4
6 | DESIGN_DOMAIN = 0
7 | INMOUTH1 = 6
8 | INMOUTH2 = 7
9 | OUTMOUTH1 = 8
10 | OUTMOUTH2 = 9
11 |
12 | inlet_1_coords = (-0.2, 0.8, 0.15)
13 | inlet_2_coords = (-0.2, 0.2, 0.15)
14 | outlet_1_coords = (1.2, 0.8, 0.15)
15 | outlet_2_coords = (1.2, 0.2, 0.15)
16 |
17 | # Inlet radius
18 | R1 = 0.1
19 | R2 = 0.1
20 |
--------------------------------------------------------------------------------
/letop_examples/heat_exchanger_3D/physics_solvers.py:
--------------------------------------------------------------------------------
1 | import firedrake as fd
2 | from letop.physics import (
3 | AdvectionDiffusionGLS,
4 | NavierStokesBrinkmannForm,
5 | NavierStokesBrinkmannSolver,
6 | InteriorBC,
7 | )
8 |
9 |
10 | def temperature_solver(
11 | T, t, beta, PeInv, solver_parameters, *, t1, INLET1, t2, INLET2
12 | ):
13 | F_T = AdvectionDiffusionGLS(T, beta, t, PeInv=PeInv)
14 |
15 | bc1 = fd.DirichletBC(T, t1, INLET1)
16 | bc2 = fd.DirichletBC(T, t2, INLET2)
17 | bcs = [bc1, bc2]
18 | problem_T = fd.NonlinearVariationalProblem(F_T, t, bcs=bcs)
19 | return fd.NonlinearVariationalSolver(
20 | problem_T, solver_parameters=solver_parameters
21 | )
22 |
23 |
24 | def flow_solver(
25 | W,
26 | w_sol,
27 | no_flow=None,
28 | phi=None,
29 | beta_gls=0.9,
30 | design_domain=None,
31 | solver_parameters=None,
32 | brinkmann_penalty=0.0,
33 | *,
34 | inflow,
35 | inlet,
36 | walls,
37 | nu,
38 | ):
39 | F = NavierStokesBrinkmannForm(
40 | W,
41 | w_sol,
42 | nu,
43 | phi=phi,
44 | brinkmann_penalty=brinkmann_penalty,
45 | design_domain=design_domain,
46 | beta_gls=beta_gls,
47 | )
48 |
49 | noslip = fd.Constant((0.0, 0.0, 0.0))
50 | bcs_1 = fd.DirichletBC(W.sub(0), noslip, walls)
51 | bcs_2 = fd.DirichletBC(W.sub(0), inflow, inlet)
52 | bcs = [bcs_1, bcs_2]
53 | if no_flow:
54 | bcs_no_flow = InteriorBC(W.sub(0), noslip, no_flow)
55 | bcs.append(bcs_no_flow)
56 |
57 | problem = fd.NonlinearVariationalProblem(F, w_sol, bcs=bcs)
58 |
59 | return NavierStokesBrinkmannSolver(
60 | problem, solver_parameters=solver_parameters
61 | )
62 |
63 |
64 | def bc_velocity_profile(type_he, X):
65 | u_inflow = 1.0
66 | from parameters_box import (
67 | inlet_1_coords,
68 | inlet_2_coords,
69 | outlet_1_coords,
70 | R1,
71 | R2,
72 | )
73 |
74 | if type_he == "parallel":
75 | sign_inlet_1 = 1
76 | sign_inlet_2 = 1
77 | elif type_he == "counter":
78 | inlet_1_coords, outlet_1_coords = outlet_1_coords, inlet_1_coords
79 | sign_inlet_1 = -1
80 | sign_inlet_2 = 1
81 | elif type_he == "u_flow":
82 | inlet_2_coords, outlet_1_coords = outlet_1_coords, inlet_2_coords
83 | R1, R2 = R2, R1
84 | sign_inlet_1 = 1
85 | sign_inlet_2 = -1
86 | else:
87 | raise RuntimeError(
88 | f"type of heat exchanger {type_he} is invalid. \
89 | Choose parallel, counter or u_flow"
90 | )
91 |
92 | x1, y1, z1 = inlet_1_coords
93 | x, y, z = X
94 | r1_2 = (x - x1) ** 2 + (y - y1) ** 2 + (z - z1) ** 2
95 | inflow1 = fd.as_vector(
96 | [sign_inlet_1 * u_inflow / R1 ** 2 * (R1 ** 2 - r1_2), 0.0, 0.0]
97 | )
98 | x2, y2, z2 = inlet_2_coords
99 | r2_2 = (x - x2) ** 2 + (y - y2) ** 2 + (z - z2) ** 2
100 | inflow2 = fd.as_vector(
101 | [sign_inlet_2 * u_inflow / R2 ** 2 * (R2 ** 2 - r2_2), 0.0, 0.0]
102 | )
103 |
104 | return (
105 | inflow1,
106 | inflow2,
107 | )
108 |
--------------------------------------------------------------------------------
/letop_examples/heat_exchanger_3D/qois.py:
--------------------------------------------------------------------------------
1 | import firedrake as fd
2 | from firedrake import ds, inner
3 |
4 |
5 | def pressure_drop(w_sol, INLET, OUTLET, pdrop_constr):
6 | _, p_sol = fd.split(w_sol)
7 |
8 | return fd.assemble(p_sol * ds(INLET) - p_sol * ds(OUTLET)) / pdrop_constr
9 |
10 |
11 | def heat_flux(w_sol, t, OUTLET):
12 | u_sol, _ = fd.split(w_sol)
13 | scale_factor = 5.0
14 | n = fd.FacetNormal(w_sol.ufl_domain())
15 | return fd.assemble(
16 | fd.Constant(-scale_factor) * inner(t * u_sol, n) * ds(OUTLET)
17 | )
18 |
--------------------------------------------------------------------------------
/letop_examples/heat_exchanger_3D/solver_parameters.py:
--------------------------------------------------------------------------------
1 | reinit_solver_parameters = {
2 | "ksp_type": "cg",
3 | "ksp_rtol": 1e-6,
4 | "pc_type": "hypre",
5 | "pc_hypre_type": "boomeramg",
6 | "pc_hypre_boomeramg_max_iter": 5,
7 | "pc_hypre_boomeramg_coarsen_type": "PMIS",
8 | "pc_hypre_boomeramg_agg_nl": 2,
9 | "pc_hypre_boomeramg_strong_threshold": 0.95,
10 | "pc_hypre_boomeramg_interp_type": "ext+i",
11 | "pc_hypre_boomeramg_P_max": 2,
12 | "pc_hypre_boomeramg_relax_type_all": "sequential-Gauss-Seidel",
13 | "pc_hypre_boomeramg_grid_sweeps_all": 1,
14 | "pc_hypre_boomeramg_truncfactor": 0.3,
15 | "pc_hypre_boomeramg_max_levels": 6,
16 | "ksp_max_it": 200,
17 | "ksp_converged_maxits": None,
18 | }
19 |
20 | hj_solver_parameters = {
21 | "peclet_number": 1e-3,
22 | "ksp_type": "gmres",
23 | "ksp_converged_maxits": None,
24 | "snes_type": "ksponly",
25 | "snes_no_convergence_test": None,
26 | "snes_atol": 1e-5,
27 | "ksp_atol": 1e-5,
28 | "ksp_rtol": 1e-5,
29 | "ts_type": "beuler",
30 | "pc_type": "bjacobi",
31 | "sub_pc_type": "ilu",
32 | "sub_pc_factor_levels": 1,
33 | "ksp_max_it": 5000,
34 | "ksp_gmres_restart": 200,
35 | "ts_atol": 1e-5,
36 | "ts_rtol": 1e-5,
37 | "ts_max_steps": 400,
38 | # "ksp_monitor": None
39 | # "snes_monitor": None,
40 | # "ts_monitor": None,
41 | # "ksp_converged_reason": None,
42 | }
43 | regularization_solver_parameters = {
44 | "mat_type": "aij",
45 | "ksp_type": "cg",
46 | "pc_type": "hypre",
47 | "pc_hypre_type": "boomeramg",
48 | "pc_hypre_boomeramg_max_iter": 200,
49 | "pc_hypre_boomeramg_coarsen_type": "HMIS",
50 | "pc_hypre_boomeramg_agg_nl": 1,
51 | "pc_hypre_boomeramg_strong_threshold": 0.7,
52 | "pc_hypre_boomeramg_interp_type": "ext+i",
53 | "pc_hypre_boomeramg_P_max": 4,
54 | "pc_hypre_boomeramg_relax_type_all": "sequential-Gauss-Seidel",
55 | "pc_hypre_boomeramg_grid_sweeps_all": 1,
56 | "pc_hypre_boomeramg_max_levels": 15,
57 | "ksp_converged_reason": None,
58 | }
59 | temperature_solver_parameters = {
60 | "ksp_type": "fgmres",
61 | "snes_atol": 1e-6,
62 | "pc_type": "python",
63 | "pc_python_type": "firedrake.AssembledPC",
64 | "assembled_mat_type": "aij",
65 | "assembled_pc_type": "hypre",
66 | "assembled_pc_hypre_boomeramg_P_max": 4,
67 | "assembled_pc_hypre_boomeramg_agg_nl": 1,
68 | "assembled_pc_hypre_boomeramg_agg_num_paths": 2,
69 | "assembled_pc_hypre_boomeramg_coarsen_type": "HMIS",
70 | "assembled_pc_hypre_boomeramg_interp_type": "ext+i",
71 | "assembled_pc_hypre_boomeramg_no_CF": True,
72 | "ksp_max_it": 300,
73 | }
74 |
75 | ns_solver_tolerances = 1e-4
76 | ns_solver_parameters = {
77 | "ksp_converged_maxits": None,
78 | "ksp_max_it": 1000,
79 | "ksp_atol": ns_solver_tolerances,
80 | "ksp_rtol": ns_solver_tolerances,
81 | # "ksp_monitor": None,
82 | "snes_atol": 1e-4,
83 | "snes_rtol": 1e-4,
84 | "snes_max_it": 10,
85 | "snes_no_convergence_test": None,
86 | "ksp_converged_reason": None,
87 | }
88 |
--------------------------------------------------------------------------------
/letop_examples/heat_exchanger_ns/2D_mesh.geo:
--------------------------------------------------------------------------------
1 | // This code was created by pygmsh vunknown.
2 | SetFactory("OpenCASCADE");
3 | Mesh.CharacteristicLengthMin = 0.02;
4 | Mesh.CharacteristicLengthMax = 0.02;
5 | s0 = news;
6 | Rectangle(s0) = {0.0, 0.0, 0.0, 1.2, 1.5};
7 | s1 = news;
8 | Rectangle(s1) = {-0.2, -2.7755575615628914e-17, 0.0, 0.2, 0.2};
9 | s2 = news;
10 | Rectangle(s2) = {-0.2, 0.26, 0.0, 0.2, 0.2};
11 | s3 = news;
12 | Rectangle(s3) = {1.2, -2.7755575615628914e-17, 0.0, 0.2, 0.2};
13 | s4 = news;
14 | Rectangle(s4) = {1.2, 0.26, 0.0, 0.2, 0.2};
15 | Physical Surface(2) = {s1};
16 | Physical Surface(3) = {s2};
17 | Physical Surface(4) = {s3};
18 | Physical Surface(5) = {s4};
19 | Physical Surface(0) = {s0};
20 | bo1[] = BooleanFragments{ Surface{s0}; Delete; } { Surface{s1};Surface{s2};Surface{s3};Surface{s4}; Delete;};
21 | vb1[] = Boundary{Surface{ s1 };};
22 | vb2[] = Boundary{Surface{ s2 };};
23 | vb3[] = Boundary{Surface{ s3 };};
24 | vb4[] = Boundary{Surface{ s4 };};
25 | vb0[] = Boundary{Surface{ s0 };};
26 | Physical Curve(5) = {vb0[],
27 | vb1[0], vb1[2],
28 | vb2[0], vb2[2],
29 | vb3[0], vb3[2],
30 | vb4[0], vb4[2]};
31 | Physical Curve(5) -= {-vb1[1], -vb2[1], -vb3[3], -vb4[3]};
32 | Physical Curve(1) = {vb1[3]};
33 | Physical Curve(2) = {vb3[1]};
34 | Physical Curve(3) = {vb2[3]};
35 | Physical Curve(4) = {vb4[1]};
36 |
--------------------------------------------------------------------------------
/letop_examples/heat_exchanger_ns/2D_mesh.py:
--------------------------------------------------------------------------------
1 | import pygmsh as pg
2 | from parameters import (
3 | height,
4 | width,
5 | dist_center,
6 | inlet_width,
7 | inlet_depth,
8 | line_sep,
9 | ymin1,
10 | ymin2,
11 | )
12 | from parameters import (
13 | INMOUTH1,
14 | INMOUTH2,
15 | OUTMOUTH1,
16 | OUTMOUTH2,
17 | INLET1,
18 | INLET2,
19 | OUTLET1,
20 | OUTLET2,
21 | WALLS,
22 | DOMAIN,
23 | )
24 |
25 |
26 | def main():
27 | # geom = pg.built_in.Geometry()
28 | size = 0.02
29 | geom = pg.opencascade.Geometry(
30 | characteristic_length_min=size, characteristic_length_max=size
31 | )
32 |
33 | main_rect = geom.add_rectangle([0.0, 0.0, 0.0], width, height)
34 | mouth_inlet1 = geom.add_rectangle(
35 | [-inlet_depth, ymin1, 0.0], inlet_depth, inlet_width
36 | )
37 | mouth_inlet2 = geom.add_rectangle(
38 | [-inlet_depth, ymin2, 0.0], inlet_depth, inlet_width
39 | )
40 |
41 | mouth_outlet1 = geom.add_rectangle(
42 | [width, ymin1, 0.0], inlet_depth, inlet_width
43 | )
44 | mouth_outlet2 = geom.add_rectangle(
45 | [width, ymin2, 0.0], inlet_depth, inlet_width
46 | )
47 |
48 | print("ymin1 :{}".format(ymin1))
49 | print("ymin2 :{}".format(ymin2))
50 |
51 | geom.add_physical(mouth_inlet1, INMOUTH1)
52 | geom.add_physical(mouth_inlet2, INMOUTH2)
53 | geom.add_physical(mouth_outlet1, OUTMOUTH1)
54 | geom.add_physical(mouth_outlet2, OUTMOUTH2)
55 | geom.add_physical([main_rect], DOMAIN)
56 |
57 | _ = geom.boolean_fragments(
58 | [main_rect], [mouth_inlet1, mouth_inlet2, mouth_outlet1, mouth_outlet2]
59 | )
60 |
61 | geom.add_raw_code(
62 | """vb1[] = Boundary{{Surface{{ {0} }};}};
63 | vb2[] = Boundary{{Surface{{ {1} }};}};
64 | vb3[] = Boundary{{Surface{{ {2} }};}};
65 | vb4[] = Boundary{{Surface{{ {3} }};}};
66 | vb0[] = Boundary{{Surface{{ {4} }};}};""".format(
67 | mouth_inlet1.id,
68 | mouth_inlet2.id,
69 | mouth_outlet1.id,
70 | mouth_outlet2.id,
71 | main_rect.id,
72 | )
73 | )
74 | geom.add_raw_code(
75 | """Physical Curve({0}) = {{vb0[],
76 | vb1[0], vb1[2],
77 | vb2[0], vb2[2],
78 | vb3[0], vb3[2],
79 | vb4[0], vb4[2]}};""".format(
80 | WALLS
81 | )
82 | )
83 |
84 | geom.add_raw_code(
85 | "Physical Curve({0}) -= {{-vb1[1], -vb2[1], -vb3[3], -vb4[3]}};\n \
86 | Physical Curve({1}) = {{vb1[3]}};\n \
87 | Physical Curve({2}) = {{vb3[1]}};\n \
88 | Physical Curve({3}) = {{vb2[3]}};\n \
89 | Physical Curve({4}) = {{vb4[1]}};".format(
90 | WALLS, INLET1, OUTLET1, INLET2, OUTLET2
91 | )
92 | )
93 |
94 | mesh = pg.generate_mesh(geom, geo_filename="2D_mesh.geo")
95 | import meshio
96 |
97 | meshio.write("2D_mesh_heat_exchanger.vtk", mesh)
98 |
99 |
100 | if __name__ == "__main__":
101 | main()
102 |
--------------------------------------------------------------------------------
/letop_examples/heat_exchanger_ns/heat_exchanger.py:
--------------------------------------------------------------------------------
1 | import firedrake as fd
2 | import firedrake_adjoint as fda
3 | from firedrake import inner, grad, ds, dx, sin, cos, pi, dot, div
4 |
5 | from letop.levelset import LevelSetFunctional, RegularizationSolver
6 | from letop.optimization import Constraint, InfDimProblem, nullspace_shape
7 | from letop.physics import (
8 | NavierStokesBrinkmannForm,
9 | mark_no_flow_regions,
10 | InteriorBC,
11 | hs,
12 | )
13 | from pyadjoint import get_working_tape
14 |
15 | from params import (
16 | line_sep,
17 | dist_center,
18 | inlet_width,
19 | WALLS,
20 | INLET1,
21 | INLET2,
22 | OUTLET1,
23 | OUTLET2,
24 | INMOUTH1,
25 | INMOUTH2,
26 | OUTMOUTH1,
27 | OUTMOUTH2,
28 | DOMAIN,
29 | )
30 |
31 | from pyadjoint import stop_annotating
32 | import argparse
33 |
34 |
35 | def heat_exchanger_navier_stokes():
36 |
37 | parser = argparse.ArgumentParser(description="Level set method parameters")
38 | parser.add_argument(
39 | "--nu",
40 | action="store",
41 | dest="nu",
42 | type=float,
43 | help="Viscosity",
44 | default=0.5,
45 | )
46 | opts, unknown = parser.parse_known_args()
47 |
48 | output_dir = "2D/"
49 |
50 | mesh = fd.Mesh("./2D_mesh.msh")
51 | no_flow_domain_1 = [3, 5]
52 | no_flow_domain_1_markers = [7, 8]
53 | no_flow_domain_2 = [2, 4]
54 | no_flow_domain_2_markers = [9, 10]
55 | no_flow = no_flow_domain_1.copy()
56 | no_flow.extend(no_flow_domain_2)
57 | no_flow_markers = no_flow_domain_1_markers.copy()
58 | no_flow_markers.extend(no_flow_domain_2_markers)
59 | mesh = mark_no_flow_regions(mesh, no_flow, no_flow_markers)
60 |
61 | S = fd.VectorFunctionSpace(mesh, "CG", 1)
62 |
63 | x, y = fd.SpatialCoordinate(mesh)
64 | PHI = fd.FunctionSpace(mesh, "CG", 1)
65 | phi_expr = sin(y * pi / 0.2) * cos(x * pi / 0.2) - fd.Constant(0.8)
66 | with stop_annotating():
67 | phi = fd.interpolate(phi_expr, PHI)
68 | phi.rename("LevelSet")
69 | fd.File(output_dir + "phi_initial.pvd").write(phi)
70 |
71 | # Parameters
72 | solver_parameters = {
73 | "mat_type": "aij",
74 | "ksp_type": "preonly",
75 | "ksp_converged_reason": None,
76 | "pc_type": "lu",
77 | "pc_factor_mat_solver_type": "mumps",
78 | # "snes_monitor" : None,
79 | # "snes_type" : "ksponly",
80 | # "snes_no_convergence_test" : None,
81 | # "snes_max_it": 1,
82 | }
83 | u_inflow = 1.0
84 | nu = fd.Constant(opts.nu)
85 | brinkmann_penalty = fd.Constant(1e5)
86 |
87 | P2 = fd.VectorElement("CG", mesh.ufl_cell(), 1)
88 | P1 = fd.FiniteElement("CG", mesh.ufl_cell(), 1)
89 | TH = P2 * P1
90 | W = fd.FunctionSpace(mesh, TH)
91 | n = fd.FacetNormal(mesh)
92 |
93 | def forward(brinkmann_penalty):
94 |
95 | # Stokes 1
96 | w_sol1 = fd.Function(W)
97 | F1 = NavierStokesBrinkmannForm(
98 | W,
99 | w_sol1,
100 | nu,
101 | phi=-phi,
102 | brinkmann_penalty=brinkmann_penalty,
103 | design_domain=DOMAIN,
104 | )
105 |
106 | # Dirichelt boundary conditions
107 | inflow1 = fd.as_vector(
108 | [
109 | u_inflow
110 | * sin(
111 | ((y - (line_sep - (dist_center + inlet_width))) * pi)
112 | / inlet_width
113 | ),
114 | 0.0,
115 | ]
116 | )
117 |
118 | noslip = fd.Constant((0.0, 0.0))
119 |
120 | bcs1_1 = fd.DirichletBC(W.sub(0), noslip, WALLS)
121 | bcs1_2 = fd.DirichletBC(W.sub(0), inflow1, INLET1)
122 | bcs1_3 = fd.DirichletBC(W.sub(1), fd.Constant(0.0), OUTLET1)
123 | bcs1_4 = fd.DirichletBC(W.sub(0), noslip, INLET2)
124 | bcs1_5 = fd.DirichletBC(W.sub(0), noslip, OUTLET2)
125 | bc_no_flow_1 = InteriorBC(W.sub(0), noslip, no_flow_domain_1_markers)
126 | bcs1 = [bcs1_1, bcs1_2, bcs1_3, bcs1_4, bcs1_5, bc_no_flow_1]
127 | # bcs1 = [bcs1_1, bcs1_2, bcs1_3, bcs1_4, bcs1_5]
128 |
129 | # Stokes 2
130 | w_sol2 = fd.Function(W)
131 | F2 = NavierStokesBrinkmannForm(
132 | W,
133 | w_sol2,
134 | nu,
135 | phi=phi,
136 | brinkmann_penalty=brinkmann_penalty,
137 | design_domain=DOMAIN,
138 | )
139 | inflow2 = fd.as_vector(
140 | [
141 | u_inflow
142 | * sin(((y - (line_sep + dist_center)) * pi) / inlet_width),
143 | 0.0,
144 | ]
145 | )
146 | bcs2_1 = fd.DirichletBC(W.sub(0), noslip, WALLS)
147 | bcs2_2 = fd.DirichletBC(W.sub(0), inflow2, INLET2)
148 | bcs2_3 = fd.DirichletBC(W.sub(1), fd.Constant(0.0), OUTLET2)
149 | bcs2_4 = fd.DirichletBC(W.sub(0), noslip, INLET1)
150 | bcs2_5 = fd.DirichletBC(W.sub(0), noslip, OUTLET1)
151 | bc_no_flow_2 = InteriorBC(W.sub(0), noslip, no_flow_domain_2_markers)
152 | bcs2 = [bcs2_1, bcs2_2, bcs2_3, bcs2_4, bcs2_5, bc_no_flow_2]
153 |
154 | # Forward problems
155 | problem1 = fd.NonlinearVariationalProblem(F1, w_sol1, bcs=bcs1)
156 | problem2 = fd.NonlinearVariationalProblem(F2, w_sol2, bcs=bcs2)
157 | solver1 = fd.NonlinearVariationalSolver(
158 | problem1,
159 | solver_parameters=solver_parameters,
160 | options_prefix="ns_stokes1",
161 | )
162 | solver1.solve()
163 | solver2 = fd.NonlinearVariationalSolver(
164 | problem2,
165 | solver_parameters=solver_parameters,
166 | options_prefix="ns_stokes2",
167 | )
168 | solver2.solve()
169 | u1, p1 = fd.split(w_sol1)
170 | u2, p2 = fd.split(w_sol2)
171 |
172 | # Convection difussion equation
173 | k = fd.Constant(1e-3)
174 | t1 = fd.Constant(1.0)
175 | t2 = fd.Constant(10.0)
176 |
177 | h = fd.CellDiameter(mesh)
178 | T = fd.FunctionSpace(mesh, "CG", 1)
179 | t, rho = fd.Function(T), fd.TestFunction(T)
180 | n = fd.FacetNormal(mesh)
181 | beta = u1 + u2
182 | F = (
183 | inner(beta, grad(t)) * rho + k * inner(grad(t), grad(rho))
184 | ) * dx - inner(k * grad(t), n) * rho * (ds(OUTLET1) + ds(OUTLET2))
185 |
186 | R_U = dot(beta, grad(t)) - k * div(grad(t))
187 | beta_gls = 0.9
188 | h = fd.CellSize(mesh)
189 | tau_gls = beta_gls * (
190 | (4.0 * dot(beta, beta) / h ** 2) + 9.0 * (4.0 * k / h ** 2) ** 2
191 | ) ** (-0.5)
192 | degree = 4
193 |
194 | theta_U = dot(beta, grad(rho)) - k * div(grad(rho))
195 | F_T = F + tau_gls * inner(R_U, theta_U) * dx(degree=degree)
196 |
197 | bc1 = fd.DirichletBC(T, t1, INLET1)
198 | bc2 = fd.DirichletBC(T, t2, INLET2)
199 | bcs = [bc1, bc2]
200 | problem_T = fd.NonlinearVariationalProblem(F_T, t, bcs=bcs)
201 | solver_T = fd.NonlinearVariationalSolver(
202 | problem_T,
203 | solver_parameters=solver_parameters,
204 | options_prefix="temperature",
205 | )
206 | solver_T.solve()
207 | t.rename("Temperature")
208 |
209 | fd.File("temperature.pvd").write(t)
210 |
211 | return w_sol1, w_sol2, t
212 |
213 | def opti_problem(w_sol1, w_sol2, t):
214 | u1, p1 = fd.split(w_sol1)
215 | u2, p2 = fd.split(w_sol2)
216 |
217 | power_drop = 20
218 | Power1 = fd.assemble(p1 / power_drop * ds(INLET1))
219 | Power2 = fd.assemble(p2 / power_drop * ds(INLET2))
220 | scale_factor = 1.0
221 | Jform = fd.assemble(
222 | fd.Constant(-scale_factor) * inner(t * u1, n) * ds(OUTLET1)
223 | )
224 | return Jform, Power1, Power2
225 |
226 | # Plotting files and functions
227 | phi_pvd = fd.File("phi_evolution.pvd")
228 | flow_pvd = fd.File("flow_opti.pvd")
229 |
230 | w_pvd_1 = fd.Function(W)
231 | w_pvd_2 = fd.Function(W)
232 |
233 | def termination_event_1():
234 | p1_constraint = P1control.tape_value() - 1
235 | p2_constraint = P2control.tape_value() - 1
236 | event_value = max(p1_constraint, p2_constraint) - 1
237 | print(f"Value event: {event_value}")
238 | return event_value
239 |
240 | for termination_event, brinkmann_penalty in zip(
241 | [termination_event_1, None], [fd.Constant(1e4), fd.Constant(1e5)]
242 | ):
243 | s = fd.Function(S, name="deform")
244 | mesh.coordinates.assign(mesh.coordinates + s)
245 | w_sol1, w_sol2, t = forward(brinkmann_penalty)
246 | w_sol1_control = fda.Control(w_sol1)
247 | w_sol2_control = fda.Control(w_sol2)
248 |
249 | Jform, Power1, Power2 = opti_problem(w_sol1, w_sol2, t)
250 |
251 | def deriv_cb(phi):
252 | with stop_annotating():
253 | phi_pvd.write(phi[0])
254 | w_pvd_1.assign(w_sol1_control.tape_value())
255 | u1, p1 = w_pvd_1.split()
256 | u1.rename("vel1")
257 | w_pvd_2.assign(w_sol2_control.tape_value())
258 | u2, p2 = w_pvd_2.split()
259 | u2.rename("vel2")
260 | flow_pvd.write(u1, u2)
261 |
262 | c = fda.Control(s)
263 |
264 | # Reduced Functionals
265 | Jhat = LevelSetFunctional(Jform, c, phi, derivative_cb_pre=deriv_cb)
266 | P1hat = LevelSetFunctional(Power1, c, phi)
267 | P1control = fda.Control(Power1)
268 |
269 | P2hat = LevelSetFunctional(Power2, c, phi)
270 | P2control = fda.Control(Power2)
271 |
272 | print("Power drop 1 {:.5f}".format(Power1), flush=True)
273 | print("Power drop 2 {:.5f}".format(Power2), flush=True)
274 |
275 | beta_param = 0.08
276 | reg_solver = RegularizationSolver(
277 | S, mesh, beta=beta_param, gamma=1e5, dx=dx, design_domain=DOMAIN
278 | )
279 |
280 | tol = 1e-5
281 | dt = 0.05
282 | params = {
283 | "alphaC": 1.0,
284 | "debug": 5,
285 | "alphaJ": 1.0,
286 | "dt": dt,
287 | "K": 1e-3,
288 | "maxit": 200,
289 | # "maxit": 2,
290 | "maxtrials": 5,
291 | "itnormalisation": 10,
292 | "tol_merit": 5e-3, # new merit can be within 0.5% of the previous merit
293 | # "normalize_tol" : -1,
294 | "tol": tol,
295 | "monitor_time": True,
296 | }
297 |
298 | problem = InfDimProblem(
299 | Jhat,
300 | reg_solver,
301 | ineqconstraints=[
302 | Constraint(P1hat, 1.0, P1control),
303 | Constraint(P2hat, 1.0, P2control),
304 | ],
305 | reinit_distance=0.1,
306 | termination_event=termination_event,
307 | )
308 | _ = nullspace_shape(problem, params)
309 | get_working_tape().clear_tape()
310 |
311 |
312 | if __name__ == "__main__":
313 | heat_exchanger_navier_stokes()
314 |
--------------------------------------------------------------------------------
/letop_examples/heat_exchanger_ns/params.py:
--------------------------------------------------------------------------------
1 | height = 1.5
2 | shift_center = 0.52
3 | dist_center = 0.03
4 | inlet_width = 0.2
5 | inlet_depth = 0.2
6 | width = 1.2
7 | line_sep = height / 2.0 - shift_center
8 | # Line separating both inlets
9 | DOMAIN = 0
10 | INMOUTH1 = 2
11 | INMOUTH2 = 3
12 | OUTMOUTH1 = 4
13 | OUTMOUTH2 = 5
14 | INLET1, OUTLET1, INLET2, OUTLET2, WALLS, DESIGNBC = 1, 2, 3, 4, 5, 6
15 | ymin1 = line_sep - (dist_center + inlet_width)
16 | ymin2 = line_sep + dist_center
17 |
--------------------------------------------------------------------------------
/letop_examples/navier_stokes/mesh_stokes.geo:
--------------------------------------------------------------------------------
1 | // This code was created by pygmsh vunknown.
2 | p0 = newp;
3 | Point(p0) = {0.0, 0.0, 0.0, 0.02};
4 | p1 = newp;
5 | Point(p1) = {0.0, 0.2, 0.0, 0.02};
6 | p2 = newp;
7 | Point(p2) = {0.0, 0.4, 0.0, 0.02};
8 | p3 = newp;
9 | Point(p3) = {0.0, 0.6000000000000001, 0.0, 0.02};
10 | p4 = newp;
11 | Point(p4) = {0.0, 0.8, 0.0, 0.02};
12 | p5 = newp;
13 | Point(p5) = {0.0, 1.0, 0.0, 0.02};
14 | p6 = newp;
15 | Point(p6) = {1.0, 1.0, 0.0, 0.02};
16 | p7 = newp;
17 | Point(p7) = {1.0, 0.8, 0.0, 0.02};
18 | p8 = newp;
19 | Point(p8) = {1.0, 0.6000000000000001, 0.0, 0.02};
20 | p9 = newp;
21 | Point(p9) = {1.0, 0.4, 0.0, 0.02};
22 | p10 = newp;
23 | Point(p10) = {1.0, 0.2, 0.0, 0.02};
24 | p11 = newp;
25 | Point(p11) = {1.0, 0.0, 0.0, 0.02};
26 | l0 = newl;
27 | Line(l0) = {p0, p1};
28 | l1 = newl;
29 | Line(l1) = {p1, p2};
30 | l2 = newl;
31 | Line(l2) = {p2, p3};
32 | l3 = newl;
33 | Line(l3) = {p3, p4};
34 | l4 = newl;
35 | Line(l4) = {p4, p5};
36 | l5 = newl;
37 | Line(l5) = {p5, p6};
38 | l6 = newl;
39 | Line(l6) = {p6, p7};
40 | l7 = newl;
41 | Line(l7) = {p7, p8};
42 | l8 = newl;
43 | Line(l8) = {p8, p9};
44 | l9 = newl;
45 | Line(l9) = {p9, p10};
46 | l10 = newl;
47 | Line(l10) = {p10, p11};
48 | l11 = newl;
49 | Line(l11) = {p11, p0};
50 | ll0 = newll;
51 | Line Loop(ll0) = {l0, l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11};
52 | s0 = news;
53 | Plane Surface(s0) = {ll0};
54 | Physical Line(1) = {l1};
55 | Physical Line(2) = {l3};
56 | Physical Line(3) = {l9};
57 | Physical Line(4) = {l7};
58 | Physical Line(5) = {l0, l2, l4, l5, l6, l8, l10, l11};
59 | Physical Surface(0) = {s0};
60 |
--------------------------------------------------------------------------------
/letop_examples/navier_stokes/navier_stokes.py:
--------------------------------------------------------------------------------
1 | import firedrake as fd
2 | from firedrake import cos, pi, dx, inner, grad
3 | import firedrake_adjoint as fda
4 | from pyadjoint import stop_annotating
5 | from letop.physics import (
6 | NavierStokesBrinkmannForm,
7 | NavierStokesBrinkmannSolver,
8 | hs,
9 | )
10 | from letop.levelset import LevelSetFunctional
11 | from letop.levelset import RegularizationSolver
12 | from letop.optimization import InfDimProblem, Constraint, nullspace_shape
13 |
14 |
15 | def main():
16 | mesh = fd.Mesh("./mesh_stokes.msh")
17 | mh = fd.MeshHierarchy(mesh, 1)
18 | mesh = mh[-1]
19 | # mesh = fd.Mesh("./mesh_stokes_inlets.msh")
20 | S = fd.VectorFunctionSpace(mesh, "CG", 1)
21 | s = fd.Function(S, name="deform")
22 | mesh.coordinates.assign(mesh.coordinates + s)
23 |
24 | x, y = fd.SpatialCoordinate(mesh)
25 | PHI = fd.FunctionSpace(mesh, "DG", 1)
26 | lx = 1.0
27 | # phi_expr = -0.5 * cos(3.0 / lx * pi * x + 1.0) * cos(3.0 * pi * y) - 0.3
28 | lx = 2.0
29 | phi_expr = -cos(6.0 / lx * pi * x + 1.0) * cos(4.0 * pi * y) - 0.6
30 |
31 | with stop_annotating():
32 | phi = fd.interpolate(phi_expr, PHI)
33 | phi.rename("LevelSet")
34 |
35 | nu = fd.Constant(1.0)
36 | V = fd.VectorFunctionSpace(mesh, "CG", 1)
37 | P = fd.FunctionSpace(mesh, "CG", 1)
38 | W = V * P
39 | w_sol = fd.Function(W)
40 | brinkmann_penalty = 1e6
41 | F = NavierStokesBrinkmannForm(
42 | W, w_sol, phi, nu, brinkmann_penalty=brinkmann_penalty
43 | )
44 |
45 | x, y = fd.SpatialCoordinate(mesh)
46 | u_inflow = 1.0
47 | y_inlet_1_1 = 0.2
48 | y_inlet_1_2 = 0.4
49 | inflow1 = fd.as_vector(
50 | [
51 | u_inflow * 100 * (y - y_inlet_1_1) * (y - y_inlet_1_2),
52 | 0.0,
53 | ]
54 | )
55 | y_inlet_2_1 = 0.6
56 | y_inlet_2_2 = 0.8
57 | inflow2 = fd.as_vector(
58 | [
59 | u_inflow * 100 * (y - y_inlet_2_1) * (y - y_inlet_2_2),
60 | 0.0,
61 | ]
62 | )
63 |
64 | noslip = fd.Constant((0.0, 0.0))
65 | bc1 = fd.DirichletBC(W.sub(0), noslip, 5)
66 | bc2 = fd.DirichletBC(W.sub(0), inflow1, (1))
67 | bc3 = fd.DirichletBC(W.sub(0), inflow2, (2))
68 | bcs = [bc1, bc2, bc3]
69 |
70 | problem = fd.NonlinearVariationalProblem(F, w_sol, bcs=bcs)
71 | solver_parameters = {
72 | "ksp_type": "preonly",
73 | "pc_type": "lu",
74 | "mat_type": "aij",
75 | "ksp_converged_reason": None,
76 | "pc_factor_mat_solver_type": "mumps",
77 | }
78 | # solver_parameters = {
79 | # "ksp_type": "fgmres",
80 | # "pc_type": "hypre",
81 | # "pc_hypre_type": "euclid",
82 | # "pc_hypre_euclid_level": 5,
83 | # "mat_type": "aij",
84 | # "ksp_converged_reason": None,
85 | # "ksp_atol": 1e-3,
86 | # "ksp_rtol": 1e-3,
87 | # "snes_atol": 1e-3,
88 | # "snes_rtol": 1e-3,
89 | # }
90 | solver = NavierStokesBrinkmannSolver(
91 | problem, solver_parameters=solver_parameters
92 | )
93 | solver.solve()
94 | pvd_file = fd.File("ns_solution.pvd")
95 | u, p = w_sol.split()
96 | pvd_file.write(u, p)
97 |
98 | u, p = fd.split(w_sol)
99 |
100 | Vol = fd.assemble(hs(-phi) * fd.Constant(1.0) * dx(0, domain=mesh))
101 | VControl = fda.Control(Vol)
102 | Vval = fd.assemble(fd.Constant(0.5) * dx(domain=mesh), annotate=False)
103 | with stop_annotating():
104 | print("Initial constraint function value {}".format(Vol))
105 |
106 | J = fd.assemble(
107 | fd.Constant(brinkmann_penalty) * hs(phi) * inner(u, u) * dx(0)
108 | + nu / fd.Constant(2.0) * inner(grad(u), grad(u)) * dx
109 | )
110 |
111 | c = fda.Control(s)
112 |
113 | phi_pvd = fd.File("phi_evolution_euclid.pvd", target_continuity=fd.H1)
114 |
115 | def deriv_cb(phi):
116 | with stop_annotating():
117 | phi_pvd.write(phi[0])
118 |
119 | Jhat = LevelSetFunctional(J, c, phi, derivative_cb_pre=deriv_cb)
120 | Vhat = LevelSetFunctional(Vol, c, phi)
121 |
122 | bcs_vel_1 = fd.DirichletBC(S, noslip, (1, 2, 3, 4))
123 | bcs_vel = [bcs_vel_1]
124 | reg_solver = RegularizationSolver(
125 | S, mesh, beta=0.5, gamma=1e5, dx=dx, bcs=bcs_vel, design_domain=0
126 | )
127 |
128 | tol = 1e-5
129 | dt = 0.0002
130 | params = {
131 | "alphaC": 1.0,
132 | "debug": 5,
133 | "alphaJ": 1.0,
134 | "dt": dt,
135 | "K": 0.1,
136 | "maxit": 2000,
137 | "maxtrials": 5,
138 | "itnormalisation": 10,
139 | "tol_merit": 1e-4, # new merit can be within 5% of the previous merit
140 | # "normalize_tol" : -1,
141 | "tol": tol,
142 | }
143 |
144 | problem = InfDimProblem(
145 | Jhat,
146 | reg_solver,
147 | ineqconstraints=[Constraint(Vhat, Vval, VControl)],
148 | )
149 | _ = nullspace_shape(problem, params)
150 |
151 |
152 | if __name__ == "__main__":
153 | main()
154 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.black]
2 | line-length = 79
3 | include = '\.pyi?$'
4 | exclude = '''
5 | /(
6 | \.git
7 | | \.hg
8 | | \.mypy_cache
9 | | \.tox
10 | | \.venv
11 | | _build
12 | | buck-out
13 | | build
14 | | dist
15 | )/
16 | '''
17 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | import codecs
4 | import os
5 |
6 | from setuptools import find_packages, setup
7 |
8 | # https://packaging.python.org/single_source_version/
9 | base_dir = os.path.abspath(os.path.dirname(__file__))
10 | about = {}
11 | with open(os.path.join(base_dir, "letop", "__about__.py"), "rb") as f:
12 | exec(f.read(), about)
13 |
14 |
15 | def read(fname):
16 | return codecs.open(os.path.join(base_dir, fname), encoding="utf-8").read()
17 |
18 |
19 | setup(
20 | name="letop",
21 | version=about["__version__"],
22 | packages=find_packages(),
23 | author=about["__author__"],
24 | author_email=about["__email__"],
25 | install_requires=[
26 | "numpy>=1.12.1",
27 | "scipy>=0.19.1",
28 | "cvxopt>=1.2.1",
29 | "gmsh>=4.8.4",
30 | "pygmsh>=6.1.0",
31 | "pre-commit>=2.12.1",
32 | "firedrake-ts @ git+https://github.com/IvanYashchuk/firedrake-ts.git",
33 | ],
34 | description="A little bit of foobar in your life",
35 | long_description=read("README.md"),
36 | long_description_content_type="text/markdown",
37 | license=about["__license__"],
38 | classifiers=[
39 | about["__license__"],
40 | about["__status__"],
41 | # See for all classifiers.
42 | "Operating System :: OS Independent",
43 | "Programming Language :: Python",
44 | "Programming Language :: Python :: 3",
45 | "Topic :: Scientific/Engineering",
46 | "Topic :: Scientific/Engineering :: Mathematics",
47 | ],
48 | python_requires=">=3",
49 | entry_points={"console_scripts": ["letop-show = letop.cli:show"]},
50 | )
51 |
--------------------------------------------------------------------------------
/test/2D_mesh.geo:
--------------------------------------------------------------------------------
1 | // This code was created by pygmsh v6.0.2.
2 | SetFactory("OpenCASCADE");
3 | Mesh.CharacteristicLengthMin = 0.01;
4 | Mesh.CharacteristicLengthMax = 0.01;
5 | s0 = news;
6 | Rectangle(s0) = {0.0, 0.0, 0.0, 1.0, 1.0};
7 | s1 = news;
8 | Rectangle(s1) = {-0.2, -2.7755575615628914e-17, 0.0, 0.2, 0.2};
9 | s2 = news;
10 | Physical Surface(2) = {s1};
11 | Physical Surface(0) = {s0};
12 | bo1[] = BooleanFragments{ Surface{s0}; Delete; } { Surface{s1};};
13 |
--------------------------------------------------------------------------------
/test/3D_mesh.geo:
--------------------------------------------------------------------------------
1 | // This code was created by pygmsh v6.0.2.
2 | SetFactory("OpenCASCADE");
3 | Mesh.CharacteristicLengthMin = 0.02;
4 | Mesh.CharacteristicLengthMax = 0.02;
5 | s0 = news;
6 | Box(s0) = {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
7 | s1 = news;
8 | Box(s1) = {-0.2, -2.7755575615628914e-17, 0.0, 0.2, 0.2, 0.2};
9 | s2 = news;
10 | bo1[] = BooleanFragments{ Volume{s0}; Delete; } { Volume{s1}; Delete;};
11 | Physical Volume(2) = {s1};
12 | Physical Volume(0) = {s0};
13 |
--------------------------------------------------------------------------------
/test/build_example_meshes.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 |
4 | source $1/bin/activate
5 | cd ../
6 | letop=$(pwd)
7 |
8 |
9 | for d in $letop/letop_examples/*/; do
10 | if [ "$letop/letop_examples/heat_exchanger/" = "$d" ]; then
11 | echo "Running ${d}"
12 | cd $d
13 | python3 2D_mesh.py
14 | gmsh -2 -option 2D_mesh.geo.opt 2D_mesh.geo
15 | fi
16 | if [ "$letop/letop_examples/cantilever/" = "$d" ]; then
17 | echo "Running ${d}"
18 | cd $d
19 | gmsh -2 -option mesh_cantilever.geo.opt mesh_cantilever.geo
20 | fi
21 | done
22 |
--------------------------------------------------------------------------------
/test/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | import pytest
4 | import numpy.random
5 | from pyadjoint import set_working_tape, Tape
6 |
7 |
8 | def pytest_runtest_setup(item):
9 | """ Hook function which is called before every test """
10 | set_working_tape(Tape())
11 |
12 | # Fix the seed to avoid random test failures due to slight tolerance variations
13 | numpy.random.seed(21)
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test/nullspace/test_line_search.py:
--------------------------------------------------------------------------------
1 | import firedrake as fd
2 | import firedrake_adjoint as fda
3 | import numpy as np
4 | from firedrake import dx
5 | from letop.optimization.nullspace_shape import line_search
6 | from letop.optimization import InfDimProblem
7 | from letop.levelset import LevelSetFunctional, RegularizationSolver
8 | from letop.physics import hs
9 | from numpy.testing import assert_allclose
10 |
11 |
12 | def merit_eval_new(AJ, J, AC, C):
13 | return J
14 |
15 |
16 | def test_line_search():
17 | mesh = fd.UnitSquareMesh(50, 50)
18 |
19 | # Shape derivative
20 | S = fd.VectorFunctionSpace(mesh, "CG", 1)
21 | s = fd.Function(S, name="deform")
22 | mesh.coordinates.assign(mesh.coordinates + s)
23 |
24 | # Level set
25 | PHI = fd.FunctionSpace(mesh, "CG", 1)
26 | x, y = fd.SpatialCoordinate(mesh)
27 |
28 | with fda.stop_annotating():
29 | phi = fd.interpolate(-(x - 0.5), PHI)
30 |
31 | solver_parameters = {
32 | "ts_atol": 1e-4,
33 | "ts_rtol": 1e-4,
34 | "ts_dt": 1e-2,
35 | "ts_exact_final_time": "matchstep",
36 | "ts_monitor": None,
37 | }
38 |
39 | ## Search direction
40 | with fda.stop_annotating():
41 | delta_x = fd.interpolate(fd.as_vector([-100 * x, 0.0]), S)
42 | delta_x.rename("velocity")
43 |
44 | # Cost function
45 | J = fd.assemble(hs(-phi) * dx)
46 |
47 | # Reduced Functional
48 | c = fda.Control(s)
49 | Jhat = LevelSetFunctional(J, c, phi)
50 |
51 | # InfDim Problem
52 | beta_param = 0.08
53 | reg_solver = RegularizationSolver(
54 | S, mesh, beta=beta_param, gamma=1e5, dx=dx
55 | )
56 | solver_parameters = {"hj_solver": solver_parameters}
57 | problem = InfDimProblem(
58 | Jhat, reg_solver, solver_parameters=solver_parameters
59 | )
60 |
61 | with fda.stop_annotating():
62 | problem.delta_x.assign(delta_x)
63 |
64 | AJ, AC = 1.0, 1.0
65 | C = np.array([])
66 | merit = merit_eval_new(AJ, J, AC, C)
67 |
68 | rtol = 1e-4
69 | newJ, newG, newH = line_search(
70 | problem,
71 | merit_eval_new,
72 | merit,
73 | AJ,
74 | AC,
75 | dt=1.0,
76 | tol_merit=rtol,
77 | maxtrials=20,
78 | )
79 |
80 | assert_allclose(newJ, J, rtol=rtol)
81 |
82 |
83 | if __name__ == "__main__":
84 | test_line_search()
85 |
--------------------------------------------------------------------------------
/test/physics/test_navier_stokes.py:
--------------------------------------------------------------------------------
1 | import firedrake as fd
2 | from firedrake import sqrt, inner, dx
3 | from firedrake import sin, grad, pi, sym, div
4 | from letop.physics import (
5 | NavierStokesBrinkmannForm,
6 | NavierStokesBrinkmannSolver,
7 | mark_no_flow_regions,
8 | InteriorBC,
9 | )
10 | import pytest
11 |
12 |
13 | def test_solver_no_flow_region():
14 | mesh = fd.Mesh("./2D_mesh.msh")
15 | no_flow = [2]
16 | no_flow_markers = [1]
17 | mesh = mark_no_flow_regions(mesh, no_flow, no_flow_markers)
18 | P2 = fd.VectorElement("CG", mesh.ufl_cell(), 1)
19 | P1 = fd.FiniteElement("CG", mesh.ufl_cell(), 1)
20 | TH = P2 * P1
21 | W = fd.FunctionSpace(mesh, TH)
22 | (v, q) = fd.TestFunctions(W)
23 |
24 | # Stokes 1
25 | w_sol1 = fd.Function(W)
26 | nu = fd.Constant(0.05)
27 | F = NavierStokesBrinkmannForm(W, w_sol1, nu, beta_gls=2.0)
28 |
29 | x, y = fd.SpatialCoordinate(mesh)
30 | u_mms = fd.as_vector(
31 | [sin(2.0 * pi * x) * sin(pi * y), sin(pi * x) * sin(2.0 * pi * y)]
32 | )
33 | p_mms = -0.5 * (u_mms[0] ** 2 + u_mms[1] ** 2)
34 | f_mms_u = (
35 | grad(u_mms) * u_mms + grad(p_mms) - 2.0 * nu * div(sym(grad(u_mms)))
36 | )
37 | f_mms_p = div(u_mms)
38 | F += -inner(f_mms_u, v) * dx - f_mms_p * q * dx
39 | bc1 = fd.DirichletBC(W.sub(0), u_mms, "on_boundary")
40 | bc2 = fd.DirichletBC(W.sub(1), p_mms, "on_boundary")
41 | bc_no_flow = InteriorBC(W.sub(0), fd.Constant((0.0, 0.0)), no_flow_markers)
42 |
43 | solver_parameters = {"ksp_max_it": 500, "ksp_monitor": None}
44 |
45 | problem1 = fd.NonlinearVariationalProblem(
46 | F, w_sol1, bcs=[bc1, bc2, bc_no_flow]
47 | )
48 | solver1 = NavierStokesBrinkmannSolver(
49 | problem1,
50 | options_prefix="navier_stokes",
51 | solver_parameters=solver_parameters,
52 | )
53 | solver1.solve()
54 | u_sol, _ = w_sol1.split()
55 | u_mms_func = fd.interpolate(u_mms, W.sub(0))
56 | error = fd.errornorm(u_sol, u_mms_func)
57 | assert error < 0.07
58 |
59 |
60 | def run_solver(r):
61 | mesh = fd.UnitSquareMesh(2 ** r, 2 ** r)
62 | P2 = fd.VectorElement("CG", mesh.ufl_cell(), 1)
63 | P1 = fd.FiniteElement("CG", mesh.ufl_cell(), 1)
64 | TH = P2 * P1
65 | W = fd.FunctionSpace(mesh, TH)
66 | (v, q) = fd.TestFunctions(W)
67 |
68 | # Stokes 1
69 | w_sol1 = fd.Function(W)
70 | nu = fd.Constant(0.05)
71 | F = NavierStokesBrinkmannForm(W, w_sol1, nu, beta_gls=2.0)
72 |
73 | from firedrake import sin, grad, pi, sym, div, inner
74 |
75 | x, y = fd.SpatialCoordinate(mesh)
76 | u_mms = fd.as_vector(
77 | [sin(2.0 * pi * x) * sin(pi * y), sin(pi * x) * sin(2.0 * pi * y)]
78 | )
79 | p_mms = -0.5 * (u_mms[0] ** 2 + u_mms[1] ** 2)
80 | f_mms_u = (
81 | grad(u_mms) * u_mms + grad(p_mms) - 2.0 * nu * div(sym(grad(u_mms)))
82 | )
83 | f_mms_p = div(u_mms)
84 | F += -inner(f_mms_u, v) * dx - f_mms_p * q * dx
85 | bc1 = fd.DirichletBC(W.sub(0), u_mms, "on_boundary")
86 | bc2 = fd.DirichletBC(W.sub(1), p_mms, "on_boundary")
87 |
88 | solver_parameters = {"ksp_max_it": 200}
89 |
90 | problem1 = fd.NonlinearVariationalProblem(F, w_sol1, bcs=[bc1, bc2])
91 | solver1 = NavierStokesBrinkmannSolver(
92 | problem1,
93 | options_prefix="navier_stokes",
94 | solver_parameters=solver_parameters,
95 | )
96 | solver1.solve()
97 | u_sol, _ = w_sol1.split()
98 | fd.File("test_u_sol.pvd").write(u_sol)
99 | u_mms_func = fd.interpolate(u_mms, W.sub(0))
100 | error = fd.errornorm(u_sol, u_mms_func)
101 | print(f"Error: {error}")
102 | return error
103 |
104 |
105 | def run_convergence_test():
106 | import numpy as np
107 |
108 | diff = np.array([run_solver(i) for i in range(4, 7)])
109 | return np.log2(diff[:-1] / diff[1:])
110 |
111 |
112 | def test_l2_conv():
113 | assert (run_convergence_test() > 0.8).all()
114 |
--------------------------------------------------------------------------------
/test/test_examples.py:
--------------------------------------------------------------------------------
1 | from letop_examples import (
2 | compliance_optimization,
3 | heat_exchanger_optimization,
4 | )
5 | from numpy.testing import assert_allclose
6 |
7 |
8 | def test_heat_exchanger():
9 | results = heat_exchanger_optimization(n_iters=10)
10 | cost_func = results["J"][-1]
11 | assert_allclose(
12 | cost_func,
13 | -1.316,
14 | rtol=1e-2,
15 | )
16 |
17 |
18 | def test_compliance():
19 | results = compliance_optimization(n_iters=10)
20 | cost_func = results["J"][-1]
21 | assert_allclose(
22 | cost_func,
23 | 11.86,
24 | rtol=1e-2,
25 | )
26 |
27 | if __name__ == "__main__":
28 | test_heat_exchanger()
29 |
--------------------------------------------------------------------------------
/test/test_hj_solver.py:
--------------------------------------------------------------------------------
1 | import firedrake as fd
2 | from firedrake.bcs import DirichletBC
3 | from firedrake.interpolation import interpolate
4 | from firedrake import inner
5 | from ufl.algebra import Abs
6 |
7 |
8 | import pytest
9 |
10 | from letop.optimization import (
11 | HamiltonJacobiCGSolver,
12 | )
13 |
14 | mesh = fd.UnitSquareMesh(50, 50, quadrilateral=True)
15 | x, y = fd.SpatialCoordinate(mesh)
16 | final_t = 0.2
17 |
18 | # First case
19 | phi_init_1 = -(x - 1.0) * y
20 | phi_exact_1 = (-(x + final_t - 1.0) * (y - final_t)) * (x < 1.0 - final_t) * (
21 | y > final_t
22 | )
23 | # Second case
24 | phi_init_2 = x * y
25 | phi_exact_2 = ((x - final_t) * y) * (x > final_t)
26 |
27 | # Third case
28 | phi_init_3 = -(x - 1.0) * y
29 | phi_exact_3 = (-(x + final_t - 1.0) * y) * (x < 1.0 - final_t)
30 |
31 | # Fourth case
32 | phi_init_4 = -(x) * (y - 1)
33 | phi_exact_4 = (-(x - final_t) * (y + final_t - 1.0)) * (x > final_t) * (
34 | y < 1.0 - final_t
35 | )
36 |
37 |
38 | @pytest.mark.parametrize(
39 | "phi_init, phi_exact, bc_tuple, velocity",
40 | [
41 | (
42 | phi_init_1,
43 | phi_exact_1,
44 | (fd.Constant(0.0), (2, 3)),
45 | fd.Constant((-1.0, 1.0)),
46 | ),
47 | (
48 | phi_init_2,
49 | phi_exact_2,
50 | (fd.Constant(0.0), (1)),
51 | fd.Constant((1.0, 0.0)),
52 | ),
53 | (
54 | phi_init_3,
55 | phi_exact_3,
56 | (fd.Constant(0.0), (2)),
57 | fd.Constant((-1.0, 0.0)),
58 | ),
59 | (
60 | phi_init_4,
61 | phi_exact_4,
62 | (fd.Constant(0.0), (1, 4)),
63 | fd.Constant((1.0, -1.0)),
64 | ),
65 | ],
66 | )
67 | def test_cg_hj_solver(phi_init, phi_exact, bc_tuple, velocity):
68 | V = fd.FunctionSpace(mesh, "CG", 1)
69 | fd.File('init.pvd').write(fd.interpolate(phi_init, V))
70 | bc = fd.DirichletBC(V, *bc_tuple)
71 |
72 | phi0 = interpolate(phi_init, V)
73 | solver_parameters = {
74 | "ts_atol": 1e-6,
75 | "ts_rtol": 1e-6,
76 | "ts_dt": 1e-4,
77 | "ts_exact_final_time": "matchstep",
78 | "ts_monitor": None,
79 | "ts_type": "rosw",
80 | "ts_rows_type": "2m",
81 | "ts_adapt_type": "dsp",
82 | "ts_exact_final_time": "matchstep",
83 | "ksp_type": "preonly",
84 | "pc_type": "lu",
85 | "pc_factor_mat_solver_type": "mumps",
86 | }
87 |
88 | solver = HamiltonJacobiCGSolver(
89 | V,
90 | velocity,
91 | phi0,
92 | t_end=final_t,
93 | bcs=bc,
94 | solver_parameters=solver_parameters,
95 | )
96 |
97 | solver.solve()
98 | error = fd.errornorm(interpolate(phi_exact, V), phi0)
99 | assert error < 1e-3
100 | print(f"Error: {error}")
101 |
--------------------------------------------------------------------------------
/test/test_regularization_solver.py:
--------------------------------------------------------------------------------
1 | from firedrake import (
2 | UnitSquareMesh,
3 | SpatialCoordinate,
4 | VectorFunctionSpace,
5 | Function,
6 | TestFunction,
7 | assemble,
8 | norm,
9 | project,
10 | inner,
11 | as_vector,
12 | cos,
13 | pi,
14 | sin,
15 | dx,
16 | File,
17 | Mesh,
18 | conditional,
19 | )
20 | import numpy as np
21 | from letop.levelset import RegularizationSolver
22 |
23 |
24 | def regularization_form(r):
25 | mesh = UnitSquareMesh(2 ** r, 2 ** r)
26 | x = SpatialCoordinate(mesh)
27 |
28 | S = VectorFunctionSpace(mesh, "CG", 1)
29 | beta = 4.0
30 | reg_solver = RegularizationSolver(S, mesh, beta=beta, gamma=0.0, dx=dx)
31 |
32 | # Exact solution with free Neumann boundary conditions for this domain
33 | u_exact = Function(S)
34 | u_exact_component = cos(x[0] * pi * 2) * cos(x[1] * pi * 2)
35 | u_exact.interpolate(as_vector((u_exact_component, u_exact_component)))
36 | f = Function(S)
37 | theta = TestFunction(S)
38 | f_component = (1 + beta * 8 * pi * pi) * u_exact_component
39 | f.interpolate(as_vector((f_component, f_component)))
40 | rhs_form = inner(f, theta) * dx
41 |
42 | velocity = Function(S)
43 | rhs = assemble(rhs_form)
44 | reg_solver.solve(velocity, rhs)
45 | File("solution_vel_unitsquare.pvd").write(velocity)
46 | return norm(project(u_exact - velocity, S))
47 |
48 |
49 | def test_regularization_convergence():
50 | diff = np.array([regularization_form(i) for i in range(3, 6)])
51 | conv = np.log2(diff[:-1] / diff[1:])
52 | assert (np.array(conv) > 1.8).all()
53 |
54 |
55 | def domainify(vector, x):
56 | dim = len(x)
57 | return conditional(
58 | x[0] > 0.0, vector, as_vector(tuple(0.0 for _ in range(dim)))
59 | )
60 |
61 |
62 | def test_2D_dirichlet_regions():
63 | # Can't do mesh refinement because MeshHierarchy ruins the domain tags
64 | mesh = Mesh("./2D_mesh.msh")
65 | dim = mesh.geometric_dimension()
66 | x = SpatialCoordinate(mesh)
67 |
68 | S = VectorFunctionSpace(mesh, "CG", 1)
69 | beta = 1.0
70 | reg_solver = RegularizationSolver(
71 | S, mesh, beta=beta, gamma=0.0, dx=dx, design_domain=0
72 | )
73 |
74 | # Exact solution with free Neumann boundary conditions for this domain
75 | u_exact = Function(S)
76 | u_exact_component = (-cos(x[0] * pi * 2) + 1) * (-cos(x[1] * pi * 2) + 1)
77 |
78 | u_exact.interpolate(
79 | as_vector(tuple(u_exact_component for _ in range(dim)))
80 | )
81 | f = Function(S)
82 | f_component = (
83 | -beta
84 | * (
85 | 4.0 * pi * pi * cos(2 * pi * x[0]) * (-cos(2 * pi * x[1]) + 1)
86 | + 4.0 * pi * pi * cos(2 * pi * x[1]) * (-cos(2 * pi * x[0]) + 1)
87 | )
88 | + u_exact_component
89 | )
90 | f.interpolate(as_vector(tuple(f_component for _ in range(dim))))
91 |
92 | theta = TestFunction(S)
93 | rhs_form = inner(f, theta) * dx
94 |
95 | velocity = Function(S)
96 | rhs = assemble(rhs_form)
97 | reg_solver.solve(velocity, rhs)
98 | assert norm(project(domainify(u_exact, x) - velocity, S)) < 1e-3
99 |
100 |
101 | def test_3D_dirichlet_regions():
102 | # Can't do mesh refinement because MeshHierarchy ruins the domain tags
103 | mesh = Mesh("./3D_mesh.msh")
104 | dim = mesh.geometric_dimension()
105 | x = SpatialCoordinate(mesh)
106 | solver_parameters_amg = {
107 | "ksp_type": "cg",
108 | "ksp_converged_reason": None,
109 | "ksp_rtol": 1e-7,
110 | "pc_type": "hypre",
111 | "pc_hypre_type": "boomeramg",
112 | "pc_hypre_boomeramg_max_iter": 5,
113 | "pc_hypre_boomeramg_coarsen_type": "PMIS",
114 | "pc_hypre_boomeramg_agg_nl": 2,
115 | "pc_hypre_boomeramg_strong_threshold": 0.95,
116 | "pc_hypre_boomeramg_interp_type": "ext+i",
117 | "pc_hypre_boomeramg_P_max": 2,
118 | "pc_hypre_boomeramg_relax_type_all": "sequential-Gauss-Seidel",
119 | "pc_hypre_boomeramg_grid_sweeps_all": 1,
120 | "pc_hypre_boomeramg_truncfactor": 0.3,
121 | "pc_hypre_boomeramg_max_levels": 6,
122 | }
123 |
124 | S = VectorFunctionSpace(mesh, "CG", 1)
125 | beta = 1.0
126 | reg_solver = RegularizationSolver(
127 | S,
128 | mesh,
129 | beta=beta,
130 | gamma=0.0,
131 | dx=dx,
132 | design_domain=0,
133 | solver_parameters=solver_parameters_amg,
134 | )
135 |
136 | # Exact solution with free Neumann boundary conditions for this domain
137 | u_exact = Function(S)
138 | u_exact_component = (
139 | (-cos(x[0] * pi * 2) + 1)
140 | * (-cos(x[1] * pi * 2) + 1)
141 | * (-cos(x[2] * pi * 2) + 1)
142 | )
143 |
144 | u_exact.interpolate(
145 | as_vector(tuple(u_exact_component for _ in range(dim)))
146 | )
147 | f = Function(S)
148 | f_component = (
149 | -beta
150 | * (
151 | 4.0
152 | * pi
153 | * pi
154 | * cos(2 * pi * x[0])
155 | * (-cos(2 * pi * x[1]) + 1)
156 | * (-cos(2 * pi * x[2]) + 1)
157 | + 4.0
158 | * pi
159 | * pi
160 | * cos(2 * pi * x[1])
161 | * (-cos(2 * pi * x[0]) + 1)
162 | * (-cos(2 * pi * x[2]) + 1)
163 | + 4.0
164 | * pi
165 | * pi
166 | * cos(2 * pi * x[2])
167 | * (-cos(2 * pi * x[1]) + 1)
168 | * (-cos(2 * pi * x[0]) + 1)
169 | )
170 | + u_exact_component
171 | )
172 | f.interpolate(as_vector(tuple(f_component for _ in range(dim))))
173 |
174 | theta = TestFunction(S)
175 | rhs_form = inner(f, theta) * dx
176 |
177 | velocity = Function(S)
178 | rhs = assemble(rhs_form)
179 | reg_solver.solve(velocity, rhs)
180 | error = norm(
181 | project(
182 | domainify(u_exact, x) - velocity,
183 | S,
184 | solver_parameters=solver_parameters_amg,
185 | )
186 | )
187 | assert error < 5e-2
188 |
189 |
190 | if __name__ == "__main__":
191 | test_regularization_convergence()
192 | test_2D_dirichlet_regions()
193 | test_3D_dirichlet_regions()
194 |
--------------------------------------------------------------------------------
/test/test_reinit_solver.py:
--------------------------------------------------------------------------------
1 | import firedrake as fd
2 | from firedrake import sqrt
3 | from letop.optimization import ReinitSolverCG
4 | from letop.physics import max_mesh_dimension
5 | import pytest
6 |
7 |
8 | def test_max_dim():
9 | mesh = fd.UnitSquareMesh(100, 100)
10 |
11 | assert pytest.approx(max_mesh_dimension(mesh), 1.0)
12 |
13 | mesh = fd.RectangleMesh(10, 20, 5.0, 3.0)
14 |
15 | assert pytest.approx(max_mesh_dimension(mesh), 5.0)
16 |
17 | mesh = fd.CubeMesh(10, 10, 10, 8.0)
18 |
19 | assert pytest.approx(max_mesh_dimension(mesh), 8.0)
20 |
21 |
22 | def test_cone_cg_2D():
23 | mesh = fd.UnitSquareMesh(100, 100)
24 | V = fd.FunctionSpace(mesh, "CG", 1)
25 |
26 | radius = 0.2
27 | x, y = fd.SpatialCoordinate(mesh)
28 | x_shift = 0.5
29 | phi_init = (
30 | (x - x_shift) * (x - x_shift) + (y - 0.5) * (y - 0.5) - radius ** 2
31 | )
32 | phi0 = fd.Function(V).interpolate(phi_init)
33 |
34 | phi_pvd = fd.File("phi_reinit.pvd")
35 |
36 | phi = fd.Function(V)
37 | reinit_solver = ReinitSolverCG(phi)
38 | phin = reinit_solver.solve(phi0, iters=10)
39 | phi_pvd.write(phin)
40 |
41 | phi_solution = fd.interpolate(
42 | sqrt((x - x_shift) * (x - x_shift) + (y - 0.5) * (y - 0.5)) - radius,
43 | V,
44 | )
45 | error_numeri = fd.errornorm(phin, phi_solution)
46 | print(f"error: {error_numeri}")
47 | assert error_numeri < 1e-4
48 |
--------------------------------------------------------------------------------
/test/unstructured_rectangle.geo:
--------------------------------------------------------------------------------
1 | //+
2 | SetFactory("OpenCASCADE");
3 | //+
4 | Rectangle(1) = {0, 0, 0, 1, 1.0, 0};
5 | MeshSize{ PointsOf{ Surface{1}; } } = 0.02;
6 |
--------------------------------------------------------------------------------