├── .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 | ![CircleCI](https://img.shields.io/circleci/build/gh/LLNL/letop) 2 | [![codecov](https://codecov.io/gh/LLNL/lestofire/branch/main/graph/badge.svg)](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 | ![cantilever](https://media.giphy.com/media/eWze54pzWhoBiiJDmK/giphy.gif) 41 | 42 | ## Heat exchanger 43 | 44 | ![heat_exchanger_3D](https://user-images.githubusercontent.com/7770764/139540221-8195f162-3850-4939-b116-e466fdb9d8b5.gif) 45 | 46 | ## Bridge 47 | 48 | ![bridge](https://user-images.githubusercontent.com/7770764/139540289-b7daff65-5c98-4828-8a07-445aab79b7bb.gif) 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 | ![heat_exchanger](https://media.giphy.com/media/YhgqJt24PCXJgUdmLu/giphy.gif) 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 | --------------------------------------------------------------------------------