├── .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 | "
"
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 | "
"
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 | "
"
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
--------------------------------------------------------------------------------