├── .github ├── dependabot.yml └── workflows │ ├── build_and_publish.yml │ └── test-build.yml ├── .gitignore ├── Abs(u).html ├── Dockerfile ├── LICENSE ├── README.md ├── _config.yml ├── _toc.yml ├── access.md ├── beam.html ├── comparing_elements.ipynb ├── example.ipynb ├── fenics_logo.png ├── heat_eq.ipynb ├── helmholtz.ipynb ├── img ├── element-Crouzeix-Raviart-triangle-1-dofs-large.png ├── element-Lagrange-triangle-0-dofs-large.png ├── element-Lagrange-triangle-1-dofs-large.png ├── element-Lagrange-triangle-2-dofs-large.png ├── element-Lagrange-triangle-3-dofs-large.png ├── element-bubble-enriched-Lagrange-triangle-2-dofs-large.png ├── element-bubble-enriched-Lagrange-triangle-3-dofs-large.png ├── vtx.png └── xdmf.png ├── intro.md ├── jupyter_server_config.py ├── mesh.html ├── mesh_generation.py ├── presentation-comparing_elements.html ├── presentation-example.html ├── presentation-heat_eq.html ├── presentation-helmholtz.html ├── pyproject.toml ├── references.bib ├── u_time.gif ├── w.html ├── wave.gif └── wavenumber.html /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/build_and_publish.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | name: Build and publish 3 | 4 | # Controls when the action will run. 5 | on: 6 | # Triggers the workflow on push or pull request events but only for the master branch 7 | push: 8 | branches: [main] 9 | 10 | workflow_dispatch: 11 | 12 | schedule: 13 | - cron: "0 8 * * 1" 14 | 15 | permissions: 16 | contents: read 17 | pages: write 18 | id-token: write 19 | 20 | # Allow one concurrent deployment 21 | concurrency: 22 | group: "pages" 23 | cancel-in-progress: true 24 | 25 | jobs: 26 | build-docs: 27 | uses: ./.github/workflows/test-build.yml 28 | with: 29 | tag: "stable" 30 | 31 | deploy: 32 | needs: [build-docs] 33 | 34 | environment: 35 | name: github-pages 36 | url: ${{ steps.deployment.outputs.page_url }} 37 | 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Download docs artifact 41 | # docs artifact is uploaded by build-docs job 42 | uses: actions/download-artifact@v4 43 | with: 44 | name: docs 45 | path: "./public" 46 | 47 | - name: Upload artifact 48 | uses: actions/upload-pages-artifact@v3 49 | with: 50 | path: "./public" 51 | 52 | - name: Checkout 53 | uses: actions/checkout@v4 54 | 55 | - name: Setup Pages 56 | uses: actions/configure-pages@v5 57 | 58 | - name: Deploy to GitHub Pages 59 | id: deployment 60 | uses: actions/deploy-pages@v4 61 | -------------------------------------------------------------------------------- /.github/workflows/test-build.yml: -------------------------------------------------------------------------------- 1 | name: Test tutorial against DOLFINx 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | workflow_call: 12 | inputs: 13 | tag: 14 | description: "Tag of DOLFINx docker image" 15 | default: "stable" 16 | required: true 17 | type: string 18 | workflow_dispatch: 19 | inputs: 20 | tag: 21 | description: "Tag of DOLFINx docker image" 22 | default: "stable" 23 | required: true 24 | type: string 25 | schedule: 26 | - cron: "* 9 * * 1" 27 | 28 | env: 29 | DEFAULT_TAG: nightly 30 | jobs: 31 | get_image_tag: 32 | runs-on: ubuntu-latest 33 | outputs: 34 | image: ${{ steps.docker_tag.outputs.image }} 35 | steps: 36 | - id: docker_tag 37 | run: echo "image=${{ inputs.tag || env.DEFAULT_TAG }}" >> $GITHUB_OUTPUT 38 | 39 | build-book: 40 | needs: get_image_tag 41 | runs-on: ubuntu-latest 42 | container: dolfinx/lab:${{ needs.get_image_tag.outputs.image }} 43 | env: 44 | PYVISTA_JUPYTER_BACKEND: static 45 | PYVISTA_OFF_SCREEN: false 46 | 47 | steps: 48 | - uses: actions/checkout@v4 49 | 50 | - name: Install dependencies 51 | run: | 52 | python3 -m pip install --upgrade pip 53 | python3 -m pip install -e .[dev] -U 54 | 55 | - name: Ruff formatting checks 56 | run: | 57 | ruff format . 58 | ruff check . 59 | 60 | - name: Run mypy 61 | run: python3 -m mypy . 62 | 63 | - name: Test building the book 64 | run: jupyter-book build . -W --all 65 | 66 | - name: Upload the book 67 | if: always() 68 | uses: actions/upload-artifact@v4 69 | with: 70 | name: docs 71 | path: _build/html 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # Jupybook 132 | _build/* 133 | 134 | # nano 135 | .*.swp 136 | 137 | # output 138 | *.msh 139 | *.h5 140 | *.xdmf 141 | *.bp 142 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM dolfinx/lab:stable 2 | 3 | # create user with a home directory 4 | ARG NB_USER 5 | ARG NB_UID=1000 6 | ENV USER ${NB_USER} 7 | ENV HOME /home/${NB_USER} 8 | 9 | # Copy home directory for usage in binder 10 | WORKDIR ${HOME} 11 | COPY . ${HOME} 12 | RUN python3 -m pip install -U .[dev] 13 | 14 | USER root 15 | RUN chown -R ${NB_UID} ${HOME} 16 | 17 | USER ${NB_USER} 18 | ENTRYPOINT [] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jørgen Schartum Dokken 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FEniCSx Tutorial @ [FEniCS 2022](https://fenicsproject.org/fenics-2022/) in San Diego. 2 | 3 | This repository contains the material that was used for the FEniCSx tutorial at the FEniCS 2022 conference. 4 | 5 | All the resources from this tutorial can be found in [this Jupyter book](https://jorgensd.github.io/fenics22-tutorial). 6 | 7 | ## Developer notes 8 | 9 | ### Rendering the HTML presentation files directly on Github 10 | 11 | Use [githack](https://raw.githack.com/) and add the link to the relevant presentation. 12 | 13 | Example: 14 | 15 | - [Example page](https://raw.githack.com/jorgensd/fenics22-tutorial/main/presentation-example.html#/) 16 | - [Time dependent problem](https://raw.githack.com/jorgensd/fenics22-tutorial/main/presentation-heat_eq.html#/) 17 | - [Helmholtz](https://raw.githack.com/jorgensd/fenics22-tutorial/main/presentation-helmholtz.html#/) 18 | - [Stokes](https://raw.githack.com/jorgensd/fenics22-tutorial/main/presentation-comparing_elements.html#/) 19 | 20 | ### Adding a tutorial to the book 21 | 22 | Add a chapter to `_toc.yml`. 23 | 24 | Inside the Jupyter notebook, go to `Property Inspector` (the two cogwheels in the top right corner of JupyterLab) 25 | and add the following as notebook metadata: 26 | 27 | ```yml 28 | { 29 | "jupytext": { "formats": "ipynb,py:light" }, 30 | "kernelspec": 31 | { 32 | "display_name": "Python 3 (ipykernel)", 33 | "language": "python", 34 | "name": "python3", 35 | }, 36 | "language_info": 37 | { 38 | "codemirror_mode": { "name": "ipython", "version": 3 }, 39 | "file_extension": ".py", 40 | "mimetype": "text/x-python", 41 | "name": "python", 42 | "nbconvert_exporter": "python", 43 | "pygments_lexer": "ipython3", 44 | "version": "3.10.4", 45 | }, 46 | } 47 | ``` 48 | 49 | This will choose the default kernel in the `dolfinx/lab` docker image, and automatically convert the notebooks to a `.py` file at saving. 50 | 51 | If you want to use complex numbers, change: 52 | 53 | ```bash 54 | "kernelspec": { 55 | "display_name": "Python 3 (ipykernel)", 56 | "language": "python", 57 | "name": "python3" 58 | }, 59 | ``` 60 | 61 | to 62 | 63 | ```bash 64 | "kernelspec": { 65 | "display_name": "Python 3 (DOLFINx complex)", 66 | "language": "python", 67 | "name": "python3-complex" 68 | }, 69 | ``` 70 | 71 | ### Create slides from your notebook 72 | 73 | You can use `nbconvert` (`pip3 install nbconvert`) to convert the `.ipynb` to a presentation. 74 | The command to run is: 75 | 76 | ```bash 77 | jupyter nbconvert example.ipynb --to html --template reveal 78 | ``` 79 | 80 | To change what is rendered on each slide, you can change the notebook metadata, 81 | which is in `Property Inspector` (the two cogwheels in the top right corner of JupyterLab), and change the `Slide Type` to `Slide` to start a new slide. If you want to add the cell below to the same slide, change the type to `-`. 82 | 83 | If a cell should be revealed with `Right Arrow`, choose `Fragment`. 84 | 85 | If you want a sub-slide, i.e. navigating downwards with arrows when rendering the presentation, change the type to `Sub-Slide`. 86 | 87 | If a cell should be ignored in presentation mode, set it to `Notes`. 88 | 89 | ### Hiding cells/output 90 | 91 | See https://jupyterbook.org/en/stable/interactive/hiding.htm for more details. The setting is also in advanced tools on the RHS of the Jupyterlab interface 92 | 93 | ### Automatically generate slides 94 | 95 | By adding the following file to the (`jupyter_server_config.py`) `.jupyter` folder on your system. 96 | You might need to rename it to `jupyter_notebook_config.py`. 97 | To check the config paths, call: 98 | 99 | ```bash 100 | jupyter server --show-config 101 | jupyter notebook --show-config 102 | jupyer lab --show-config 103 | ``` 104 | 105 | If you run the code with `dolfinx/lab:stable` using for instance: 106 | 107 | ```bash 108 | docker run -ti -p 8888:8888 --rm -v $(pwd):/root/shared -w /root/shared dolfinx/lab:stable 109 | ``` 110 | 111 | no copying is required. 112 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | # Book settings 2 | # Learn more at https://jupyterbook.org/customize/config.html 3 | 4 | title: FEniCS 22 tutorial 5 | author: The FEniCS project 6 | logo: fenics_logo.png 7 | 8 | # Force re-execution of notebooks on each build. 9 | # See https://jupyterbook.org/content/execute.html 10 | execute: 11 | execute_notebooks: force 12 | 13 | # Set timeout for any example to 20 minutes 14 | timeout: 1800 15 | 16 | # Add a bibtex file so that we can create citations 17 | bibtex_bibfiles: 18 | - references.bib 19 | 20 | # Information about where the book exists on the web 21 | repository: 22 | url: https://github.com/jorgensd/fenics22-tutorial # Online location of your book 23 | path_to_book: . # Optional path to your book, relative to the repository root 24 | branch: main # Which branch of the repository should be used when creating links (optional) 25 | 26 | parse: 27 | myst_enable_extensions: 28 | - "amsmath" 29 | - "colon_fence" 30 | - "deflist" 31 | - "dollarmath" 32 | - "html_admonition" 33 | - "html_image" 34 | - "linkify" 35 | - "replacements" 36 | - "smartquotes" 37 | - "substitution" 38 | 39 | # Add GitHub buttons to your book 40 | # See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository 41 | html: 42 | use_issues_button: true 43 | use_repository_button: true 44 | use_edit_page_button: true 45 | 46 | exclude_patterns: [README.md] 47 | 48 | # Add HTML generated figures here 49 | sphinx: 50 | config: 51 | html_extra_path: 52 | ["mesh.html", "wavenumber.html", "Abs(u).html", "beam.html", "w.html"] 53 | html_last_updated_fmt: "%b %d, %Y" 54 | suppress_warnings: ["mystnb.unknown_mime_type"] 55 | -------------------------------------------------------------------------------- /_toc.yml: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | # Learn more at https://jupyterbook.org/customize/toc.html 3 | 4 | format: jb-book 5 | root: intro 6 | chapters: 7 | - file: example 8 | - file: heat_eq 9 | - file: helmholtz 10 | - file: comparing_elements 11 | - file: access 12 | 13 | -------------------------------------------------------------------------------- /access.md: -------------------------------------------------------------------------------- 1 | # Further information 2 | 3 | The tutorial can be found at [jorgensd.github.io/fenics22-tutorial](https://jorgensd.github.io/fenics22-tutorial) 4 | 5 | ## Further FEniCSx help 6 | 7 | Documentation of all the FEniCSx packages can be found at [docs.fenicsproject.org](https://docs.fenicsproject.org/). 8 | A large collection of code examples can be found in [the FEniCSx tutorial](https://jorgensd.github.io/dolfinx-tutorial/). 9 | 10 | Questions about using FEniCSx can be posted on the [FEniCS Discourse forum](https://fenicsproject.discourse.group/). 11 | 12 | ## Citing this tutorial 13 | 14 | If you wish to cite this tutorial, you can cite it as: 15 | 16 | Jørgen S. Dokken, Igor A. Baratta, Joseph Dean, Sarah Roggendorf, Matthew W. Scroggs, David Kamensky, Adeeb Arif Kor, Michal Habera, Chris Richardson, and Nathan Sime. 17 | FEniCSx Tutorial @ FEniCS 2022 (2022). Available online: [jorgensd.github.io/fenics22-tutorial](https://jorgensd.github.io/fenics22-tutorial). 18 | 19 | The BibTeX for this citation is: 20 | 21 | ``` 22 | @misc{fenics2022tutorial, 23 | AUTHOR = {Dokken, J{\o}rgen S. and Baratta, Igor A. and Dean, Joseph P. and Roggendorf, Sarah and Scroggs, Matthew W. and Kamensky, David and Kor, Adeeb Arif and Habera, Michal and Richardson, Chris and Sime, Nathan}, 24 | TITLE = {FEniCSx Tutorial @ FEniCS 2022}, 25 | YEAR = {{2022}}, 26 | HOWPUBLISHED = {\url{https://jorgensd.github.io/fenics22-tutorial}}, 27 | NOTE = {[Online; accessed 22-August-2022]} 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | }, 9 | "tags": [] 10 | }, 11 | "source": [ 12 | "# Introduction to DOLFINx" 13 | ] 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "metadata": { 18 | "slideshow": { 19 | "slide_type": "notes" 20 | }, 21 | "tags": [] 22 | }, 23 | "source": [ 24 | "We start by importing DOLFINx, and check the version and git commit hash" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 1, 30 | "metadata": { 31 | "tags": [] 32 | }, 33 | "outputs": [ 34 | { 35 | "name": "stdout", 36 | "output_type": "stream", 37 | "text": [ 38 | "You have DOLFINx 0.8.0 installed, based on commit \n", 39 | "https://github.com/FEniCS/dolfinx/commit/5a20e2ba3907c1f108cb0af45931f46e32250351\n" 40 | ] 41 | } 42 | ], 43 | "source": [ 44 | "import dolfinx\n", 45 | "\n", 46 | "print(\n", 47 | " f\"You have DOLFINx {dolfinx.__version__} installed, \"\n", 48 | " \"based on commit \\nhttps://github.com/FEniCS/dolfinx/commit/\"\n", 49 | " f\"{dolfinx.common.git_commit_hash}\"\n", 50 | ")" 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "metadata": { 56 | "slideshow": { 57 | "slide_type": "slide" 58 | }, 59 | "tags": [] 60 | }, 61 | "source": [ 62 | "## Using a 'built-in' mesh" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "metadata": { 68 | "slideshow": { 69 | "slide_type": "notes" 70 | }, 71 | "tags": [] 72 | }, 73 | "source": [ 74 | "In DOLFINx, we do not use wildcard imports as we used to in legacy DOLFIN, ie" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "metadata": { 80 | "tags": [] 81 | }, 82 | "source": [ 83 | "```python\n", 84 | "from dolfin import *\n", 85 | "```" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "metadata": { 91 | "slideshow": { 92 | "slide_type": "notes" 93 | }, 94 | "tags": [] 95 | }, 96 | "source": [ 97 | "We instead import `dolfinx.mesh` as a module:" 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": 2, 103 | "metadata": { 104 | "lines_to_next_cell": 0, 105 | "slideshow": { 106 | "slide_type": "fragment" 107 | }, 108 | "tags": [] 109 | }, 110 | "outputs": [], 111 | "source": [ 112 | "from mpi4py import MPI\n", 113 | "\n", 114 | "import dolfinx\n", 115 | "\n", 116 | "mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 10, 10)" 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "metadata": { 122 | "slideshow": { 123 | "slide_type": "slide" 124 | }, 125 | "tags": [] 126 | }, 127 | "source": [ 128 | "## Interface to external libraries" 129 | ] 130 | }, 131 | { 132 | "cell_type": "markdown", 133 | "metadata": { 134 | "slideshow": { 135 | "slide_type": "notes" 136 | }, 137 | "tags": [] 138 | }, 139 | "source": [ 140 | "We use external libraries, such as `pyvista` for plotting." 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": 3, 146 | "metadata": {}, 147 | "outputs": [], 148 | "source": [ 149 | "import pyvista\n", 150 | "\n", 151 | "import dolfinx.plot\n", 152 | "\n", 153 | "topology, cells, geometry = dolfinx.plot.vtk_mesh(mesh)\n", 154 | "grid = pyvista.UnstructuredGrid(topology, cells, geometry)" 155 | ] 156 | }, 157 | { 158 | "cell_type": "markdown", 159 | "metadata": { 160 | "slideshow": { 161 | "slide_type": "notes" 162 | }, 163 | "tags": [] 164 | }, 165 | "source": [ 166 | "We add settings for both static and interactive plotting" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": 5, 172 | "metadata": { 173 | "slideshow": { 174 | "slide_type": "skip" 175 | }, 176 | "tags": [ 177 | "hide-cell" 178 | ] 179 | }, 180 | "outputs": [], 181 | "source": [ 182 | "pyvista.start_xvfb(0.5)" 183 | ] 184 | }, 185 | { 186 | "cell_type": "code", 187 | "execution_count": 6, 188 | "metadata": { 189 | "slideshow": { 190 | "slide_type": "fragment" 191 | }, 192 | "tags": [] 193 | }, 194 | "outputs": [], 195 | "source": [ 196 | "plotter = pyvista.Plotter(window_size=(600, 600))\n", 197 | "renderer = plotter.add_mesh(grid, show_edges=True)" 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": 7, 203 | "metadata": { 204 | "slideshow": { 205 | "slide_type": "skip" 206 | }, 207 | "tags": [ 208 | "hide-cell" 209 | ] 210 | }, 211 | "outputs": [], 212 | "source": [ 213 | "# Settings for presentation mode\n", 214 | "plotter.view_xy()\n", 215 | "plotter.camera.zoom(1.35)\n", 216 | "plotter.export_html(\"./mesh.html\")" 217 | ] 218 | }, 219 | { 220 | "cell_type": "markdown", 221 | "metadata": { 222 | "slideshow": { 223 | "slide_type": "slide" 224 | }, 225 | "tags": [] 226 | }, 227 | "source": [ 228 | "## Interactive plot" 229 | ] 230 | }, 231 | { 232 | "cell_type": "markdown", 233 | "metadata": { 234 | "slideshow": { 235 | "slide_type": "notes" 236 | }, 237 | "tags": [] 238 | }, 239 | "source": [ 240 | "We can get interactive plots in notebook by calling." 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": 8, 246 | "metadata": { 247 | "lines_to_next_cell": 0, 248 | "tags": [ 249 | "hide-input" 250 | ] 251 | }, 252 | "outputs": [ 253 | { 254 | "data": { 255 | "text/html": [ 256 | " \n" 257 | ], 258 | "text/plain": [ 259 | "" 260 | ] 261 | }, 262 | "metadata": {}, 263 | "output_type": "display_data" 264 | } 265 | ], 266 | "source": [ 267 | "%%html\n", 268 | " " 269 | ] 270 | }, 271 | { 272 | "cell_type": "code", 273 | "execution_count": null, 274 | "metadata": { 275 | "lines_to_next_cell": 2 276 | }, 277 | "outputs": [], 278 | "source": [] 279 | } 280 | ], 281 | "metadata": { 282 | "jupytext": { 283 | "formats": "ipynb" 284 | }, 285 | "kernelspec": { 286 | "display_name": "Python 3 (ipykernel)", 287 | "language": "python", 288 | "name": "python3" 289 | }, 290 | "language_info": { 291 | "codemirror_mode": { 292 | "name": "ipython", 293 | "version": 3 294 | }, 295 | "file_extension": ".py", 296 | "mimetype": "text/x-python", 297 | "name": "python", 298 | "nbconvert_exporter": "python", 299 | "pygments_lexer": "ipython3", 300 | "version": "3.10.12" 301 | }, 302 | "vscode": { 303 | "interpreter": { 304 | "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" 305 | } 306 | }, 307 | "widgets": { 308 | "application/vnd.jupyter.widget-state+json": { 309 | "state": {}, 310 | "version_major": 2, 311 | "version_minor": 0 312 | } 313 | } 314 | }, 315 | "nbformat": 4, 316 | "nbformat_minor": 4 317 | } 318 | -------------------------------------------------------------------------------- /fenics_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgensd/fenics22-tutorial/f4d64ed90b0667016d65f3d4918456b0dcd73111/fenics_logo.png -------------------------------------------------------------------------------- /heat_eq.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "68d7991c-6664-49d6-a8a2-71b43fc0abda", 6 | "metadata": { 7 | "slideshow": { 8 | "slide_type": "slide" 9 | }, 10 | "tags": [] 11 | }, 12 | "source": [ 13 | "# Solving a time-dependent problem" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "id": "f44f5cf7-1344-474e-b121-936caa13797a", 19 | "metadata": { 20 | "slideshow": { 21 | "slide_type": "notes" 22 | }, 23 | "tags": [] 24 | }, 25 | "source": [ 26 | "This notebook will show you how to solve a transient problem using DOLFINx, and highlight differences between legacy DOLFIN and DOLFINx.\n", 27 | "We start by looking at the structure of DOLFINx:" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "id": "36ccb951-19ab-4b69-a13c-e0a3356f305b", 33 | "metadata": {}, 34 | "source": [ 35 | "Relevant DOLFINx modules:\n", 36 | "- `dolfinx.mesh`: Classes and functions related to the computational domain\n", 37 | "- `dolfinx.fem`: Finite element method functionality\n", 38 | "- `dolfinx.io`: Input/Output (read/write) functionality\n", 39 | "- `dolfinx.plot`: Convenience functions for exporting plotting data\n", 40 | "- `dolfinx.la`: Functions related to linear algebra structures (matrices/vectors)" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": 1, 46 | "id": "a9fbb5d1-0dad-44f6-9a32-93535f74db4e", 47 | "metadata": { 48 | "tags": [] 49 | }, 50 | "outputs": [], 51 | "source": [ 52 | "from dolfinx import fem, la, mesh, plot" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "id": "28904567-9391-44c1-9e7b-b55e7486a93b", 58 | "metadata": { 59 | "slideshow": { 60 | "slide_type": "slide" 61 | }, 62 | "tags": [] 63 | }, 64 | "source": [ 65 | "## Creating a distributed computational domain (mesh)" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "id": "54fb77a5-7b70-4e7f-9cf3-e1c88251b796", 71 | "metadata": { 72 | "slideshow": { 73 | "slide_type": "notes" 74 | }, 75 | "tags": [] 76 | }, 77 | "source": [ 78 | "To create a simple computational domain in DOLFINx, we use the mesh generation utilities in `dolfinx.mesh`. In this module, we have the tools to build rectangles of triangular or quadrilateral elements and boxes of tetrahedral or hexahedral elements. We start by creating a rectangle spanning $[0,0]\\times[10,3]$, with 100 and 20 elements in each direction respectively." 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": 2, 84 | "id": "e473b08c-ef9f-4756-a981-47b2958c7c4e", 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "from mpi4py import MPI\n", 89 | "\n", 90 | "length, height = 10, 3\n", 91 | "Nx, Ny = 80, 60\n", 92 | "extent = [[0.0, 0.0], [length, height]]\n", 93 | "domain = mesh.create_rectangle(MPI.COMM_WORLD, extent, [Nx, Ny], mesh.CellType.quadrilateral)" 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "id": "6282cedf-36df-4209-9a54-5f274f74c9ab", 99 | "metadata": { 100 | "slideshow": { 101 | "slide_type": "notes" 102 | }, 103 | "tags": [] 104 | }, 105 | "source": [ 106 | "In constrast to legacy DOLFIN, we work on simple Python structures (nested listes, numpy arrays, etc).\n", 107 | "We also note that we have to send in an MPI communicator. This is because we want the user to be aware of how the mesh is distributed when running in parallel. \n", 108 | "If we use the communicator `MPI.COMM_SELF`, each process initialised when running the script would have a version of the full mesh local to its process." 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "id": "5c7e163a-acdd-49f3-8873-fde09f59b8d0", 114 | "metadata": { 115 | "slideshow": { 116 | "slide_type": "slide" 117 | }, 118 | "tags": [] 119 | }, 120 | "source": [ 121 | "## Creating a mesh on each process" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": 3, 127 | "id": "23061a93-3b46-45ab-ba95-987e01db1cca", 128 | "metadata": { 129 | "lines_to_next_cell": 0, 130 | "tags": [] 131 | }, 132 | "outputs": [], 133 | "source": [ 134 | "local_domain = mesh.create_rectangle(MPI.COMM_SELF, extent, [Nx, Ny], mesh.CellType.quadrilateral)" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "id": "e96dbc06-c46d-4455-8d85-a5eb4764e532", 140 | "metadata": { 141 | "slideshow": { 142 | "slide_type": "skip" 143 | }, 144 | "tags": [] 145 | }, 146 | "source": [ 147 | "We plot the mesh." 148 | ] 149 | }, 150 | { 151 | "cell_type": "code", 152 | "execution_count": 4, 153 | "id": "2233da8a-ff57-4771-88af-fa8fd51c1ba7", 154 | "metadata": { 155 | "slideshow": { 156 | "slide_type": "skip" 157 | }, 158 | "tags": [ 159 | "remove-cell" 160 | ] 161 | }, 162 | "outputs": [], 163 | "source": [ 164 | "import pyvista\n", 165 | "\n", 166 | "pyvista.start_xvfb()\n", 167 | "grid = pyvista.UnstructuredGrid(*plot.vtk_mesh(local_domain))\n", 168 | "plotter = pyvista.Plotter(window_size=(800, 400))\n", 169 | "renderer = plotter.add_mesh(grid, show_edges=True)" 170 | ] 171 | }, 172 | { 173 | "cell_type": "markdown", 174 | "id": "56b553a6-8bad-4d22-b9fb-dc6657b5d99b", 175 | "metadata": { 176 | "slideshow": { 177 | "slide_type": "notes" 178 | }, 179 | "tags": [] 180 | }, 181 | "source": [ 182 | "With Pyvista, we can export the plots to many formats including pngs, interactive notebook plots, and html" 183 | ] 184 | }, 185 | { 186 | "cell_type": "code", 187 | "execution_count": 5, 188 | "id": "49d7c755-d8ae-4c9a-8259-9c5fe8ef47ea", 189 | "metadata": { 190 | "slideshow": { 191 | "slide_type": "notes" 192 | }, 193 | "tags": [ 194 | "hide-output" 195 | ] 196 | }, 197 | "outputs": [], 198 | "source": [ 199 | "plotter.view_xy()\n", 200 | "plotter.camera.zoom(2)\n", 201 | "plotter.export_html(\"./beam.html\")" 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": 6, 207 | "id": "4eb5a932-4d6c-46be-96a6-2a515f693cfd", 208 | "metadata": { 209 | "tags": [ 210 | "hide-input" 211 | ] 212 | }, 213 | "outputs": [ 214 | { 215 | "data": { 216 | "text/html": [ 217 | " \n" 218 | ], 219 | "text/plain": [ 220 | "" 221 | ] 222 | }, 223 | "metadata": {}, 224 | "output_type": "display_data" 225 | } 226 | ], 227 | "source": [ 228 | "%%html\n", 229 | " " 230 | ] 231 | }, 232 | { 233 | "cell_type": "markdown", 234 | "id": "7f792ea5-ce66-4f91-a2d7-10f5a361b9d6", 235 | "metadata": { 236 | "lines_to_next_cell": 2, 237 | "slideshow": { 238 | "slide_type": "slide" 239 | }, 240 | "tags": [] 241 | }, 242 | "source": [ 243 | "## Setting up a variational problem" 244 | ] 245 | }, 246 | { 247 | "cell_type": "markdown", 248 | "id": "eea38f11-101f-4fd8-a8bb-af43979cc839", 249 | "metadata": { 250 | "slideshow": { 251 | "slide_type": "notes" 252 | }, 253 | "tags": [] 254 | }, 255 | "source": [ 256 | "We will solve the heat equation, with a backward Euler time stepping scheme, ie" 257 | ] 258 | }, 259 | { 260 | "cell_type": "markdown", 261 | "id": "3578231c-f2ff-4cbe-beba-aeef1e7a3c40", 262 | "metadata": {}, 263 | "source": [ 264 | "$$\n", 265 | "\\begin{align*}\n", 266 | "\\frac{u_{n+1}-u_n}{\\Delta t} - \\nabla \\cdot (\\mu \\nabla u_{n+1}) &= f(x,t_{n+1}) && \\text{in } \\Omega,\\\\\n", 267 | "u &= u_D(x,t_{n+1}) &&\\text{on } \\partial\\Omega_\\text{D},\\\\\n", 268 | "\\mu\\frac{\\partial u_{n+1}}{\\partial n} &=0 &&\\text{on } \\partial\\Omega_\\text{N},\n", 269 | "\\end{align*}\n", 270 | "$$ \n", 271 | "with $u_D = y\\cos(0.25t)$, $f=0$. For this example, we take $\\Omega$ to be rectangle defined above, $\\Omega_\\text{D}$ if the left-hand edge of the rectangle, and $\\Omega_\\text{N}$ is the remaining three edges of the rectangle." 272 | ] 273 | }, 274 | { 275 | "cell_type": "markdown", 276 | "id": "7fdc0d5f-e35b-4146-9839-ddf3a791f3c0", 277 | "metadata": { 278 | "slideshow": { 279 | "slide_type": "notes" 280 | }, 281 | "tags": [] 282 | }, 283 | "source": [ 284 | "We start by defining the function space, the corresponding test and trial functions, as well as material and temporal parameters. Note that we use explicit imports from UFL to create the test and trial functions, to avoid confusion as to where they originate from. DOLFINx and UFL support both real and complex valued functions. However, to be able to use the PETSc linear algebra backend, which only supports a single floating type at compilation, we need to use appropriate scalar types in our variational form. This ensures that we generate consistent matrices and vectors." 285 | ] 286 | }, 287 | { 288 | "cell_type": "code", 289 | "execution_count": 7, 290 | "id": "1d859674-2bb7-4454-a538-f935b0f56558", 291 | "metadata": {}, 292 | "outputs": [], 293 | "source": [ 294 | "from ufl import (\n", 295 | " SpatialCoordinate,\n", 296 | " TestFunction,\n", 297 | " TrialFunction,\n", 298 | " as_vector,\n", 299 | " dx,\n", 300 | " grad,\n", 301 | " inner,\n", 302 | " system,\n", 303 | ")\n", 304 | "\n", 305 | "V = fem.functionspace(domain, (\"Lagrange\", 1))\n", 306 | "u = TrialFunction(V)\n", 307 | "v = TestFunction(V)\n", 308 | "un = fem.Function(V)\n", 309 | "f = fem.Constant(domain, 0.0)\n", 310 | "mu = fem.Constant(domain, 2.3)\n", 311 | "dt = fem.Constant(domain, 0.05)" 312 | ] 313 | }, 314 | { 315 | "cell_type": "markdown", 316 | "id": "83e93c23-e8ea-47c0-8912-38be470bc43a", 317 | "metadata": { 318 | "slideshow": { 319 | "slide_type": "notes" 320 | }, 321 | "tags": [] 322 | }, 323 | "source": [ 324 | "The variational form can be written in UFL syntax, as done in legacy DOLFIN:" 325 | ] 326 | }, 327 | { 328 | "cell_type": "code", 329 | "execution_count": 8, 330 | "id": "b4e8878f-5f35-427c-9318-71d4bcdfdcce", 331 | "metadata": { 332 | "slideshow": { 333 | "slide_type": "fragment" 334 | }, 335 | "tags": [] 336 | }, 337 | "outputs": [], 338 | "source": [ 339 | "F = inner(u - un, v) * dx + dt * mu * inner(grad(u), grad(v)) * dx\n", 340 | "F -= dt * inner(f, v) * dx\n", 341 | "(a, L) = system(F)" 342 | ] 343 | }, 344 | { 345 | "cell_type": "markdown", 346 | "id": "a7b2f012-1b98-466b-bc6a-5634f91a431a", 347 | "metadata": { 348 | "slideshow": { 349 | "slide_type": "slide" 350 | }, 351 | "tags": [] 352 | }, 353 | "source": [ 354 | "## Creating Dirichlet boundary conditions" 355 | ] 356 | }, 357 | { 358 | "cell_type": "markdown", 359 | "id": "736df497-ad53-4d45-ac67-09c4b5c97d58", 360 | "metadata": { 361 | "slideshow": { 362 | "slide_type": "notes" 363 | }, 364 | "tags": [] 365 | }, 366 | "source": [ 367 | "### Creating a time dependent boundary condition" 368 | ] 369 | }, 370 | { 371 | "cell_type": "markdown", 372 | "id": "a1e26c6f-40df-40f4-adef-cf734511ae2a", 373 | "metadata": { 374 | "slideshow": { 375 | "slide_type": "notes" 376 | }, 377 | "tags": [] 378 | }, 379 | "source": [ 380 | "There are many ways of creating boundary conditions. In this example, we will create function $u_\\text{D}(x,t)$ dependent on both space and time. To do this, we define a function that takes a 2-dimensional array `x`. Each column of `x` corresponds to an input coordinate $(x,y,z)$ and this function operates directly on the columns of `x`." 381 | ] 382 | }, 383 | { 384 | "cell_type": "code", 385 | "execution_count": 9, 386 | "id": "5060c070-1cd2-43b5-b162-d18ca7490d96", 387 | "metadata": {}, 388 | "outputs": [], 389 | "source": [ 390 | "import numpy as np\n", 391 | "\n", 392 | "\n", 393 | "def uD_function(t):\n", 394 | " return lambda x: x[1] * np.cos(0.25 * t)\n", 395 | "\n", 396 | "\n", 397 | "uD = fem.Function(V)\n", 398 | "t = 0\n", 399 | "uD.interpolate(uD_function(t))" 400 | ] 401 | }, 402 | { 403 | "cell_type": "markdown", 404 | "id": "fd839336-90e1-4383-9b1f-1fcd2e6e82de", 405 | "metadata": { 406 | "slideshow": { 407 | "slide_type": "notes" 408 | }, 409 | "tags": [] 410 | }, 411 | "source": [ 412 | "To give the user freedom to set boundary conditions on single degrees of freedom, the function `dolfinx.fem.dirichletbc` takes in the list of degrees of freedom (DOFs) as input. The DOFs on the boundary can be obtained in many ways: DOLFINx supplies a few convenience functions, such as `dolfinx.fem.locate_dofs_topological` and `dolfinx.fem.locate_dofs_geometrical`.\n", 413 | "Locating dofs topologically is generally advised, as certain finite elements have DOFs that do not have a geometrical coordinates associated with them (eg Nédélec and Raviart--Thomas). DOLFINx also has convenicence functions to obtain a list of all boundary facets." 414 | ] 415 | }, 416 | { 417 | "cell_type": "code", 418 | "execution_count": 10, 419 | "id": "e3c7dd5e-af0b-4223-a0aa-1ca3c217b93a", 420 | "metadata": { 421 | "slideshow": { 422 | "slide_type": "fragment" 423 | }, 424 | "tags": [] 425 | }, 426 | "outputs": [], 427 | "source": [ 428 | "def dirichlet_facets(x):\n", 429 | " return np.isclose(x[0], length)\n", 430 | "\n", 431 | "\n", 432 | "tdim = domain.topology.dim\n", 433 | "bc_facets = mesh.locate_entities_boundary(domain, tdim - 1, dirichlet_facets)" 434 | ] 435 | }, 436 | { 437 | "cell_type": "code", 438 | "execution_count": 11, 439 | "id": "bb57b84a-e230-45f4-9a11-466fca63f61b", 440 | "metadata": { 441 | "slideshow": { 442 | "slide_type": "fragment" 443 | }, 444 | "tags": [] 445 | }, 446 | "outputs": [], 447 | "source": [ 448 | "bndry_dofs = fem.locate_dofs_topological(V, tdim - 1, bc_facets)" 449 | ] 450 | }, 451 | { 452 | "cell_type": "code", 453 | "execution_count": 12, 454 | "id": "e207eeca-5e43-47b0-8993-460a7341eb56", 455 | "metadata": { 456 | "slideshow": { 457 | "slide_type": "fragment" 458 | }, 459 | "tags": [] 460 | }, 461 | "outputs": [], 462 | "source": [ 463 | "bcs = [fem.dirichletbc(uD, bndry_dofs)]" 464 | ] 465 | }, 466 | { 467 | "cell_type": "markdown", 468 | "id": "fd747071-0c80-4c48-b018-58769e121fd9", 469 | "metadata": { 470 | "slideshow": { 471 | "slide_type": "slide" 472 | }, 473 | "tags": [] 474 | }, 475 | "source": [ 476 | "## Setting up a time dependent solver" 477 | ] 478 | }, 479 | { 480 | "cell_type": "markdown", 481 | "id": "fb7586ac-6788-41c8-a828-d1f4618c98a2", 482 | "metadata": { 483 | "lines_to_next_cell": 0, 484 | "slideshow": { 485 | "slide_type": "notes" 486 | }, 487 | "tags": [] 488 | }, 489 | "source": [ 490 | "As the left hand side of our problem (the matrix) is time independent, we would like avoid re-assembling it at every time step. DOLFINx gives the user more control over assembly so that this can be done. We assemble the matrix once outside the temporal loop." 491 | ] 492 | }, 493 | { 494 | "cell_type": "code", 495 | "execution_count": 13, 496 | "id": "78c9e008-6532-42b5-9369-e58cb422be2a", 497 | "metadata": {}, 498 | "outputs": [], 499 | "source": [ 500 | "import dolfinx.fem.petsc as petsc\n", 501 | "\n", 502 | "compiled_a = fem.form(a)\n", 503 | "A = petsc.assemble_matrix(compiled_a, bcs=bcs)\n", 504 | "A.assemble()" 505 | ] 506 | }, 507 | { 508 | "cell_type": "markdown", 509 | "id": "8d4515c5-0284-4293-b527-7bdbd79bd31a", 510 | "metadata": { 511 | "slideshow": { 512 | "slide_type": "notes" 513 | }, 514 | "tags": [] 515 | }, 516 | "source": [ 517 | "Next, we can generate the integration kernel for the right hand side (RHS), and create the RHS vector `b` that we will assemble into at each time step." 518 | ] 519 | }, 520 | { 521 | "cell_type": "code", 522 | "execution_count": 14, 523 | "id": "4fc3678a-48f6-42b7-b7f5-f1bc33566fa5", 524 | "metadata": { 525 | "slideshow": { 526 | "slide_type": "fragment" 527 | }, 528 | "tags": [] 529 | }, 530 | "outputs": [], 531 | "source": [ 532 | "compiled_L = fem.form(L)\n", 533 | "b = fem.Function(V)" 534 | ] 535 | }, 536 | { 537 | "cell_type": "markdown", 538 | "id": "4ae18fe0-81a1-42f8-ba3a-38797cfc41ac", 539 | "metadata": { 540 | "slideshow": { 541 | "slide_type": "notes" 542 | }, 543 | "tags": [] 544 | }, 545 | "source": [ 546 | "We next create the PETSc KSP (Krylov subspace method) solver, and set it to solve using an [algebraic multigrid method](https://hypre.readthedocs.io/en/latest/solvers-boomeramg.html)." 547 | ] 548 | }, 549 | { 550 | "cell_type": "code", 551 | "execution_count": 15, 552 | "id": "624d6a1f-7854-4a15-bfd9-fb1b4fdddccb", 553 | "metadata": { 554 | "slideshow": { 555 | "slide_type": "fragment" 556 | }, 557 | "tags": [] 558 | }, 559 | "outputs": [], 560 | "source": [ 561 | "from petsc4py import PETSc\n", 562 | "\n", 563 | "solver = PETSc.KSP().create(domain.comm)\n", 564 | "solver.setOperators(A)\n", 565 | "solver.setType(PETSc.KSP.Type.CG)\n", 566 | "pc = solver.getPC()\n", 567 | "pc.setType(PETSc.PC.Type.HYPRE)\n", 568 | "pc.setHYPREType(\"boomeramg\")" 569 | ] 570 | }, 571 | { 572 | "cell_type": "markdown", 573 | "id": "7fd83dec-1a2c-4364-b013-cee06da3577d", 574 | "metadata": { 575 | "slideshow": { 576 | "slide_type": "slide" 577 | }, 578 | "tags": [] 579 | }, 580 | "source": [ 581 | "## Plotting a time dependent problem\n" 582 | ] 583 | }, 584 | { 585 | "cell_type": "markdown", 586 | "id": "94070aa8-0e5f-4306-a51e-feb8ad429cb0", 587 | "metadata": { 588 | "slideshow": { 589 | "slide_type": "notes" 590 | }, 591 | "tags": [] 592 | }, 593 | "source": [ 594 | "As we are solving a time dependent problem, we would like to create a time dependent animation of the solution. \n", 595 | "We do this by using [pyvista](https://docs.pyvista.org/), which uses VTK structures for plotting.\n", 596 | "In DOLFINx, we have the convenience function `dolfinx.plot.create_vtk_mesh` that can create meshes compatible with VTK formatting, based on meshes of (discontinuous) Lagrange function spaces." 597 | ] 598 | }, 599 | { 600 | "cell_type": "code", 601 | "execution_count": 16, 602 | "id": "e74f1c58-aa5f-4609-b467-89014857d217", 603 | "metadata": { 604 | "tags": [ 605 | "hide-cell" 606 | ] 607 | }, 608 | "outputs": [], 609 | "source": [ 610 | "import matplotlib.pyplot as plt\n", 611 | "import pyvista\n", 612 | "\n", 613 | "pyvista.start_xvfb(0.5) # Start virtual framebuffer for plotting\n", 614 | "plotter = pyvista.Plotter()\n", 615 | "plotter.open_gif(\"u_time.gif\")" 616 | ] 617 | }, 618 | { 619 | "cell_type": "code", 620 | "execution_count": 17, 621 | "id": "ab139ce4-5204-4756-83f3-df25ec66579d", 622 | "metadata": { 623 | "slideshow": { 624 | "slide_type": "fragment" 625 | }, 626 | "tags": [] 627 | }, 628 | "outputs": [], 629 | "source": [ 630 | "topology, cells, geometry = plot.vtk_mesh(V)\n", 631 | "uh = fem.Function(V)\n", 632 | "grid = pyvista.UnstructuredGrid(topology, cells, geometry)\n", 633 | "grid.point_data[\"uh\"] = uh.x.array" 634 | ] 635 | }, 636 | { 637 | "cell_type": "code", 638 | "execution_count": 18, 639 | "id": "5791a8ab-b377-468a-9fa8-47a093658873", 640 | "metadata": { 641 | "slideshow": { 642 | "slide_type": "skip" 643 | }, 644 | "tags": [] 645 | }, 646 | "outputs": [], 647 | "source": [ 648 | "viridis = plt.cm.get_cmap(\"viridis\", 25)\n", 649 | "sargs = dict(\n", 650 | " title_font_size=25,\n", 651 | " label_font_size=20,\n", 652 | " fmt=\"%.2e\",\n", 653 | " color=\"black\",\n", 654 | " position_x=0.1,\n", 655 | " position_y=0.8,\n", 656 | " width=0.8,\n", 657 | " height=0.1,\n", 658 | ")" 659 | ] 660 | }, 661 | { 662 | "cell_type": "code", 663 | "execution_count": 19, 664 | "id": "8217df02-6cb5-433a-9bc2-7c1903e69e6d", 665 | "metadata": { 666 | "slideshow": { 667 | "slide_type": "fragment" 668 | }, 669 | "tags": [] 670 | }, 671 | "outputs": [], 672 | "source": [ 673 | "renderer = plotter.add_mesh(\n", 674 | " grid,\n", 675 | " show_edges=True,\n", 676 | " lighting=False,\n", 677 | " cmap=viridis,\n", 678 | " scalar_bar_args=sargs,\n", 679 | " clim=[0, height],\n", 680 | ")" 681 | ] 682 | }, 683 | { 684 | "cell_type": "code", 685 | "execution_count": 20, 686 | "id": "318eda2e-3a8b-4f72-87ff-8f31ea2f9a47", 687 | "metadata": { 688 | "slideshow": { 689 | "slide_type": "skip" 690 | }, 691 | "tags": [] 692 | }, 693 | "outputs": [], 694 | "source": [ 695 | "plotter.view_xy()\n", 696 | "plotter.camera.zoom(1.3)" 697 | ] 698 | }, 699 | { 700 | "cell_type": "markdown", 701 | "id": "6e83a638-fd11-41e6-a1de-4c5828fcccc1", 702 | "metadata": { 703 | "slideshow": { 704 | "slide_type": "slide" 705 | }, 706 | "tags": [] 707 | }, 708 | "source": [ 709 | "## Solving a time dependent problem" 710 | ] 711 | }, 712 | { 713 | "cell_type": "markdown", 714 | "id": "e47c0e0a-54bb-441e-a30d-7ee4d48d596a", 715 | "metadata": { 716 | "slideshow": { 717 | "slide_type": "notes" 718 | }, 719 | "tags": [] 720 | }, 721 | "source": [ 722 | "We are now ready to solve the time dependent problem. At each time step, we need to:\n", 723 | "1. Update the time dependent boundary condition and source\n", 724 | "2. Reassemble the right hand side vector `b`\n", 725 | "3. Apply boundary conditions to `b`\n", 726 | "4. Solve linear problem `Au = b`\n", 727 | "5. Update previous time step, `un = u`" 728 | ] 729 | }, 730 | { 731 | "cell_type": "code", 732 | "execution_count": null, 733 | "id": "54d60e10-5b23-4d53-b38d-a499ba7cc67a", 734 | "metadata": { 735 | "lines_to_next_cell": 0 736 | }, 737 | "outputs": [], 738 | "source": [ 739 | "T = np.pi\n", 740 | "while t < T:\n", 741 | " # Update boundary condition\n", 742 | " t += dt.value\n", 743 | " uD.interpolate(uD_function(t))\n", 744 | "\n", 745 | " # Assemble RHS\n", 746 | " b.x.array[:] = 0\n", 747 | " petsc.assemble_vector(b.x.petsc_vec, compiled_L)\n", 748 | "\n", 749 | " # Apply boundary condition\n", 750 | " petsc.apply_lifting(b.x.petsc_vec, [compiled_a], [bcs])\n", 751 | " b.x.scatter_reverse(la.InsertMode.add)\n", 752 | " fem.petsc.set_bc(b.x.petsc_vec, bcs)\n", 753 | "\n", 754 | " # Solve linear problem\n", 755 | " solver.solve(b.x.petsc_vec, uh.x.petsc_vec)\n", 756 | " uh.x.scatter_forward()\n", 757 | "\n", 758 | " # Update un\n", 759 | " un.x.array[:] = uh.x.array\n", 760 | "\n", 761 | " # Update plotter\n", 762 | " plotter.update_scalars(uh.x.array, render=False)\n", 763 | " plotter.write_frame()" 764 | ] 765 | }, 766 | { 767 | "cell_type": "code", 768 | "execution_count": 22, 769 | "id": "6e6334c5-12bc-4ec9-bea9-118be535c24d", 770 | "metadata": { 771 | "slideshow": { 772 | "slide_type": "skip" 773 | }, 774 | "tags": [] 775 | }, 776 | "outputs": [], 777 | "source": [ 778 | "plotter.close()" 779 | ] 780 | }, 781 | { 782 | "cell_type": "markdown", 783 | "id": "728caf7a-eebc-46b4-94f8-dcab930f330c", 784 | "metadata": { 785 | "lines_to_next_cell": 0, 786 | "slideshow": { 787 | "slide_type": "slide" 788 | }, 789 | "tags": [] 790 | }, 791 | "source": [ 792 | "\"gif\"" 793 | ] 794 | }, 795 | { 796 | "cell_type": "markdown", 797 | "id": "783d0bde-13de-41db-9335-22cd4576b345", 798 | "metadata": { 799 | "slideshow": { 800 | "slide_type": "slide" 801 | }, 802 | "tags": [] 803 | }, 804 | "source": [ 805 | "## Post-processing without projections" 806 | ] 807 | }, 808 | { 809 | "cell_type": "markdown", 810 | "id": "e6675b27-2b78-4c71-98d7-ead71188ba2b", 811 | "metadata": { 812 | "slideshow": { 813 | "slide_type": "notes" 814 | }, 815 | "tags": [] 816 | }, 817 | "source": [ 818 | "In legacy dolfin, the only way of post-processing a `ufl`-expression over the domain, would be by using a projection. This would not be scalable in most cases. Therefore, we have introduced `dolfinx.fem.Expression`, which can be used to evaluate a `ufl`-expression at any given (reference) point in any cell (local to process). Let us consider" 819 | ] 820 | }, 821 | { 822 | "cell_type": "markdown", 823 | "id": "b196e678-f94e-497e-b181-cd5198fdafed", 824 | "metadata": {}, 825 | "source": [ 826 | "$$(y,x) \\cdot (\\nabla u)$$" 827 | ] 828 | }, 829 | { 830 | "cell_type": "code", 831 | "execution_count": 23, 832 | "id": "e8d62a1f-9a35-4969-9834-46e0760e5237", 833 | "metadata": { 834 | "slideshow": { 835 | "slide_type": "fragment" 836 | }, 837 | "tags": [] 838 | }, 839 | "outputs": [], 840 | "source": [ 841 | "x = SpatialCoordinate(domain)\n", 842 | "x_grad = inner(as_vector((x[1], x[0])), grad(uh))" 843 | ] 844 | }, 845 | { 846 | "cell_type": "code", 847 | "execution_count": 24, 848 | "id": "a2e76d7f-c612-4ac0-bcaf-f93299022fb0", 849 | "metadata": {}, 850 | "outputs": [], 851 | "source": [ 852 | "W = fem.functionspace(domain, (\"DQ\", 1))" 853 | ] 854 | }, 855 | { 856 | "cell_type": "code", 857 | "execution_count": 25, 858 | "id": "fd0ee540-9e24-4dbb-9b63-b0d4c36f7d93", 859 | "metadata": { 860 | "slideshow": { 861 | "slide_type": "fragment" 862 | }, 863 | "tags": [] 864 | }, 865 | "outputs": [], 866 | "source": [ 867 | "expr = fem.Expression(x_grad, W.element.interpolation_points())\n", 868 | "w = fem.Function(W)\n", 869 | "w.interpolate(expr)" 870 | ] 871 | }, 872 | { 873 | "cell_type": "code", 874 | "execution_count": 26, 875 | "id": "5a42c894-ef3f-4ca9-acbb-2d20c66f53f0", 876 | "metadata": { 877 | "slideshow": { 878 | "slide_type": "skip" 879 | }, 880 | "tags": [ 881 | "hide-cell" 882 | ] 883 | }, 884 | "outputs": [], 885 | "source": [ 886 | "w_grid = pyvista.UnstructuredGrid(*plot.vtk_mesh(W))\n", 887 | "w_plotter = pyvista.Plotter(window_size=(800, 400))\n", 888 | "w_grid.point_data[\"w\"] = w.x.array[:].real\n", 889 | "w_plotter.add_mesh(w_grid, show_edges=True, cmap=viridis, scalar_bar_args=sargs)\n", 890 | "w_plotter.view_xy()\n", 891 | "w_plotter.export_html(\"./w.html\")" 892 | ] 893 | }, 894 | { 895 | "cell_type": "code", 896 | "execution_count": 27, 897 | "id": "a6245fee-553e-40ca-a69f-6f2ddd266cff", 898 | "metadata": { 899 | "tags": [ 900 | "hide-input" 901 | ] 902 | }, 903 | "outputs": [ 904 | { 905 | "data": { 906 | "text/html": [ 907 | " \n" 908 | ], 909 | "text/plain": [ 910 | "" 911 | ] 912 | }, 913 | "metadata": {}, 914 | "output_type": "display_data" 915 | } 916 | ], 917 | "source": [ 918 | "%%html\n", 919 | " " 920 | ] 921 | }, 922 | { 923 | "cell_type": "code", 924 | "execution_count": null, 925 | "id": "cf1452dc-c607-4cf0-99ba-8a1454cb7b0c", 926 | "metadata": {}, 927 | "outputs": [], 928 | "source": [] 929 | } 930 | ], 931 | "metadata": { 932 | "jupytext": { 933 | "formats": "ipynb" 934 | }, 935 | "kernelspec": { 936 | "display_name": "Python 3 (ipykernel)", 937 | "language": "python", 938 | "name": "python3" 939 | }, 940 | "language_info": { 941 | "codemirror_mode": { 942 | "name": "ipython", 943 | "version": 3 944 | }, 945 | "file_extension": ".py", 946 | "mimetype": "text/x-python", 947 | "name": "python", 948 | "nbconvert_exporter": "python", 949 | "pygments_lexer": "ipython3", 950 | "version": "3.10.12" 951 | } 952 | }, 953 | "nbformat": 4, 954 | "nbformat_minor": 5 955 | } 956 | -------------------------------------------------------------------------------- /helmholtz.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "e5450a52-a8e9-4616-8cd0-41cfac7c097f", 6 | "metadata": { 7 | "incorrectly_encoded_metadata": "slideshow={\"slide_type\": \"slide\"} tags=[] jp-MarkdownHeadingCollapsed=true", 8 | "lines_to_next_cell": 0, 9 | "slideshow": { 10 | "slide_type": "slide" 11 | }, 12 | "tags": [] 13 | }, 14 | "source": [ 15 | "# The Helmholtz equation" 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "id": "e7f39062-2993-455a-9c35-207f3fd06234", 21 | "metadata": {}, 22 | "source": [ 23 | "In this tutorial, we will learn:\n", 24 | "\n", 25 | " - How to solve PDEs with complex-valued fields,\n", 26 | " - How to import and use high-order meshes from Gmsh,\n", 27 | " - How to use high order discretizations,\n", 28 | " - How to use UFL expressions." 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "id": "ca874c12-82db-46d4-b826-b6aea843bf06", 34 | "metadata": { 35 | "slideshow": { 36 | "slide_type": "slide" 37 | }, 38 | "tags": [] 39 | }, 40 | "source": [ 41 | "## Problem statement\n", 42 | "We will solve the Helmholtz equation subject to a first order absorbing boundary condition:\n", 43 | "\n", 44 | "$$\n", 45 | "\\begin{align*}\n", 46 | "\\Delta u + k^2 u &= 0 && \\text{in } \\Omega,\\\\\n", 47 | "\\nabla u \\cdot \\mathbf{n} - \\mathrm{j}ku &= g && \\text{on } \\partial\\Omega,\n", 48 | "\\end{align*}\n", 49 | "$$\n", 50 | "\n", 51 | "where $k$ is a piecewise constant wavenumber, $\\mathrm{j}=\\sqrt{-1}$, and $g$ is the boundary source term computed as\n", 52 | "\n", 53 | "$$g = \\nabla u_\\text{inc} \\cdot \\mathbf{n} - \\mathrm{j}ku_\\text{inc}$$\n", 54 | "\n", 55 | "for an incoming plane wave $u_\\text{inc}$." 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 1, 61 | "id": "433d9338-15c9-4ed5-9fa0-c83281da39ee", 62 | "metadata": { 63 | "slideshow": { 64 | "slide_type": "skip" 65 | }, 66 | "tags": [] 67 | }, 68 | "outputs": [], 69 | "source": [ 70 | "from mpi4py import MPI\n", 71 | "\n", 72 | "import numpy as np\n", 73 | "\n", 74 | "import dolfinx.fem.petsc\n", 75 | "import ufl" 76 | ] 77 | }, 78 | { 79 | "cell_type": "markdown", 80 | "id": "de959fc8-6fce-4ecb-83ab-b2ca6edb0475", 81 | "metadata": { 82 | "slideshow": { 83 | "slide_type": "skip" 84 | }, 85 | "tags": [] 86 | }, 87 | "source": [ 88 | "This example is designed to be executed with complex-valued degrees of freedom. To be able to solve this problem, we use the complex build of PETSc." 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": 2, 94 | "id": "257a7024-7fb4-4d10-9dee-eacd607bea97", 95 | "metadata": { 96 | "slideshow": { 97 | "slide_type": "skip" 98 | }, 99 | "tags": [] 100 | }, 101 | "outputs": [ 102 | { 103 | "name": "stdout", 104 | "output_type": "stream", 105 | "text": [ 106 | "Using .\n" 107 | ] 108 | } 109 | ], 110 | "source": [ 111 | "import sys\n", 112 | "\n", 113 | "from petsc4py import PETSc\n", 114 | "\n", 115 | "if not np.issubdtype(PETSc.ScalarType, np.complexfloating):\n", 116 | " print(\"This tutorial requires complex number support\")\n", 117 | " sys.exit(0)\n", 118 | "else:\n", 119 | " print(f\"Using {PETSc.ScalarType}.\")" 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "id": "da41ebf9-34dd-4238-bbcc-393648210608", 125 | "metadata": { 126 | "slideshow": { 127 | "slide_type": "skip" 128 | }, 129 | "tags": [] 130 | }, 131 | "source": [ 132 | "## Defining model parameters" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": 3, 138 | "id": "dd8366af-6cc1-4fee-80b0-a499b7c4cb8f", 139 | "metadata": { 140 | "slideshow": { 141 | "slide_type": "skip" 142 | }, 143 | "tags": [] 144 | }, 145 | "outputs": [], 146 | "source": [ 147 | "# wavenumber in free space (air)\n", 148 | "k0 = 10 * np.pi\n", 149 | "\n", 150 | "# Corresponding wavelength\n", 151 | "lmbda = 2 * np.pi / k0\n", 152 | "\n", 153 | "# Polynomial degree\n", 154 | "degree = 6\n", 155 | "\n", 156 | "# Mesh order\n", 157 | "mesh_order = 2" 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "id": "7bfd09b8-9a20-4294-8ab1-781ab2c08cd8", 163 | "metadata": { 164 | "slideshow": { 165 | "slide_type": "slide" 166 | }, 167 | "tags": [] 168 | }, 169 | "source": [ 170 | "## Interfacing with GMSH" 171 | ] 172 | }, 173 | { 174 | "cell_type": "markdown", 175 | "id": "2cd01dce-8a93-4753-a7ea-0eac1fed9ada", 176 | "metadata": { 177 | "slideshow": { 178 | "slide_type": "skip" 179 | }, 180 | "tags": [] 181 | }, 182 | "source": [ 183 | "We will use [Gmsh](http://gmsh.info/) to generate the computational domain (mesh) for this example. As long as Gmsh has been installed (including its Python API), DOLFINx supports direct input of Gmsh models (generated on one process). DOLFINx will then in turn distribute the mesh over all processes in the communicator passed to `dolfinx.io.gmshio.model_to_mesh`.\n", 184 | "\n", 185 | "The function `generate_mesh` creates a Gmsh model on rank 0 of `MPI.COMM_WORLD`." 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": 4, 191 | "id": "eec83fe0-eced-437c-b4de-116792338d07", 192 | "metadata": { 193 | "scrolled": true, 194 | "tags": [] 195 | }, 196 | "outputs": [ 197 | { 198 | "name": "stdout", 199 | "output_type": "stream", 200 | "text": [ 201 | "Info : Reading 'domain.msh'...\n", 202 | "Info : 15 entities\n", 203 | "Info : 2985 nodes\n", 204 | "Info : 1444 elements\n", 205 | "Info : Done reading 'domain.msh'\n" 206 | ] 207 | } 208 | ], 209 | "source": [ 210 | "from dolfinx.io import gmshio\n", 211 | "from mesh_generation import generate_mesh\n", 212 | "\n", 213 | "# MPI communicator\n", 214 | "comm = MPI.COMM_WORLD\n", 215 | "\n", 216 | "file_name = \"domain.msh\"\n", 217 | "generate_mesh(file_name, lmbda, order=mesh_order)\n", 218 | "mesh, cell_tags, _ = gmshio.read_from_msh(file_name, comm, rank=0, gdim=2)" 219 | ] 220 | }, 221 | { 222 | "cell_type": "markdown", 223 | "id": "e3566dea-f954-484c-b41d-d6787c44f1af", 224 | "metadata": { 225 | "slideshow": { 226 | "slide_type": "slide" 227 | }, 228 | "tags": [] 229 | }, 230 | "source": [ 231 | "## Material parameters" 232 | ] 233 | }, 234 | { 235 | "cell_type": "markdown", 236 | "id": "eb727dcf-b350-4707-8ba6-63eb5ce7f739", 237 | "metadata": { 238 | "slideshow": { 239 | "slide_type": "notes" 240 | }, 241 | "tags": [] 242 | }, 243 | "source": [ 244 | "In this problem, the wave number in the different parts of the domain depends on cell markers, inputted through `cell_tags`.\n", 245 | "We use the fact that a discontinuous Lagrange space of order 0 (cell-wise constants) has a one-to-one mapping with the cells local to the process." 246 | ] 247 | }, 248 | { 249 | "cell_type": "code", 250 | "execution_count": 5, 251 | "id": "d6b41f72-c9ae-4111-b268-0a7e53c7d459", 252 | "metadata": { 253 | "tags": [] 254 | }, 255 | "outputs": [], 256 | "source": [ 257 | "W = dolfinx.fem.functionspace(mesh, (\"DG\", 0))\n", 258 | "k = dolfinx.fem.Function(W)\n", 259 | "k.x.array[:] = k0\n", 260 | "k.x.array[cell_tags.find(1)] = 3 * k0" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": 6, 266 | "id": "58dd0acb-43f3-4aa3-9fcf-441793cfab3d", 267 | "metadata": { 268 | "slideshow": { 269 | "slide_type": "skip" 270 | }, 271 | "tags": [ 272 | "hide-output" 273 | ] 274 | }, 275 | "outputs": [], 276 | "source": [ 277 | "import matplotlib.pyplot as plt\n", 278 | "import pyvista\n", 279 | "\n", 280 | "from dolfinx.plot import vtk_mesh\n", 281 | "\n", 282 | "pyvista.start_xvfb()\n", 283 | "pyvista.set_plot_theme(\"paraview\")\n", 284 | "sargs = dict(\n", 285 | " title_font_size=25,\n", 286 | " label_font_size=20,\n", 287 | " fmt=\"%.2e\",\n", 288 | " color=\"black\",\n", 289 | " position_x=0.1,\n", 290 | " position_y=0.8,\n", 291 | " width=0.8,\n", 292 | " height=0.1,\n", 293 | ")\n", 294 | "\n", 295 | "\n", 296 | "def export_function(grid, name, show_mesh=False, tessellate=False):\n", 297 | " grid.set_active_scalars(name)\n", 298 | " plotter = pyvista.Plotter(window_size=(700, 700))\n", 299 | " t_grid = grid.tessellate() if tessellate else grid\n", 300 | " plotter.add_mesh(t_grid, show_edges=False, scalar_bar_args=sargs)\n", 301 | " if show_mesh:\n", 302 | " V = dolfinx.fem.functionspace(mesh, (\"Lagrange\", 1))\n", 303 | " grid_mesh = pyvista.UnstructuredGrid(*vtk_mesh(V))\n", 304 | " plotter.add_mesh(grid_mesh, style=\"wireframe\", line_width=0.1, color=\"k\")\n", 305 | " plotter.view_xy()\n", 306 | " plotter.view_xy()\n", 307 | " plotter.camera.zoom(1.3)\n", 308 | " plotter.export_html(f\"./{name}.html\")" 309 | ] 310 | }, 311 | { 312 | "cell_type": "code", 313 | "execution_count": 7, 314 | "id": "6e33792d-50b9-4e0c-92d6-c43745243439", 315 | "metadata": { 316 | "lines_to_next_cell": 2, 317 | "tags": [ 318 | "ignore-output", 319 | "hide-input", 320 | "hide-output" 321 | ] 322 | }, 323 | "outputs": [], 324 | "source": [ 325 | "grid = pyvista.UnstructuredGrid(*vtk_mesh(mesh))\n", 326 | "grid.cell_data[\"wavenumber\"] = k.x.array.real" 327 | ] 328 | }, 329 | { 330 | "cell_type": "code", 331 | "execution_count": 8, 332 | "id": "1841452a-799c-429c-96ce-b0a638d56e8f", 333 | "metadata": { 334 | "slideshow": { 335 | "slide_type": "skip" 336 | }, 337 | "tags": [] 338 | }, 339 | "outputs": [], 340 | "source": [ 341 | "export_function(grid, \"wavenumber\", show_mesh=True, tessellate=True)" 342 | ] 343 | }, 344 | { 345 | "cell_type": "code", 346 | "execution_count": 9, 347 | "id": "b623a5ff-72b3-48a7-a3de-42d614a101e7", 348 | "metadata": { 349 | "tags": [ 350 | "hide-input" 351 | ] 352 | }, 353 | "outputs": [ 354 | { 355 | "data": { 356 | "text/html": [ 357 | " \n" 358 | ], 359 | "text/plain": [ 360 | "" 361 | ] 362 | }, 363 | "metadata": {}, 364 | "output_type": "display_data" 365 | } 366 | ], 367 | "source": [ 368 | "%%html\n", 369 | " " 370 | ] 371 | }, 372 | { 373 | "cell_type": "markdown", 374 | "id": "27cc978a-bc0d-4a77-856d-f17e27f06094", 375 | "metadata": { 376 | "slideshow": { 377 | "slide_type": "slide" 378 | }, 379 | "tags": [] 380 | }, 381 | "source": [ 382 | "## Boundary source term\n", 383 | "\n", 384 | "$$g = \\nabla u_{inc} \\cdot \\mathbf{n} - \\mathrm{j}ku_{inc}$$\n", 385 | "\n", 386 | "where $u_{inc} = e^{-jkx}$, the incoming wave, is a plane wave propagating in the $x$ direction." 387 | ] 388 | }, 389 | { 390 | "cell_type": "markdown", 391 | "id": "2a72cc1c-1043-46ab-bb7b-2d7f89823449", 392 | "metadata": { 393 | "slideshow": { 394 | "slide_type": "notes" 395 | }, 396 | "tags": [] 397 | }, 398 | "source": [ 399 | "\n", 400 | "Next, we define the boundary source term by using `ufl.SpatialCoordinate`. When using this function, all quantities using this expression will be evaluated at quadrature points." 401 | ] 402 | }, 403 | { 404 | "cell_type": "code", 405 | "execution_count": 10, 406 | "id": "cf99d1fe-55df-4630-910e-a3caf118153b", 407 | "metadata": { 408 | "tags": [] 409 | }, 410 | "outputs": [], 411 | "source": [ 412 | "n = ufl.FacetNormal(mesh)\n", 413 | "x = ufl.SpatialCoordinate(mesh)\n", 414 | "uinc = ufl.exp(1j * k * x[0])\n", 415 | "g = ufl.dot(ufl.grad(uinc), n) - 1j * k * uinc" 416 | ] 417 | }, 418 | { 419 | "cell_type": "markdown", 420 | "id": "eae8efd8-d18c-4075-b3fd-db1b3d142335", 421 | "metadata": { 422 | "slideshow": { 423 | "slide_type": "slide" 424 | }, 425 | "tags": [] 426 | }, 427 | "source": [ 428 | "## Variational form" 429 | ] 430 | }, 431 | { 432 | "cell_type": "markdown", 433 | "id": "cef49f19-5c6e-4437-b6b0-2af9e3392763", 434 | "metadata": { 435 | "slideshow": { 436 | "slide_type": "notes" 437 | }, 438 | "tags": [] 439 | }, 440 | "source": [ 441 | "Next, we define the variational problem using a 4th order Lagrange space. Note that as we are using complex valued functions, we have to use the appropriate inner product; see [DOLFINx tutorial: Complex numbers](https://jorgensd.github.io/dolfinx-tutorial/chapter1/complex_mode.html) for more information." 442 | ] 443 | }, 444 | { 445 | "cell_type": "markdown", 446 | "id": "e36a8dc7-ff02-4b72-ad79-6bff7b40a2cf", 447 | "metadata": {}, 448 | "source": [ 449 | "$$ -\\int_\\Omega \\nabla u \\cdot \\nabla \\bar{v} ~ dx + \\int_\\Omega k^2 u \\,\\bar{v}~ dx - j\\int_{\\partial \\Omega} ku \\bar{v} ~ ds = \\int_{\\partial \\Omega} g \\, \\bar{v}~ ds \\qquad \\forall v \\in \\widehat{V}. $$" 450 | ] 451 | }, 452 | { 453 | "cell_type": "code", 454 | "execution_count": 11, 455 | "id": "f7b6e171-6832-4174-8804-b0315f5a3c11", 456 | "metadata": { 457 | "slideshow": { 458 | "slide_type": "fragment" 459 | }, 460 | "tags": [] 461 | }, 462 | "outputs": [], 463 | "source": [ 464 | "import basix.ufl\n", 465 | "\n", 466 | "element = basix.ufl.element(\"Lagrange\", mesh.topology.cell_name(), degree)\n", 467 | "V = dolfinx.fem.functionspace(mesh, element)\n", 468 | "\n", 469 | "u = ufl.TrialFunction(V)\n", 470 | "v = ufl.TestFunction(V)" 471 | ] 472 | }, 473 | { 474 | "cell_type": "code", 475 | "execution_count": 12, 476 | "id": "b4327cad-a8f7-4009-9f17-e50341fc4082", 477 | "metadata": { 478 | "slideshow": { 479 | "slide_type": "fragment" 480 | }, 481 | "tags": [] 482 | }, 483 | "outputs": [], 484 | "source": [ 485 | "a = (\n", 486 | " -ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx\n", 487 | " + k**2 * ufl.inner(u, v) * ufl.dx\n", 488 | " - 1j * k * ufl.inner(u, v) * ufl.ds\n", 489 | ")\n", 490 | "L = ufl.inner(g, v) * ufl.ds" 491 | ] 492 | }, 493 | { 494 | "cell_type": "markdown", 495 | "id": "078f094e-4ae9-4403-98ea-bc2dcf053483", 496 | "metadata": { 497 | "slideshow": { 498 | "slide_type": "slide" 499 | }, 500 | "tags": [] 501 | }, 502 | "source": [ 503 | "## Linear solver" 504 | ] 505 | }, 506 | { 507 | "cell_type": "markdown", 508 | "id": "9547f3e1-38f3-43f2-9a0b-b6ea297dfa59", 509 | "metadata": { 510 | "slideshow": { 511 | "slide_type": "notes" 512 | }, 513 | "tags": [] 514 | }, 515 | "source": [ 516 | "Next, we will solve the problem using a direct solver (LU)." 517 | ] 518 | }, 519 | { 520 | "cell_type": "code", 521 | "execution_count": 13, 522 | "id": "6c8ea5ef-77df-4a44-a06e-85ed2f186940", 523 | "metadata": { 524 | "tags": [] 525 | }, 526 | "outputs": [], 527 | "source": [ 528 | "opt = {\"ksp_type\": \"preonly\", \"pc_type\": \"lu\"}\n", 529 | "problem = dolfinx.fem.petsc.LinearProblem(a, L, petsc_options=opt)\n", 530 | "uh = problem.solve()\n", 531 | "uh.name = \"u\"" 532 | ] 533 | }, 534 | { 535 | "cell_type": "markdown", 536 | "id": "31456b13-bec6-4e82-9e33-975d92b0be55", 537 | "metadata": { 538 | "lines_to_next_cell": 0, 539 | "slideshow": { 540 | "slide_type": "skip" 541 | }, 542 | "tags": [] 543 | }, 544 | "source": [ 545 | "## Postprocessing\n", 546 | "\n", 547 | "### Visualising using PyVista" 548 | ] 549 | }, 550 | { 551 | "cell_type": "code", 552 | "execution_count": 14, 553 | "id": "006df3cc-5436-4ba2-ae2e-4205e5a1f117", 554 | "metadata": { 555 | "lines_to_next_cell": 2, 556 | "slideshow": { 557 | "slide_type": "skip" 558 | }, 559 | "tags": [] 560 | }, 561 | "outputs": [], 562 | "source": [ 563 | "topology, cells, geometry = vtk_mesh(V)\n", 564 | "grid = pyvista.UnstructuredGrid(topology, cells, geometry)\n", 565 | "grid.point_data[\"Abs(u)\"] = np.abs(uh.x.array)" 566 | ] 567 | }, 568 | { 569 | "cell_type": "code", 570 | "execution_count": 15, 571 | "id": "34817139-1016-44cc-8d40-c16f239d9ab4", 572 | "metadata": { 573 | "slideshow": { 574 | "slide_type": "skip" 575 | }, 576 | "tags": [ 577 | "remove-input" 578 | ] 579 | }, 580 | "outputs": [], 581 | "source": [ 582 | "export_function(grid, \"Abs(u)\")" 583 | ] 584 | }, 585 | { 586 | "cell_type": "code", 587 | "execution_count": 16, 588 | "id": "358a3a09-18dd-457d-a8ab-b08ade039f43", 589 | "metadata": { 590 | "tags": [ 591 | "hide-input" 592 | ] 593 | }, 594 | "outputs": [ 595 | { 596 | "data": { 597 | "text/html": [ 598 | " \n" 599 | ], 600 | "text/plain": [ 601 | "" 602 | ] 603 | }, 604 | "metadata": {}, 605 | "output_type": "display_data" 606 | } 607 | ], 608 | "source": [ 609 | "%%html\n", 610 | " " 611 | ] 612 | }, 613 | { 614 | "cell_type": "code", 615 | "execution_count": 17, 616 | "id": "6a966a99-0f93-45be-bae0-4d0676fe2fc3", 617 | "metadata": { 618 | "slideshow": { 619 | "slide_type": "skip" 620 | }, 621 | "tags": [ 622 | "remove-cell" 623 | ] 624 | }, 625 | "outputs": [], 626 | "source": [ 627 | "# Open a gif\n", 628 | "plotter = pyvista.Plotter()\n", 629 | "plotter.open_gif(\"wave.gif\")\n", 630 | "boring_cmap = plt.cm.get_cmap(\"coolwarm\", 25)\n", 631 | "\n", 632 | "pts = grid.points.copy()\n", 633 | "warped = grid.warp_by_scalar()\n", 634 | "renderer = plotter.add_mesh(\n", 635 | " warped, show_edges=False, clim=[-2, 2.5], lighting=False, cmap=boring_cmap\n", 636 | ")\n", 637 | "\n", 638 | "nframe = 27\n", 639 | "for phase in np.linspace(0, 2 * np.pi, nframe + 1)[:nframe]:\n", 640 | " data = uh.x.array * np.exp(1j * phase)\n", 641 | " plotter.update_scalars(data.real, render=False)\n", 642 | " warped = grid.warp_by_scalar(factor=0.1)\n", 643 | " plotter.update_coordinates(warped.points.copy(), render=False)\n", 644 | "\n", 645 | " # Write a frame. This triggers a render.\n", 646 | " plotter.write_frame()\n", 647 | "plotter.close()" 648 | ] 649 | }, 650 | { 651 | "cell_type": "markdown", 652 | "id": "067b6793-6bba-407a-b468-3c251082b610", 653 | "metadata": { 654 | "slideshow": { 655 | "slide_type": "slide" 656 | }, 657 | "tags": [] 658 | }, 659 | "source": [ 660 | "\"gif\"" 661 | ] 662 | }, 663 | { 664 | "cell_type": "markdown", 665 | "id": "0cbf8d9b-04e1-4013-95ff-cce689897a64", 666 | "metadata": { 667 | "slideshow": { 668 | "slide_type": "slide" 669 | }, 670 | "tags": [] 671 | }, 672 | "source": [ 673 | "### Post-processing with Paraview" 674 | ] 675 | }, 676 | { 677 | "cell_type": "code", 678 | "execution_count": 18, 679 | "id": "e93e9893-5111-4389-8ba2-e615b393cdb4", 680 | "metadata": { 681 | "tags": [] 682 | }, 683 | "outputs": [], 684 | "source": [ 685 | "from dolfinx.io import VTXWriter\n", 686 | "\n", 687 | "u_abs = dolfinx.fem.Function(V, dtype=np.float64)\n", 688 | "u_abs.x.array[:] = np.abs(uh.x.array)" 689 | ] 690 | }, 691 | { 692 | "cell_type": "markdown", 693 | "id": "018960ee-5fac-45a0-8d24-6c4ce881cb72", 694 | "metadata": { 695 | "slideshow": { 696 | "slide_type": "slide" 697 | }, 698 | "tags": [ 699 | "ignore-output" 700 | ] 701 | }, 702 | "source": [ 703 | "### VTXWriter" 704 | ] 705 | }, 706 | { 707 | "cell_type": "code", 708 | "execution_count": 19, 709 | "id": "e23cbd75-d089-4fa6-8d68-478f36a1ff32", 710 | "metadata": {}, 711 | "outputs": [], 712 | "source": [ 713 | "# VTX can write higher order functions\n", 714 | "with VTXWriter(comm, \"out_high_order.bp\", [u_abs], engine=\"BP4\") as f:\n", 715 | " f.write(0.0)" 716 | ] 717 | }, 718 | { 719 | "cell_type": "markdown", 720 | "id": "48734442-c1bf-434b-921c-d3f2a677d63d", 721 | "metadata": {}, 722 | "source": [ 723 | "\"vtx\"" 724 | ] 725 | } 726 | ], 727 | "metadata": { 728 | "jupytext": { 729 | "formats": "ipynb" 730 | }, 731 | "kernelspec": { 732 | "display_name": "Python 3 (DOLFINx complex)", 733 | "language": "python", 734 | "name": "python3-complex" 735 | }, 736 | "language_info": { 737 | "codemirror_mode": { 738 | "name": "ipython", 739 | "version": 3 740 | }, 741 | "file_extension": ".py", 742 | "mimetype": "text/x-python", 743 | "name": "python", 744 | "nbconvert_exporter": "python", 745 | "pygments_lexer": "ipython3", 746 | "version": "3.10.12" 747 | }, 748 | "vscode": { 749 | "interpreter": { 750 | "hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49" 751 | } 752 | } 753 | }, 754 | "nbformat": 4, 755 | "nbformat_minor": 5 756 | } 757 | -------------------------------------------------------------------------------- /img/element-Crouzeix-Raviart-triangle-1-dofs-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgensd/fenics22-tutorial/f4d64ed90b0667016d65f3d4918456b0dcd73111/img/element-Crouzeix-Raviart-triangle-1-dofs-large.png -------------------------------------------------------------------------------- /img/element-Lagrange-triangle-0-dofs-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgensd/fenics22-tutorial/f4d64ed90b0667016d65f3d4918456b0dcd73111/img/element-Lagrange-triangle-0-dofs-large.png -------------------------------------------------------------------------------- /img/element-Lagrange-triangle-1-dofs-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgensd/fenics22-tutorial/f4d64ed90b0667016d65f3d4918456b0dcd73111/img/element-Lagrange-triangle-1-dofs-large.png -------------------------------------------------------------------------------- /img/element-Lagrange-triangle-2-dofs-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgensd/fenics22-tutorial/f4d64ed90b0667016d65f3d4918456b0dcd73111/img/element-Lagrange-triangle-2-dofs-large.png -------------------------------------------------------------------------------- /img/element-Lagrange-triangle-3-dofs-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgensd/fenics22-tutorial/f4d64ed90b0667016d65f3d4918456b0dcd73111/img/element-Lagrange-triangle-3-dofs-large.png -------------------------------------------------------------------------------- /img/element-bubble-enriched-Lagrange-triangle-2-dofs-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgensd/fenics22-tutorial/f4d64ed90b0667016d65f3d4918456b0dcd73111/img/element-bubble-enriched-Lagrange-triangle-2-dofs-large.png -------------------------------------------------------------------------------- /img/element-bubble-enriched-Lagrange-triangle-3-dofs-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgensd/fenics22-tutorial/f4d64ed90b0667016d65f3d4918456b0dcd73111/img/element-bubble-enriched-Lagrange-triangle-3-dofs-large.png -------------------------------------------------------------------------------- /img/vtx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgensd/fenics22-tutorial/f4d64ed90b0667016d65f3d4918456b0dcd73111/img/vtx.png -------------------------------------------------------------------------------- /img/xdmf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgensd/fenics22-tutorial/f4d64ed90b0667016d65f3d4918456b0dcd73111/img/xdmf.png -------------------------------------------------------------------------------- /intro.md: -------------------------------------------------------------------------------- 1 | # FEniCSx Tutorial @ [FEniCS 2022](https://fenicsproject.org/fenics-2022/) 2 | 3 | ## Presenters 4 | - Igor A. Baratta (University of Cambridge) 5 | - [IgorBaratta](https://github.com/IgorBaratta) 6 | - [IgorBaratta](https://fenicsproject.discourse.group/u/IgorBaratta) 7 | - Joseph Dean (University of Cambridge) 8 | - [jpdean](https://github.com/jpdean) 9 | - [jpdean](https://fenicsproject.discourse.group/u/jpdean) 10 | - Jørgen S. Dokken (University of Cambridge) 11 | - [jorgensd](https://github.com/jorgensd) 12 | - [jsdokken.com](https://jsdokken.com) 13 | - [dokken](https://fenicsproject.discourse.group/u/dokken) 14 | - Sarah Roggendorf (University of Cambridge) 15 | - [SarahRo](https://github.com/SarahRo) 16 | - Matthew W. Scroggs (University of Cambridge) 17 | - [mscroggs](https://github.com/mscroggs) 18 | - [mscroggs.co.uk](https://mscroggs.co.uk) 19 | - [mscroggs](https://fenicsproject.discourse.group/u/mscroggs) 20 | - [@mscroggs](https://twitter.com/mscroggs) 21 | 22 | ## Contributors 23 | - [David Kamensky](https://github.com/david-kamensky) (University of California San Diego) 24 | - [Adeeb Arif Kor](https://github.com/adeebkor) (University of Cambridge) 25 | - [Michal Habera](https://github.com/michalhabera/) (Université du Luxembourg) 26 | - [Chris Richardson](https://github.com/chrisrichardson) (University of Cambridge) 27 | - [Nathan Sime](https://github.com/nate-sime) (Carnegie Institution for Science) 28 | 29 | ## Contents 30 | ```{tableofcontents} 31 | ``` 32 | -------------------------------------------------------------------------------- /jupyter_server_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | from subprocess import check_call 3 | 4 | from traitlets.config import Config 5 | 6 | 7 | def post_save(model, os_path, contents_manager): 8 | """post-save hook for converting notebooks to .py scripts""" 9 | print(model["type"]) 10 | if model["type"] != "notebook": 11 | return # only do this for notebooks 12 | d, fname = os.path.split(os_path) 13 | if ".py" in fname: 14 | return # Ignore python files 15 | 16 | check_call( 17 | [ 18 | "jupyter", 19 | "nbconvert", 20 | "--to", 21 | "html", 22 | "--TagRemovePreprocessor.remove_input_tags={'remove-input', 'hide-input'}", 23 | fname, 24 | "--template", 25 | "reveal", 26 | f"--output=presentation-{fname.split('.ipynb')[0]}", 27 | ], 28 | cwd=d, 29 | ) 30 | 31 | 32 | c = Config() 33 | c.FileContentsManager.post_save_hook = post_save 34 | -------------------------------------------------------------------------------- /mesh_generation.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from mpi4py import MPI 4 | 5 | try: 6 | import gmsh 7 | except ModuleNotFoundError: 8 | print("This demo requires gmsh to be installed") 9 | sys.exit(0) 10 | 11 | 12 | def generate_mesh(filename: str, lmbda: int, order: int, verbose: bool = False): 13 | if MPI.COMM_WORLD.rank == 0: 14 | gmsh.initialize() 15 | gmsh.model.add("helmholtz_domain") 16 | gmsh.option.setNumber("General.Terminal", verbose) 17 | # Set the mesh size 18 | gmsh.option.setNumber("Mesh.CharacteristicLengthFactor", 1.5 * lmbda) 19 | 20 | # Add scatterers 21 | c1 = gmsh.model.occ.addCircle(0.0, -1.1 * lmbda, 0.0, 0.8 * lmbda) 22 | gmsh.model.occ.addCurveLoop([c1], tag=c1) 23 | gmsh.model.occ.addPlaneSurface([c1], tag=c1) 24 | 25 | c2 = gmsh.model.occ.addCircle(0.0, 1.1 * lmbda, 0.0, 0.8 * lmbda) 26 | gmsh.model.occ.addCurveLoop([c2], tag=c2) 27 | gmsh.model.occ.addPlaneSurface([c2], tag=c2) 28 | 29 | # Add domain 30 | r0 = gmsh.model.occ.addRectangle(-5 * lmbda, -5 * lmbda, 0.0, 10 * lmbda, 10 * lmbda) 31 | inclusive_rectangle, _ = gmsh.model.occ.fragment([(2, r0)], [(2, c1), (2, c2)]) 32 | 33 | gmsh.model.occ.synchronize() 34 | 35 | # Add physical groups 36 | gmsh.model.addPhysicalGroup(2, [c1, c2], tag=1) 37 | gmsh.model.addPhysicalGroup(2, [r0], tag=2) 38 | 39 | # Generate mesh 40 | gmsh.model.mesh.generate(2) 41 | gmsh.model.mesh.setOrder(order) 42 | gmsh.model.mesh.optimize("HighOrder") 43 | gmsh.write(filename) 44 | 45 | gmsh.finalize() 46 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=64.4.0", "wheel", "pip>=22.3"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "FEniCS22_Tutorial" 7 | version = "0.8.0" 8 | dependencies = [ 9 | "fenics-dolfinx>=0.8.0", "pyvista[jupyter]", "imageio" 10 | ] 11 | 12 | [project.optional-dependencies] 13 | dev = ["jupyter-book", "ruff", "mypy"] 14 | 15 | [tool.setuptools] 16 | packages = [] 17 | 18 | 19 | [tool.jupytext] 20 | formats = "ipynb,py:percent" 21 | 22 | 23 | [tool.mypy] 24 | ignore_missing_imports = true 25 | 26 | 27 | [tool.ruff] 28 | line-length = 100 29 | indent-width = 4 30 | 31 | [tool.ruff.lint] 32 | select = [ 33 | "E", # pycodestyle 34 | "W", # pycodestyle 35 | "F", # pyflakes 36 | "I", # isort - use standalone isort 37 | "RUF", # Ruff-specific rules 38 | "UP", # pyupgrade 39 | "ICN", # flake8-import-conventions 40 | "NPY", # numpy-specific rules 41 | "FLY", # use f-string not static joins 42 | "NPY201", # numpy 2.x ruleset 43 | ] 44 | ignore = ["UP007", "RUF012"] 45 | allowed-confusables = ["σ"] 46 | 47 | [tool.ruff.lint.isort] 48 | known-first-party = ["basix", "dolfinx", "ffcx", "ufl"] 49 | known-third-party = ["gmsh", "numba", "numpy", "pytest", "pyvista", "pyamg"] 50 | section-order = [ 51 | "future", 52 | "standard-library", 53 | "mpi", 54 | "third-party", 55 | "first-party", 56 | "local-folder", 57 | ] 58 | 59 | [tool.ruff.lint.isort.sections] 60 | "mpi" = ["mpi4py", "petsc4py"] -------------------------------------------------------------------------------- /references.bib: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | @Inbook{FEniCS2012, 5 | EDITOR = {Logg, Anders and Mardal, Kent-Andre and Wells, Garth}, 6 | TITLE = {Automated Solution of Differential Equations by the Finite Element Method: The {FEniCS} Book}, 7 | YEAR = {2012}, 8 | PUBLISHER = {Springer Berlin Heidelberg}, 9 | ADDRESS = {Berlin, Heidelberg}, 10 | DOI = {10.1007/978-3-642-23099-8}, 11 | } 12 | 13 | 14 | @article{crouzeix-falk, 15 | AUTHOR = {{C}rouzeix, {M}ichel and {F}alk, {R}ichard {S}.}, 16 | TITLE = {{N}onconforming finite elements for the {S}tokes problem}, 17 | JOURNAL = {Mathematics of Computation}, 18 | VOLUME = {52}, 19 | YEAR = {1989}, 20 | DOI = {10.2307/2008475}, 21 | PAGES = {{437--456}}, 22 | } 23 | 24 | @article{crouzeix-raviart, 25 | AUTHOR = {{C}rouzeix, {M}ichel and {R}aviart, {P}ierre-{A}rnaud}, 26 | TITLE = {{C}onforming and nonconforming finite element methods for solving the stationary {S}tokes equations}, 27 | JOURNAL = {Revue Fran\c{c}aise d'Automatique, Informatique et Recherche Op\'erationnelle}, 28 | VOLUME = {3}, 29 | YEAR = {1973}, 30 | DOI = {10.1051/m2an/197307R300331}, 31 | PAGES = {{33--75}}, 32 | } 33 | 34 | @phdthesis{fortin-phd, 35 | AUTHOR = {Fortin, Michel}, 36 | TITLE = {Calcul num\'erique des \'ecoulements des fluides de Bingham et des fluides newtoniens incompressibles par la m\'ethode des \'el\'ements finis}, 37 | SCHOOL = {Univ. Paris}, 38 | YEAR = {1972}, 39 | } 40 | 41 | @misc{defelement, 42 | AUTHOR = {{The DefElement contributors}}, 43 | TITLE = {{D}ef{E}lement: an encyclopedia of finite element definitions}, 44 | YEAR = {{2022}}, 45 | HOWPUBLISHED = {\url{https://defelement.com}}, 46 | NOTE = {[Online; accessed 15-August-2022]} 47 | } 48 | -------------------------------------------------------------------------------- /u_time.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgensd/fenics22-tutorial/f4d64ed90b0667016d65f3d4918456b0dcd73111/u_time.gif -------------------------------------------------------------------------------- /wave.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgensd/fenics22-tutorial/f4d64ed90b0667016d65f3d4918456b0dcd73111/wave.gif --------------------------------------------------------------------------------