Showing preview only (2,915K chars total). Download the full file or copy to clipboard to get everything.
Repository: jorgensd/dolfinx-tutorial
Branch: main
Commit: 3626d300c5c6
Files: 88
Total size: 2.8 MB
Directory structure:
gitextract_b_9xem8d/
├── .dockerignore
├── .github/
│ ├── actions/
│ │ └── install-dependencies/
│ │ └── action.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── book_stable.yml
│ ├── deploy.yml
│ ├── publish_docker.yml
│ ├── test_nightly.yml
│ └── test_stable.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .vscode/
│ ├── c_cpp_properties.json
│ └── settings.json
├── Changelog.md
├── Dockerfile
├── README.md
├── _config.yml
├── _toc.yml
├── chapter1/
│ ├── complex_mode.ipynb
│ ├── complex_mode.py
│ ├── fundamentals.md
│ ├── fundamentals_code.ipynb
│ ├── fundamentals_code.py
│ ├── membrane.md
│ ├── membrane_code.ipynb
│ ├── membrane_code.py
│ ├── membrane_paraview.md
│ ├── nitsche.ipynb
│ └── nitsche.py
├── chapter2/
│ ├── advdiffreac.md
│ ├── amr.ipynb
│ ├── amr.py
│ ├── bdforces_lv4
│ ├── diffusion_code.ipynb
│ ├── diffusion_code.py
│ ├── elasticity_scaling.md
│ ├── heat_code.ipynb
│ ├── heat_code.py
│ ├── heat_equation.md
│ ├── helmholtz.md
│ ├── helmholtz_code.ipynb
│ ├── helmholtz_code.py
│ ├── hyperelasticity.ipynb
│ ├── hyperelasticity.py
│ ├── intro.md
│ ├── linearelasticity.md
│ ├── linearelasticity_code.ipynb
│ ├── linearelasticity_code.py
│ ├── navierstokes.md
│ ├── nonlinpoisson.md
│ ├── nonlinpoisson_code.ipynb
│ ├── nonlinpoisson_code.py
│ ├── ns_code1.ipynb
│ ├── ns_code1.py
│ ├── ns_code2.ipynb
│ ├── ns_code2.py
│ ├── pointvalues_lv4
│ ├── singular_poisson.ipynb
│ └── singular_poisson.py
├── chapter3/
│ ├── component_bc.ipynb
│ ├── component_bc.py
│ ├── em.ipynb
│ ├── em.py
│ ├── multiple_dirichlet.ipynb
│ ├── multiple_dirichlet.py
│ ├── neumann_dirichlet_code.ipynb
│ ├── neumann_dirichlet_code.py
│ ├── robin_neumann_dirichlet.ipynb
│ ├── robin_neumann_dirichlet.py
│ ├── subdomains.ipynb
│ ├── subdomains.py
│ └── wire.ipe
├── chapter4/
│ ├── compiler_parameters.ipynb
│ ├── compiler_parameters.py
│ ├── convergence.ipynb
│ ├── convergence.py
│ ├── mixed_poisson.ipynb
│ ├── mixed_poisson.py
│ ├── newton-solver.ipynb
│ ├── newton-solver.py
│ ├── solvers.ipynb
│ └── solvers.py
├── docker/
│ └── Dockerfile
├── fem.md
├── index.ipynb
├── jupyter_book.code-workspace
├── pyproject.toml
├── references.bib
└── tox.ini
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
# Don't include the .git in the image. It's big!
.git
================================================
FILE: .github/actions/install-dependencies/action.yml
================================================
name: Install dependencies
runs:
using: composite
steps:
- name: Install apt dependencies and upgrade pip
shell: bash -el {0}
run: |
apt-get update && apt-get install -y libxrender1 libgl1-mesa-dev mesa-utils gzip
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
- package-ecosystem: "pip" # See documentation for possible values
directory: "python/"
schedule:
interval: "weekly"
================================================
FILE: .github/workflows/book_stable.yml
================================================
name: Test stable build of book
on:
workflow_dispatch:
workflow_call:
pull_request:
branches: ["release"]
push:
branches: ["release"]
env:
HDF5_MPI: "ON"
HDF5_DIR: "/usr/local/"
H5PY_SETUP_REQUIRES: 0
DEB_PYTHON_INSTALL_LAYOUT: deb_system
LIBGL_ALWAYS_SOFTWARE: 1
jobs:
build-book:
runs-on: ubuntu-latest
container: ghcr.io/fenics/dolfinx/lab:stable
env:
PYVISTA_OFF_SCREEN: false
PYVISTA_JUPYTER_BACKEND: html
steps:
- uses: actions/checkout@v6
- name: Install common packages
uses: ./.github/actions/install-dependencies
- name: Install book deps
run: |
python3 -m pip install --break-system-packages -U pip setuptools pkgconfig poetry-core
python3 -m pip install --no-build-isolation --no-binary=h5py .[netgen]
- name: Build the book
run: jupyter-book build .
- uses: actions/upload-artifact@v7
if: always()
with:
name: webpage
path: ./_build/html
retention-days: 2
if-no-files-found: error
================================================
FILE: .github/workflows/deploy.yml
================================================
name: Publish book
on:
push:
branches:
- "release"
workflow_dispatch:
# Weekly build on Mondays at 8 am
schedule:
- cron: "0 8 * * 1"
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
run-tests:
uses: ./.github/workflows/test_stable.yml
build-book:
uses: ./.github/workflows/book_stable.yml
deploy:
runs-on: ubuntu-22.04
needs: [build-book, run-tests]
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Pages
uses: actions/configure-pages@v6
- name: Download docs artifact
uses: actions/download-artifact@v8
with:
name: webpage
path: "./public"
- name: Upload page artifact
uses: actions/upload-pages-artifact@v5
with:
path: "./public"
- name: Deploy coverage report to GH Pages
id: deployment
uses: actions/deploy-pages@v5
================================================
FILE: .github/workflows/publish_docker.yml
================================================
# Recipe based on: https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners
name: Build and publish platform dependent docker image
on:
push:
branches:
- "release"
tags:
- "v*"
pull_request:
branches:
- "release"
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
strategy:
matrix:
os: ["ubuntu-24.04", "ubuntu-24.04-arm"]
runs-on: ${{ matrix.os }}
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Log in to the Container registry
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Set architecture tag (amd64)
if: ${{ matrix.os == 'ubuntu-24.04' }}
run: echo "ARCH_TAG=amd64" >> $GITHUB_ENV
- name: Set architecture tag (arm)
if: ${{ contains(matrix.os, 'arm') }}
run: echo "ARCH_TAG=arm64" >> $GITHUB_ENV
- name: Build and push by digest
id: build
uses: docker/build-push-action@v7
with:
file: docker/Dockerfile
platforms: ${{ env.ARCH_TAG }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,"name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}",push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
if: github.event_name == 'push'
uses: actions/upload-artifact@v7
with:
name: digests-${{ env.ARCH_TAG }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
merge-and-publish:
if: github.event_name == 'push'
runs-on: ubuntu-latest
needs:
- build
steps:
- name: Download digests
uses: actions/download-artifact@v8
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
merge-multiple: true
- name: Log in to the Container registry
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}:${{ steps.meta.outputs.version }}
================================================
FILE: .github/workflows/test_nightly.yml
================================================
name: Test against DOLFINx nightly build
# Controls when the action will run.
on:
pull_request:
branches:
- main
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
workflow_call:
schedule:
- cron: "0 9 * * *"
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
test-nightly:
# The type of runner that the job will run on
runs-on: ubuntu-latest
container: ghcr.io/fenics/dolfinx/lab:nightly
env:
HDF5_MPI: "ON"
H5PY_SETUP_REQUIRES: 0
HDF5_DIR: "/usr/local/"
PYVISTA_OFF_SCREEN: true
PYVISTA_JUPYTER_BACKEND: html
LIBGL_ALWAYS_SOFTWARE: 1
steps:
- uses: actions/checkout@v6
- name: Special handling of some installation
uses: ./.github/actions/install-dependencies
- name: Install requirements
run: |
python3 -m pip install --break-system-packages -U pip setuptools pkgconfig poetry-core
python3 -m pip install --no-build-isolation --break-system-packages --no-cache-dir --no-binary=h5py .[netgen] --upgrade
- name: Test building the book
run: PYVISTA_OFF_SCREEN=false jupyter-book build .
- name: Test complex notebooks in parallel
working-directory: chapter1
run: |
export PKG_CONFIG_PATH=/usr/local/dolfinx-complex/lib/pkgconfig:$PKG_CONFIG_PATH
export PETSC_ARCH=linux-gnu-complex128-32
export PYTHONPATH=/usr/local/dolfinx-complex/lib/python3.12/dist-packages:$PYTHONPATH
export LD_LIBRARY_PATH=/usr/local/dolfinx-complex/lib:$LD_LIBRARY_PATH
python3 complex_mode.py
mpirun -n 2 python3 complex_mode.py
- name: Test chapter 1
working-directory: chapter1
run: |
mpirun -n 2 python3 fundamentals_code.py
mpirun -n 2 python3 nitsche.py
mpirun -n 2 python3 membrane_code.py
- name: Test chapter 2
working-directory: chapter2
run: |
mpirun -n 2 python3 diffusion_code.py
mpirun -n 2 python3 heat_code.py
mpirun -n 2 python3 linearelasticity_code.py
mpirun -n 2 python3 hyperelasticity.py
mpirun -n 2 python3 nonlinpoisson_code.py
mpirun -n 2 python3 ns_code1.py
mpirun -n 2 python3 ns_code2.py
- name: Test chapter 3
working-directory: chapter3
run: |
mpirun -n 2 python3 neumann_dirichlet_code.py
mpirun -n 2 python3 multiple_dirichlet.py
mpirun -n 2 python3 subdomains.py
mpirun -n 2 python3 robin_neumann_dirichlet.py
mpirun -n 2 python3 component_bc.py
mpirun -n 2 python3 em.py
- name: Test chapter 4
working-directory: chapter4
run: |
mpirun -n 2 python3 solvers.py
mpirun -n 2 python3 convergence.py
mpirun -n 2 python3 compiler_parameters.py
mpirun -n 2 python3 newton-solver.py
- uses: actions/upload-artifact@v7
if: always()
with:
name: webpage
path: ./_build/html
retention-days: 2
if-no-files-found: error
================================================
FILE: .github/workflows/test_stable.yml
================================================
name: Test stable release
on:
workflow_dispatch:
workflow_call:
pull_request:
branches: ["release"]
jobs:
test:
runs-on: ubuntu-latest
container: ghcr.io/fenics/dolfinx/lab:stable
env:
HDF5_MPI: "ON"
HDF5_DIR: "/usr/local/"
H5PY_SETUP_REQUIRES: "0"
DEB_PYTHON_INSTALL_LAYOUT: deb_system
PYVISTA_OFF_SCREEN: true
PYVISTA_JUPYTER_BACKEND: html
LIBGL_ALWAYS_SOFTWARE: 1
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- uses: actions/checkout@v6
with:
ref: release
- uses: ./.github/actions/install-dependencies
- name: Install additional deps
run: |
python3 -m pip install -U pip setuptools pkgconfig poetry-core
python3 -m pip install --no-binary=h5py --no-build-isolation .[netgen]
- name: Test complex notebooks in parallel
working-directory: chapter1
run: |
export PKG_CONFIG_PATH=/usr/local/dolfinx-complex/lib/pkgconfig:$PKG_CONFIG_PATH
export PETSC_ARCH=linux-gnu-complex128-32
export PYTHONPATH=/usr/local/dolfinx-complex/lib/python3.12/dist-packages:$PYTHONPATH
export LD_LIBRARY_PATH=/usr/local/dolfinx-complex/lib:$LD_LIBRARY_PATH
python3 complex_mode.py
mpirun -n 2 python3 complex_mode.py
- name: Test chapter 1
working-directory: chapter1
run: |
mpirun -n 2 python3 fundamentals_code.py
mpirun -n 2 python3 nitsche.py
mpirun -n 2 python3 membrane_code.py
- name: Test chapter 2
working-directory: chapter2
run: |
mpirun -n 2 python3 diffusion_code.py
mpirun -n 2 python3 heat_code.py
mpirun -n 2 python3 linearelasticity_code.py
mpirun -n 2 python3 hyperelasticity.py
mpirun -n 2 python3 nonlinpoisson_code.py
mpirun -n 2 python3 ns_code1.py
mpirun -n 2 python3 ns_code2.py
- name: Test chapter 3
working-directory: chapter3
run: |
mpirun -n 2 python3 neumann_dirichlet_code.py
mpirun -n 2 python3 multiple_dirichlet.py
mpirun -n 2 python3 subdomains.py
mpirun -n 2 python3 robin_neumann_dirichlet.py
mpirun -n 2 python3 component_bc.py
mpirun -n 2 python3 em.py
- name: Test chapter 4
working-directory: chapter4
run: |
mpirun -n 2 python3 solvers.py
mpirun -n 2 python3 convergence.py
mpirun -n 2 python3 compiler_parameters.py
mpirun -n 2 python3 newton-solver.py
- name: Upload Navier-Stokes DFG 2D 3 plots
uses: actions/upload-artifact@v7
with:
name: DFG2D-3
path: chapter2/figures
retention-days: 2
if-no-files-found: error
================================================
FILE: .gitignore
================================================
_build
*.pvd
*.h5
*.xdmf
*.vtu
*/.ipynb_checkpoints/*
.ipynb_checkpoints/*
**/.cache
*.png
*.pvtu
*.msh
*.bp
================================================
FILE: .pre-commit-config.yaml
================================================
repos:
- repo: https://github.com/kynan/nbstripout
rev: 0.7.1
hooks:
- id: nbstripout
================================================
FILE: .vscode/c_cpp_properties.json
================================================
{
"configurations": [
{
"name": "Linux",
"includePath": [
"/usr/include/python3.10/",
"${workspaceFolder}/",
"/usr/include/eigen3/",
"/usr/local/petsc/include/",
"/home/shared/dolfinx_src/ffcX/ffcx/codegeneration/",
"/usr/include/x86_64-linux-gnu/mpich/",
"/usr/local/dolfinx-real/include/"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c11",
"cppStandard": "c++20",
"intelliSenseMode": "clang-x64",
"configurationProvider": "vector-of-bool.cmake-tools"
}
],
"version": 4
}
================================================
FILE: .vscode/settings.json
================================================
{
"clang_format_style set": "file",
"editor.formatOnSave": true,
"cornflakes.linter.executablePath": "/usr/local/bin/flake8",
"cSpell.ignoreWords": [
"dolfinx",
"meshio",
"petsc",
"py",
"pygmsh",
"gmsh"
],
// whitelist numpy to remove lint errors
"python.linting.pylintArgs": [
"--ignored-modules=petsc4py.PETSc",
"--ignored-classes=petsc4py.PETSc",
"--extension-pkg-whitelist=petsc4py.PETSc"
],
"python.linting.flake8Enabled": true,
"python.linting.pylintEnabled": false,
"python.formatting.autopep8Path": "/usr/local/bin/autopep8",
"python.formatting.provider": "autopep8",
"python.formatting.autopep8Args": [
"--ignore=W503",
"--max-line-length=120"
],
"python.linting.enabled": true,
"python.pythonPath": "/usr/bin/python3",
"spellright.language": [
"en_GB"
],
"spellright.documentTypes": [
"markdown",
"latex",
"plaintext"
],
}
================================================
FILE: Changelog.md
================================================
# Changelog
## v0.10.0
- Full refactoring of {py:class}`dolfinx.fem.petsc.NonlinearProblem`, which now uses the PETSc SNES backend. See [the non-linear poisson demo](./chapter2/nonlinpoisson_code.ipynb) for details.
- {py:class}`dolfinx.fem.petsc.LinearProblem` now requires an additional argument, `petsc_options_prefix`. This should be a unique string identifier for each `LinearProblem` that is created.
- Change how one reads in GMSH data with `gmshio`. See [the membrane code](./chapter1/membrane_code.ipynb) for more details.
- {py:meth}`dolfinx.fem.FiniteElement.interpolation_points` -> {py:attr}`dolfinx.fem.FiniteElement.interpolation_points`.
- {py:mod}`dolfinx.io.gmshio` has been renamed to {py:mod}`dolfinx.io.gmsh`
- Input to {py:func}`dolfinx.fem.petsc.create_vector` has changed. One should now call {py:func}`dolfinx.fem.extract_function_spaces` on the input form first.
## v0.9.0
- `scale` in {py:func}`apply_lifting<dolfinx.fem.petsc.apply_lifting>` has been renamed to `alpha`
- Use `dolfinx.fem.Function.x.petsc_vec` as opposed to `dolfinx.fem.Function.vector`
## v0.8.0
- Replace all `ufl.FiniteElement` and `ufl.VectorElement` with the appropriate {py:func}`basix.ufl.element`
- Replace {py:class}`dolfinx.fem.FunctionSpace` with {py:func}`dolfinx.fem.functionspace`
## v0.7.2
- Change pyvista backend to `html`, using Pyvista main branch
- Using DOLFINx v0.7.2 https://github.com/FEniCS/dolfinx/releases/tag/v0.7.2 as base
## v0.7.1
- No API changes, release due to various bug-fixes from the 0.7.0 release, see:
https://github.com/FEniCS/dolfinx/releases/tag/v0.7.1 for more information
## v0.7.0
- Renamed `dolfinx.graph.create_adjacencylist` to {py:func}`dolfinx.graph.adjacencylist`
- Renamed `dolfinx.plot.create_vtk_mesh` to {py:func}`dolfinx.plot.vtk_mesh`
- Initialization of {py:class}`dolfinx.geometry.BoundingBoxTree` has been changed to {py:func}`dolfinx.geometry.bb_tree`
- `create_mesh` with Meshio has been modified. Note that you now need to pass dtype `np.int32` to the cell_data.
- Update dolfinx petsc API. Now one needs to explicitly import {py:mod}`dolfinx.fem.petsc` and {py:mod}`dolfinx.fem.nls`, as PETSc is no longer a strict requirement.
Replace `petsc4py.PETSc.ScalarType` with `dolfinx.default_scalar_type` in demos where we do not use {py:mod}`petsc4py` explicitly.
## v0.6.0
- Remove `ipygany` and `pythreejs` as plotting backends. Using `panel`.
- Add gif-output to [chapter2/diffusion_code] and [chapter2/hyperelasticity].
- Replace `dolfinx.fem.Function.geometric_dimension` with `len(dolfinx.fem.Function)`
- Improve [chapter2/ns_code2] to have better splitting scheme and density.
- Improve mesh quality in [chapter3/em].
- `jit_params` and `form_compiler_params` renamed to `*_options`.
## v0.5.0
- Using new GMSH interface in DOLFINx (`dolfinx.io.gmshio`) in all demos using GMSH
- Added a section on custom Newton-solvers, see [chapter4/newton-solver].
- Various minor DOLFINx API updates. `dolfinx.mesh.compute_boundary_facets` -> {py:func}`dolfinx.mesh.exterior_facet_indices` with slightly different functionality.
Use `dolfinx.mesh.MeshTagsMetaClass.find` instead of `mt.indices[mt.values==value]`.
- Various numpy updates, use `np.full_like`.
- Change all notebooks to use [jupytext](https://jupytext.readthedocs.io/en/latest/install.html) to automatically sync `.ipynb` with `.py` files.
- Add example of how to use `DOLFINx` in complex mode, see [chapter1/complex_mode].
## 0.4.1
- No changes
## 0.4.0 (05.02.2021)
- All `pyvista` plotting has been rewritten to use `ipygany` and `pythreejs` as well as using a cleaner interface.
- `dolfinx.plot.create_vtk_topology` has been renamed to `dolfinx.plot.create_vtk_mesh` and can now be directly used as input
to {py:class}`pyvista.UnstructuredGrid`.
- `dolfinx.fem.Function.compute_point_values` has been deprecated. Interpolation into a CG-1 is now the way of getting vertex values.
- Instead of initializing class with {py:class}`Form<dolfinx.fem.Form>`, use {py:func}`form<dolfinx.fem.form>`.
- Instead of initializing class with {py:class}`DirichletBC<dolfinx.fem.DirichletBC>` use {py:func}`dirichletbc<dolfinx.fem.dirichletbc>`.
- Updates on error computations in [Error control: Computing convergence rates](chapter4/convergence).
- Added tutorial on interpolation of {py:class}`ufl.core.expr.Expr` in [Deflection of a membrane](chapter1/membrane_code).
- Added tutorial on how to apply constant-valued Dirichlet conditions in [Deflection of a membrane](chapter1/membrane_code).
- Various API changes relating to the import structure of DOLFINx
## 0.3.0 (09.09.2021)
- Major improvements in [Form compiler parameters](chapter4/compiler_parameters), using pandas and seaborn for visualization of speed-ups gained using form compiler parameters.
- API change: `dolfinx.cpp.la.scatter_forward(u.x)` -> `u.x.scatter_forward`
- Various plotting updates due to new version of pyvista.
- Updating of the [Hyperelasticity demo](chapter2/hyperelasticity), now using DOLFINx wrappers to create the non-linear problem
- Internal updates due to bumping of jupyter-book versions
- Various typos and capitalizations fixed by @mscroggs in [PR 35](https://github.com/jorgensd/dolfinx-tutorial/pull/35).
## 0.1.0 (11.05.2021)
- First tagged release of DOLFINx Tutorial, compatible with [DOLFINx 0.1.0](https://github.com/FEniCS/dolfinx/releases/tag/0.1.0).
================================================
FILE: Dockerfile
================================================
FROM ghcr.io/jorgensd/dolfinx-tutorial:v0.10.0
# create user with a home directory
ARG NB_USER=jovyan
ARG NB_UID=1000
# 24.04 adds `ubuntu` as uid 1000;
# remove it if it already exists before creating our user
RUN id -nu ${NB_UID} && userdel --force $(id -nu ${NB_UID}) || true; \
useradd -m ${NB_USER} -u ${NB_UID}
ENV HOME=/home/${NB_USER}
# Copy home directory for usage in binder
WORKDIR ${HOME}
COPY --chown=${NB_UID} . ${HOME}
USER ${NB_USER}
ENTRYPOINT []
================================================
FILE: README.md
================================================
# The DOLFINx tutorial
[](https://github.com/jorgensd/dolfinx-tutorial/actions/workflows/deploy.yml)
[](https://github.com/jorgensd/dolfinx-tutorial/actions/workflows/test_nightly.yml)
Author: Jørgen S. Dokken
This is the source code for the dolfinx-tutorial [webpage](https://jorgensd.github.io/dolfinx-tutorial/).
If you have any comments, corrections or questions, please submit an issue in the issue tracker.
## Contributing
If you want to contribute to this tutorial, please make a fork of the repository, make your changes, and test that the CI passes.
Alternatively, if you want to add a separate chapter, a Jupyter notebook can be added to a pull request, without integrating it into the tutorial. If so, the notebook will be reviewed and modified to be included in the tutorial.
Any code added to the tutorial should work in parallel. If any changes are made to `ipynb` files, please ensure that these changes are reflected in the corresponding `py` files by using [`jupytext`](https://jupytext.readthedocs.io/en/latest/faq.html#can-i-use-jupytext-with-jupyterhub-binder-nteract-colab-saturn-or-azure):
## Building the book and running code
The book is built using [jupyterbook](https://jupyterbook.org/). The following environment variables should be set if you want to build the book
```bash
PYVISTA_OFF_SCREEN=false
PYVISTA_JUPYTER_BACKEND="html"
JUPYTER_EXTENSION_ENABLED=true
LIBGL_ALWAYS_SOFTWARE=1
```
If you run the tutorial using `jupyter-lab`, for instance through `conda`, one should set the following environment variables
```bash
PYVISTA_OFF_SCREEN=false
PYVISTA_JUPYTER_BACKEND="trame"
JUPYTER_EXTENSION_ENABLED=true
LIBGL_ALWAYS_SOFTWARE=1
```
If you use docker to run your code, you should set the following variables:
```bash
docker run -ti -e DISPLAY=$DISPLAY -e LIBGL_ALWAYS_SOFTWARE=1 -e PYVISTA_OFF_SCREEN=false -e PYVISTA_JUPYTER_BACKEND="trame" -e JUPYTER_EXTENSION_ENABLED=true --network=host -v $(pwd):/root/shared -w /root/shared ....
```
To run python scripts, either choose `PYVISTA_OFF_SCREEN=True` to get screenshots, or render interactive plots with `PYVISTA_OFF_SCREEN=False`
```bash
python3 -m jupytext --sync */*.ipynb --set-formats ipynb,py:light
```
or
```bash
python3 -m jupytext --sync */*.py --set-formats ipynb,py:light
```
Any code added to the tutorial should work in parallel.
To strip notebook output, one can use pre-commit.
```bash
pre-commit run --all-files
```
## Dependencies
It is advised to use a pre-installed version of DOLFINx, for instance through conda or docker. Remaining dependencies can be installed with
```bash
python3 -m pip install --no-binary=h5py --no-build-isolation -e .
```
# Docker images
Docker images for this tutorial can be found in the [packages tab](https://github.com/jorgensd/dolfinx-tutorial/pkgs/container/dolfinx-tutorial)
Additional requirements on top of the `dolfinx/lab:nightly` images can be found at [Dockerfile](docker/Dockerfile) and [pyproject.toml](./pyproject.toml)
##
An image building DOLFINx, Basix, UFL and FFCx from source can be built using:
```bash
docker build -f ./docker/Dockerfile -t local_lab_env .
```
from the root of this repository, and run
```bash
docker run --rm -ti -v $(pwd):/root/shared -w /root/shared --init -p 8888:8888 local_lab_env
```
from the main directory.
================================================
FILE: _config.yml
================================================
# Book settings
# Learn more at https://jupyterbook.org/customize/config.html
title: FEniCSx tutorial
author: Jørgen S. Dokken
logo: fenics_logo.png
# Force re-execution of notebooks on each build.
# See https://jupyterbook.org/content/execute.html
execute:
execute_notebooks: cache
# Set timeout for any example to 20 minutes
timeout: 1800
# Define the name of the latex output file for PDF builds
# latex:
# latex_documents:
# targetname: book.tex
# Information about where the book exists on the web
repository:
url: https://github.com/jorgensd/dolfinx-tutorial # Online location of your book
path_to_book: . # Optional path to your book, relative to the repository root
branch: release # Which branch of the repository should be used when creating links (optional)
# Add a bibtex file so that we can create citations
bibtex_bibfiles:
- references.bib
launch_buttons:
notebook_interface: "jupyterlab"
binderhub_url: "https://mybinder.org"
sphinx:
config:
html_last_updated_fmt: "%b %d, %Y"
suppress_warnings: ["mystnb.unknown_mime_type"]
# To avoid warning about default changing due to
# https://github.com/pydata/pydata-sphinx-theme/issues/1492
# html_theme_options:
# navigation_with_keys: false
codeautolink_concat_default: True
intersphinx_mapping:
basix: ["https://docs.fenicsproject.org/basix/main/python/", null]
ffcx: ["https://docs.fenicsproject.org/ffcx/main/", null]
ufl: ["https://docs.fenicsproject.org/ufl/main/", null]
dolfinx: ["https://docs.fenicsproject.org/dolfinx/main/python", null]
petsc4py: ["https://petsc.org/release/petsc4py", null]
mpi4py: ["https://mpi4py.readthedocs.io/en/stable", null]
numpy: ["https://numpy.org/doc/stable/", null]
pyvista: ["https://docs.pyvista.org/", null]
packaging: ["https://packaging.pypa.io/en/stable/", null]
matplotlib: ["https://matplotlib.org/stable/", null]
extra_extensions:
- "sphinx.ext.autodoc"
- "sphinx.ext.intersphinx"
- "sphinx_codeautolink"
parse:
myst_enable_extensions:
- "amsmath"
- "colon_fence"
- "deflist"
- "dollarmath"
- "html_admonition"
- "html_image"
- "linkify"
- "replacements"
- "smartquotes"
- "substitution"
# Add GitHub buttons to your book
# See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository
html:
use_issues_button: true
use_repository_button: true
use_edit_page_button: true
extra_footer: |
<div>
This webpage is an adaptation of <a href=https://www.springer.com/gp/book/9783319524610>The FEniCS tutorial</a> and
is distributed under the terms of the <a href=http://creativecommons.org/licenses/by/4.0/>Creative Commons Attribution 4.0 International License </a>
which permits use, duplication, adaptation, distribution and reproduction in any medium or format, as long as you give appropriate credit to the original author(s) and the source,
provide a link to the Creative Commons license and indicate if changes were made.
</div>
exclude_patterns: [README.md, chapter2/advdiffreac.md]
only_build_toc_files: true
================================================
FILE: _toc.yml
================================================
format: jb-book
root: index
parts:
- caption: Introduction
chapters:
- file: fem
- file: Changelog
- caption: Fundamentals
chapters:
- file: chapter1/fundamentals
sections:
- file: chapter1/fundamentals_code
- file: chapter1/complex_mode
- file: chapter1/nitsche
- file: chapter1/membrane
sections:
- file: chapter1/membrane_code
- file: chapter1/membrane_paraview
- caption: A Gallery of finite element solvers
chapters:
- file: chapter2/intro
- file: chapter2/heat_equation
sections:
- file: chapter2/diffusion_code
- file: chapter2/heat_code
- file: chapter2/singular_poisson
- file: chapter2/nonlinpoisson
sections:
- file: chapter2/nonlinpoisson_code
- file: chapter2/linearelasticity
sections:
- file: chapter2/linearelasticity_code
- file: chapter2/elasticity_scaling
- file: chapter2/navierstokes
sections:
- file: chapter2/ns_code1
- file: chapter2/ns_code2
- file: chapter2/hyperelasticity
- file: chapter2/helmholtz
sections:
- file: chapter2/helmholtz_code
- file: chapter2/amr
- caption: Subdomains and boundary conditions
chapters:
- file: chapter3/neumann_dirichlet_code
- file: chapter3/multiple_dirichlet
- file: chapter3/subdomains
- file: chapter3/robin_neumann_dirichlet
- file: chapter3/component_bc
- file: chapter3/em
- caption: Improving your FEniCSx code
chapters:
- file: chapter4/mixed_poisson
- file: chapter4/solvers
- file: chapter4/compiler_parameters
- file: chapter4/convergence
- file: chapter4/newton-solver
================================================
FILE: chapter1/complex_mode.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"id": "0",
"metadata": {},
"source": [
"# The Poisson problem with complex numbers\n",
"\n",
"Author: Jørgen S. Dokken\n",
"\n",
"Many PDEs, such as the [Helmholtz equation](https://docs.fenicsproject.org/dolfinx/main/python/demos/demo_helmholtz.html)\n",
"require complex-valued fields.\n",
"\n",
"For simplicity, let us consider a Poisson equation of the form:\n",
"\n",
"$$\n",
"\\begin{align}\n",
"-\\Delta u &= f &&\\text{in } \\Omega,\\\\\n",
"f &= -1 - 2j &&\\text{in } \\Omega,\\\\\n",
"u &= u_{exact} &&\\text{on } \\partial\\Omega,\\\\\n",
"u_{exact}(x, y) &= \\frac{1}{2}x^2 + 1j\\cdot y^2,\n",
"\\end{align}\n",
"$$\n",
"\n",
"As in [Solving the Poisson equation](./fundamentals) we want to express our partial differential equation\n",
"as a weak formulation.\n",
"\n",
"We start by defining our discrete function space $V_h$, such that $u_h\\in V_h$ and\n",
"$u_h = \\sum_{i=1}^N c_i \\phi_i(x, y)$ where $\\phi_i$ are **real valued** global basis\n",
"functions of our space $V_h$, and $c_i \\in \\mathcal{C}$ are the **complex valued** degrees of freedom.\n",
"\n",
"Next, we choose a test function $v\\in \\hat V_h$ where $\\hat V_h\\subset V_h$ such that $v\\vert_{\\partial\\Omega}=0$, as done in the [first tutorial](./fundamentals).\n",
"We now need to define our inner product space.\n",
"We choose the $L^2$ inner product spaces, which is a _[sesquilinear](https://en.wikipedia.org/wiki/Sesquilinear_form) 2-form_,\n",
"meaning that $\\langle u, v\\rangle$ is a map from $V_h\\times V_h\\mapsto K$, and\n",
"$\\langle u, v \\rangle = \\int_\\Omega u \\cdot \\bar v ~\\mathrm{d} x$. As it is sesquilinear, we have the following properties:\n",
"\n",
"$$\n",
"\\begin{align}\n",
"\\langle u , v \\rangle &= \\overline{\\langle v, u \\rangle},\\\\\n",
"\\langle u , u \\rangle &\\geq 0.\n",
"\\end{align}\n",
"$$\n",
"\n",
"We can now use this inner product space to do integration by parts\n",
"\n",
"$$\n",
"\\int_\\Omega \\nabla u_h \\cdot \\nabla \\overline{v}~\\mathrm{dx} =\n",
"\\int_{\\Omega} f \\cdot \\overline{v} ~\\mathrm{d} s \\qquad \\forall v \\in \\hat{V}_h.\n",
"$$\n",
"\n",
"## Installation of FEniCSx with complex number support\n",
"\n",
"FEniCSx supports both real and complex numbers, so we can create a {py:class}`function space <dolfinx.fem.FunctionSpace>`\n",
"with either real valued or complex valued coefficients.\n",
"```{admonition} Function or Coefficient\n",
"In FEniCSx, the term *function* and *coefficient* are used interchangeably.\n",
"A function is a linear combination of basis functions with coefficients, and the coefficients can be real or complex numbers.\n",
"In {py:mod}`ufl`, the term {py:class}`Coefficient <ufl.Coefficient>`, while in {py:mod}`dolfinx` we use {py:class}`Function<dolfinx.fem.Function>`\n",
"to represent the same concept (through inheritance). This is because most people think of finding the **unknown** function that solves a PDE,\n",
"while the coefficients are the set of values that define the function.\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1",
"metadata": {},
"outputs": [],
"source": [
"from mpi4py import MPI\n",
"import dolfinx\n",
"import numpy as np\n",
"\n",
"mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 10, 10)\n",
"V = dolfinx.fem.functionspace(mesh, (\"Lagrange\", 1))\n",
"u_r = dolfinx.fem.Function(V, dtype=np.float64)\n",
"u_r.interpolate(lambda x: x[0])\n",
"u_c = dolfinx.fem.Function(V, dtype=np.complex128)\n",
"u_c.interpolate(lambda x: 0.5 * x[0] ** 2 + 1j * x[1] ** 2)\n",
"print(u_r.x.array.dtype)\n",
"print(u_c.x.array.dtype)"
]
},
{
"cell_type": "markdown",
"id": "2",
"metadata": {},
"source": [
"However, as we would like to solve linear algebra problems of the form $Ax=b$, we need to be able to use matrices and vectors that support real and complex numbers.\n",
"As {[PETSc](https://petsc.org/release/)} is the most popular interfaces to linear algebra packages, we need to be able to work with their matrix and vector structures.\n",
"\n",
"Unfortunately, PETSc only supports one floating type in their matrices, thus we need to install two versions of PETSc,\n",
"one that supports `float64` and one that supports `complex128`.\n",
"In the [Docker images]https://github.com/orgs/FEniCS/packages/container/package/dolfinx%2Fdolfinx) for DOLFINx, both versions are installed,\n",
"and one can switch between them by calling `source dolfinx-real-mode` or `source dolfinx-complex-mode`.\n",
"For the [dolfinx/lab](https://github.com/FEniCS/dolfinx/pkgs/container/dolfinx%2Flab) images,\n",
"one can change the Python kernel to be either the real or complex mode, by going to\n",
"`Kernel->Change Kernel...` and choosing `Python3 (ipykernel)` (for real mode) or `Python3 (DOLFINx complex)` (for complex mode).\n",
"\n",
"We check that we are using the correct installation of PETSc by inspecting the scalar type."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3",
"metadata": {},
"outputs": [],
"source": [
"from petsc4py import PETSc\n",
"from dolfinx.fem.petsc import assemble_vector\n",
"\n",
"print(PETSc.ScalarType)\n",
"assert np.dtype(PETSc.ScalarType).kind == \"c\""
]
},
{
"cell_type": "markdown",
"id": "4",
"metadata": {},
"source": [
"## Variational problem\n",
"We are now ready to define our variational problem"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5",
"metadata": {},
"outputs": [],
"source": [
"import ufl\n",
"\n",
"u = ufl.TrialFunction(V)\n",
"v = ufl.TestFunction(V)\n",
"f = dolfinx.fem.Constant(mesh, PETSc.ScalarType(-1 - 2j))\n",
"a = ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx\n",
"L = ufl.inner(f, v) * ufl.dx"
]
},
{
"cell_type": "markdown",
"id": "6",
"metadata": {},
"source": [
"Note that we have used the `PETSc.ScalarType` to wrap the constant source on the right hand side.\n",
"This is because we want the integration kernels to assemble into the correct floating type.\n",
"\n",
"Secondly, note that we are using {py:func}`ufl.inner` to describe multiplication of $f$ and $v$,\n",
"even if they are scalar values.\n",
"This is because {py:func}`ufl.inner` takes the conjugate of the second argument,\n",
"as decribed by the $L^2$ inner product.\n",
"One could alternatively write this out explicitly\n",
"\n",
"### Inner-products and derivatives"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7",
"metadata": {},
"outputs": [],
"source": [
"L2 = f * ufl.conj(v) * ufl.dx\n",
"print(L)\n",
"print(L2)"
]
},
{
"cell_type": "markdown",
"id": "8",
"metadata": {},
"source": [
"Similarly, if we want to use the function {py:func}`ufl.derivative` to take derivatives of functionals,\n",
"we need to take some special care.\n",
"As {py:func}`ufl.derivative` inserts a {py:func}`ufl.TestFunction` to represent the variation,\n",
"we need to take the conjugate of this to be able to use it to assemble vectors."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9",
"metadata": {},
"outputs": [],
"source": [
"J = u_c**2 * ufl.dx\n",
"F = ufl.derivative(J, u_c, ufl.conj(v))\n",
"residual = assemble_vector(dolfinx.fem.form(F))\n",
"print(residual.array)"
]
},
{
"cell_type": "markdown",
"id": "10",
"metadata": {},
"source": [
"We define our Dirichlet condition and setup and solve the variational problem.\n",
"## Solve variational problem"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "11",
"metadata": {},
"outputs": [],
"source": [
"mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim)\n",
"boundary_facets = dolfinx.mesh.exterior_facet_indices(mesh.topology)\n",
"boundary_dofs = dolfinx.fem.locate_dofs_topological(\n",
" V, mesh.topology.dim - 1, boundary_facets\n",
")\n",
"bc = dolfinx.fem.dirichletbc(u_c, boundary_dofs)\n",
"problem = dolfinx.fem.petsc.LinearProblem(\n",
" a, L, bcs=[bc], petsc_options_prefix=\"complex_poisson\"\n",
")\n",
"uh = problem.solve()"
]
},
{
"cell_type": "markdown",
"id": "12",
"metadata": {},
"source": [
"We compute the $L^2$ error and the max error.\n",
"\n",
"## Error computation\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "13",
"metadata": {},
"outputs": [],
"source": [
"x = ufl.SpatialCoordinate(mesh)\n",
"u_ex = 0.5 * x[0] ** 2 + 1j * x[1] ** 2\n",
"L2_error = dolfinx.fem.form(\n",
" ufl.dot(uh - u_ex, uh - u_ex) * ufl.dx(metadata={\"quadrature_degree\": 5})\n",
")\n",
"local_error = dolfinx.fem.assemble_scalar(L2_error)\n",
"global_error = np.sqrt(mesh.comm.allreduce(local_error, op=MPI.SUM))\n",
"max_error = mesh.comm.allreduce(np.max(np.abs(u_c.x.array - uh.x.array)))\n",
"print(global_error, max_error)"
]
},
{
"cell_type": "markdown",
"id": "14",
"metadata": {},
"source": [
"## Plotting\n",
"\n",
"Finally, we plot the real and imaginary solutions.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "15",
"metadata": {},
"outputs": [],
"source": [
"import pyvista\n",
"\n",
"mesh.topology.create_connectivity(mesh.topology.dim, mesh.topology.dim)\n",
"p_mesh = pyvista.UnstructuredGrid(*dolfinx.plot.vtk_mesh(mesh, mesh.topology.dim))\n",
"pyvista_cells, cell_types, geometry = dolfinx.plot.vtk_mesh(V)\n",
"grid = pyvista.UnstructuredGrid(pyvista_cells, cell_types, geometry)\n",
"grid.point_data[\"u_real\"] = uh.x.array.real\n",
"grid.point_data[\"u_imag\"] = uh.x.array.imag\n",
"_ = grid.set_active_scalars(\"u_real\")\n",
"\n",
"p_real = pyvista.Plotter()\n",
"p_real.add_text(\"uh real\", position=\"upper_edge\", font_size=14, color=\"black\")\n",
"p_real.add_mesh(grid, show_edges=True)\n",
"p_real.view_xy()\n",
"if not pyvista.OFF_SCREEN:\n",
" p_real.show()\n",
"\n",
"grid.set_active_scalars(\"u_imag\")\n",
"p_imag = pyvista.Plotter()\n",
"p_imag.add_text(\"uh imag\", position=\"upper_edge\", font_size=14, color=\"black\")\n",
"p_imag.add_mesh(grid, show_edges=True)\n",
"p_imag.view_xy()\n",
"if not pyvista.OFF_SCREEN:\n",
" p_imag.show()"
]
}
],
"metadata": {
"jupytext": {
"formats": "ipynb,py:light"
},
"kernelspec": {
"display_name": "Python 3 (DOLFINx complex)",
"language": "python",
"name": "python3-complex"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
================================================
FILE: chapter1/complex_mode.py
================================================
# ---
# jupyter:
# jupytext:
# formats: ipynb,py:light
# text_representation:
# extension: .py
# format_name: light
# format_version: '1.5'
# jupytext_version: 1.18.1
# kernelspec:
# display_name: Python 3 (DOLFINx complex)
# language: python
# name: python3-complex
# ---
# # The Poisson problem with complex numbers
#
# Author: Jørgen S. Dokken
#
# Many PDEs, such as the [Helmholtz equation](https://docs.fenicsproject.org/dolfinx/main/python/demos/demo_helmholtz.html)
# require complex-valued fields.
#
# For simplicity, let us consider a Poisson equation of the form:
#
# $$
# \begin{align}
# -\Delta u &= f &&\text{in } \Omega,\\
# f &= -1 - 2j &&\text{in } \Omega,\\
# u &= u_{exact} &&\text{on } \partial\Omega,\\
# u_{exact}(x, y) &= \frac{1}{2}x^2 + 1j\cdot y^2,
# \end{align}
# $$
#
# As in [Solving the Poisson equation](./fundamentals) we want to express our partial differential equation
# as a weak formulation.
#
# We start by defining our discrete function space $V_h$, such that $u_h\in V_h$ and
# $u_h = \sum_{i=1}^N c_i \phi_i(x, y)$ where $\phi_i$ are **real valued** global basis
# functions of our space $V_h$, and $c_i \in \mathcal{C}$ are the **complex valued** degrees of freedom.
#
# Next, we choose a test function $v\in \hat V_h$ where $\hat V_h\subset V_h$ such that $v\vert_{\partial\Omega}=0$, as done in the [first tutorial](./fundamentals).
# We now need to define our inner product space.
# We choose the $L^2$ inner product spaces, which is a _[sesquilinear](https://en.wikipedia.org/wiki/Sesquilinear_form) 2-form_,
# meaning that $\langle u, v\rangle$ is a map from $V_h\times V_h\mapsto K$, and
# $\langle u, v \rangle = \int_\Omega u \cdot \bar v ~\mathrm{d} x$. As it is sesquilinear, we have the following properties:
#
# $$
# \begin{align}
# \langle u , v \rangle &= \overline{\langle v, u \rangle},\\
# \langle u , u \rangle &\geq 0.
# \end{align}
# $$
#
# We can now use this inner product space to do integration by parts
#
# $$
# \int_\Omega \nabla u_h \cdot \nabla \overline{v}~\mathrm{dx} =
# \int_{\Omega} f \cdot \overline{v} ~\mathrm{d} s \qquad \forall v \in \hat{V}_h.
# $$
#
# ## Installation of FEniCSx with complex number support
#
# FEniCSx supports both real and complex numbers, so we can create a {py:class}`function space <dolfinx.fem.FunctionSpace>`
# with either real valued or complex valued coefficients.
# ```{admonition} Function or Coefficient
# In FEniCSx, the term *function* and *coefficient* are used interchangeably.
# A function is a linear combination of basis functions with coefficients, and the coefficients can be real or complex numbers.
# In {py:mod}`ufl`, the term {py:class}`Coefficient <ufl.Coefficient>`, while in {py:mod}`dolfinx` we use {py:class}`Function<dolfinx.fem.Function>`
# to represent the same concept (through inheritance). This is because most people think of finding the **unknown** function that solves a PDE,
# while the coefficients are the set of values that define the function.
# ```
# +
from mpi4py import MPI
import dolfinx
import numpy as np
mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 10, 10)
V = dolfinx.fem.functionspace(mesh, ("Lagrange", 1))
u_r = dolfinx.fem.Function(V, dtype=np.float64)
u_r.interpolate(lambda x: x[0])
u_c = dolfinx.fem.Function(V, dtype=np.complex128)
u_c.interpolate(lambda x: 0.5 * x[0] ** 2 + 1j * x[1] ** 2)
print(u_r.x.array.dtype)
print(u_c.x.array.dtype)
# -
# However, as we would like to solve linear algebra problems of the form $Ax=b$, we need to be able to use matrices and vectors that support real and complex numbers.
# As {[PETSc](https://petsc.org/release/)} is the most popular interfaces to linear algebra packages, we need to be able to work with their matrix and vector structures.
#
# Unfortunately, PETSc only supports one floating type in their matrices, thus we need to install two versions of PETSc,
# one that supports `float64` and one that supports `complex128`.
# In the [Docker images]https://github.com/orgs/FEniCS/packages/container/package/dolfinx%2Fdolfinx) for DOLFINx, both versions are installed,
# and one can switch between them by calling `source dolfinx-real-mode` or `source dolfinx-complex-mode`.
# For the [dolfinx/lab](https://github.com/FEniCS/dolfinx/pkgs/container/dolfinx%2Flab) images,
# one can change the Python kernel to be either the real or complex mode, by going to
# `Kernel->Change Kernel...` and choosing `Python3 (ipykernel)` (for real mode) or `Python3 (DOLFINx complex)` (for complex mode).
#
# We check that we are using the correct installation of PETSc by inspecting the scalar type.
# +
from petsc4py import PETSc
from dolfinx.fem.petsc import assemble_vector
print(PETSc.ScalarType)
assert np.dtype(PETSc.ScalarType).kind == "c"
# -
# ## Variational problem
# We are now ready to define our variational problem
# +
import ufl
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
f = dolfinx.fem.Constant(mesh, PETSc.ScalarType(-1 - 2j))
a = ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx
L = ufl.inner(f, v) * ufl.dx
# -
# Note that we have used the `PETSc.ScalarType` to wrap the constant source on the right hand side.
# This is because we want the integration kernels to assemble into the correct floating type.
#
# Secondly, note that we are using {py:func}`ufl.inner` to describe multiplication of $f$ and $v$,
# even if they are scalar values.
# This is because {py:func}`ufl.inner` takes the conjugate of the second argument,
# as decribed by the $L^2$ inner product.
# One could alternatively write this out explicitly
#
# ### Inner-products and derivatives
L2 = f * ufl.conj(v) * ufl.dx
print(L)
print(L2)
# Similarly, if we want to use the function {py:func}`ufl.derivative` to take derivatives of functionals,
# we need to take some special care.
# As {py:func}`ufl.derivative` inserts a {py:func}`ufl.TestFunction` to represent the variation,
# we need to take the conjugate of this to be able to use it to assemble vectors.
J = u_c**2 * ufl.dx
F = ufl.derivative(J, u_c, ufl.conj(v))
residual = assemble_vector(dolfinx.fem.form(F))
print(residual.array)
# We define our Dirichlet condition and setup and solve the variational problem.
# ## Solve variational problem
mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim)
boundary_facets = dolfinx.mesh.exterior_facet_indices(mesh.topology)
boundary_dofs = dolfinx.fem.locate_dofs_topological(
V, mesh.topology.dim - 1, boundary_facets
)
bc = dolfinx.fem.dirichletbc(u_c, boundary_dofs)
problem = dolfinx.fem.petsc.LinearProblem(
a, L, bcs=[bc], petsc_options_prefix="complex_poisson"
)
uh = problem.solve()
# We compute the $L^2$ error and the max error.
#
# ## Error computation
#
x = ufl.SpatialCoordinate(mesh)
u_ex = 0.5 * x[0] ** 2 + 1j * x[1] ** 2
L2_error = dolfinx.fem.form(
ufl.dot(uh - u_ex, uh - u_ex) * ufl.dx(metadata={"quadrature_degree": 5})
)
local_error = dolfinx.fem.assemble_scalar(L2_error)
global_error = np.sqrt(mesh.comm.allreduce(local_error, op=MPI.SUM))
max_error = mesh.comm.allreduce(np.max(np.abs(u_c.x.array - uh.x.array)))
print(global_error, max_error)
# ## Plotting
#
# Finally, we plot the real and imaginary solutions.
#
# +
import pyvista
mesh.topology.create_connectivity(mesh.topology.dim, mesh.topology.dim)
p_mesh = pyvista.UnstructuredGrid(*dolfinx.plot.vtk_mesh(mesh, mesh.topology.dim))
pyvista_cells, cell_types, geometry = dolfinx.plot.vtk_mesh(V)
grid = pyvista.UnstructuredGrid(pyvista_cells, cell_types, geometry)
grid.point_data["u_real"] = uh.x.array.real
grid.point_data["u_imag"] = uh.x.array.imag
_ = grid.set_active_scalars("u_real")
p_real = pyvista.Plotter()
p_real.add_text("uh real", position="upper_edge", font_size=14, color="black")
p_real.add_mesh(grid, show_edges=True)
p_real.view_xy()
if not pyvista.OFF_SCREEN:
p_real.show()
grid.set_active_scalars("u_imag")
p_imag = pyvista.Plotter()
p_imag.add_text("uh imag", position="upper_edge", font_size=14, color="black")
p_imag.add_mesh(grid, show_edges=True)
p_imag.view_xy()
if not pyvista.OFF_SCREEN:
p_imag.show()
================================================
FILE: chapter1/fundamentals.md
================================================
# Solving the Poisson equation
Authors: Hans Petter Langtangen, Anders Logg
Adapted to FEniCSx by Jørgen S. Dokken
The goal of this tutorial is to solve one of the most basic PDEs, the Poisson equation, with a few lines of code in FEniCSx.
We start by introducing some fundamental FEniCSx objects, such as {py:class}`Function<dolfinx.fem.Function>`,
{py:func}`functionspace<dolfinx.fem.functionspace>`, {py:func}`TrialFunction<ufl.TrialFunction>` and {py:func}`TestFunction<ufl.TestFunction>`,
and learn how to write a basic PDE solver.
This will include:
- How to formulate a mathematical variational problem
- How to apply boundary conditions
- How to solve the discrete linear system
- How to visualize the solution
The Poisson equation is the following boundary-value problem
\begin{align}
-\nabla^2 u(\mathbf{x}) &= f(\mathbf{x})&&\mathbf{x} \in \Omega\\
u(\mathbf{x}) &= u_D(\mathbf{x})&& \mathbf{x} \in \partial\Omega
\end{align}
Here, $u=u(\mathbf{x})$ is the unknown function, $f=f(\mathbf{x})$ a prescribed function, $\nabla^2$ the Laplace operator
(often written as $\Delta$), $\Omega$ the spatial domain, and $\partial\Omega$ the boundary of $\Omega$.
The Poisson problem, including both the PDE, $-\nabla^2 u = f$, and the boundary condition, $u=u_D$ on $\partial\Omega$, is an example of a _boundary-value problem_, which must be precisely stated before we can start solving it numerically with FEniCSx.
In the two-dimensional space with coordinates $x$ and $y$, we can expand the Poisson equation as
$$-\frac{\partial^2 u}{\partial x^2} - \frac{\partial^2 u}{\partial y^2} = f(x,y)$$
The unknown $u$ is now a function of two variables, $u=u(x,y)$, defined over the two-dimensional domain $\Omega$.
The Poisson equation arises in numerous physical contexts, including
heat conduction, electrostatics, diffusion of substances, twisting of
elastic rods, inviscid fluid flow, and water waves. Moreover, the
equation appears in numerical splitting strategies for more complicated
systems of PDEs, in particular the Navier--Stokes equations.
Solving a boundary value problem in FEniCSx consists of the following steps:
1. Identify the computational domain $\Omega$, the PDE, and its corresponding boundary conditions and source terms $f$.
2. Reformulate the PDE as a finite element variational problem.
3. Write a Python program defining the computational domain, the boundary conditions, the variational problem, and the source terms, using FEniCSx.
4. Run the Python program to solve the boundary-value problem. Optionally, you can extend the program to derive quantities such as fluxes and averages,
and visualize the results.
As we have already covered step 1, we shall now cover steps 2-4.
## Finite element variational formulation
FEniCSx is based on the finite element method, which is a general and
efficient mathematical technique for the numerical solution of
PDEs. The starting point for finite element methods is a PDE
expressed in _variational form_. For readers not familiar with variational problems, we suggest reading a proper treatment on the finite element method, as this tutorial is meant as a brief introduction to the subject. See the original tutorial {cite}`fd-FenicsTutorial` (Chapter 1.6.2).
The basic recipe for turning a PDE into a variational problem is:
- Multiply the PDE by a function $v$
- Integrate the resulting equation over the domain $\Omega$
- Perform integration by parts of those terms with second order derivatives
The function $v$ which multiplies the PDE is called a _test function_. The unknown function $u$ that is to be approximated is referred to as a _trial function_.
The terms trial and test functions are used in FEniCSx too. The test and trial functions belong to certain _function spaces_ that specify the properties of the functions.
In the present case, we multiply the Poisson equation by a test function $v$ and integrate over $\Omega$:
$$\int_\Omega (-\nabla^2 u) v~\mathrm{d} x = \int_\Omega f v~\mathrm{d} x.$$
Here $\mathrm{d} x$ denotes the differential element for integration over the domain $\Omega$. We will later let $\mathrm{d} s$ denote the differential element for integration over $\partial\Omega$, the boundary of $\Omega$.
A rule of thumb when deriving variational formulations is that one tries to keep the order of derivatives of $u$ and $v$ as small as possible.
Here, we have a second-order differential of $u$, which can be transformed to a first derivative by employing the technique of
[integration by parts](https://en.wikipedia.org/wiki/Integration_by_parts).
The formula reads
$$-\int_\Omega (\nabla^2 u)v~\mathrm{d}x
= \int_\Omega\nabla u\cdot\nabla v~\mathrm{d}x-
\int_{\partial\Omega}\frac{\partial u}{\partial n}v~\mathrm{d}s,$$
where $\dfrac{\partial u}{\partial n}=\nabla u \cdot \vec{n}$ is the derivative of $u$ in the outward normal direction $\vec{n}$ on the boundary.
Another feature of variational formulations is that the test function $v$ is required to vanish on the parts of the boundary where the solution $u$ is known. See for instance {cite}`fd-Langtangen_Mardal_FEM_2019`.
In the present problem, this means that $v$ is $0$ on the whole boundary $\partial\Omega$. Thus, the second term in the integration by parts formula vanishes, and we have that
$$\int_\Omega \nabla u \cdot \nabla v~\mathrm{d} x = \int_\Omega f v~\mathrm{d} x.$$
If we require that this equation holds for all test functions $v$ in some suitable space $\hat{V}$, the so-called _test space_, we obtain a well-defined mathematical problem that uniquely determines the solution $u$ which lies in some function space $V$. Note that $V$ does not have to be the same space as
$\hat{V}$. We call the space $V$ the _trial space_. We refer to the equation above as the _weak form_/_variational form_ of the original boundary-value problem. We now properly state our variational problem:
Find $u\in V$ such that
$$\int_\Omega \nabla u \cdot \nabla v~\mathrm{d} x = \int_\Omega f v~\mathrm{d} x\qquad \forall v \in \hat{V}.$$
For the present problem, the trial and test spaces $V$ and $\hat{V}$ are defined as
\begin{equation}
\begin{alignedat}{2}
V &= \{v \in H^1(\Omega) \mid v = u_D && \quad \text{on } \partial \Omega \}, \\
\hat{V} &= \{v \in H^1(\Omega) \mid v = 0 && \quad \text{on } \partial \Omega \}.
\end{alignedat}
\end{equation}
In short, $H^1(\Omega)$ is the Sobolev space containing functions $v$ such that $v^2$ and $\vert \nabla v \vert ^2$ have finite integrals over $\Omega$. The solution of the underlying
PDE must lie in a function space where the derivatives are
also continuous, but the Sobolev space $H^1(\Omega)$ allows functions with discontinuous derivatives.
This weaker continuity requirement in our weak formulation (caused by the integration by parts) is of great importance when it comes to constructing the finite element function space. In particular, it allows the use of piecewise polynomial function spaces. This means that the function spaces are constructed
by stitching together polynomial functions on simple domains
such as intervals, triangles, quadrilaterals, tetrahedra and
hexahedra.
The variational problem is a _continuous problem_: it defines the solution $u$ in the infinite-dimensional function space $V$.
The finite element method for the Poisson equation finds an approximate solution of the variational problem by replacing the infinite-dimensional function spaces $V$ and $\hat{V}$ by _discrete_ (finite dimensional) trial and test spaces $V_h\subset V$ and $\hat{V}_h \subset \hat{V}$. The discrete
variational problem reads: Find $u_h\in V_h$ such that
\begin{align}
\int_\Omega \nabla u_h \cdot \nabla v~\mathrm{d} x &= \int_\Omega fv~\mathrm{d} x && \forall v \in \hat{V}_h.
\end{align}
This variational problem, together with suitable definitions of $V_h$ and $\hat{V}_h$ uniquely define our approximate numerical solution of the Poisson equation.
Note that the boundary condition is encoded as part of the test and trial spaces. This might seem complicated at first glance,
but means that the finite element variational problem and the continuous variational problem look the same.
## Abstract finite element variational formulation
We will introduce the following notation for variational problems:
Find $u\in V$ such that
\begin{align}
a(u,v)&=L(v)&& \forall v \in \hat{V}.
\end{align}
For the Poisson equation, we have:
\begin{align}
a(u,v) &= \int_{\Omega} \nabla u \cdot \nabla v~\mathrm{d} x,\\
L(v) &= \int_{\Omega} fv~\mathrm{d} x.
\end{align}
In the literature $a(u,v)$ is known as the _bilinear form_ and $L(v)$ as a _linear form_.
For every linear problem, we will identify all terms with the unknown $u$ and collect them in $a(u,v)$, and collect all terms with only known functions in $L(v)$.
To solve a linear PDE in FEniCSx, such as the Poisson equation, a user thus needs to perform two steps:
1. Choose the finite element spaces $V$ and $\hat{V}$ by specifying the domain (the mesh) and the type of function space (polynomial degree and type).
2. Express the PDE as a (discrete) variational problem: Find $u\in V$ such that $a(u,v)=L(v)$ for all $v \in \hat{V}$.
## References
```{bibliography}
:filter: cited
:labelprefix:
:keyprefix: fd-
```
================================================
FILE: chapter1/fundamentals_code.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"id": "0",
"metadata": {},
"source": [
"# Implementation\n",
"\n",
"Author: Jørgen Schartum Dokken\n",
"\n",
"This implementation is an adaptation of the work in {cite}`fundamentals-FenicsTutorial` to DOLFINx.\n",
"\n",
"In this section, you will learn:\n",
"- How to use the built-in meshes in DOLFINx\n",
"- How to create a spatially varying Dirichlet boundary conditions on the whole domain boundary\n",
"- How to define a weak formulation of your PDE\n",
"- How to solve the resulting system of linear equations\n",
"- How to visualize the solution using a variety of tools\n",
"- How to compute the $L^2(\\Omega)$ error and the error at mesh vertices\n",
"\n",
"## Interactive tutorials\n",
"```{admonition} Run the tutorial as Jupyter notebook in browser\n",
"As this book has been published as a Jupyter Book, each code can be run in your browser as a Jupyter notebook.\n",
"To start such a notebook click the rocket symbol in the top right corner of the relevant tutorial.\n",
"```\n",
"\n",
"The Poisson problem has so far featured a general domain $\\Omega$ and general functions $u_D$ for\n",
"the boundary conditions and $f$ for the right hand side.\n",
"Therefore, we need to make specific choices of $\\Omega, u_D$ and $f$.\n",
"A wise choice is to construct a problem with a known analytical solution,\n",
"so that we can check that the computed solution is correct.\n",
"The primary candidates are lower-order polynomials.\n",
"The continuous Galerkin finite element spaces of degree $r$ will exactly reproduce polynomials of degree $r$.\n",
"<!-- Particularly, piecewise linear continuous Galerkin finite elements are able to exactly reproduce a quadratic polynomial on\n",
"a uniformly partitioned mesh. -->\n",
" We use this fact to construct a quadratic function in $2D$. In particular we choose\n",
"\n",
"$$\n",
"\\begin{align}\n",
" u_e(x,y)=1+x^2+2y^2\n",
" \\end{align}\n",
"$$\n",
"\n",
"Inserting $u_e$ in the original boundary problem, we find that\n",
"\n",
"$$\n",
"\\begin{align}\n",
" f(x,y)= -6,\\qquad u_D(x,y)=u_e(x,y)=1+x^2+2y^2,\n",
"\\end{align}\n",
"$$\n",
"\n",
"regardless of the shape of the domain as long as we prescribe\n",
"$u_e$ on the boundary.\n",
"\n",
"For simplicity, we choose the domain to be a unit square $\\Omega=[0,1]\\times [0,1]$\n",
"\n",
"This simple but very powerful method for constructing test problems is called _the method of manufactured solutions_.\n",
"First pick a simple expression for the exact solution, plug into\n",
"the equation to obtain the right-hand side (source term $f$).\n",
"Then solve the equation with this right hand side, and using the exact solution as boundary condition.\n",
"Finally, we create a program that tries to reproduce the exact solution.\n",
"\n",
"Note that in many cases, it can be hard to determine if the program works if it produces an error of size\n",
"$10^{-5}$ on a $20 \\times 20$ grid.\n",
"However, since we are using Sobolev spaces, we usually know about the numerical errors _asymptotic properties_.\n",
"For instance that it is proportional to $h^2$ if $h$ is the size of a cell in the mesh.\n",
"We can then compare the error on meshes with different $h$-values to see if the asymptotic behavior is correct.\n",
"This technique will be explained in detail in the chapter [Improving your fenics code](./../chapter4/convergence).\n",
"\n",
"However, in cases where we have a solution we know that should have no approximation error,\n",
"we know that the solution should be produced to machine precision by the program."
]
},
{
"cell_type": "markdown",
"id": "1",
"metadata": {},
"source": [
"A major difference between a traditional FEniCS code and a FEniCSx code,\n",
"is that one is not advised to use the wildcard import.\n",
"We will see this throughout this first example.\n",
"\n",
"## Generating simple meshes\n",
"The next step is to define the discrete domain, _the mesh_.\n",
"We do this by importing one of the built-in mesh generators.\n",
"We will build a {py:func}`unit square mesh<dolfinx.mesh.create_unit_square>`, i.e. a mesh spanning $[0,1]\\times[0,1]$.\n",
"It can consist of either triangles or quadrilaterals."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2",
"metadata": {},
"outputs": [],
"source": [
"from mpi4py import MPI\n",
"from dolfinx import mesh\n",
"import numpy\n",
"\n",
"domain = mesh.create_unit_square(MPI.COMM_WORLD, 8, 8, mesh.CellType.quadrilateral)"
]
},
{
"cell_type": "markdown",
"id": "3",
"metadata": {},
"source": [
"Note that in addition to give how many elements we would like to have in each direction,\n",
"we also have to supply the _MPI-communicator_.\n",
"This is to specify how we would like the program to behave in parallel.\n",
"If we supply {py:data}`MPI.COMM_WORLD<mpi4py.MPI.COMM_WORLD>` we create a single mesh,\n",
"whose data is distributed over the number of processors we would like to use.\n",
"We can for instance run the program in parallel on two processors by using `mpirun`, as:\n",
"``` bash\n",
" mpirun -n 2 python3 t1.py\n",
"```\n",
"However, if we would like to create a separate mesh on each processor,\n",
"we can use {py:data}`MPI.COMM_SELF<mpi4py.MPI.COMM_SELF>`.\n",
"This is for instance useful if we run a small problem, and would like to run it with multiple parameters.\n",
"\n",
"## Defining the finite element function space\n",
" Once the mesh has been created, we can create the finite element function space $V$.\n",
"The finite element function space does not need to be the same as the one used to describe the mesh.\n",
"DOLFINx supports a wide range of arbitrary order finite element function spaces, see:\n",
"[Supported elements in DOLFINx](https://defelement.org/lists/implementations/basix.ufl.html)\n",
"for an extensive list.\n",
"To create a function space, we need to specify what mesh the space is defined on,\n",
"what element famil the space is based on, and the degree of the element.\n",
"These can for instance be defned through a tuple `(\"family\", degree)`, as shown below"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4",
"metadata": {},
"outputs": [],
"source": [
"from dolfinx import fem\n",
"\n",
"V = fem.functionspace(domain, (\"Lagrange\", 1))"
]
},
{
"cell_type": "markdown",
"id": "960048ad",
"metadata": {},
"source": [
"Further details about specification/customization of this tuple, see {py:class}`dolfinx.fem.ElementMetaData`."
]
},
{
"cell_type": "markdown",
"id": "5",
"metadata": {},
"source": [
"## Dirichlet boundary conditions\n",
"Next, we create a function that will hold the Dirichlet boundary data, and use interpolation to\n",
"fill it with the appropriate data."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6",
"metadata": {},
"outputs": [],
"source": [
"uD = fem.Function(V)\n",
"uD.interpolate(lambda x: 1 + x[0] ** 2 + 2 * x[1] ** 2)"
]
},
{
"cell_type": "markdown",
"id": "7",
"metadata": {},
"source": [
"We now have the boundary data (and in this case the solution of the finite element problem)\n",
"represented in the discrete function space.\n",
"Next we would like to apply the boundary values to all degrees of freedom that are on the\n",
"boundary of the discrete domain.\n",
"We start by identifying the facets (line-segments) representing the outer boundary,\n",
"using {py:func}`dolfinx.mesh.exterior_facet_indices`.\n",
"We start by creating the facet to cell connectivity required to determine boundary facets by\n",
"calling {py:meth}`dolfinx.mesh.Topology.create_connectivity`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "10",
"metadata": {},
"outputs": [],
"source": [
"tdim = domain.topology.dim\n",
"fdim = tdim - 1\n",
"domain.topology.create_connectivity(fdim, tdim)\n",
"boundary_facets = mesh.exterior_facet_indices(domain.topology)"
]
},
{
"cell_type": "markdown",
"id": "11",
"metadata": {
"lines_to_next_cell": 2
},
"source": [
"For the current problem, as we are using the first order Lagrange function space,\n",
"the degrees of freedom are located at the vertices of each cell, thus each facet contains two degrees of freedom.\n",
"\n",
"To find the local indices of these degrees of freedom, we use {py:func}`dolfinx.fem.locate_dofs_topological`\n",
"which takes in the function space, the dimension of entities in the mesh we would like to identify and the local entities.\n",
"```{admonition} Local ordering of degrees of freedom and mesh vertices\n",
"Many people expect there to be a 1-1 correspondence between the mesh coordinates and the coordinates of the degrees of freedom.\n",
"However, this is only true in the case of `Lagrange` 1 elements on a first order mesh.\n",
"Therefore, in DOLFINx we use separate local numbering for the mesh coordinates and the dof coordinates.\n",
"To obtain the local dof coordinates we can use\n",
"{py:meth}`V.tabulate_dof_coordinates()<dolfinx.fem.FunctionSpace.tabulate_dof_coordinates>`,\n",
"while the ordering of the local vertices can be obtained by {py:attr}`mesh.geometry.x<dolfinx.mesh.Geometry.x>`.\n",
"```\n",
"With this data at hand, we can create the Dirichlet boundary condition"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "12",
"metadata": {},
"outputs": [],
"source": [
"boundary_dofs = fem.locate_dofs_topological(V, fdim, boundary_facets)\n",
"bc = fem.dirichletbc(uD, boundary_dofs)"
]
},
{
"cell_type": "markdown",
"id": "13",
"metadata": {
"lines_to_next_cell": 2
},
"source": [
"## Defining the trial and test function\n",
"\n",
"In mathematics, we distinguish between trial and test spaces $V$ and $\\hat{V}$.\n",
"The only difference in the present problem is the boundary conditions.\n",
"In FEniCSx, we do not specify boundary conditions as part of the function space,\n",
"so it is sufficient to use a common space for the trial and test function.\n",
"\n",
"We use the {py:mod}`Unified Form Language<ufl>` (UFL) to specify the variational formulations.\n",
"See {cite}`fundamentals-ufl2014` for more details."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "14",
"metadata": {},
"outputs": [],
"source": [
"import ufl\n",
"\n",
"u = ufl.TrialFunction(V)\n",
"v = ufl.TestFunction(V)"
]
},
{
"cell_type": "markdown",
"id": "15",
"metadata": {},
"source": [
"## Defining the source term\n",
"As the source term is constant over the domain, we use {py:class}`dolfinx.fem.Constant`"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "16",
"metadata": {},
"outputs": [],
"source": [
"from dolfinx import default_scalar_type\n",
"\n",
"f = fem.Constant(domain, default_scalar_type(-6))"
]
},
{
"cell_type": "markdown",
"id": "17",
"metadata": {},
"source": [
"```{admonition} Compilation speed-up\n",
"Instead of wrapping $-6$ in a {py:class}`dolfinx.fem.Constant`, we could simply define $f$ as `f=-6`.\n",
"However, if we would like to change this parameter later in the simulation,\n",
"we would have to redefine our variational formulation.\n",
"The {py:attr}`dolfinx.fem.Constant.value` allows us to update the value in $f$ by using `f.value=5`.\n",
"Additionally, by indicating that $f$ is a constant, we speed up compilation of the variational\n",
"formulations required for the created linear system.\n",
"```\n",
"\n",
"## Defining the variational problem\n",
"As we now have defined all variables used to describe our variational problem, we can create the weak formulation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "18",
"metadata": {},
"outputs": [],
"source": [
"a = ufl.dot(ufl.grad(u), ufl.grad(v)) * ufl.dx\n",
"L = f * v * ufl.dx"
]
},
{
"cell_type": "markdown",
"id": "19",
"metadata": {},
"source": [
"Note that there is a very close correspondence between the Python syntax and the mathematical syntax\n",
"$\\int_{\\Omega} \\nabla u \\cdot \\nabla v ~\\mathrm{d} x$ and $\\int_{\\Omega}fv~\\mathrm{d} x$.\n",
"The integration over the domain $\\Omega$ is defined by using {py:func}`ufl.dx`, an integration\n",
"{py:class}`measure<ufl.Measure>` over all cells of the mesh.\n",
"\n",
"This is the key strength of FEniCSx:\n",
"the formulas in the variational formulation translate directly to very similar Python code,\n",
"a feature that makes it easy to specify and solve complicated PDE problems.\n",
"\n",
"## Expressing inner products\n",
"The inner product $\\int_\\Omega \\nabla u \\cdot \\nabla v ~\\mathrm{d} x$ can be expressed in various ways in UFL.\n",
"We have used the notation `ufl.dot(ufl.grad(u), ufl.grad(v))*ufl.dx`.\n",
"The {py:func}`dot<ufl.dot>` product in UFL computes the sum (contraction) over the last index\n",
"of the first factor and first index of the second factor.\n",
"In this case, both factors are tensors of rank one (vectors) and so the sum is just over\n",
"the single index of both $\\nabla u$ and $\\nabla v$.\n",
"To compute an inner product of matrices (with two indices),\n",
"one must use the function {py:func}`ufl.inner` instead of {py:func}`ufl.dot`.\n",
"For real-valued vectors, {py:func}`ufl.dot` and {py:func}`ufl.inner` are equivalent.\n",
"\n",
"```{admonition} Complex numbers\n",
"In DOLFINx, one can solve complex number problems by using an installation of PETSc using complex numbers.\n",
"For variational formulations with complex numbers, one cannot use {py:func}`ufl.dot` to compute inner products.\n",
"One has to use {py:func}`ufl.inner`, with the test-function as the second input argument for {py:func}`ufl.inner`.\n",
"See [Running DOLFINx in complex mode](./complex_mode) for more information.\n",
"```\n",
"\n",
"\n",
"## Forming and solving the linear system\n",
"\n",
"Having defined the finite element variational problem and boundary condition,\n",
"we can create our {py:class}`LinearProblem<dolfinx.fem.petsc.LinearProblem>` to solve the variational problem:\n",
"Find $u_h\\in V$ such that $a(u_h, v)==L(v) \\quad \\forall v \\in \\hat{V}$.\n",
"We will use {py:mod}`PETSc<petsc4py.PETSc>` as our linear algebra backend, using a direct solver (LU-factorization).\n",
"See the [PETSc-documentation](https://petsc.org/main/docs/manual/ksp/?highlight=ksp#ksp-linear-system-solvers) of the method for more information.\n",
"PETSc is not a required dependency of DOLFINx, and therefore we explicitly import the DOLFINx wrapper for interfacing with PETSc.\n",
"To ensure that the options passed to the {py:class}`LinearProblem<dolfinx.fem.petsc.LinearProblem>`\n",
"is only used for the given KSP solver, we pass a **unique** option prefix as well."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "20",
"metadata": {},
"outputs": [],
"source": [
"from dolfinx.fem.petsc import LinearProblem\n",
"\n",
"problem = LinearProblem(\n",
" a,\n",
" L,\n",
" bcs=[bc],\n",
" petsc_options={\"ksp_type\": \"preonly\", \"pc_type\": \"lu\"},\n",
" petsc_options_prefix=\"Poisson\",\n",
")\n",
"uh = problem.solve()"
]
},
{
"cell_type": "markdown",
"id": "c82e1ec7",
"metadata": {},
"source": [
"Using {py:meth}`problem.solve()<dolfinx.fem.petsc.LinearProblem.solve>` we solve the linear system of equations and\n",
"return a {py:class}`Function<dolfinx.fem.Function>` containing the solution.\n",
"\n",
"(error-norm)=\n",
"## Computing the error\n",
"Finally, we want to compute the error to check the accuracy of the solution.\n",
"We do this by comparing the finite element solution `u` with the exact solution.\n",
"First we interpolate the exact solution into a function space that contains it"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "21",
"metadata": {},
"outputs": [],
"source": [
"V2 = fem.functionspace(domain, (\"Lagrange\", 2))\n",
"uex = fem.Function(V2, name=\"u_exact\")\n",
"uex.interpolate(lambda x: 1 + x[0] ** 2 + 2 * x[1] ** 2)"
]
},
{
"cell_type": "markdown",
"id": "22",
"metadata": {},
"source": [
"We compute the error in two different ways.\n",
"First, we compute the $L^2$-norm of the error, defined by $E=\\sqrt{\\int_\\Omega (u_D-u_h)^2\\mathrm{d} x}$.\n",
"We use UFL to express the $L^2$-error, and use {py:func}`dolfinx.fem.assemble_scalar` to compute the scalar value.\n",
"In DOLFINx, {py:func}`assemble_scalar<dolfinx.fem.assemble_scalar>`\n",
"only assembles over the cells on the local process.\n",
"This means that if we use 2 processes to solve our problem,\n",
"we need to accumulate the local contributions to get the global error (on one or all processes).\n",
"We can do this with the {py:meth}`Comm.allreduce<mpi4py.MPI.Comm.allreduce>` function."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "25",
"metadata": {},
"outputs": [],
"source": [
"L2_error = fem.form(ufl.inner(uh - uex, uh - uex) * ufl.dx)\n",
"error_local = fem.assemble_scalar(L2_error)\n",
"error_L2 = numpy.sqrt(domain.comm.allreduce(error_local, op=MPI.SUM))"
]
},
{
"cell_type": "markdown",
"id": "26",
"metadata": {},
"source": [
"Secondly, we compute the maximum error at any degree of freedom.\n",
"As the finite element function $u$ can be expressed as a linear combination of basis functions $\\phi_j$,\n",
"spanning the space $V$: $ u = \\sum_{j=1}^N U_j\\phi_j.$\n",
"By writing {py:meth}`problem.solve()<dolfinx.fem.petsc.LinearProblem.sovle>`\n",
"we compute all the coefficients $U_1,\\dots, U_N$.\n",
"These values are known as the _degrees of freedom_ (dofs).\n",
"We can access the degrees of freedom by accessing the underlying vector in `uh`.\n",
"However, as a second order function space has more dofs than a linear function space,\n",
"we cannot compare these arrays directly.\n",
"As we already have interpolated the exact solution into the first order space when creating the boundary condition,\n",
"we can compare the maximum values at any degree of freedom of the approximation space."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "27",
"metadata": {},
"outputs": [],
"source": [
"error_max = numpy.max(numpy.abs(uD.x.array - uh.x.array))\n",
"if domain.comm.rank == 0: # Only print the error on one process\n",
" print(f\"Error_L2 : {error_L2:.2e}\")\n",
" print(f\"Error_max : {error_max:.2e}\")"
]
},
{
"cell_type": "markdown",
"id": "28",
"metadata": {},
"source": [
"## Plotting the mesh using pyvista\n",
"We will visualizing the mesh using [pyvista](https://docs.pyvista.org/), an interface to the VTK toolkit.\n",
"We start by converting the mesh to a format that can be used with {py:mod}`pyvista`.\n",
"To do this we use the function {py:func}`dolfinx.plot.vtk_mesh`.\n",
"It creates the data required to create a {py:class}`pyvista.UnstructuredGrid`.\n",
"You can print the current backend and change it with {py:func}`pyvista.set_jupyter_backend`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "29",
"metadata": {},
"outputs": [],
"source": [
"import pyvista\n",
"\n",
"print(pyvista.global_theme.jupyter_backend)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "30",
"metadata": {},
"outputs": [],
"source": [
"from dolfinx import plot\n",
"\n",
"domain.topology.create_connectivity(tdim, tdim)\n",
"topology, cell_types, geometry = plot.vtk_mesh(domain, tdim)\n",
"grid = pyvista.UnstructuredGrid(topology, cell_types, geometry)"
]
},
{
"cell_type": "markdown",
"id": "31",
"metadata": {},
"source": [
"There are several backends that can be used with pyvista, and they have different benefits and drawbacks.\n",
"See the [pyvista documentation](https://docs.pyvista.org/user-guide/jupyter/index.html#state-of-3d-interactive-jupyterlab-plotting)\n",
"for more information and installation details."
]
},
{
"cell_type": "markdown",
"id": "32",
"metadata": {},
"source": [
"We can now use the {py:class}`pyvista.Plotter` to visualize the mesh. We visualize it by showing it in 2D and warped in 3D.\n",
"In the jupyter notebook environment, we use the default setting of `pyvista.OFF_SCREEN=False`,\n",
"which will render plots directly in the notebook."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "33",
"metadata": {},
"outputs": [],
"source": [
"plotter = pyvista.Plotter()\n",
"plotter.add_mesh(grid, show_edges=True)\n",
"plotter.view_xy()\n",
"if not pyvista.OFF_SCREEN:\n",
" plotter.show()\n",
"else:\n",
" figure = plotter.screenshot(\"fundamentals_mesh.png\")"
]
},
{
"cell_type": "markdown",
"id": "34",
"metadata": {},
"source": [
"## Plotting a function using pyvista\n",
"We want to plot the solution `uh`.\n",
"As the function space used to defined the mesh is decoupled from the representation of the mesh,\n",
"we create a mesh based on the dof coordinates for the function space `V`.\n",
"We use {py:func}`dolfinx.plot.vtk_mesh` with the function space as input to create a mesh with\n",
"mesh geometry based on the dof coordinates."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "35",
"metadata": {},
"outputs": [],
"source": [
"u_topology, u_cell_types, u_geometry = plot.vtk_mesh(V)"
]
},
{
"cell_type": "markdown",
"id": "36",
"metadata": {},
"source": [
"Next, we create the {py:class}`pyvista.UnstructuredGrid` and add the dof-values to the mesh."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "37",
"metadata": {},
"outputs": [],
"source": [
"u_grid = pyvista.UnstructuredGrid(u_topology, u_cell_types, u_geometry)\n",
"u_grid.point_data[\"u\"] = uh.x.array.real\n",
"u_grid.set_active_scalars(\"u\")\n",
"u_plotter = pyvista.Plotter()\n",
"u_plotter.add_mesh(u_grid, show_edges=True)\n",
"u_plotter.view_xy()\n",
"if not pyvista.OFF_SCREEN:\n",
" u_plotter.show()"
]
},
{
"cell_type": "markdown",
"id": "38",
"metadata": {},
"source": [
"We can also warp the mesh by scalar to make use of the 3D plotting."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "39",
"metadata": {},
"outputs": [],
"source": [
"warped = u_grid.warp_by_scalar()\n",
"plotter2 = pyvista.Plotter()\n",
"plotter2.add_mesh(warped, show_edges=True, show_scalar_bar=True)\n",
"if not pyvista.OFF_SCREEN:\n",
" plotter2.show()"
]
},
{
"cell_type": "markdown",
"id": "40",
"metadata": {},
"source": [
"## External post-processing\n",
"For post-processing outside the python code, it is suggested to save the solution to file using either\n",
"{py:class}`dolfinx.io.VTXWriter` or {py:class}`dolfinx.io.XDMFFile` and using [Paraview](https://www.paraview.org/).\n",
"This is especially suggested for 3D visualization."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "41",
"metadata": {},
"outputs": [],
"source": [
"from dolfinx import io\n",
"from pathlib import Path\n",
"\n",
"results_folder = Path(\"results\")\n",
"results_folder.mkdir(exist_ok=True, parents=True)\n",
"filename = results_folder / \"fundamentals\"\n",
"with io.VTXWriter(domain.comm, filename.with_suffix(\".bp\"), [uh]) as vtx:\n",
" vtx.write(0.0)\n",
"with io.XDMFFile(domain.comm, filename.with_suffix(\".xdmf\"), \"w\") as xdmf:\n",
" xdmf.write_mesh(domain)\n",
" xdmf.write_function(uh)"
]
},
{
"cell_type": "markdown",
"id": "42",
"metadata": {},
"source": [
"```{bibliography}\n",
" :filter: cited\n",
" :labelprefix:\n",
" :keyprefix: fundamentals-\n",
"```"
]
}
],
"metadata": {
"jupytext": {
"formats": "ipynb,py:light"
},
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
},
"vscode": {
"interpreter": {
"hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
}
}
},
"nbformat": 4,
"nbformat_minor": 5
}
================================================
FILE: chapter1/fundamentals_code.py
================================================
# ---
# jupyter:
# jupytext:
# formats: ipynb,py:light
# text_representation:
# extension: .py
# format_name: light
# format_version: '1.5'
# jupytext_version: 1.19.1
# kernelspec:
# display_name: Python 3 (ipykernel)
# language: python
# name: python3
# ---
# # Implementation
#
# Author: Jørgen Schartum Dokken
#
# This implementation is an adaptation of the work in {cite}`fundamentals-FenicsTutorial` to DOLFINx.
#
# In this section, you will learn:
# - How to use the built-in meshes in DOLFINx
# - How to create a spatially varying Dirichlet boundary conditions on the whole domain boundary
# - How to define a weak formulation of your PDE
# - How to solve the resulting system of linear equations
# - How to visualize the solution using a variety of tools
# - How to compute the $L^2(\Omega)$ error and the error at mesh vertices
#
# ## Interactive tutorials
# ```{admonition} Run the tutorial as Jupyter notebook in browser
# As this book has been published as a Jupyter Book, each code can be run in your browser as a Jupyter notebook.
# To start such a notebook click the rocket symbol in the top right corner of the relevant tutorial.
# ```
#
# The Poisson problem has so far featured a general domain $\Omega$ and general functions $u_D$ for
# the boundary conditions and $f$ for the right hand side.
# Therefore, we need to make specific choices of $\Omega, u_D$ and $f$.
# A wise choice is to construct a problem with a known analytical solution,
# so that we can check that the computed solution is correct.
# The primary candidates are lower-order polynomials.
# The continuous Galerkin finite element spaces of degree $r$ will exactly reproduce polynomials of degree $r$.
# <!-- Particularly, piecewise linear continuous Galerkin finite elements are able to exactly reproduce a quadratic polynomial on
# a uniformly partitioned mesh. -->
# We use this fact to construct a quadratic function in $2D$. In particular we choose
#
# $$
# \begin{align}
# u_e(x,y)=1+x^2+2y^2
# \end{align}
# $$
#
# Inserting $u_e$ in the original boundary problem, we find that
#
# $$
# \begin{align}
# f(x,y)= -6,\qquad u_D(x,y)=u_e(x,y)=1+x^2+2y^2,
# \end{align}
# $$
#
# regardless of the shape of the domain as long as we prescribe
# $u_e$ on the boundary.
#
# For simplicity, we choose the domain to be a unit square $\Omega=[0,1]\times [0,1]$
#
# This simple but very powerful method for constructing test problems is called _the method of manufactured solutions_.
# First pick a simple expression for the exact solution, plug into
# the equation to obtain the right-hand side (source term $f$).
# Then solve the equation with this right hand side, and using the exact solution as boundary condition.
# Finally, we create a program that tries to reproduce the exact solution.
#
# Note that in many cases, it can be hard to determine if the program works if it produces an error of size
# $10^{-5}$ on a $20 \times 20$ grid.
# However, since we are using Sobolev spaces, we usually know about the numerical errors _asymptotic properties_.
# For instance that it is proportional to $h^2$ if $h$ is the size of a cell in the mesh.
# We can then compare the error on meshes with different $h$-values to see if the asymptotic behavior is correct.
# This technique will be explained in detail in the chapter [Improving your fenics code](./../chapter4/convergence).
#
# However, in cases where we have a solution we know that should have no approximation error,
# we know that the solution should be produced to machine precision by the program.
# A major difference between a traditional FEniCS code and a FEniCSx code,
# is that one is not advised to use the wildcard import.
# We will see this throughout this first example.
#
# ## Generating simple meshes
# The next step is to define the discrete domain, _the mesh_.
# We do this by importing one of the built-in mesh generators.
# We will build a {py:func}`unit square mesh<dolfinx.mesh.create_unit_square>`, i.e. a mesh spanning $[0,1]\times[0,1]$.
# It can consist of either triangles or quadrilaterals.
# +
from mpi4py import MPI
from dolfinx import mesh
import numpy
domain = mesh.create_unit_square(MPI.COMM_WORLD, 8, 8, mesh.CellType.quadrilateral)
# -
# Note that in addition to give how many elements we would like to have in each direction,
# we also have to supply the _MPI-communicator_.
# This is to specify how we would like the program to behave in parallel.
# If we supply {py:data}`MPI.COMM_WORLD<mpi4py.MPI.COMM_WORLD>` we create a single mesh,
# whose data is distributed over the number of processors we would like to use.
# We can for instance run the program in parallel on two processors by using `mpirun`, as:
# ``` bash
# mpirun -n 2 python3 t1.py
# ```
# However, if we would like to create a separate mesh on each processor,
# we can use {py:data}`MPI.COMM_SELF<mpi4py.MPI.COMM_SELF>`.
# This is for instance useful if we run a small problem, and would like to run it with multiple parameters.
#
# ## Defining the finite element function space
# Once the mesh has been created, we can create the finite element function space $V$.
# The finite element function space does not need to be the same as the one used to describe the mesh.
# DOLFINx supports a wide range of arbitrary order finite element function spaces, see:
# [Supported elements in DOLFINx](https://defelement.org/lists/implementations/basix.ufl.html)
# for an extensive list.
# To create a function space, we need to specify what mesh the space is defined on,
# what element famil the space is based on, and the degree of the element.
# These can for instance be defned through a tuple `("family", degree)`, as shown below
# +
from dolfinx import fem
V = fem.functionspace(domain, ("Lagrange", 1))
# -
# Further details about specification/customization of this tuple, see {py:class}`dolfinx.fem.ElementMetaData`.
# ## Dirichlet boundary conditions
# Next, we create a function that will hold the Dirichlet boundary data, and use interpolation to
# fill it with the appropriate data.
uD = fem.Function(V)
uD.interpolate(lambda x: 1 + x[0] ** 2 + 2 * x[1] ** 2)
# We now have the boundary data (and in this case the solution of the finite element problem)
# represented in the discrete function space.
# Next we would like to apply the boundary values to all degrees of freedom that are on the
# boundary of the discrete domain.
# We start by identifying the facets (line-segments) representing the outer boundary,
# using {py:func}`dolfinx.mesh.exterior_facet_indices`.
# We start by creating the facet to cell connectivity required to determine boundary facets by
# calling {py:meth}`dolfinx.mesh.Topology.create_connectivity`.
tdim = domain.topology.dim
fdim = tdim - 1
domain.topology.create_connectivity(fdim, tdim)
boundary_facets = mesh.exterior_facet_indices(domain.topology)
# For the current problem, as we are using the first order Lagrange function space,
# the degrees of freedom are located at the vertices of each cell, thus each facet contains two degrees of freedom.
#
# To find the local indices of these degrees of freedom, we use {py:func}`dolfinx.fem.locate_dofs_topological`
# which takes in the function space, the dimension of entities in the mesh we would like to identify and the local entities.
# ```{admonition} Local ordering of degrees of freedom and mesh vertices
# Many people expect there to be a 1-1 correspondence between the mesh coordinates and the coordinates of the degrees of freedom.
# However, this is only true in the case of `Lagrange` 1 elements on a first order mesh.
# Therefore, in DOLFINx we use separate local numbering for the mesh coordinates and the dof coordinates.
# To obtain the local dof coordinates we can use
# {py:meth}`V.tabulate_dof_coordinates()<dolfinx.fem.FunctionSpace.tabulate_dof_coordinates>`,
# while the ordering of the local vertices can be obtained by {py:attr}`mesh.geometry.x<dolfinx.mesh.Geometry.x>`.
# ```
# With this data at hand, we can create the Dirichlet boundary condition
boundary_dofs = fem.locate_dofs_topological(V, fdim, boundary_facets)
bc = fem.dirichletbc(uD, boundary_dofs)
# ## Defining the trial and test function
#
# In mathematics, we distinguish between trial and test spaces $V$ and $\hat{V}$.
# The only difference in the present problem is the boundary conditions.
# In FEniCSx, we do not specify boundary conditions as part of the function space,
# so it is sufficient to use a common space for the trial and test function.
#
# We use the {py:mod}`Unified Form Language<ufl>` (UFL) to specify the variational formulations.
# See {cite}`fundamentals-ufl2014` for more details.
# +
import ufl
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
# -
# ## Defining the source term
# As the source term is constant over the domain, we use {py:class}`dolfinx.fem.Constant`
# +
from dolfinx import default_scalar_type
f = fem.Constant(domain, default_scalar_type(-6))
# -
# ```{admonition} Compilation speed-up
# Instead of wrapping $-6$ in a {py:class}`dolfinx.fem.Constant`, we could simply define $f$ as `f=-6`.
# However, if we would like to change this parameter later in the simulation,
# we would have to redefine our variational formulation.
# The {py:attr}`dolfinx.fem.Constant.value` allows us to update the value in $f$ by using `f.value=5`.
# Additionally, by indicating that $f$ is a constant, we speed up compilation of the variational
# formulations required for the created linear system.
# ```
#
# ## Defining the variational problem
# As we now have defined all variables used to describe our variational problem, we can create the weak formulation
a = ufl.dot(ufl.grad(u), ufl.grad(v)) * ufl.dx
L = f * v * ufl.dx
# Note that there is a very close correspondence between the Python syntax and the mathematical syntax
# $\int_{\Omega} \nabla u \cdot \nabla v ~\mathrm{d} x$ and $\int_{\Omega}fv~\mathrm{d} x$.
# The integration over the domain $\Omega$ is defined by using {py:func}`ufl.dx`, an integration
# {py:class}`measure<ufl.Measure>` over all cells of the mesh.
#
# This is the key strength of FEniCSx:
# the formulas in the variational formulation translate directly to very similar Python code,
# a feature that makes it easy to specify and solve complicated PDE problems.
#
# ## Expressing inner products
# The inner product $\int_\Omega \nabla u \cdot \nabla v ~\mathrm{d} x$ can be expressed in various ways in UFL.
# We have used the notation `ufl.dot(ufl.grad(u), ufl.grad(v))*ufl.dx`.
# The {py:func}`dot<ufl.dot>` product in UFL computes the sum (contraction) over the last index
# of the first factor and first index of the second factor.
# In this case, both factors are tensors of rank one (vectors) and so the sum is just over
# the single index of both $\nabla u$ and $\nabla v$.
# To compute an inner product of matrices (with two indices),
# one must use the function {py:func}`ufl.inner` instead of {py:func}`ufl.dot`.
# For real-valued vectors, {py:func}`ufl.dot` and {py:func}`ufl.inner` are equivalent.
#
# ```{admonition} Complex numbers
# In DOLFINx, one can solve complex number problems by using an installation of PETSc using complex numbers.
# For variational formulations with complex numbers, one cannot use {py:func}`ufl.dot` to compute inner products.
# One has to use {py:func}`ufl.inner`, with the test-function as the second input argument for {py:func}`ufl.inner`.
# See [Running DOLFINx in complex mode](./complex_mode) for more information.
# ```
#
#
# ## Forming and solving the linear system
#
# Having defined the finite element variational problem and boundary condition,
# we can create our {py:class}`LinearProblem<dolfinx.fem.petsc.LinearProblem>` to solve the variational problem:
# Find $u_h\in V$ such that $a(u_h, v)==L(v) \quad \forall v \in \hat{V}$.
# We will use {py:mod}`PETSc<petsc4py.PETSc>` as our linear algebra backend, using a direct solver (LU-factorization).
# See the [PETSc-documentation](https://petsc.org/main/docs/manual/ksp/?highlight=ksp#ksp-linear-system-solvers) of the method for more information.
# PETSc is not a required dependency of DOLFINx, and therefore we explicitly import the DOLFINx wrapper for interfacing with PETSc.
# To ensure that the options passed to the {py:class}`LinearProblem<dolfinx.fem.petsc.LinearProblem>`
# is only used for the given KSP solver, we pass a **unique** option prefix as well.
# +
from dolfinx.fem.petsc import LinearProblem
problem = LinearProblem(
a,
L,
bcs=[bc],
petsc_options={"ksp_type": "preonly", "pc_type": "lu"},
petsc_options_prefix="Poisson",
)
uh = problem.solve()
# -
# Using {py:meth}`problem.solve()<dolfinx.fem.petsc.LinearProblem.solve>` we solve the linear system of equations and
# return a {py:class}`Function<dolfinx.fem.Function>` containing the solution.
#
# (error-norm)=
# ## Computing the error
# Finally, we want to compute the error to check the accuracy of the solution.
# We do this by comparing the finite element solution `u` with the exact solution.
# First we interpolate the exact solution into a function space that contains it
V2 = fem.functionspace(domain, ("Lagrange", 2))
uex = fem.Function(V2, name="u_exact")
uex.interpolate(lambda x: 1 + x[0] ** 2 + 2 * x[1] ** 2)
# We compute the error in two different ways.
# First, we compute the $L^2$-norm of the error, defined by $E=\sqrt{\int_\Omega (u_D-u_h)^2\mathrm{d} x}$.
# We use UFL to express the $L^2$-error, and use {py:func}`dolfinx.fem.assemble_scalar` to compute the scalar value.
# In DOLFINx, {py:func}`assemble_scalar<dolfinx.fem.assemble_scalar>`
# only assembles over the cells on the local process.
# This means that if we use 2 processes to solve our problem,
# we need to accumulate the local contributions to get the global error (on one or all processes).
# We can do this with the {py:meth}`Comm.allreduce<mpi4py.MPI.Comm.allreduce>` function.
L2_error = fem.form(ufl.inner(uh - uex, uh - uex) * ufl.dx)
error_local = fem.assemble_scalar(L2_error)
error_L2 = numpy.sqrt(domain.comm.allreduce(error_local, op=MPI.SUM))
# Secondly, we compute the maximum error at any degree of freedom.
# As the finite element function $u$ can be expressed as a linear combination of basis functions $\phi_j$,
# spanning the space $V$: $ u = \sum_{j=1}^N U_j\phi_j.$
# By writing {py:meth}`problem.solve()<dolfinx.fem.petsc.LinearProblem.sovle>`
# we compute all the coefficients $U_1,\dots, U_N$.
# These values are known as the _degrees of freedom_ (dofs).
# We can access the degrees of freedom by accessing the underlying vector in `uh`.
# However, as a second order function space has more dofs than a linear function space,
# we cannot compare these arrays directly.
# As we already have interpolated the exact solution into the first order space when creating the boundary condition,
# we can compare the maximum values at any degree of freedom of the approximation space.
error_max = numpy.max(numpy.abs(uD.x.array - uh.x.array))
if domain.comm.rank == 0: # Only print the error on one process
print(f"Error_L2 : {error_L2:.2e}")
print(f"Error_max : {error_max:.2e}")
# ## Plotting the mesh using pyvista
# We will visualizing the mesh using [pyvista](https://docs.pyvista.org/), an interface to the VTK toolkit.
# We start by converting the mesh to a format that can be used with {py:mod}`pyvista`.
# To do this we use the function {py:func}`dolfinx.plot.vtk_mesh`.
# It creates the data required to create a {py:class}`pyvista.UnstructuredGrid`.
# You can print the current backend and change it with {py:func}`pyvista.set_jupyter_backend`.
# +
import pyvista
print(pyvista.global_theme.jupyter_backend)
# +
from dolfinx import plot
domain.topology.create_connectivity(tdim, tdim)
topology, cell_types, geometry = plot.vtk_mesh(domain, tdim)
grid = pyvista.UnstructuredGrid(topology, cell_types, geometry)
# -
# There are several backends that can be used with pyvista, and they have different benefits and drawbacks.
# See the [pyvista documentation](https://docs.pyvista.org/user-guide/jupyter/index.html#state-of-3d-interactive-jupyterlab-plotting)
# for more information and installation details.
# We can now use the {py:class}`pyvista.Plotter` to visualize the mesh. We visualize it by showing it in 2D and warped in 3D.
# In the jupyter notebook environment, we use the default setting of `pyvista.OFF_SCREEN=False`,
# which will render plots directly in the notebook.
plotter = pyvista.Plotter()
plotter.add_mesh(grid, show_edges=True)
plotter.view_xy()
if not pyvista.OFF_SCREEN:
plotter.show()
else:
figure = plotter.screenshot("fundamentals_mesh.png")
# ## Plotting a function using pyvista
# We want to plot the solution `uh`.
# As the function space used to defined the mesh is decoupled from the representation of the mesh,
# we create a mesh based on the dof coordinates for the function space `V`.
# We use {py:func}`dolfinx.plot.vtk_mesh` with the function space as input to create a mesh with
# mesh geometry based on the dof coordinates.
u_topology, u_cell_types, u_geometry = plot.vtk_mesh(V)
# Next, we create the {py:class}`pyvista.UnstructuredGrid` and add the dof-values to the mesh.
u_grid = pyvista.UnstructuredGrid(u_topology, u_cell_types, u_geometry)
u_grid.point_data["u"] = uh.x.array.real
u_grid.set_active_scalars("u")
u_plotter = pyvista.Plotter()
u_plotter.add_mesh(u_grid, show_edges=True)
u_plotter.view_xy()
if not pyvista.OFF_SCREEN:
u_plotter.show()
# We can also warp the mesh by scalar to make use of the 3D plotting.
warped = u_grid.warp_by_scalar()
plotter2 = pyvista.Plotter()
plotter2.add_mesh(warped, show_edges=True, show_scalar_bar=True)
if not pyvista.OFF_SCREEN:
plotter2.show()
# ## External post-processing
# For post-processing outside the python code, it is suggested to save the solution to file using either
# {py:class}`dolfinx.io.VTXWriter` or {py:class}`dolfinx.io.XDMFFile` and using [Paraview](https://www.paraview.org/).
# This is especially suggested for 3D visualization.
# +
from dolfinx import io
from pathlib import Path
results_folder = Path("results")
results_folder.mkdir(exist_ok=True, parents=True)
filename = results_folder / "fundamentals"
with io.VTXWriter(domain.comm, filename.with_suffix(".bp"), [uh]) as vtx:
vtx.write(0.0)
with io.XDMFFile(domain.comm, filename.with_suffix(".xdmf"), "w") as xdmf:
xdmf.write_mesh(domain)
xdmf.write_function(uh)
# -
# ```{bibliography}
# :filter: cited
# :labelprefix:
# :keyprefix: fundamentals-
# ```
================================================
FILE: chapter1/membrane.md
================================================
# Deflection of a membrane
Authors: Hans Petter Langtangen and Anders Logg.
Modified for DOLFINx by Jørgen S. Dokken
In the first FEniCSx program, we solved a simple problem which we could easily use to verify the implementation.
In this section, we will turn our attentition to a physically more relevant problem with solutions of a somewhat more exciting shape.
We would like to compute the deflection $D(x,y)$ of a two-dimensional, circular membrane of radius $R$, subject to a load $p$ over the membrane. The appropriate PDE model is
\begin{align}
-T \nabla^2D&=p \quad\text{in }\quad \Omega=\{(x,y)\vert x^2+y^2\leq R^2 \}.
\end{align}
Here, $T$ is the tension in the membrane (constant), and $p$ is the external pressure load. The boundary of the membrane has no deflection. This implies that $D=0$ is the boundary condition. We model a localized load as a Gaussian function:
\begin{align}
p(x,y)&=\frac{A}{2\pi\sigma}e^{-\frac{1}{2}\left(\frac{x-x_0}{\sigma}\right)^2-\frac{1}{2}\left(\frac{y-y_0}{\sigma}\right)^2}.
\end{align}
The parameter $A$ is the amplitude of the pressure, $(x_0, y_0)$ the location of the maximum point of the load, and $\sigma$ the "width" of $p$. We will take the center $(x_0,y_0)$ to be $(0,R_0)$ for some $0<R_0<R$.
Then we have
\begin{align}
p(x,y)&=\frac{A}{2\pi\sigma}e^{-\frac{1}{2}\left(\left(\frac{x}{\sigma}\right)^2
+\left(\frac{y-R_0}{\sigma}\right)^2\right)}.
\end{align}
## Scaling the equation
There are many physical parameters in this problem, and we can benefit from grouping them by means of scaling. Let us introduce dimensionless coordinates
$\bar{x}=\frac{x}{R}$, $\bar{y}=\frac{y}{R}$, and a dimensionless deflection $w=\frac{D}{D_e}$, where $D_e$ is a characteristic size of the deflection. Introducing $\bar{R}_0=\frac{R_0}{R}$, we obtain
\begin{align}
-\frac{\partial^2 w}{\partial \bar{x}^2} -\frac{\partial^2 w}{\partial \bar{y}^2}
&=\frac{R^2A}{2\pi\sigma TD_e}e^{-\frac{R^2}{2\sigma^2}\left(\bar{x}^2+(\bar{y}-\bar{R}_0)^2\right)}\\
&=\alpha e^{-\beta^2(\bar{x}^2+(\bar{y}-\bar{R}_0)^2}
\end{align}
for $\bar{x}^2+\bar{y}^2<1$ where $\alpha = \frac{R^2A}{2\pi\sigma TD_e}$ and $\beta=\frac{R}{\sqrt{2}\sigma}$.
With an appropriate scaling, $w$ and its derivatives are of size unity, so the left-hand side of the scaled PDE is about unity in size, while the right hand side has $\alpha$ as its characteristic size. This suggests choosing alpha to be unity, or around unity. In this particular case, we choose $\alpha=4$. (One can also find the analytical solution in scaled coordinates and show that the maximum deflection $D(0,0)$ is $D_e$ if we choose $\alpha=4$ to determine $D_e$.)
With $D_e=\frac{AR^2}{8\pi\sigma T}$ and dropping the bars we obtain the scaled problem
\begin{align}
-\nabla^2 w = 4e^{-\beta^2(x^2+(y-R_0)^2)}
\end{align}
to be solved over the unit disc with $w=0$ on the boundary.
Now there are only two parameters which vary the dimensionless extent of the pressure, $\beta$, and the location of the pressure peak, $R_0\in[0,1]$. As $\beta\to 0$, the solution will approach the special case $w=1-x^2-y^2$.
Given a computed scaled solution $w$, the physical deflection can be computed by
\begin{align}
D=\frac{AR^2}{8\pi\sigma T}w.
\end{align}
================================================
FILE: chapter1/membrane_code.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"id": "0",
"metadata": {},
"source": [
"# Implementation\n",
"Author: Jørgen S. Dokken\n",
"\n",
"In this section, we will solve the deflection of the membrane problem.\n",
"After finishing this section, you should be able to:\n",
"- Create a simple mesh using the GMSH Python API and load it into DOLFINx\n",
"- Create constant boundary conditions using a {py:func}`geometrical identifier<dolfinx.fem.locate_dofs_geometrical>`\n",
"- Use {py:class}`ufl.SpatialCoordinate` to create a spatially varying function\n",
"- Interpolate a {py:class}`ufl-Expression<ufl.core.expr.Expr>` into an appropriate function space\n",
"- Evaluate a {py:class}`dolfinx.fem.Function` at any point $x$\n",
"- Use Paraview to visualize the solution of a PDE\n",
"\n",
"## Creating the mesh\n",
"\n",
"To create the computational geometry, we use the Python-API of [GMSH](https://gmsh.info/).\n",
"We start by importing the gmsh-module and initializing it."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1",
"metadata": {},
"outputs": [],
"source": [
"import gmsh\n",
"\n",
"gmsh.initialize()"
]
},
{
"cell_type": "markdown",
"id": "2",
"metadata": {},
"source": [
"The next step is to create the membrane and start the computations by the GMSH CAD kernel,\n",
"to generate the relevant underlying data structures.\n",
"The first arguments of `addDisk` are the x, y and z coordinate of the center of the circle,\n",
"while the two last arguments are the x-radius and y-radius."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3",
"metadata": {},
"outputs": [],
"source": [
"membrane = gmsh.model.occ.addDisk(0, 0, 0, 1, 1)\n",
"gmsh.model.occ.synchronize()"
]
},
{
"cell_type": "markdown",
"id": "4",
"metadata": {},
"source": [
"After that, we make the membrane a physical surface, such that it is recognized by `gmsh` when generating the mesh.\n",
"As a surface is a two-dimensional entity, we add `2` as the first argument,\n",
"the entity tag of the membrane as the second argument, and the physical tag as the last argument.\n",
"In a later demo, we will get into when this tag matters."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5",
"metadata": {},
"outputs": [],
"source": [
"gdim = 2\n",
"gmsh.model.addPhysicalGroup(gdim, [membrane], 1)"
]
},
{
"cell_type": "markdown",
"id": "6",
"metadata": {},
"source": [
"Finally, we generate the two-dimensional mesh.\n",
"We set a uniform mesh size by modifying the GMSH options."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7",
"metadata": {},
"outputs": [],
"source": [
"gmsh.option.setNumber(\"Mesh.CharacteristicLengthMin\", 0.05)\n",
"gmsh.option.setNumber(\"Mesh.CharacteristicLengthMax\", 0.05)\n",
"gmsh.model.mesh.generate(gdim)"
]
},
{
"cell_type": "markdown",
"id": "8",
"metadata": {},
"source": [
"# Interfacing with GMSH in DOLFINx\n",
"We will import the GMSH-mesh directly from GMSH into DOLFINx via the {py:mod}`dolfinx.io.gmsh` interface.\n",
"The {py:mod}`dolfinx.io.gmsh` module contains two functions\n",
"1. {py:func}`model_to_mesh<dolfinx.io.gmsh.model_to_mesh>` which takes in a `gmsh.model`\n",
" and returns a {py:class}`dolfinx.io.gmsh.MeshData` object.\n",
"2. {py:func}`read_from_msh<dolfinx.io.gmsh.read_from_msh>` which takes in a path to a `.msh`-file\n",
" and returns a {py:class}`dolfinx.io.gmsh.MeshData` object.\n",
"\n",
"The {py:class}`MeshData` object will contain a {py:class}`dolfinx.mesh.Mesh`,\n",
"under the attribute {py:attr}`mesh<dolfinx.io.gmsh.MeshData.mesh>`.\n",
"This mesh will contain all GMSH Physical Groups of the highest topological dimension.\n",
"```{note}\n",
"If you do not use `gmsh.model.addPhysicalGroup` when creating the mesh with GMSH, it can not be read into DOLFINx.\n",
"```\n",
"The {py:class}`MeshData<dolfinx.io.gmsh.MeshData>` object can also contain tags for\n",
"all other `PhysicalGroups` that has been added to the mesh, that being\n",
"{py:attr}`cell_tags<dolfinx.io.gmsh.MeshData.cell_tags>`, {py:attr}`facet_tags<dolfinx.io.gmsh.MeshData.facet_tags>`,\n",
"{py:attr}`ridge_tags<dolfinx.io.gmsh.MeshData.ridge_tags>` and\n",
"{py:attr}`peak_tags<dolfinx.io.gmsh.MeshData.peak_tags>`.\n",
"To read either `gmsh.model` or a `.msh`-file, one has to distribute the mesh to all processes used by DOLFINx.\n",
"As GMSH does not support mesh creation with MPI, we currently have a `gmsh.model.mesh` on each process.\n",
"To distribute the mesh, we have to specify which process the mesh was created on,\n",
"and which communicator rank should distribute the mesh.\n",
"The {py:func}`model_to_mesh<dolfinx.io.gmsh.model_to_mesh>` will then load the mesh on the specified rank,\n",
"and distribute it to the communicator using a mesh partitioner."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9",
"metadata": {},
"outputs": [],
"source": [
"from dolfinx.io import gmsh as gmshio\n",
"from dolfinx.fem.petsc import LinearProblem\n",
"from mpi4py import MPI\n",
"\n",
"gmsh_model_rank = 0\n",
"mesh_comm = MPI.COMM_WORLD\n",
"mesh_data = gmshio.model_to_mesh(gmsh.model, mesh_comm, gmsh_model_rank, gdim=gdim)\n",
"assert mesh_data.cell_tags is not None\n",
"cell_markers = mesh_data.cell_tags\n",
"domain = mesh_data.mesh"
]
},
{
"cell_type": "markdown",
"id": "10",
"metadata": {},
"source": [
"We define the function space as in the previous tutorial"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "11",
"metadata": {},
"outputs": [],
"source": [
"from dolfinx import fem\n",
"\n",
"V = fem.functionspace(domain, (\"Lagrange\", 1))"
]
},
{
"cell_type": "markdown",
"id": "12",
"metadata": {},
"source": [
"## Defining a spatially varying load\n",
"The right hand side pressure function is represented using {py:class}`ufl.SpatialCoordinate` and two constants,\n",
"one for $\\beta$ and one for $R_0$."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "13",
"metadata": {},
"outputs": [],
"source": [
"import ufl\n",
"from dolfinx import default_scalar_type\n",
"\n",
"x = ufl.SpatialCoordinate(domain)\n",
"beta = fem.Constant(domain, default_scalar_type(12))\n",
"R0 = fem.Constant(domain, default_scalar_type(0.3))\n",
"p = 4 * ufl.exp(-(beta**2) * (x[0] ** 2 + (x[1] - R0) ** 2))"
]
},
{
"cell_type": "markdown",
"id": "9f059bd4",
"metadata": {},
"source": [
"## Create a Dirichlet boundary condition using geometrical conditions\n",
"The next step is to create the homogeneous boundary condition.\n",
"As opposed to the [first tutorial](./fundamentals_code.ipynb) we will use\n",
"{py:func}`locate_dofs_geometrical<dolfinx.fem.locate_dofs_geometrical>` to locate the degrees of freedom on the boundary.\n",
"As we know that our domain is a circle with radius 1, we know that any degree of freedom should be\n",
"located at a coordinate $(x,y)$ such that $\\sqrt{x^2+y^2}=1$."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "16",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"\n",
"\n",
"def on_boundary(x):\n",
" return np.isclose(np.sqrt(x[0] ** 2 + x[1] ** 2), 1)\n",
"\n",
"\n",
"boundary_dofs = fem.locate_dofs_geometrical(V, on_boundary)"
]
},
{
"cell_type": "markdown",
"id": "17",
"metadata": {},
"source": [
"As our Dirichlet condition is homogeneous (`u=0` on the whole boundary), we can initialize the\n",
"{py:class}`dolfinx.fem.DirichletBC` with a constant value, the degrees of freedom and the function\n",
"space to apply the boundary condition on. We use the constructor {py:func}`dolfinx.fem.dirichletbc`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "18",
"metadata": {},
"outputs": [],
"source": [
"bc = fem.dirichletbc(default_scalar_type(0), boundary_dofs, V)"
]
},
{
"cell_type": "markdown",
"id": "19",
"metadata": {},
"source": [
"## Defining the variational problem\n",
"The variational problem is the same as in our first Poisson problem, where `f` is replaced by `p`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "20",
"metadata": {},
"outputs": [],
"source": [
"u = ufl.TrialFunction(V)\n",
"v = ufl.TestFunction(V)\n",
"a = ufl.dot(ufl.grad(u), ufl.grad(v)) * ufl.dx\n",
"L = p * v * ufl.dx\n",
"problem = LinearProblem(\n",
" a,\n",
" L,\n",
" bcs=[bc],\n",
" petsc_options={\"ksp_type\": \"preonly\", \"pc_type\": \"lu\"},\n",
" petsc_options_prefix=\"membrane_\",\n",
")\n",
"uh = problem.solve()"
]
},
{
"cell_type": "markdown",
"id": "21",
"metadata": {},
"source": [
"## Interpolation of a UFL-expression\n",
"As we previously defined the load `p` as a spatially varying function,\n",
"we would like to interpolate this function into an appropriate function space for visualization.\n",
"To do this we use the class {py:class}`Expression<dolfinx.fem.Expression>`.\n",
"The expression takes in any UFL-expression, and a set of points on the reference element.\n",
"We will use the {py:attr}`interpolation points<dolfinx.fem.FiniteElement.interpolation_points>`\n",
"of the space we want to interpolate in to.\n",
"We choose a high order function space to represent the function `p`, as it is rapidly varying in space."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "22",
"metadata": {},
"outputs": [],
"source": [
"Q = fem.functionspace(domain, (\"Lagrange\", 5))\n",
"expr = fem.Expression(p, Q.element.interpolation_points)\n",
"pressure = fem.Function(Q)\n",
"pressure.interpolate(expr)"
]
},
{
"cell_type": "markdown",
"id": "23",
"metadata": {},
"source": [
"## Plotting the solution over a line\n",
"We first plot the deflection $u_h$ over the domain $\\Omega$."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "24",
"metadata": {},
"outputs": [],
"source": [
"from dolfinx.plot import vtk_mesh\n",
"import pyvista"
]
},
{
"cell_type": "markdown",
"id": "55b28bcd",
"metadata": {},
"source": [
"Extract topology from mesh and create {py:class}`pyvista.UnstructuredGrid`"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "26",
"metadata": {},
"outputs": [],
"source": [
"topology, cell_types, x = vtk_mesh(V)\n",
"grid = pyvista.UnstructuredGrid(topology, cell_types, x)"
]
},
{
"cell_type": "markdown",
"id": "b2b183e2",
"metadata": {},
"source": [
"Set deflection values and add it to plotter"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "28",
"metadata": {},
"outputs": [],
"source": [
"grid.point_data[\"u\"] = uh.x.array\n",
"warped = grid.warp_by_scalar(\"u\", factor=25)\n",
"\n",
"plotter = pyvista.Plotter()\n",
"plotter.add_mesh(warped, show_edges=True, show_scalar_bar=True, scalars=\"u\")\n",
"if not pyvista.OFF_SCREEN:\n",
" plotter.show()\n",
"else:\n",
" plotter.screenshot(\"deflection.png\")"
]
},
{
"cell_type": "markdown",
"id": "29",
"metadata": {},
"source": [
"We next plot the load on the domain"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "30",
"metadata": {},
"outputs": [],
"source": [
"load_plotter = pyvista.Plotter()\n",
"p_grid = pyvista.UnstructuredGrid(*vtk_mesh(Q))\n",
"p_grid.point_data[\"p\"] = pressure.x.array.real\n",
"warped_p = p_grid.warp_by_scalar(\"p\", factor=0.5)\n",
"warped_p.set_active_scalars(\"p\")\n",
"load_plotter.add_mesh(warped_p, show_scalar_bar=True)\n",
"load_plotter.view_xy()\n",
"if not pyvista.OFF_SCREEN:\n",
" load_plotter.show()\n",
"else:\n",
" load_plotter.screenshot(\"load.png\")"
]
},
{
"cell_type": "markdown",
"id": "31",
"metadata": {},
"source": [
"## Making curve plots throughout the domain\n",
"Another way to compare the deflection and the load is to make a plot along the line $x=0$.\n",
"This is just a matter of defining a set of points along the $y$-axis and evaluating the\n",
"finite element functions $u$ and $p$ at these points."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "32",
"metadata": {},
"outputs": [],
"source": [
"tol = 0.001 # Avoid hitting the outside of the domain\n",
"y = np.linspace(-1 + tol, 1 - tol, 101)\n",
"points = np.zeros((3, 101))\n",
"points[1] = y\n",
"u_values = []\n",
"p_values = []"
]
},
{
"cell_type": "markdown",
"id": "33",
"metadata": {},
"source": [
"As a finite element function is the linear combination of all degrees of freedom,\n",
"$u_h(x)=\\sum_{i=1}^N c_i \\phi_i(x)$ where $c_i$ are the coefficients of $u_h$ and $\\phi_i$\n",
"is the $i$-th basis function, we can compute the exact solution at any point in $\\Omega$.\n",
"However, as a mesh consists of a large set of degrees of freedom (i.e. $N$ is large),\n",
"we want to reduce the number of evaluations of the basis function $\\phi_i(x)$.\n",
"We do this by identifying which cell of the mesh $x$ is in.\n",
"This is efficiently done by creating a {py:class}`bounding box tree<dolfinx.geometry.BoundingBoxTree`\n",
"of the cells of the mesh,\n",
"allowing a quick recursive search through the mesh entities."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "34",
"metadata": {},
"outputs": [],
"source": [
"from dolfinx import geometry\n",
"\n",
"bb_tree = geometry.bb_tree(domain, domain.topology.dim)"
]
},
{
"cell_type": "markdown",
"id": "35",
"metadata": {},
"source": [
"Now we can compute which cells the bounding box tree collides with using\n",
"{py:func}`dolfinx.geometry.compute_collisions_points`.\n",
"This function returns a list of cells whose bounding box collide for each input point.\n",
"As different points might have different number of cells, the data is stored in\n",
"{py:class}`dolfinx.graph.AdjacencyList`, where one can access the cells for the\n",
"`i`th point by calling {py:meth}`links(i)<dolfinx.graph.AdjacencyList.links>`.\n",
"However, as the bounding box of a cell spans more of $\\mathbb{R}^n$ than the actual cell,\n",
"we check that the actual cell collides with the input point using\n",
"{py:func}`dolfinx.geometry.compute_colliding_cells`,\n",
"which measures the exact distance between the point and the cell\n",
"(approximated as a convex hull for higher order geometries).\n",
"This function also returns an adjacency-list, as the point might align with a facet,\n",
"edge or vertex that is shared between multiple cells in the mesh.\n",
"\n",
"Finally, we would like the code below to run in parallel,\n",
"when the mesh is distributed over multiple processors.\n",
"In that case, it is not guaranteed that every point in `points` is on each processor.\n",
"Therefore we create a subset `points_on_proc` only containing the points found on the current processor."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "36",
"metadata": {},
"outputs": [],
"source": [
"cells = []\n",
"points_on_proc = []\n",
"# Find cells whose bounding-box collide with the the points\n",
"cell_candidates = geometry.compute_collisions_points(bb_tree, points.T)\n",
"# Choose one of the cells that contains the point\n",
"colliding_cells = geometry.compute_colliding_cells(domain, cell_candidates, points.T)\n",
"for i, point in enumerate(points.T):\n",
" if len(colliding_cells.links(i)) > 0:\n",
" points_on_proc.append(point)\n",
" cells.append(colliding_cells.links(i)[0])"
]
},
{
"cell_type": "markdown",
"id": "37",
"metadata": {},
"source": [
"We now have a list of points on the processor, on in which cell each point belongs.\n",
"We can then call {py:meth}`uh.eval<dolfinx.fem.Function.eval>` and\n",
"{py:meth}`pressure.eval<dolfinx.fem.Function.eval>` to obtain the set of values for all the points."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "38",
"metadata": {},
"outputs": [],
"source": [
"points_on_proc = np.array(points_on_proc, dtype=np.float64)\n",
"u_values = uh.eval(points_on_proc, cells)\n",
"p_values = pressure.eval(points_on_proc, cells)"
]
},
{
"cell_type": "markdown",
"id": "39",
"metadata": {},
"source": [
"As we now have an array of coordinates and two arrays of function values,\n",
"we can use {py:mod}`matplotlib<matplotlib.pyplot>` to plot them"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "40",
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"\n",
"fig = plt.figure()\n",
"plt.plot(\n",
" points_on_proc[:, 1],\n",
" 50 * u_values,\n",
" \"k\",\n",
" linewidth=2,\n",
" label=\"Deflection ($\\\\times 50$)\",\n",
")\n",
"plt.plot(points_on_proc[:, 1], p_values, \"b--\", linewidth=2, label=\"Load\")\n",
"plt.grid(True)\n",
"plt.xlabel(\"y\")\n",
"plt.legend()"
]
},
{
"cell_type": "markdown",
"id": "41",
"metadata": {},
"source": [
"If executed in parallel as a Python file, we save a plot per processor"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "42",
"metadata": {},
"outputs": [],
"source": [
"plt.savefig(f\"membrane_rank{MPI.COMM_WORLD.rank:d}.png\")"
]
},
{
"cell_type": "markdown",
"id": "43",
"metadata": {},
"source": [
"## Saving functions to file\n",
"As mentioned in the previous section, we can also use Paraview to visualize the solution."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "44",
"metadata": {},
"outputs": [],
"source": [
"import dolfinx.io\n",
"from pathlib import Path\n",
"\n",
"pressure.name = \"Load\"\n",
"uh.name = \"Deflection\"\n",
"results_folder = Path(\"results\")\n",
"results_folder.mkdir(exist_ok=True, parents=True)\n",
"with dolfinx.io.VTXWriter(\n",
" MPI.COMM_WORLD, results_folder / \"membrane_pressure.bp\", [pressure], engine=\"BP4\"\n",
") as vtx:\n",
" vtx.write(0.0)\n",
"with dolfinx.io.VTXWriter(\n",
" MPI.COMM_WORLD, results_folder / \"membrane_deflection.bp\", [uh], engine=\"BP4\"\n",
") as vtx:\n",
" vtx.write(0.0)"
]
}
],
"metadata": {
"jupytext": {
"formats": "ipynb,py:light"
},
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
================================================
FILE: chapter1/membrane_code.py
================================================
# ---
# jupyter:
# jupytext:
# formats: ipynb,py:light
# text_representation:
# extension: .py
# format_name: light
# format_version: '1.5'
# jupytext_version: 1.18.1
# kernelspec:
# display_name: Python 3 (ipykernel)
# language: python
# name: python3
# ---
# # Implementation
# Author: Jørgen S. Dokken
#
# In this section, we will solve the deflection of the membrane problem.
# After finishing this section, you should be able to:
# - Create a simple mesh using the GMSH Python API and load it into DOLFINx
# - Create constant boundary conditions using a {py:func}`geometrical identifier<dolfinx.fem.locate_dofs_geometrical>`
# - Use {py:class}`ufl.SpatialCoordinate` to create a spatially varying function
# - Interpolate a {py:class}`ufl-Expression<ufl.core.expr.Expr>` into an appropriate function space
# - Evaluate a {py:class}`dolfinx.fem.Function` at any point $x$
# - Use Paraview to visualize the solution of a PDE
#
# ## Creating the mesh
#
# To create the computational geometry, we use the Python-API of [GMSH](https://gmsh.info/).
# We start by importing the gmsh-module and initializing it.
# +
import gmsh
gmsh.initialize()
# -
# The next step is to create the membrane and start the computations by the GMSH CAD kernel,
# to generate the relevant underlying data structures.
# The first arguments of `addDisk` are the x, y and z coordinate of the center of the circle,
# while the two last arguments are the x-radius and y-radius.
membrane = gmsh.model.occ.addDisk(0, 0, 0, 1, 1)
gmsh.model.occ.synchronize()
# After that, we make the membrane a physical surface, such that it is recognized by `gmsh` when generating the mesh.
# As a surface is a two-dimensional entity, we add `2` as the first argument,
# the entity tag of the membrane as the second argument, and the physical tag as the last argument.
# In a later demo, we will get into when this tag matters.
gdim = 2
gmsh.model.addPhysicalGroup(gdim, [membrane], 1)
# Finally, we generate the two-dimensional mesh.
# We set a uniform mesh size by modifying the GMSH options.
gmsh.option.setNumber("Mesh.CharacteristicLengthMin", 0.05)
gmsh.option.setNumber("Mesh.CharacteristicLengthMax", 0.05)
gmsh.model.mesh.generate(gdim)
# # Interfacing with GMSH in DOLFINx
# We will import the GMSH-mesh directly from GMSH into DOLFINx via the {py:mod}`dolfinx.io.gmsh` interface.
# The {py:mod}`dolfinx.io.gmsh` module contains two functions
# 1. {py:func}`model_to_mesh<dolfinx.io.gmsh.model_to_mesh>` which takes in a `gmsh.model`
# and returns a {py:class}`dolfinx.io.gmsh.MeshData` object.
# 2. {py:func}`read_from_msh<dolfinx.io.gmsh.read_from_msh>` which takes in a path to a `.msh`-file
# and returns a {py:class}`dolfinx.io.gmsh.MeshData` object.
#
# The {py:class}`MeshData` object will contain a {py:class}`dolfinx.mesh.Mesh`,
# under the attribute {py:attr}`mesh<dolfinx.io.gmsh.MeshData.mesh>`.
# This mesh will contain all GMSH Physical Groups of the highest topological dimension.
# ```{note}
# If you do not use `gmsh.model.addPhysicalGroup` when creating the mesh with GMSH, it can not be read into DOLFINx.
# ```
# The {py:class}`MeshData<dolfinx.io.gmsh.MeshData>` object can also contain tags for
# all other `PhysicalGroups` that has been added to the mesh, that being
# {py:attr}`cell_tags<dolfinx.io.gmsh.MeshData.cell_tags>`, {py:attr}`facet_tags<dolfinx.io.gmsh.MeshData.facet_tags>`,
# {py:attr}`ridge_tags<dolfinx.io.gmsh.MeshData.ridge_tags>` and
# {py:attr}`peak_tags<dolfinx.io.gmsh.MeshData.peak_tags>`.
# To read either `gmsh.model` or a `.msh`-file, one has to distribute the mesh to all processes used by DOLFINx.
# As GMSH does not support mesh creation with MPI, we currently have a `gmsh.model.mesh` on each process.
# To distribute the mesh, we have to specify which process the mesh was created on,
# and which communicator rank should distribute the mesh.
# The {py:func}`model_to_mesh<dolfinx.io.gmsh.model_to_mesh>` will then load the mesh on the specified rank,
# and distribute it to the communicator using a mesh partitioner.
# +
from dolfinx.io import gmsh as gmshio
from dolfinx.fem.petsc import LinearProblem
from mpi4py import MPI
gmsh_model_rank = 0
mesh_comm = MPI.COMM_WORLD
mesh_data = gmshio.model_to_mesh(gmsh.model, mesh_comm, gmsh_model_rank, gdim=gdim)
assert mesh_data.cell_tags is not None
cell_markers = mesh_data.cell_tags
domain = mesh_data.mesh
# -
# We define the function space as in the previous tutorial
# +
from dolfinx import fem
V = fem.functionspace(domain, ("Lagrange", 1))
# -
# ## Defining a spatially varying load
# The right hand side pressure function is represented using {py:class}`ufl.SpatialCoordinate` and two constants,
# one for $\beta$ and one for $R_0$.
# +
import ufl
from dolfinx import default_scalar_type
x = ufl.SpatialCoordinate(domain)
beta = fem.Constant(domain, default_scalar_type(12))
R0 = fem.Constant(domain, default_scalar_type(0.3))
p = 4 * ufl.exp(-(beta**2) * (x[0] ** 2 + (x[1] - R0) ** 2))
# -
# ## Create a Dirichlet boundary condition using geometrical conditions
# The next step is to create the homogeneous boundary condition.
# As opposed to the [first tutorial](./fundamentals_code.ipynb) we will use
# {py:func}`locate_dofs_geometrical<dolfinx.fem.locate_dofs_geometrical>` to locate the degrees of freedom on the boundary.
# As we know that our domain is a circle with radius 1, we know that any degree of freedom should be
# located at a coordinate $(x,y)$ such that $\sqrt{x^2+y^2}=1$.
# +
import numpy as np
def on_boundary(x):
return np.isclose(np.sqrt(x[0] ** 2 + x[1] ** 2), 1)
boundary_dofs = fem.locate_dofs_geometrical(V, on_boundary)
# -
# As our Dirichlet condition is homogeneous (`u=0` on the whole boundary), we can initialize the
# {py:class}`dolfinx.fem.DirichletBC` with a constant value, the degrees of freedom and the function
# space to apply the boundary condition on. We use the constructor {py:func}`dolfinx.fem.dirichletbc`.
bc = fem.dirichletbc(default_scalar_type(0), boundary_dofs, V)
# ## Defining the variational problem
# The variational problem is the same as in our first Poisson problem, where `f` is replaced by `p`.
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
a = ufl.dot(ufl.grad(u), ufl.grad(v)) * ufl.dx
L = p * v * ufl.dx
problem = LinearProblem(
a,
L,
bcs=[bc],
petsc_options={"ksp_type": "preonly", "pc_type": "lu"},
petsc_options_prefix="membrane_",
)
uh = problem.solve()
# ## Interpolation of a UFL-expression
# As we previously defined the load `p` as a spatially varying function,
# we would like to interpolate this function into an appropriate function space for visualization.
# To do this we use the class {py:class}`Expression<dolfinx.fem.Expression>`.
# The expression takes in any UFL-expression, and a set of points on the reference element.
# We will use the {py:attr}`interpolation points<dolfinx.fem.FiniteElement.interpolation_points>`
# of the space we want to interpolate in to.
# We choose a high order function space to represent the function `p`, as it is rapidly varying in space.
Q = fem.functionspace(domain, ("Lagrange", 5))
expr = fem.Expression(p, Q.element.interpolation_points)
pressure = fem.Function(Q)
pressure.interpolate(expr)
# ## Plotting the solution over a line
# We first plot the deflection $u_h$ over the domain $\Omega$.
from dolfinx.plot import vtk_mesh
import pyvista
# Extract topology from mesh and create {py:class}`pyvista.UnstructuredGrid`
topology, cell_types, x = vtk_mesh(V)
grid = pyvista.UnstructuredGrid(topology, cell_types, x)
# Set deflection values and add it to plotter
# +
grid.point_data["u"] = uh.x.array
warped = grid.warp_by_scalar("u", factor=25)
plotter = pyvista.Plotter()
plotter.add_mesh(warped, show_edges=True, show_scalar_bar=True, scalars="u")
if not pyvista.OFF_SCREEN:
plotter.show()
else:
plotter.screenshot("deflection.png")
# -
# We next plot the load on the domain
load_plotter = pyvista.Plotter()
p_grid = pyvista.UnstructuredGrid(*vtk_mesh(Q))
p_grid.point_data["p"] = pressure.x.array.real
warped_p = p_grid.warp_by_scalar("p", factor=0.5)
warped_p.set_active_scalars("p")
load_plotter.add_mesh(warped_p, show_scalar_bar=True)
load_plotter.view_xy()
if not pyvista.OFF_SCREEN:
load_plotter.show()
else:
load_plotter.screenshot("load.png")
# ## Making curve plots throughout the domain
# Another way to compare the deflection and the load is to make a plot along the line $x=0$.
# This is just a matter of defining a set of points along the $y$-axis and evaluating the
# finite element functions $u$ and $p$ at these points.
tol = 0.001 # Avoid hitting the outside of the domain
y = np.linspace(-1 + tol, 1 - tol, 101)
points = np.zeros((3, 101))
points[1] = y
u_values = []
p_values = []
# As a finite element function is the linear combination of all degrees of freedom,
# $u_h(x)=\sum_{i=1}^N c_i \phi_i(x)$ where $c_i$ are the coefficients of $u_h$ and $\phi_i$
# is the $i$-th basis function, we can compute the exact solution at any point in $\Omega$.
# However, as a mesh consists of a large set of degrees of freedom (i.e. $N$ is large),
# we want to reduce the number of evaluations of the basis function $\phi_i(x)$.
# We do this by identifying which cell of the mesh $x$ is in.
# This is efficiently done by creating a {py:class}`bounding box tree<dolfinx.geometry.BoundingBoxTree`
# of the cells of the mesh,
# allowing a quick recursive search through the mesh entities.
# +
from dolfinx import geometry
bb_tree = geometry.bb_tree(domain, domain.topology.dim)
# -
# Now we can compute which cells the bounding box tree collides with using
# {py:func}`dolfinx.geometry.compute_collisions_points`.
# This function returns a list of cells whose bounding box collide for each input point.
# As different points might have different number of cells, the data is stored in
# {py:class}`dolfinx.graph.AdjacencyList`, where one can access the cells for the
# `i`th point by calling {py:meth}`links(i)<dolfinx.graph.AdjacencyList.links>`.
# However, as the bounding box of a cell spans more of $\mathbb{R}^n$ than the actual cell,
# we check that the actual cell collides with the input point using
# {py:func}`dolfinx.geometry.compute_colliding_cells`,
# which measures the exact distance between the point and the cell
# (approximated as a convex hull for higher order geometries).
# This function also returns an adjacency-list, as the point might align with a facet,
# edge or vertex that is shared between multiple cells in the mesh.
#
# Finally, we would like the code below to run in parallel,
# when the mesh is distributed over multiple processors.
# In that case, it is not guaranteed that every point in `points` is on each processor.
# Therefore we create a subset `points_on_proc` only containing the points found on the current processor.
cells = []
points_on_proc = []
# Find cells whose bounding-box collide with the the points
cell_candidates = geometry.compute_collisions_points(bb_tree, points.T)
# Choose one of the cells that contains the point
colliding_cells = geometry.compute_colliding_cells(domain, cell_candidates, points.T)
for i, point in enumerate(points.T):
if len(colliding_cells.links(i)) > 0:
points_on_proc.append(point)
cells.append(colliding_cells.links(i)[0])
# We now have a list of points on the processor, on in which cell each point belongs.
# We can then call {py:meth}`uh.eval<dolfinx.fem.Function.eval>` and
# {py:meth}`pressure.eval<dolfinx.fem.Function.eval>` to obtain the set of values for all the points.
points_on_proc = np.array(points_on_proc, dtype=np.float64)
u_values = uh.eval(points_on_proc, cells)
p_values = pressure.eval(points_on_proc, cells)
# As we now have an array of coordinates and two arrays of function values,
# we can use {py:mod}`matplotlib<matplotlib.pyplot>` to plot them
# +
import matplotlib.pyplot as plt
fig = plt.figure()
plt.plot(
points_on_proc[:, 1],
50 * u_values,
"k",
linewidth=2,
label="Deflection ($\\times 50$)",
)
plt.plot(points_on_proc[:, 1], p_values, "b--", linewidth=2, label="Load")
plt.grid(True)
plt.xlabel("y")
plt.legend()
# -
# If executed in parallel as a Python file, we save a plot per processor
plt.savefig(f"membrane_rank{MPI.COMM_WORLD.rank:d}.png")
# ## Saving functions to file
# As mentioned in the previous section, we can also use Paraview to visualize the solution.
# +
import dolfinx.io
from pathlib import Path
pressure.name = "Load"
uh.name = "Deflection"
results_folder = Path("results")
results_folder.mkdir(exist_ok=True, parents=True)
with dolfinx.io.VTXWriter(
MPI.COMM_WORLD, results_folder / "membrane_pressure.bp", [pressure], engine="BP4"
) as vtx:
vtx.write(0.0)
with dolfinx.io.VTXWriter(
MPI.COMM_WORLD, results_folder / "membrane_deflection.bp", [uh], engine="BP4"
) as vtx:
vtx.write(0.0)
================================================
FILE: chapter1/membrane_paraview.md
================================================
# Using Paraview for visualization
We start by opening [Paraview](https://www.paraview.org/). We start by opening the file by pressing `File->Open`. Next you should choose the `Xdmf3ReaderT` to open the data with.
The next step is to visualize each of the functions. To do this, we choose `Filters->Alphabetic->ExtractBlock`. The next step is to select the first Unstructured Grid and press `Apply` as shown below:

We can select the other block to visualize the deflection. There are also options to visualize the deflection in three dimensions using `Filters->Alphabetical->Warp By Scalar`, and change the layout to 3D by pressing the `2D`-button.

Finally, press the `Set view direction button`.

With these instructions, you can obtain the following figures

================================================
FILE: chapter1/nitsche.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"id": "0",
"metadata": {},
"source": [
"# Weak imposition of Dirichlet conditions for the Poisson problem\n",
"Author: Jørgen S. Dokken\n",
"\n",
"In this section, we will go through how to solve the Poisson problem from the\n",
"[Fundamentals](./fundamentals_code.ipynb) tutorial using Nitsche's method {cite}`nitsche-Nitsche1971`.\n",
"The idea of weak imposition is that we add additional terms to the variational formulation to\n",
"impose the boundary condition, instead of modifying the matrix system using strong imposition (lifting).\n",
"\n",
"We start by importing the required modules and creating the mesh and function space for our solution"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1",
"metadata": {},
"outputs": [],
"source": [
"from dolfinx import fem, mesh, plot, default_scalar_type\n",
"from dolfinx.fem.petsc import LinearProblem\n",
"import numpy\n",
"from mpi4py import MPI\n",
"from ufl import (\n",
" Circumradius,\n",
" FacetNormal,\n",
" SpatialCoordinate,\n",
" TrialFunction,\n",
" TestFunction,\n",
" div,\n",
" dx,\n",
" ds,\n",
" grad,\n",
" inner,\n",
")\n",
"\n",
"N = 8\n",
"domain = mesh.create_unit_square(MPI.COMM_WORLD, N, N)\n",
"V = fem.functionspace(domain, (\"Lagrange\", 1))"
]
},
{
"cell_type": "markdown",
"id": "2",
"metadata": {},
"source": [
"Next, we create a function containing the exact solution (which will also be used in the Dirichlet boundary condition)\n",
"and the corresponding source function for the right hand side. Note that we use {py:class}`ufl.SpatialCoordinate`\n",
"to define the exact solution, which in turn is interpolated into {py:class}`uD<dolfinx.fem.Function>`\n",
"by wrapping the {py:class}`ufl-expression<ufl.core.expr.Expr>` as a {py:class}`dolfinx.fem.Expression`.\n",
"For the source function, we use the symbolic differentiation capabilities of UFL to\n",
"compute the negative Laplacian of the exact solution, which we can use directly in the variational formulation."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3",
"metadata": {},
"outputs": [],
"source": [
"uD = fem.Function(V)\n",
"x = SpatialCoordinate(domain)\n",
"u_ex = 1 + x[0] ** 2 + 2 * x[1] ** 2\n",
"uD.interpolate(fem.Expression(u_ex, V.element.interpolation_points))\n",
"f = -div(grad(u_ex))"
]
},
{
"cell_type": "markdown",
"id": "4",
"metadata": {},
"source": [
"As opposed to the first tutorial, we now have to have another look at the variational form.\n",
"We start by integrating the problem by parts, to obtain\n",
"\n",
"$$\n",
"\\begin{align}\n",
" \\int_{\\Omega} \\nabla u \\cdot \\nabla v~\\mathrm{d}x\n",
" - \\int_{\\partial\\Omega}\\nabla u \\cdot n v~\\mathrm{d}s = \\int_{\\Omega} f v~\\mathrm{d}x.\n",
"\\end{align}\n",
"$$\n",
"\n",
"As we are not using strong enforcement, we do not set the trace of the test function to $0$ on the outer boundary.\n",
"Instead, we add the following two terms to the variational formulation\n",
"\n",
"$$\n",
"\\begin{align}\n",
" -\\int_{\\partial\\Omega} \\nabla v \\cdot n (u-u_D)~\\mathrm{d}s\n",
" + \\frac{\\alpha}{h} \\int_{\\partial\\Omega} (u-u_D)v~\\mathrm{d}s.\n",
"\\end{align}\n",
"$$\n",
"\n",
"where the first term enforces symmetry to the bilinear form, while the latter term enforces coercivity.\n",
"$u_D$ is the known Dirichlet condition, and $h$ is the diameter of the circumscribed sphere of the mesh element.\n",
"We create bilinear and linear form, $a$ and $L$\n",
"\n",
"$$\n",
"\\begin{align}\n",
" a(u, v) &= \\int_{\\Omega} \\nabla u \\cdot \\nabla v~\\mathrm{d}x + \\int_{\\partial\\Omega}-(n \\cdot\\nabla u) v - (n \\cdot \\nabla v) u + \\frac{\\alpha}{h} uv~\\mathrm{d}s,\\\\\n",
" L(v) &= \\int_{\\Omega} fv~\\mathrm{d}x + \\int_{\\partial\\Omega} -(n \\cdot \\nabla v) u_D + \\frac{\\alpha}{h} u_Dv~\\mathrm{d}s\n",
"\\end{align}\n",
"$$"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5",
"metadata": {},
"outputs": [],
"source": [
"u = TrialFunction(V)\n",
"v = TestFunction(V)\n",
"n = FacetNormal(domain)\n",
"h = 2 * Circumradius(domain)\n",
"alpha = fem.Constant(domain, default_scalar_type(10))\n",
"a = inner(grad(u), grad(v)) * dx - inner(n, grad(u)) * v * ds\n",
"a += -inner(n, grad(v)) * u * ds + alpha / h * inner(u, v) * ds\n",
"L = inner(f, v) * dx\n",
"L += -inner(n, grad(v)) * uD * ds + alpha / h * inner(uD, v) * ds"
]
},
{
"cell_type": "markdown",
"id": "6",
"metadata": {},
"source": [
"As we now have the variational form, we can solve the arising\n",
"{py:class}`linear problem<dolfinx.fem.petsc.LinearProblem>`"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7",
"metadata": {},
"outputs": [],
"source": [
"problem = LinearProblem(a, L, petsc_options_prefix=\"nitsche_poisson\")\n",
"uh = problem.solve()"
]
},
{
"cell_type": "markdown",
"id": "8",
"metadata": {},
"source": [
"We compute the error of the computation by comparing it to the analytical solution"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9",
"metadata": {},
"outputs": [],
"source": [
"error_form = fem.form(inner(uh - uD, uh - uD) * dx)\n",
"error_local = fem.assemble_scalar(error_form)\n",
"errorL2 = numpy.sqrt(domain.comm.allreduce(error_local, op=MPI.SUM))\n",
"if domain.comm.rank == 0:\n",
" print(rf\"$L^2$-error: {errorL2:.2e}\")"
]
},
{
"cell_type": "markdown",
"id": "10",
"metadata": {},
"source": [
"We observe that the $L^2$-error is of the same magnitude as in the first tutorial.\n",
"As in the previous tutorial, we also compute the maximal error for all the degrees of freedom."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "11",
"metadata": {},
"outputs": [],
"source": [
"error_max = domain.comm.allreduce(\n",
" numpy.max(numpy.abs(uD.x.array - uh.x.array)), op=MPI.MAX\n",
")\n",
"if domain.comm.rank == 0:\n",
" print(f\"Error_max : {error_max:.2e}\")"
]
},
{
"cell_type": "markdown",
"id": "12",
"metadata": {},
"source": [
"We observe that as we weakly impose the boundary condition,\n",
"we no longer fullfill the equation to machine precision at the mesh vertices.\n",
"We also plot the solution using {py:mod}`pyvista`"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "13",
"metadata": {},
"outputs": [],
"source": [
"import pyvista\n",
"\n",
"grid = pyvista.UnstructuredGrid(*plot.vtk_mesh(V))\n",
"grid.point_data[\"u\"] = uh.x.array.real\n",
"grid.set_active_scalars(\"u\")\n",
"plotter = pyvista.Plotter()\n",
"plotter.add_mesh(grid, show_edges=True, show_scalar_bar=True)\n",
"plotter.view_xy()\n",
"if not pyvista.OFF_SCREEN:\n",
" plotter.show()\n",
"else:\n",
" figure = plotter.screenshot(\"nitsche.png\")"
]
},
{
"cell_type": "markdown",
"id": "14",
"metadata": {},
"source": [
"```{bibliography}\n",
" :filter: cited\n",
" :labelprefix:\n",
" :keyprefix: nitsche-\n",
"```"
]
}
],
"metadata": {
"jupytext": {
"formats": "ipynb,py:light"
},
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
================================================
FILE: chapter1/nitsche.py
================================================
# ---
# jupyter:
# jupytext:
# formats: ipynb,py:light
# text_representation:
# extension: .py
# format_name: light
# format_version: '1.5'
# jupytext_version: 1.18.1
# kernelspec:
# display_name: Python 3 (ipykernel)
# language: python
# name: python3
# ---
# # Weak imposition of Dirichlet conditions for the Poisson problem
# Author: Jørgen S. Dokken
#
# In this section, we will go through how to solve the Poisson problem from the
# [Fundamentals](./fundamentals_code.ipynb) tutorial using Nitsche's method {cite}`nitsche-Nitsche1971`.
# The idea of weak imposition is that we add additional terms to the variational formulation to
# impose the boundary condition, instead of modifying the matrix system using strong imposition (lifting).
#
# We start by importing the required modules and creating the mesh and function space for our solution
# +
from dolfinx import fem, mesh, plot, default_scalar_type
from dolfinx.fem.petsc import LinearProblem
import numpy
from mpi4py import MPI
from ufl import (
Circumradius,
FacetNormal,
SpatialCoordinate,
TrialFunction,
TestFunction,
div,
dx,
ds,
grad,
inner,
)
N = 8
domain = mesh.create_unit_square(MPI.COMM_WORLD, N, N)
V = fem.functionspace(domain, ("Lagrange", 1))
# -
# Next, we create a function containing the exact solution (which will also be used in the Dirichlet boundary condition)
# and the corresponding source function for the right hand side. Note that we use {py:class}`ufl.SpatialCoordinate`
# to define the exact solution, which in turn is interpolated into {py:class}`uD<dolfinx.fem.Function>`
# by wrapping the {py:class}`ufl-expression<ufl.core.expr.Expr>` as a {py:class}`dolfinx.fem.Expression`.
# For the source function, we use the symbolic differentiation capabilities of UFL to
# compute the negative Laplacian of the exact solution, which we can use directly in the variational formulation.
uD = fem.Function(V)
x = SpatialCoordinate(domain)
u_ex = 1 + x[0] ** 2 + 2 * x[1] ** 2
uD.interpolate(fem.Expression(u_ex, V.element.interpolation_points))
f = -div(grad(u_ex))
# As opposed to the first tutorial, we now have to have another look at the variational form.
# We start by integrating the problem by parts, to obtain
#
# $$
# \begin{align}
# \int_{\Omega} \nabla u \cdot \nabla v~\mathrm{d}x
# - \int_{\partial\Omega}\nabla u \cdot n v~\mathrm{d}s = \int_{\Omega} f v~\mathrm{d}x.
# \end{align}
# $$
#
# As we are not using strong enforcement, we do not set the trace of the test function to $0$ on the outer boundary.
# Instead, we add the following two terms to the variational formulation
#
# $$
# \begin{align}
# -\int_{\partial\Omega} \nabla v \cdot n (u-u_D)~\mathrm{d}s
# + \frac{\alpha}{h} \int_{\partial\Omega} (u-u_D)v~\mathrm{d}s.
# \end{align}
# $$
#
# where the first term enforces symmetry to the bilinear form, while the latter term enforces coercivity.
# $u_D$ is the known Dirichlet condition, and $h$ is the diameter of the circumscribed sphere of the mesh element.
# We create bilinear and linear form, $a$ and $L$
#
# $$
# \begin{align}
# a(u, v) &= \int_{\Omega} \nabla u \cdot \nabla v~\mathrm{d}x + \int_{\partial\Omega}-(n \cdot\nabla u) v - (n \cdot \nabla v) u + \frac{\alpha}{h} uv~\mathrm{d}s,\\
# L(v) &= \int_{\Omega} fv~\mathrm{d}x + \int_{\partial\Omega} -(n \cdot \nabla v) u_D + \frac{\alpha}{h} u_Dv~\mathrm{d}s
# \end{align}
# $$
u = TrialFunction(V)
v = TestFunction(V)
n = FacetNormal(domain)
h = 2 * Circumradius(domain)
alpha = fem.Constant(domain, default_scalar_type(10))
a = inner(grad(u), grad(v)) * dx - inner(n, grad(u)) * v * ds
a += -inner(n, grad(v)) * u * ds + alpha / h * inner(u, v) * ds
L = inner(f, v) * dx
L += -inner(n, grad(v)) * uD * ds + alpha / h * inner(uD, v) * ds
# As we now have the variational form, we can solve the arising
# {py:class}`linear problem<dolfinx.fem.petsc.LinearProblem>`
problem = LinearProblem(a, L, petsc_options_prefix="nitsche_poisson")
uh = problem.solve()
# We compute the error of the computation by comparing it to the analytical solution
error_form = fem.form(inner(uh - uD, uh - uD) * dx)
error_local = fem.assemble_scalar(error_form)
errorL2 = numpy.sqrt(domain.comm.allreduce(error_local, op=MPI.SUM))
if domain.comm.rank == 0:
print(rf"$L^2$-error: {errorL2:.2e}")
# We observe that the $L^2$-error is of the same magnitude as in the first tutorial.
# As in the previous tutorial, we also compute the maximal error for all the degrees of freedom.
error_max = domain.comm.allreduce(
numpy.max(numpy.abs(uD.x.array - uh.x.array)), op=MPI.MAX
)
if domain.comm.rank == 0:
print(f"Error_max : {error_max:.2e}")
# We observe that as we weakly impose the boundary condition,
# we no longer fullfill the equation to machine precision at the mesh vertices.
# We also plot the solution using {py:mod}`pyvista`
# +
import pyvista
grid = pyvista.UnstructuredGrid(*plot.vtk_mesh(V))
grid.point_data["u"] = uh.x.array.real
grid.set_active_scalars("u")
plotter = pyvista.Plotter()
plotter.add_mesh(grid, show_edges=True, show_scalar_bar=True)
plotter.view_xy()
if not pyvista.OFF_SCREEN:
plotter.show()
else:
figure = plotter.screenshot("nitsche.png")
# -
# ```{bibliography}
# :filter: cited
# :labelprefix:
# :keyprefix: nitsche-
# ```
================================================
FILE: chapter2/advdiffreac.md
================================================
# TO BE IMPLEMETED: A system of advection-diffusion-reaction equations
Authors: Hans Petter Langtangen and Anders Logg
Most of the problems we have encountered so far have a common feature: they all invlove models expressed by a single scalar or vector PDE.
In many situations the model is instead expressed as a system of PDEs, describing different quantities possibly govered by (very) different physics.
As we saw for the Navier-Stokes equations, one way to solve one equation a system of PDEs in FEniCSx is to use a splitting method where we solve one equation at a time and feed the solution from one equation into the next. However, one of the strengths with FEniCSx is the ease by which one can instead define variational problems that couple several PDEs into one compound system. In this system, we will look at how to use FEniCSx to write solvers for such a system of coupled PDEs. The goal is to demonstrate how easy it is to implement fully implicit, also known as monolithic, solvers in FEniCSx.
## The PDE problem
Our model problem is the following system of advection-diffusion-reaction equations:
```{math}
:label: adv-diff-reac
\frac{\partial u_1}{\partial t} + w \cdot \nabla u_1 - \nabla \cdot (\epsilon \nabla u_1) &= f_1 - Ku_1 u_2,\\
\frac{\partial u_2}{\partial t} + w \cdot \nabla u_2 - \nabla \cdot (\epsilon \nabla u_2) &= f_2 - Ku_1 u_2,\\
\frac{\partial u_3}{\partial t} + w \cdot \nabla u_3 - \nabla \cdot (\epsilon \nabla u_3) &= f_3 - Ku_1 u_2 - K u_3,\\
```
This system models the chemical reaction between two species $A$ and $B$ in some domain $\Omega$:
```{math}
A + B \rightarrow C.
```
We assume that the reaction is *first-order*, meaning that the reaction rate is proportional to concentrations $[A]$ and $[B]$ of the two species $A$ and $B$:
```{math}
\frac{\mathrm{d}}{\mathrm{d}t}[C]= K[A][B].
```
We also assume that the formed species $C$ spontaneously decaas with a rate proportional to the concentration [C]. In the [PDE system](adv-diff-reac), we use the variatbles $u_1, u_2$ and $u_3$ to denote the concentrations of the three species:
```{math}
u_1=[A],\qquad u_2=[B], \qquad u_3=[C].
```
We see that the chemical reactions are accounted for in the right-hand sides of the PDE system.
The chemical reactions takes part at each point in the domain $\Omega$.
In addition, we assume that the species $A, B$ and $C$ diffuse throughout the domain with diffusivity $\epsilon$ (the terms $-\nabla \cdot (\epsilon \nabla u_i)$) and are advected with velocity $w$ (the terms $w\cdot \nabla u_i$).
```{admonition} Implementation note
For this demo to work, we need to have checkpointing implemented
```
================================================
FILE: chapter2/amr.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"id": "0",
"metadata": {},
"source": [
"# Adaptive mesh refinement with NetGen and DOLFINx\n",
"\n",
"Author: Jørgen S. Dokken\n",
"\n",
"```{admonition} NetGen and linux/arm64\n",
"NetGen is not available on PyPi on linux/arm64, so to run this tutorial on such machine, please use the\n",
"docker image [ghcr.io/jorgensd/dolfinx-tutorial:release](https://github.com/jorgensd/dolfinx-tutorial/pkgs/container/dolfinx-tutorial/489387776?tag=release).\n",
"You can also install NetGen from source. See the [Dockerfile](https://github.com/jorgensd/dolfinx-tutorial/blob/main/docker/Dockerfile) for instructions.\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "1",
"metadata": {},
"source": [
"In this tutorial, we will consider an adaptive mesh refinement method, applied to\n",
"the Laplace eigenvalue problem.\n",
"This demo is an adaptation of [Firedrake - Adaptive Mesh Refinement](https://www.firedrakeproject.org/firedrake/demos/netgen_mesh.py.html).\n",
"In this tutorial we will use the mesh generator [NetGen](https://ngsolve.org/) from NGSolve.\n",
"First, we import the packages needed for this demo:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2",
"metadata": {},
"outputs": [],
"source": [
"from mpi4py import MPI\n",
"from petsc4py import PETSc\n",
"from slepc4py import SLEPc\n",
"from packaging.version import Version\n",
"import dolfinx.fem.petsc\n",
"import numpy as np\n",
"import ufl\n",
"import pyvista\n",
"import ngsPETSc.utils.fenicsx as ngfx\n",
"\n",
"from netgen.geom2d import SplineGeometry"
]
},
{
"cell_type": "markdown",
"id": "3",
"metadata": {},
"source": [
"## Generating a higher-order mesh with NetGen\n",
"Next, we generate a PacMan-like geometry using NetGen."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4",
"metadata": {},
"outputs": [],
"source": [
"geo = SplineGeometry()\n",
"pnts = [(0, 0), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1)]\n",
"p1, p2, p3, p4, p5, p6, p7, p8 = [geo.AppendPoint(*pnt) for pnt in pnts]\n",
"curves = [\n",
" [[\"line\", p1, p2], \"line\"],\n",
" [[\"spline3\", p2, p3, p4], \"curve\"],\n",
" [[\"spline3\", p4, p5, p6], \"curve\"],\n",
" [[\"spline3\", p6, p7, p8], \"curve\"],\n",
" [[\"line\", p8, p1], \"line\"],\n",
"]\n",
"for c, bc in curves:\n",
" geo.Append(c, bc=bc)"
]
},
{
"cell_type": "markdown",
"id": "5",
"metadata": {},
"source": [
"## Loading a mesh into DOLFINx\n",
"The ngsPETSc package provides a communication layer between NetGen and DOLFINx.\n",
"We initialize this layer by passing in a NetGen-model, as well as an MPI communicator,\n",
"which will be used to distribute the mesh."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6",
"metadata": {},
"outputs": [],
"source": [
"geoModel = ngfx.GeometricModel(geo, MPI.COMM_WORLD)"
]
},
{
"cell_type": "markdown",
"id": "7",
"metadata": {},
"source": [
"Next, we generate the mesh with the function :py:func:`ngsPETSc.utils.fenicsx.GeometricModel.model_to_mesh`.\n",
"Which takes in the target geometric dimension of the mesh (2 for triangular meshes, 3 for tetrahedral), the\n",
"maximum mesh size (`hmax`) and a few optional parameters."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8",
"metadata": {},
"outputs": [],
"source": [
"mesh, (ct, ft), region_map = geoModel.model_to_mesh(gdim=2, hmax=0.5)"
]
},
{
"cell_type": "markdown",
"id": "9",
"metadata": {},
"source": [
"We use pyvista to visualize the mesh."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "10",
"metadata": {
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"grid = pyvista.UnstructuredGrid(*dolfinx.plot.vtk_mesh(mesh))\n",
"grid.cell_data[\"ct\"] = ct.values\n",
"\n",
"plotter = pyvista.Plotter()\n",
"plotter.add_mesh(\n",
" grid, show_edges=True, scalars=\"ct\", cmap=\"blues\", show_scalar_bar=False\n",
")\n",
"plotter.view_xy()\n",
"if not pyvista.OFF_SCREEN:\n",
" plotter.show()"
]
},
{
"cell_type": "markdown",
"id": "11",
"metadata": {},
"source": [
"We have read in any cell and facet markers that have been defined in the NetGen model,\n",
"as well as a map from their names to their integer ids in `ct`, `ft` and `region_map` respectively.\n",
"We can curve the grids with the command `curveField`.\n",
"In this example, we use third order Lagrange elements to represent the geometry."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "12",
"metadata": {},
"outputs": [],
"source": [
"order = 3\n",
"curved_mesh = geoModel.curveField(order)"
]
},
{
"cell_type": "markdown",
"id": "13",
"metadata": {},
"source": [
"Again, we visualize the curved mesh with pyvista."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "14",
"metadata": {
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"curved_grid = pyvista.UnstructuredGrid(*dolfinx.plot.vtk_mesh(curved_mesh))\n",
"curved_grid.cell_data[\"ct\"] = ct.values\n",
"plotter = pyvista.Plotter()\n",
"plotter.add_mesh(\n",
" curved_grid, show_edges=False, scalars=\"ct\", cmap=\"blues\", show_scalar_bar=False\n",
")\n",
"plotter.add_mesh(grid, style=\"wireframe\", color=\"black\")\n",
"plotter.view_xy()\n",
"if not pyvista.OFF_SCREEN:\n",
" plotter.show()"
]
},
{
"cell_type": "markdown",
"id": "15",
"metadata": {},
"source": [
"## Solving the eigenvalue problem\n",
"In this section we will solve the eigenvalue problem:\n",
"\n",
"Find $u_h\\in H_0^1(\\Omega)$ and $\\lambda\\in\\mathbb{R}$ such that\n",
"\n",
"$$\n",
"\\begin{align}\n",
"\\int_\\Omega \\nabla u \\cdot \\nabla v~\\mathrm{d} x &= \\lambda \\int_\\Omega u v~\\mathrm{d} x \\qquad\n",
"\\forall v \\in H_0^1(\\Omega).\n",
"\\end{align}\n",
"$$"
]
},
{
"cell_type": "markdown",
"id": "16",
"metadata": {
"lines_to_next_cell": 2
},
"source": [
"Next, we define a convenience function to solve the eigenvalue problem using [SLEPc](https://slepc.upv.es/)\n",
"given a discretized domain, its facet markers and the region map."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "17",
"metadata": {
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"def solve(\n",
" mesh: dolfinx.mesh.Mesh,\n",
" facet_tags: dolfinx.mesh.MeshTags,\n",
" region_map: dict[tuple[int, str], tuple[int, ...]],\n",
") -> tuple[float, dolfinx.fem.Function, dolfinx.fem.Function]:\n",
" # We define the lhs and rhs bilinear forms\n",
" V = dolfinx.fem.functionspace(mesh, (\"Lagrange\", 3))\n",
" u = ufl.TrialFunction(V)\n",
" v = ufl.TestFunction(V)\n",
" a = ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx\n",
" m = ufl.inner(u, v) * ufl.dx\n",
"\n",
" # We identify the boundary facets and their corresponding dofs\n",
" straight_facets = facet_tags.indices[\n",
" np.isin(facet_tags.values, region_map[(1, \"line\")])\n",
" ]\n",
" curved_facets = facet_tags.indices[\n",
" np.isin(facet_tags.values, region_map[(1, \"curve\")])\n",
" ]\n",
" boundary_facets = np.concatenate([straight_facets, curved_facets])\n",
" mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim)\n",
" boundary_dofs = dolfinx.fem.locate_dofs_topological(\n",
" V, mesh.topology.dim - 1, boundary_facets\n",
" )\n",
"\n",
" # We create a zero boundary condition for these dofs to be in the suitable space, and\n",
" # set up the discrete matrices `A` and `M`\n",
" bc = dolfinx.fem.dirichletbc(0.0, boundary_dofs, V)\n",
" A = dolfinx.fem.petsc.assemble_matrix(dolfinx.fem.form(a), bcs=[bc])\n",
" A.assemble()\n",
" if Version(dolfinx.__version__) < Version(\"0.10.0\"):\n",
" diag_kwargs = {\"diagonal\": 0.0}\n",
" else:\n",
" diag_kwargs = {\"diag\": 0.0}\n",
"\n",
" M = dolfinx.fem.petsc.assemble_matrix(dolfinx.fem.form(m), bcs=[bc], **diag_kwargs)\n",
" M.assemble()\n",
"\n",
" # Next, we define the SLEPc Eigenvalue Problem Solver (EPS), and set up to use a shift\n",
" # and invert (SINVERT) spectral transformation where the preconditioner factorisation\n",
" # is computed using [MUMPS](https://mumps-solver.org/index.php).\n",
"\n",
" E = SLEPc.EPS().create(mesh.comm)\n",
" E.setType(SLEPc.EPS.Type.ARNOLDI)\n",
" E.setProblemType(SLEPc.EPS.ProblemType.GHEP)\n",
" E.setDimensions(1, SLEPc.DECIDE)\n",
" E.setOperators(A, M)\n",
" ST = E.getST()\n",
" ST.setType(SLEPc.ST.Type.SINVERT)\n",
" PC = ST.getKSP().getPC()\n",
" PC.setType(\"lu\")\n",
" PC.setFactorSolverType(\"mumps\")\n",
" E.setST(ST)\n",
" E.solve()\n",
" assert E.getConvergedReason() >= 0, \"Eigenvalue solver did not converge\"\n",
"\n",
" # We get the real and imaginary parts of the first eigenvector along with the eigenvalue.\n",
" uh_r = dolfinx.fem.Function(V)\n",
" uh_i = dolfinx.fem.Function(V)\n",
" lam = E.getEigenpair(0, uh_r.x.petsc_vec, uh_i.x.petsc_vec)\n",
" E.destroy()\n",
" uh_r.x.scatter_forward()\n",
" uh_i.x.scatter_forward()\n",
" return (lam, uh_r, uh_i)"
]
},
{
"cell_type": "markdown",
"id": "18",
"metadata": {
"lines_to_next_cell": 2
},
"source": [
"## Error-indicator\n",
"In this example, we will use an error-indicator $\\eta$ to decide what cells should be refined.\n",
"Specifically, the estimator $\\eta$ is defined as:\n",
"\n",
"\\begin{align*}\n",
" \\eta^2 = \\sum_{K\\in \\mathcal{T}_h(\\Omega)}\\left(h^2\\int_K \\vert \\lambda u_h + \\Delta u_h\\vert^2~\\mathrm{d}x\\right)\n",
"+ \\sum_{E\\in\\mathcal{F}_i}\\frac{h}{2} \\vert [\\nabla \\cdot \\mathbf{n}_E ]\\vert^2~\\mathrm{d}s\n",
"\\end{align*}\n",
"\n",
"where $\\mathcal{T}_h$ is the collection of cells in the mesh, $\\mathcal{F}_i$ the collection of interior facets\n",
"(those connected to two cells)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "19",
"metadata": {},
"outputs": [],
"source": [
"def mark_cells(uh_r: dolfinx.fem.Function, lam: float):\n",
" mesh = uh_r.function_space.mesh\n",
" W = dolfinx.fem.functionspace(mesh, (\"DG\", 0))\n",
" w = ufl.TestFunction(W)\n",
" eta_squared = dolfinx.fem.Function(W)\n",
" f = dolfinx.fem.Constant(mesh, 1.0)\n",
" h = dolfinx.fem.Function(W)\n",
" h.x.array[:] = mesh.h(mesh.topology.dim, np.arange(len(h.x.array), dtype=np.int32))\n",
" n = ufl.FacetNormal(mesh)\n",
"\n",
" G = ( # compute cellwise error estimator\n",
" ufl.inner(h**2 * (f + ufl.div(ufl.grad(uh_r))) ** 2, w) * ufl.dx\n",
" + ufl.inner(h(\"+\") / 2 * ufl.jump(ufl.grad(uh_r), n) ** 2, w(\"+\")) * ufl.dS\n",
" + ufl.inner(h(\"-\") / 2 * ufl.jump(ufl.grad(uh_r), n) ** 2, w(\"-\")) * ufl.dS\n",
" )\n",
" dolfinx.fem.petsc.assemble_vector(eta_squared.x.petsc_vec, dolfinx.fem.form(G))\n",
" eta = dolfinx.fem.Function(W)\n",
" eta.x.array[:] = np.sqrt(eta_squared.x.array[:])\n",
"\n",
" eta_max = eta.x.petsc_vec.max()[1]\n",
"\n",
" theta = 0.5\n",
" should_refine = ufl.conditional(ufl.gt(eta, theta * eta_max), 1, 0)\n",
" markers = dolfinx.fem.Function(W)\n",
" ip = W.element.interpolation_points\n",
" if Version(dolfinx.__version__) < Version(\"0.10.0\"):\n",
" ip = ip()\n",
" markers.interpolate(dolfinx.fem.Expression(should_refine, ip))\n",
" return np.flatnonzero(np.isclose(markers.x.array.astype(np.int32), 1))"
]
},
{
"cell_type": "markdown",
"id": "20",
"metadata": {},
"source": [
"## Running the adaptive refinement algorithm\n",
"Next, we will run the adaptive mesh refinement algorithm."
]
},
{
"cell_type": "markdown",
"id": "21",
"metadata": {},
"source": [
"We will track the progress of the adaptive mesh refinement as a GIF."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "22",
"metadata": {},
"outputs": [],
"source": [
"plotter = pyvista.Plotter()\n",
"plotter.open_gif(\"amr.gif\", fps=1)"
]
},
{
"cell_type": "markdown",
"id": "23",
"metadata": {
"lines_to_next_cell": 2
},
"source": [
"We make a convenience function to attach the relevant data to the plotter at a given\n",
"refinement step."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "24",
"metadata": {
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"def write_frame(plotter: pyvista.Plotter, uh_r: dolfinx.fem.Function):\n",
" # Scale uh_r to be consistent between refinement steps, as it can be multiplied by -1\n",
" uh_r_min = curved_mesh.comm.allreduce(uh_r.x.array.min(), op=MPI.MIN)\n",
" uh_r_max = curved_mesh.comm.allreduce(uh_r.x.array.max(), op=MPI.MAX)\n",
" uh_sign = np.sign(uh_r_min)\n",
" if np.isclose(uh_sign, 0):\n",
" uh_sign = np.sign(uh_r_max)\n",
" assert not np.isclose(uh_sign, 0), \"uh_r has zero values, cannot determine sign.\"\n",
" uh_r.x.array[:] *= uh_sign\n",
"\n",
" # Update plot with refined mesh\n",
" grid = pyvista.UnstructuredGrid(*dolfinx.plot.vtk_mesh(mesh))\n",
" curved_grid = pyvista.UnstructuredGrid(*dolfinx.plot.vtk_mesh(uh_r.function_space))\n",
" curved_grid.point_data[\"u\"] = uh_r.x.array\n",
" curved_grid = curved_grid.tessellate()\n",
" curved_actor = plotter.add_mesh(\n",
" curved_grid,\n",
" show_edges=False,\n",
" )\n",
"\n",
" actor = plotter.add_mesh(grid, style=\"wireframe\", color=\"black\")\n",
" plotter.view_xy()\n",
" plotter.write_frame()\n",
" plotter.remove_actor(actor)\n",
" plotter.remove_actor(curved_actor)"
]
},
{
"cell_type": "markdown",
"id": "25",
"metadata": {},
"source": [
"We set some parameters for checking convergence of the algorithm, and provide the exact eigenvalue\n",
"for comparison.\n",
"```{admonition} Using ngsPETSc for mesh refinement\n",
"In `ngsPETSc`, we provide the function `GeometricModel.refineMarkedElements` which we\n",
"pass the entities we would like to refine, and the topological dimensions of those entities.\n",
"The function returns a refined mesh, with corresponding cell and facet markers extracted from\n",
"the NetGen model.\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "26",
"metadata": {
"tags": [
"scroll-output"
]
},
"outputs": [],
"source": [
"max_iterations = 15\n",
"exact = 3.375610652693620492628**2\n",
"termination_criteria = 1e-5\n",
"for i in range(max_iterations):\n",
" lam, uh_r, _ = solve(curved_mesh, ft, region_map)\n",
"\n",
" relative_error = (lam - exact) / abs(exact)\n",
" PETSc.Sys.Print(\n",
" f\"Iteration {i + 1}/{max_iterations}, {lam=:.5e}, {exact=:.5e}, {relative_error=:.2e}\"\n",
" )\n",
"\n",
" cells_to_mark = mark_cells(uh_r, lam)\n",
" mesh, (_, ft) = geoModel.refineMarkedElements(mesh.topology.dim, cells_to_mark)\n",
" curved_mesh = geoModel.curveField(order)\n",
" write_frame(plotter, uh_r)\n",
"\n",
" if relative_error < termination_criteria:\n",
" PETSc.Sys.Print(f\"Converged in {i + 1} iterations.\")\n",
" break\n",
"plotter.close()"
]
},
{
"cell_type": "markdown",
"id": "27",
"metadata": {},
"source": [
"<img src=\"./amr.gif\" alt=\"gif\" class=\"bg-primary mb-1\" width=\"800px\">"
]
}
],
"metadata": {
"jupytext": {
"cell_metadata_filter": "tags,-all",
"formats": "ipynb,py:light",
"main_language": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
================================================
FILE: chapter2/amr.py
================================================
# ---
# jupyter:
# jupytext:
# cell_metadata_filter: tags,-all
# formats: ipynb,py:light
# text_representation:
# extension: .py
# format_name: light
# format_version: '1.5'
# jupytext_version: 1.19.1
# ---
# # Adaptive mesh refinement with NetGen and DOLFINx
#
# Author: Jørgen S. Dokken
#
# ```{admonition} NetGen and linux/arm64
# NetGen is not available on PyPi on linux/arm64, so to run this tutorial on such machine, please use the
# docker image [ghcr.io/jorgensd/dolfinx-tutorial:release](https://github.com/jorgensd/dolfinx-tutorial/pkgs/container/dolfinx-tutorial/489387776?tag=release).
# You can also install NetGen from source. See the [Dockerfile](https://github.com/jorgensd/dolfinx-tutorial/blob/main/docker/Dockerfile) for instructions.
# ```
# In this tutorial, we will consider an adaptive mesh refinement method, applied to
# the Laplace eigenvalue problem.
# This demo is an adaptation of [Firedrake - Adaptive Mesh Refinement](https://www.firedrakeproject.org/firedrake/demos/netgen_mesh.py.html).
# In this tutorial we will use the mesh generator [NetGen](https://ngsolve.org/) from NGSolve.
# First, we import the packages needed for this demo:
# +
from mpi4py import MPI
from petsc4py import PETSc
from slepc4py import SLEPc
from packaging.version import Version
import dolfinx.fem.petsc
import numpy as np
import ufl
import pyvista
import ngsPETSc.utils.fenicsx as ngfx
from netgen.geom2d import SplineGeometry
# -
# ## Generating a higher-order mesh with NetGen
# Next, we generate a PacMan-like geometry using NetGen.
geo = SplineGeometry()
pnts = [(0, 0), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1)]
p1, p2, p3, p4, p5, p6, p7, p8 = [geo.AppendPoint(*pnt) for pnt in pnts]
curves = [
[["line", p1, p2], "line"],
[["spline3", p2, p3, p4], "curve"],
[["spline3", p4, p5, p6], "curve"],
[["spline3", p6, p7, p8], "curve"],
[["line", p8, p1], "line"],
]
for c, bc in curves:
geo.Append(c, bc=bc)
# ## Loading a mesh into DOLFINx
# The ngsPETSc package provides a communication layer between NetGen and DOLFINx.
# We initialize this layer by passing in a NetGen-model, as well as an MPI communicator,
# which will be used to distribute the mesh.
geoModel = ngfx.GeometricModel(geo, MPI.COMM_WORLD)
# Next, we generate the mesh with the function :py:func:`ngsPETSc.utils.fenicsx.GeometricModel.model_to_mesh`.
# Which takes in the target geometric dimension of the mesh (2 for triangular meshes, 3 for tetrahedral), the
# maximum mesh size (`hmax`) and a few optional parameters.
mesh, (ct, ft), region_map = geoModel.model_to_mesh(gdim=2, hmax=0.5)
# We use pyvista to visualize the mesh.
# + tags=["hide-input"]
grid = pyvista.UnstructuredGrid(*dolfinx.plot.vtk_mesh(mesh))
grid.cell_data["ct"] = ct.values
plotter = pyvista.Plotter()
plotter.add_mesh(
grid, show_edges=True, scalars="ct", cmap="blues", show_scalar_bar=False
)
plotter.view_xy()
if not pyvista.OFF_SCREEN:
plotter.show()
# -
# We have read in any cell and facet markers that have been defined in the NetGen model,
# as well as a map from their names to their integer ids in `ct`, `ft` and `region_map` respectively.
# We can curve the grids with the command `curveField`.
# In this example, we use third order Lagrange elements to represent the geometry.
order = 3
curved_mesh = geoModel.curveField(order)
# Again, we visualize the curved mesh with pyvista.
# + tags=["hide-input"]
curved_grid = pyvista.UnstructuredGrid(*dolfinx.plot.vtk_mesh(curved_mesh))
curved_grid.cell_data["ct"] = ct.values
plotter = pyvista.Plotter()
plotter.add_mesh(
curved_grid, show_edges=False, scalars="ct", cmap="blues", show_scalar_bar=False
)
plotter.add_mesh(grid, style="wireframe", color="black")
plotter.view_xy()
if not pyvista.OFF_SCREEN:
plotter.show()
# -
# ## Solving the eigenvalue problem
# In this section we will solve the eigenvalue problem:
#
# Find $u_h\in H_0^1(\Omega)$ and $\lambda\in\mathbb{R}$ such that
#
# $$
# \begin{align}
# \int_\Omega \nabla u \cdot \nabla v~\mathrm{d} x &= \lambda \int_\Omega u v~\mathrm{d} x \qquad
# \forall v \in H_0^1(\Omega).
# \end{align}
# $$
# Next, we define a convenience function to solve the eigenvalue problem using [SLEPc](https://slepc.upv.es/)
# given a discretized domain, its facet markers and the region map.
def solve(
mesh: dolfinx.mesh.Mesh,
facet_tags: dolfinx.mesh.MeshTags,
region_map: dict[tuple[int, str], tuple[int, ...]],
) -> tuple[float, dolfinx.fem.Function, dolfinx.fem.Function]:
# We define the lhs and rhs bilinear forms
V = dolfinx.fem.functionspace(mesh, ("Lagrange", 3))
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
a = ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx
m = ufl.inner(u, v) * ufl.dx
# We identify the boundary facets and their corresponding dofs
straight_facets = facet_tags.indices[
np.isin(facet_tags.values, region_map[(1, "line")])
]
curved_facets = facet_tags.indices[
np.isin(facet_tags.values, region_map[(1, "curve")])
]
boundary_facets = np.concatenate([straight_facets, curved_facets])
mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim)
boundary_dofs = dolfinx.fem.locate_dofs_topological(
V, mesh.topology.dim - 1, boundary_facets
)
# We create a zero boundary condition for these dofs to be in the suitable space, and
# set up the discrete matrices `A` and `M`
bc = dolfinx.fem.dirichletbc(0.0, boundary_dofs, V)
A = dolfinx.fem.petsc.assemble_matrix(dolfinx.fem.form(a), bcs=[bc])
A.assemble()
if Version(dolfinx.__version__) < Version("0.10.0"):
diag_kwargs = {"diagonal": 0.0}
else:
diag_kwargs = {"diag": 0.0}
M = dolfinx.fem.petsc.assemble_matrix(dolfinx.fem.form(m), bcs=[bc], **diag_kwargs)
M.assemble()
# Next, we define the SLEPc Eigenvalue Problem Solver (EPS), and set up to use a shift
# and invert (SINVERT) spectral transformation where the preconditioner factorisation
# is computed using [MUMPS](https://mumps-solver.org/index.php).
E = SLEPc.EPS().create(mesh.comm)
E.setType(SLEPc.EPS.Type.ARNOLDI)
E.setProblemType(SLEPc.EPS.ProblemType.GHEP)
E.setDimensions(1, SLEPc.DECIDE)
E.setOperators(A, M)
ST = E.getST()
ST.setType(SLEPc.ST.Type.SINVERT)
PC = ST.getKSP().getPC()
PC.setType("lu")
PC.setFactorSolverType("mumps")
E.setST(ST)
E.solve()
assert E.getConvergedReason() >= 0, "Eigenvalue solver did not converge"
# We get the real and imaginary parts of the first eigenvector along with the eigenvalue.
uh_r = dolfinx.fem.Function(V)
uh_i = dolfinx.fem.Function(V)
lam = E.getEigenpair(0, uh_r.x.petsc_vec, uh_i.x.petsc_vec)
E.destroy()
uh_r.x.scatter_forward()
uh_i.x.scatter_forward()
return (lam, uh_r, uh_i)
# ## Error-indicator
# In this example, we will use an error-indicator $\eta$ to decide what cells should be refined.
# Specifically, the estimator $\eta$ is defined as:
#
# \begin{align*}
# \eta^2 = \sum_{K\in \mathcal{T}_h(\Omega)}\left(h^2\int_K \vert \lambda u_h + \Delta u_h\vert^2~\mathrm{d}x\right)
# + \sum_{E\in\mathcal{F}_i}\frac{h}{2} \vert [\nabla \cdot \mathbf{n}_E ]\vert^2~\mathrm{d}s
# \end{align*}
#
# where $\mathcal{T}_h$ is the collection of cells in the mesh, $\mathcal{F}_i$ the collection of interior facets
# (those connected to two cells).
def mark_cells(uh_r: dolfinx.fem.Function, lam: float):
mesh = uh_r.function_space.mesh
W = dolfinx.fem.functionspace(mesh, ("DG", 0))
w = ufl.TestFunction(W)
eta_squared = dolfinx.fem.Function(W)
f = dolfinx.fem.Constant(mesh, 1.0)
h = dolfinx.fem.Function(W)
h.x.array[:] = mesh.h(mesh.topology.dim, np.arange(len(h.x.array), dtype=np.int32))
n = ufl.FacetNormal(mesh)
G = ( # compute cellwise error estimator
ufl.inner(h**2 * (f + ufl.div(ufl.grad(uh_r))) ** 2, w) * ufl.dx
+ ufl.inner(h("+") / 2 * ufl.jump(ufl.grad(uh_r), n) ** 2, w("+")) * ufl.dS
+ ufl.inner(h("-") / 2 * ufl.jump(ufl.grad(uh_r), n) ** 2, w("-")) * ufl.dS
)
dolfinx.fem.petsc.assemble_vector(eta_squared.x.petsc_vec, dolfinx.fem.form(G))
eta = dolfinx.fem.Function(W)
eta.x.array[:] = np.sqrt(eta_squared.x.array[:])
eta_max = eta.x.petsc_vec.max()[1]
theta = 0.5
should_refine = ufl.conditional(ufl.gt(eta, theta * eta_max), 1, 0)
markers = dolfinx.fem.Function(W)
ip = W.element.interpolation_points
if Version(dolfinx.__version__) < Version("0.10.0"):
ip = ip()
markers.interpolate(dolfinx.fem.Expression(should_refine, ip))
return np.flatnonzero(np.isclose(markers.x.array.astype(np.int32), 1))
# ## Running the adaptive refinement algorithm
# Next, we will run the adaptive mesh refinement algorithm.
# We will track the progress of the adaptive mesh refinement as a GIF.
plotter = pyvista.Plotter()
plotter.open_gif("amr.gif", fps=1)
# We make a convenience function to attach the relevant data to the plotter at a given
# refinement step.
# + tags=["hide-input"]
def write_frame(plotter: pyvista.Plotter, uh_r: dolfinx.fem.Function):
# Scale uh_r to be consistent between refinement steps, as it can be multiplied by -1
uh_r_min = curved_mesh.comm.allreduce(uh_r.x.array.min(), op=MPI.MIN)
uh_r_max = curved_mesh.comm.allreduce(uh_r.x.array.max(), op=MPI.MAX)
uh_sign = np.sign(uh_r_min)
if np.isclose(uh_sign, 0):
uh_sign = np.sign(uh_r_max)
assert not np.isclose(uh_sign, 0), "uh_r has zero values, cannot determine sign."
uh_r.x.array[:] *= uh_sign
# Update plot with refined mesh
grid = pyvista.UnstructuredGrid(*dolfinx.plot.vtk_mesh(mesh))
curved_grid = pyvista.UnstructuredGrid(*dolfinx.plot.vtk_mesh(uh_r.function_space))
curved_grid.point_data["u"] = uh_r.x.array
curved_grid = curved_grid.tessellate()
curved_actor = plotter.add_mesh(
curved_grid,
show_edges=False,
)
actor = plotter.add_mesh(grid, style="wireframe", color="black")
plotter.view_xy()
plotter.write_frame()
plotter.remove_actor(actor)
plotter.remove_actor(curved_actor)
# -
# We set some parameters for checking convergence of the algorithm, and provide the exact eigenvalue
# for comparison.
# ```{admonition} Using ngsPETSc for mesh refinement
# In `ngsPETSc`, we provide the function `GeometricModel.refineMarkedElements` which we
# pass the entities we would like to refine, and the topological dimensions of those entities.
# The function returns a refined mesh, with corresponding cell and facet markers extracted from
# the NetGen model.
# ```
# + tags=["scroll-output"]
max_iterations = 15
exact = 3.375610652693620492628**2
termination_criteria = 1e-5
for i in range(max_iterations):
lam, uh_r, _ = solve(curved_mesh, ft, region_map)
relative_error = (lam - exact) / abs(exact)
PETSc.Sys.Print(
f"Iteration {i + 1}/{max_iterations}, {lam=:.5e}, {exact=:.5e}, {relative_error=:.2e}"
)
cells_to_mark = mark_cells(uh_r, lam)
mesh, (_, ft) = geoModel.refineMarkedElements(mesh.topology.dim, cells_to_mark)
curved_mesh = geoModel.curveField(order)
write_frame(plotter, uh_r)
if relative_error < termination_criteria:
PETSc.Sys.Print(f"Converged in {i + 1} iterations.")
break
plotter.close()
# -
# <img src="./amr.gif" alt="gif" class="bg-primary mb-1" width="800px">
================================================
FILE: chapter2/bdforces_lv4
================================================
# timestep time bdc horiz vert
0 0.0000000000E+00 2 0.0000000000E+00 0.0000000000E+00
1 3.1250000000E-04 2 1.3726547634E-01 -2.3688715430E-04
2 9.3750000000E-04 2 1.4039968314E-01 -2.3605983416E-04
3 1.5625000000E-03 2 1.4235406913E-01 -2.3460352800E-04
4 2.1875000000E-03 2 1.4402969709E-01 -2.3336938713E-04
5 2.8125000000E-03 2 1.4545447740E-01 -2.3229564448E-04
6 3.4375000000E-03 2 1.4674958894E-01 -2.3140917781E-04
7 4.0625000000E-03 2 1.4793014505E-01 -2.3065928772E-04
8 4.6875000000E-03 2 1.4903278329E-01 -2.3003337267E-04
9 5.3125000000E-03 2 1.5006676841E-01 -2.2950123817E-04
10 5.9375000000E-03 2 1.5104753385E-01 -2.2905035339E-04
11 6.5625000000E-03 2 1.5198113488E-01 -2.2866272925E-04
12 7.1875000000E-03 2 1.5287551488E-01 -2.2832946794E-04
13 7.8125000000E-03 2 1.5373481415E-01 -2.2803967427E-04
14 8.4375000000E-03 2 1.5456370627E-01 -2.2778743940E-04
15 9.0625000000E-03 2 1.5536512932E-01 -2.2756604346E-04
16 9.6875000000E-03 2 1.5614210551E-01 -2.2737160850E-04
17 1.0312500000E-02 2 1.5689678714E-01 -2.2719989899E-04
18 1.0937500000E-02 2 1.5763126806E-01 -2.2704834106E-04
19 1.1562500000E-02 2 1.5834716907E-01 -2.2691419297E-04
20 1.2187500000E-02 2 1.5904601739E-01 -2.2679570183E-04
21 1.2812500000E-02 2 1.5972906332E-01 -2.2669103229E-04
22 1.3437500000E-02 2 1.6039746526E-01 -2.2659894817E-04
23 1.4062500000E-02 2 1.6105220830E-01 -2.2651817004E-04
24 1.4687500000E-02 2 1.6169419817E-01 -2.2644778862E-04
25 1.5312500000E-02 2 1.6232422557E-01 -2.2638687157E-04
26 1.5937500000E-02 2 1.6294301590E-01 -2.2633471829E-04
27 1.6562500000E-02 2 1.6355121432E-01 -2.2629061796E-04
28 1.7187500000E-02 2 1.6414941324E-01 -2.2625400753E-04
29 1.7812500000E-02 2 1.6473814670E-01 -2.2622432031E-04
30 1.8437500000E-02 2 1.6531790634E-01 -2.2620108588E-04
31 1.9062500000E-02 2 1.6588913991E-01 -2.2618383554E-04
32 1.9687500000E-02 2 1.6645226099E-01 -2.2617216494E-04
33 2.0312500000E-02 2 1.6700764924E-01 -2.2616567391E-04
34 2.0937500000E-02 2 1.6755565662E-01 -2.2616400691E-04
35 2.1562500000E-02 2 1.6809660830E-01 -2.2616681393E-04
36 2.2187500000E-02 2 1.6863080685E-01 -2.2617377840E-04
37 2.2812500000E-02 2 1.6915853325E-01 -2.2618458963E-04
38 2.3437500000E-02 2 1.6968004991E-01 -2.2619896194E-04
39 2.4062500000E-02 2 1.7019560161E-01 -2.2621661684E-04
40 2.4687500000E-02 2 1.7070541769E-01 -2.2623729490E-04
41 2.5312500000E-02 2 1.7120971295E-01 -2.2626074541E-04
42 2.5937500000E-02 2 1.7170868924E-01 -2.2628673222E-04
43 2.6562500000E-02 2 1.7220253626E-01 -2.2631502838E-04
44 2.7187500000E-02 2 1.7269143279E-01 -2.2634541917E-04
45 2.7812500000E-02 2 1.7317554736E-01 -2.2637769763E-04
46 2.8437500000E-02 2 1.7365503923E-01 -2.2641167014E-04
47 2.9062500000E-02 2 1.7413005892E-01 -2.2644714809E-04
48 2.9687500000E-02 2 1.7460074904E-01 -2.2648395541E-04
49 3.0312500000E-02 2 1.7506724469E-01 -2.2652192213E-04
50 3.0937500000E-02 2 1.7552967416E-01 -2.2656088834E-04
51 3.1562500000E-02 2 1.7598815929E-01 -2.2660070022E-04
52 3.2187500000E-02 2 1.7644281596E-01 -2.2664121261E-04
53 3.2812500000E-02 2 1.7689375450E-01 -2.2668228795E-04
54 3.3437500000E-02 2 1.7734108004E-01 -2.2672379515E-04
55 3.4062500000E-02 2 1.7778489285E-01 -2.2676560970E-04
56 3.4687500000E-02 2 1.7822528866E-01 -2.2680761406E-04
57 3.5312500000E-02 2 1.7866235892E-01 -2.2684969704E-04
58 3.5937500000E-02 2 1.7909619110E-01 -2.2689175341E-04
59 3.6562500000E-02 2 1.7952686888E-01 -2.2693368274E-04
60 3.7187500000E-02 2 1.7995447244E-01 -2.2697539213E-04
61 3.7812500000E-02 2 1.8037907860E-01 -2.2701679157E-04
62 3.8437500000E-02 2 1.8080076106E-01 -2.2705779864E-04
63 3.9062500000E-02 2 1.8121959054E-01 -2.2709833337E-04
64 3.9687500000E-02 2 1.8163563497E-01 -2.2713832234E-04
65 4.0312500000E-02 2 1.8204895965E-01 -2.2717769603E-04
66 4.0937500000E-02 2 1.8245962736E-01 -2.2721638818E-04
67 4.1562500000E-02 2 1.8286769852E-01 -2.2725433832E-04
68 4.2187500000E-02 2 1.8327323129E-01 -2.2729148856E-04
69 4.2812500000E-02 2 1.8367628172E-01 -2.2732778538E-04
70 4.3437500000E-02 2 1.8407690383E-01 -2.2736317834E-04
71 4.4062500000E-02 2 1.8447514971E-01 -2.2739762081E-04
72 4.4687500000E-02 2 1.8487106964E-01 -2.2743106899E-04
73 4.5312500000E-02 2 1.8526471216E-01 -2.2746348235E-04
74 4.5937500000E-02 2 1.8565612414E-01 -2.2749482338E-04
75 4.6562500000E-02 2 1.8604535088E-01 -2.2752505655E-04
76 4.7187500000E-02 2 1.8643243619E-01 -2.2755415047E-04
77 4.7812500000E-02 2 1.8681742244E-01 -2.2758207427E-04
78 4.8437500000E-02 2 1.8720035063E-01 -2.2760880134E-04
79 4.9062500000E-02 2 1.8758126045E-01 -2.2763430588E-04
80 4.9687500000E-02 2 1.8796019035E-01 -2.2765856497E-04
81 5.0312500000E-02 2 1.8833717757E-01 -2.2768155728E-04
82 5.0937500000E-02 2 1.8871225824E-01 -2.2770326400E-04
83 5.1562500000E-02 2 1.8908546734E-01 -2.2772366708E-04
84 5.2187500000E-02 2 1.8945683886E-01 -2.2774275088E-04
85 5.2812500000E-02 2 1.8982640576E-01 -2.2776050171E-04
86 5.3437500000E-02 2 1.9019420003E-01 -2.2777690631E-04
87 5.4062500000E-02 2 1.9056025275E-01 -2.2779195354E-04
88 5.4687500000E-02 2 1.9092459410E-01 -2.2780563366E-04
89 5.5312500000E-02 2 1.9128725342E-01 -2.2781793788E-04
90 5.5937500000E-02 2 1.9164825924E-01 -2.2782885867E-04
91 5.6562500000E-02 2 1.9200763927E-01 -2.2783838930E-04
92 5.7187500000E-02 2 1.9236542050E-01 -2.2784652529E-04
93 5.7812500000E-02 2 1.9272162918E-01 -2.2785326136E-04
94 5.8437500000E-02 2 1.9307629086E-01 -2.2785859464E-04
95 5.9062500000E-02 2 1.9342943041E-01 -2.2786252223E-04
96 5.9687500000E-02 2 1.9378107205E-01 -2.2786504232E-04
97 6.0312500000E-02 2 1.9413123939E-01 -2.2786615406E-04
98 6.0937500000E-02 2 1.9447995543E-01 -2.2786585670E-04
99 6.1562500000E-02 2 1.9482724259E-01 -2.2786415087E-04
100 6.2187500000E-02 2 1.9517312271E-01 -2.2786103756E-04
101 6.2812500000E-02 2 1.9551761711E-01 -2.2785651768E-04
102 6.3437500000E-02 2 1.9586074659E-01 -2.2785059371E-04
103 6.4062500000E-02 2 1.9620253144E-01 -2.2784326781E-04
104 6.4687500000E-02 2 1.9654299145E-01 -2.2783454288E-04
105 6.5312500000E-02 2 1.9688214596E-01 -2.2782442250E-04
106 6.5937500000E-02 2 1.9722001384E-01 -2.2781290988E-04
107 6.6562500000E-02 2 1.9755661352E-01 -2.2780000933E-04
108 6.7187500000E-02 2 1.9789196301E-01 -2.2778572533E-04
109 6.7812500000E-02 2 1.9822607992E-01 -2.2777006216E-04
110 6.8437500000E-02 2 1.9855898143E-01 -2.2775302504E-04
111 6.9062500000E-02 2 1.9889068437E-01 -2.2773461892E-04
112 6.9687500000E-02 2 1.9922120516E-01 -2.2771484892E-04
113 7.0312500000E-02 2 1.9955055988E-01 -2.2769372110E-04
114 7.0937500000E-02 2 1.9987876427E-01 -2.2767124072E-04
115 7.1562500000E-02 2 2.0020583370E-01 -2.2764741404E-04
116 7.2187500000E-02 2 2.0053178323E-01 -2.2762224652E-04
117 7.2812500000E-02 2 2.0085662760E-01 -2.2759574472E-04
118 7.3437500000E-02 2 2.0118038123E-01 -2.2756791498E-04
119 7.4062500000E-02 2 2.0150305824E-01 -2.2753876293E-04
120 7.4687500000E-02 2 2.0182467247E-01 -2.2750829593E-04
121 7.5312500000E-02 2 2.0214523746E-01 -2.2747651934E-04
122 7.5937500000E-02 2 2.0246476650E-01 -2.2744344039E-04
123 7.6562500000E-02 2 2.0278327258E-01 -2.2740906565E-04
124 7.7187500000E-02 2 2.0310076845E-01 -2.2737340107E-04
125 7.7812500000E-02 2 2.0341726660E-01 -2.2733645376E-04
126 7.8437500000E-02 2 2.0373277929E-01 -2.2729823011E-04
127 7.9062500000E-02 2 2.0404731851E-01 -2.2725873648E-04
128 7.9687500000E-02 2 2.0436089606E-01 -2.2721797963E-04
129 8.0312500000E-02 2 2.0467352347E-01 -2.2717596620E-04
130 8.0937500000E-02 2 2.0498521209E-01 -2.2713270241E-04
131 8.1562500000E-02 2 2.0529597304E-01 -2.2708819503E-04
132 8.2187500000E-02 2 2.0560581721E-01 -2.2704245027E-04
133 8.2812500000E-02 2 2.0591475533E-01 -2.2699547448E-04
134 8.3437500000E-02 2 2.0622279790E-01 -2.2694727412E-04
135 8.4062500000E-02 2 2.0652995525E-01 -2.2689785523E-04
136 8.4687500000E-02 2 2.0683623751E-01 -2.2684722440E-04
137 8.5312500000E-02 2 2.0714165462E-01 -2.2679538758E-04
138 8.5937500000E-02 2 2.0744621638E-01 -2.2674235078E-04
139 8.6562500000E-02 2 2.0774993237E-01 -2.2668812013E-04
140 8.7187500000E-02 2 2.0805281202E-01 -2.2663270146E-04
141 8.7812500000E-02 2 2.0835486462E-01 -2.2657610064E-04
142 8.8437500000E-02 2 2.0865609927E-01 -2.2651832347E-04
143 8.9062500000E-02 2 2.0895652492E-01 -2.2645937567E-04
144 8.9687500000E-02 2 2.0925615037E-01 -2.2639926266E-04
145 9.0312500000E-02 2 2.0955498428E-01 -2.2633799040E-04
146 9.0937500000E-02 2 2.0985303515E-01 -2.2627556368E-04
147 9.1562500000E-02 2 2.1015031135E-01 -2.2621198823E-04
148 9.2187500000E-02 2 2.1044682111E-01 -2.2614726941E-04
149 9.2812500000E-02 2 2.1074257252E-01 -2.2608141184E-04
150 9.3437500000E-02 2 2.1103757355E-01 -2.2601442121E-04
151 9.4062500000E-02 2 2.1133183203E-01 -2.2594630205E-04
152 9.4687500000E-02 2 2.1162535566E-01 -2.2587705937E-04
153 9.5312500000E-02 2 2.1191815203E-01 -2.2580669791E-04
154 9.5937500000E-02 2 2.1221022862E-01 -2.2573522249E-04
155 9.6562500000E-02 2 2.1250159276E-01 -2.2566263740E-04
156 9.7187500000E-02 2 2.1279225170E-01 -2.2558894725E-04
157 9.7812500000E-02 2 2.1308221256E-01 -2.2551415649E-04
158 9.8437500000E-02 2 2.1337148235E-01 -2.2543826938E-04
159 9.9062500000E-02 2 2.1366006798E-01 -2.2536128991E-04
160 9.9687500000E-02 2 2.1394797626E-01 -2.2528322279E-04
161 1.0031250000E-01 2 2.1423521387E-01 -2.2520407096E-04
162 1.0093750000E-01 2 2.1452178744E-01 -2.2512383943E-04
163 1.0156250000E-01 2 2.1480770346E-01 -2.2504253128E-04
164 1.0218750000E-01 2 2.1509296834E-01 -2.2496015030E-04
165 1.0281250000E-01 2 2.1537758840E-01 -2.2487670035E-04
166 1.0343750000E-01 2 2.1566156986E-01 -2.2479218483E-04
167 1.0406250000E-01 2 2.1594491886E-01 -2.2470660713E-04
168 1.0468750000E-01 2 2.1622764143E-01 -2.2461997060E-04
169 1.0531250000E-01 2 2.1650974355E-01 -2.2453227848E-04
170 1.0593750000E-01 2 2.1679123110E-01 -2.2444353385E-04
171 1.0656250000E-01 2 2.1707210986E-01 -2.2435373986E-04
172 1.0718750000E-01 2 2.1735238555E-01 -2.2426289948E-04
173 1.0781250000E-01 2 2.1763206381E-01 -2.2417101551E-04
174 1.0843750000E-01 2 2.1791115021E-01 -2.2407809073E-04
175 1.0906250000E-01 2 2.1818965021E-01 -2.2398412797E-04
176 1.0968750000E-01 2 2.1846756924E-01 -2.2388912962E-04
177 1.1031250000E-01 2 2.1874491262E-01 -2.2379309822E-04
178 1.1093750000E-01 2 2.1902168564E-01 -2.2369603629E-04
179 1.1156250000E-01 2 2.1929789347E-01 -2.2359794652E-04
180 1.1218750000E-01 2 2.1957354126E-01 -2.2349883051E-04
181 1.1281250000E-01 2 2.1984863406E-01 -2.2339869090E-04
182 1.1343750000E-01 2 2.2012317687E-01 -2.2329752972E-04
183 1.1406250000E-01 2 2.2039717462E-01 -2.2319534902E-04
184 1.1468750000E-01 2 2.2067063218E-01 -2.2309215043E-04
185 1.1531250000E-01 2 2.2094355436E-01 -2.2298793621E-04
186 1.1593750000E-01 2 2.2121594590E-01 -2.2288270804E-04
187 1.1656250000E-01 2 2.2148781149E-01 -2.2277646764E-04
188 1.1718750000E-01 2 2.2175915576E-01 -2.2266921640E-04
189 1.1781250000E-01 2 2.2202998328E-01 -2.2256095603E-04
190 1.1843750000E-01 2 2.2230029857E-01 -2.2245168824E-04
191 1.1906250000E-01 2 2.2257010608E-01 -2.2234141403E-04
192 1.1968750000E-01 2 2.2283941023E-01 -2.2223013511E-04
193 1.2031250000E-01 2 2.2310821536E-01 -2.2211785277E-04
194 1.2093750000E-01 2 2.2337652578E-01 -2.2200456741E-04
195 1.2156250000E-01 2 2.2364434574E-01 -2.2189028137E-04
196 1.2218750000E-01 2 2.2391167944E-01 -2.2177499489E-04
197 1.2281250000E-01 2 2.2417853103E-01 -2.2165870921E-04
198 1.2343750000E-01 2 2.2444490462E-01 -2.2154142515E-04
199 1.2406250000E-01 2 2.2471080425E-01 -2.2142314362E-04
200 1.2468750000E-01 2 2.2497623394E-01 -2.2130386538E-04
201 1.2531250000E-01 2 2.2524119765E-01 -2.2118359113E-04
202 1.2593750000E-01 2 2.2550569930E-01 -2.2106232171E-04
203 1.2656250000E-01 2 2.2576974277E-01 -2.2094005763E-04
204 1.2718750000E-01 2 2.2603333188E-01 -2.2081679924E-04
205 1.2781250000E-01 2 2.2629647043E-01 -2.2069254747E-04
206 1.2843750000E-01 2 2.2655916215E-01 -2.2056730193E-04
207 1.2906250000E-01 2 2.2682141077E-01 -2.2044106377E-04
208 1.2968750000E-01 2 2.2708321993E-01 -2.2031383289E-04
209 1.3031250000E-01 2 2.2734459328E-01 -2.2018560986E-04
210 1.3093750000E-01 2 2.2760553438E-01 -2.2005639413E-04
211 1.3156250000E-01 2 2.2786604681E-01 -2.1992618669E-04
212 1.3218750000E-01 2 2.2812613406E-01 -2.1979498712E-04
213 1.3281250000E-01 2 2.2838579
gitextract_b_9xem8d/ ├── .dockerignore ├── .github/ │ ├── actions/ │ │ └── install-dependencies/ │ │ └── action.yml │ ├── dependabot.yml │ └── workflows/ │ ├── book_stable.yml │ ├── deploy.yml │ ├── publish_docker.yml │ ├── test_nightly.yml │ └── test_stable.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .vscode/ │ ├── c_cpp_properties.json │ └── settings.json ├── Changelog.md ├── Dockerfile ├── README.md ├── _config.yml ├── _toc.yml ├── chapter1/ │ ├── complex_mode.ipynb │ ├── complex_mode.py │ ├── fundamentals.md │ ├── fundamentals_code.ipynb │ ├── fundamentals_code.py │ ├── membrane.md │ ├── membrane_code.ipynb │ ├── membrane_code.py │ ├── membrane_paraview.md │ ├── nitsche.ipynb │ └── nitsche.py ├── chapter2/ │ ├── advdiffreac.md │ ├── amr.ipynb │ ├── amr.py │ ├── bdforces_lv4 │ ├── diffusion_code.ipynb │ ├── diffusion_code.py │ ├── elasticity_scaling.md │ ├── heat_code.ipynb │ ├── heat_code.py │ ├── heat_equation.md │ ├── helmholtz.md │ ├── helmholtz_code.ipynb │ ├── helmholtz_code.py │ ├── hyperelasticity.ipynb │ ├── hyperelasticity.py │ ├── intro.md │ ├── linearelasticity.md │ ├── linearelasticity_code.ipynb │ ├── linearelasticity_code.py │ ├── navierstokes.md │ ├── nonlinpoisson.md │ ├── nonlinpoisson_code.ipynb │ ├── nonlinpoisson_code.py │ ├── ns_code1.ipynb │ ├── ns_code1.py │ ├── ns_code2.ipynb │ ├── ns_code2.py │ ├── pointvalues_lv4 │ ├── singular_poisson.ipynb │ └── singular_poisson.py ├── chapter3/ │ ├── component_bc.ipynb │ ├── component_bc.py │ ├── em.ipynb │ ├── em.py │ ├── multiple_dirichlet.ipynb │ ├── multiple_dirichlet.py │ ├── neumann_dirichlet_code.ipynb │ ├── neumann_dirichlet_code.py │ ├── robin_neumann_dirichlet.ipynb │ ├── robin_neumann_dirichlet.py │ ├── subdomains.ipynb │ ├── subdomains.py │ └── wire.ipe ├── chapter4/ │ ├── compiler_parameters.ipynb │ ├── compiler_parameters.py │ ├── convergence.ipynb │ ├── convergence.py │ ├── mixed_poisson.ipynb │ ├── mixed_poisson.py │ ├── newton-solver.ipynb │ ├── newton-solver.py │ ├── solvers.ipynb │ └── solvers.py ├── docker/ │ └── Dockerfile ├── fem.md ├── index.ipynb ├── jupyter_book.code-workspace ├── pyproject.toml ├── references.bib └── tox.ini
SYMBOL INDEX (66 symbols across 21 files)
FILE: chapter1/membrane_code.py
function on_boundary (line 134) | def on_boundary(x):
FILE: chapter2/amr.py
function solve (line 126) | def solve(
function mark_cells (line 205) | def mark_cells(uh_r: dolfinx.fem.Function, lam: float):
function write_frame (line 249) | def write_frame(plotter: pyvista.Plotter, uh_r: dolfinx.fem.Function):
FILE: chapter2/diffusion_code.py
function initial_condition (line 81) | def initial_condition(x, a=5):
FILE: chapter2/heat_code.py
class ExactSolution (line 76) | class ExactSolution:
method __init__ (line 77) | def __init__(self, alpha: float, beta: float, t: float):
method __call__ (line 82) | def __call__(self, x: npt.NDArray[numpy.floating]) -> npt.NDArray[nump...
FILE: chapter2/helmholtz_code.py
function delany_bazley_layer (line 132) | def delany_bazley_layer(f, rho0, c, sigma):
class MicrophonePressure (line 203) | class MicrophonePressure:
method __init__ (line 204) | def __init__(self, domain, microphone_position):
method compute_local_microphones (line 219) | def compute_local_microphones(
method listen (line 248) | def listen(
FILE: chapter2/hyperelasticity.py
function left (line 49) | def left(x):
function right (line 53) | def right(x):
FILE: chapter2/linearelasticity_code.py
function clamped_boundary (line 73) | def clamped_boundary(x):
function epsilon (line 99) | def epsilon(u):
function sigma (line 105) | def sigma(u):
FILE: chapter2/nonlinpoisson_code.py
function q (line 39) | def q(u):
function u_exact (line 62) | def u_exact(x):
FILE: chapter2/ns_code1.py
function walls (line 202) | def walls(x):
function inflow (line 215) | def inflow(x):
function outflow (line 230) | def outflow(x):
function epsilon (line 267) | def epsilon(u):
function sigma (line 272) | def sigma(u, p):
function u_exact (line 376) | def u_exact(x):
FILE: chapter2/ns_code2.py
class InletVelocity (line 256) | class InletVelocity:
method __init__ (line 257) | def __init__(self, t):
method __call__ (line 260) | def __call__(self, x):
FILE: chapter2/singular_poisson.py
function u_ex (line 53) | def u_ex(mod, x):
function setup_problem (line 57) | def setup_problem(
function compute_L2_error (line 153) | def compute_L2_error(uh: dolfinx.fem.Function) -> float:
FILE: chapter3/component_bc.py
function clamped_boundary (line 96) | def clamped_boundary(x):
function right (line 119) | def right(x):
function epsilon (line 148) | def epsilon(u):
function sigma (line 152) | def sigma(u):
FILE: chapter3/multiple_dirichlet.py
function u_exact (line 62) | def u_exact(x):
FILE: chapter3/neumann_dirichlet_code.py
function u_exact (line 136) | def u_exact(x):
function boundary_D (line 140) | def boundary_D(x):
FILE: chapter3/robin_neumann_dirichlet.py
function u_ex (line 152) | def u_ex(x):
class BoundaryCondition (line 211) | class BoundaryCondition:
method __init__ (line 212) | def __init__(self, type, marker, values):
method bc (line 228) | def bc(self):
method type (line 232) | def type(self):
FILE: chapter3/subdomains.py
function Omega_0 (line 64) | def Omega_0(x):
function Omega_1 (line 68) | def Omega_1(x):
function eval_kappa (line 173) | def eval_kappa(x):
function create_mesh (line 260) | def create_mesh(mesh, cell_type, prune_z=False):
FILE: chapter4/compiler_parameters.py
function compile_form (line 53) | def compile_form(space: str, degree: int, jit_options: Dict):
FILE: chapter4/convergence.py
function u_ex (line 57) | def u_ex(mod):
function solve_poisson (line 65) | def solve_poisson(N=10, degree=1):
function error_L2 (line 117) | def error_L2(uh, u_ex, degree_raise=3):
function error_infinity (line 196) | def error_infinity(u_h, u_ex):
FILE: chapter4/mixed_poisson.py
function u_ex (line 34) | def u_ex(mod, x):
function Gamma_D (line 56) | def Gamma_D(x):
function Gamma_N (line 65) | def Gamma_N(x):
function interpolate_facet_expression (line 182) | def interpolate_facet_expression(
class SchurInv (line 364) | class SchurInv:
method setUp (line 365) | def setUp(self, pc):
method apply (line 372) | def apply(self, pc, x, y):
method __del__ (line 375) | def __del__(self):
FILE: chapter4/newton-solver.py
function root_0 (line 60) | def root_0(x):
function root_1 (line 64) | def root_1(x):
function q (line 192) | def q(u):
function u_exact (line 202) | def u_exact(x):
FILE: chapter4/solvers.py
function u_ex (line 43) | def u_ex(mod):
Condensed preview — 88 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,987K chars).
[
{
"path": ".dockerignore",
"chars": 54,
"preview": "# Don't include the .git in the image. It's big!\n.git\n"
},
{
"path": ".github/actions/install-dependencies/action.yml",
"chars": 242,
"preview": "name: Install dependencies\n\nruns:\n using: composite\n steps:\n - name: Install apt dependencies and upgrade pip\n "
},
{
"path": ".github/dependabot.yml",
"chars": 669,
"preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
},
{
"path": ".github/workflows/book_stable.yml",
"chars": 1088,
"preview": "name: Test stable build of book\n\non:\n workflow_dispatch:\n workflow_call:\n pull_request:\n branches: [\"release\"]\n p"
},
{
"path": ".github/workflows/deploy.yml",
"chars": 1091,
"preview": "name: Publish book\n\non:\n push:\n branches:\n - \"release\"\n workflow_dispatch:\n\n # Weekly build on Mondays at 8 a"
},
{
"path": ".github/workflows/publish_docker.yml",
"chars": 3512,
"preview": "# Recipe based on: https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runn"
},
{
"path": ".github/workflows/test_nightly.yml",
"chars": 3185,
"preview": "name: Test against DOLFINx nightly build\n\n# Controls when the action will run.\non:\n pull_request:\n branches:\n -"
},
{
"path": ".github/workflows/test_stable.yml",
"chars": 2861,
"preview": "name: Test stable release\n\non:\n workflow_dispatch:\n workflow_call:\n pull_request:\n branches: [\"release\"]\n\njobs:\n "
},
{
"path": ".gitignore",
"chars": 108,
"preview": "_build\n*.pvd\n*.h5\n*.xdmf\n*.vtu\n*/.ipynb_checkpoints/*\n.ipynb_checkpoints/*\n**/.cache\n*.png\n*.pvtu\n*.msh\n*.bp"
},
{
"path": ".pre-commit-config.yaml",
"chars": 93,
"preview": "repos:\n- repo: https://github.com/kynan/nbstripout\n rev: 0.7.1\n hooks:\n - id: nbstripout"
},
{
"path": ".vscode/c_cpp_properties.json",
"chars": 739,
"preview": "{\n \"configurations\": [\n {\n \"name\": \"Linux\",\n \"includePath\": [\n \"/usr/incl"
},
{
"path": ".vscode/settings.json",
"chars": 1032,
"preview": "{\n \"clang_format_style set\": \"file\",\n \"editor.formatOnSave\": true,\n \"cornflakes.linter.executablePath\": \"/usr/l"
},
{
"path": "Changelog.md",
"chars": 5399,
"preview": "# Changelog\n\n## v0.10.0\n\n- Full refactoring of {py:class}`dolfinx.fem.petsc.NonlinearProblem`, which now uses the PETSc "
},
{
"path": "Dockerfile",
"chars": 471,
"preview": "FROM ghcr.io/jorgensd/dolfinx-tutorial:v0.10.0\n\n# create user with a home directory\nARG NB_USER=jovyan\nARG NB_UID=1000\n#"
},
{
"path": "README.md",
"chars": 3580,
"preview": "# The DOLFINx tutorial\n\n[. We start by opening the f"
},
{
"path": "chapter1/nitsche.ipynb",
"chars": 8136,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"id\": \"0\",\n \"metadata\": {},\n \"source\": [\n \"# Weak imposition of "
},
{
"path": "chapter1/nitsche.py",
"chars": 5352,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter2/advdiffreac.md",
"chars": 2658,
"preview": "# TO BE IMPLEMETED: A system of advection-diffusion-reaction equations\nAuthors: Hans Petter Langtangen and Anders Logg \n"
},
{
"path": "chapter2/amr.ipynb",
"chars": 16853,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"id\": \"0\",\n \"metadata\": {},\n \"source\": [\n \"# Adaptive mesh refin"
},
{
"path": "chapter2/amr.py",
"chars": 11507,
"preview": "# ---\n# jupyter:\n# jupytext:\n# cell_metadata_filter: tags,-all\n# formats: ipynb,py:light\n# text_representa"
},
{
"path": "chapter2/bdforces_lv4",
"chars": 742668,
"preview": "# timestep time bdc horiz vert\n0 0.0000000000E+00 2 0.0000000000E+00 0.0000000000E+00\n1 3.1250000000E-04 2 1.3726547634E"
},
{
"path": "chapter2/diffusion_code.ipynb",
"chars": 14497,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Diffusion of a Gaussian function\\"
},
{
"path": "chapter2/diffusion_code.py",
"chars": 9640,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter2/elasticity_scaling.md",
"chars": 2409,
"preview": "# Scaling\nAuthors: Anders Logg and Hans Petter Langtangen\n\nIt is often advantageous to scale a problem as it reduces the"
},
{
"path": "chapter2/heat_code.ipynb",
"chars": 11433,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# A known analytical solution\\n\",\n "
},
{
"path": "chapter2/heat_code.py",
"chars": 7328,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter2/heat_equation.md",
"chars": 4609,
"preview": "# The heat equation\nAuthors: Anders Logg and Hans Petter Langtangen\n\nMinor modifications by: Jørgen S. Dokken\n\nAs a firs"
},
{
"path": "chapter2/helmholtz.md",
"chars": 4663,
"preview": "# The Helmholtz equation\nAuthor: Antonio Baiano Svizzero \n \nThe study of computational acoustics is fundamental in fiel"
},
{
"path": "chapter2/helmholtz_code.ipynb",
"chars": 14074,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Implementation\\n\",\n \"Author: A"
},
{
"path": "chapter2/helmholtz_code.py",
"chars": 9417,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter2/hyperelasticity.ipynb",
"chars": 12546,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"id\": \"0\",\n \"metadata\": {},\n \"source\": [\n \"# Hyperelasticity\\n\","
},
{
"path": "chapter2/hyperelasticity.py",
"chars": 6904,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter2/intro.md",
"chars": 666,
"preview": "# A Gallery of finite element solvers\n\nThe goal of this chapter is to demonstrate how a range of important PDEs from sci"
},
{
"path": "chapter2/linearelasticity.md",
"chars": 4261,
"preview": "# The equations of linear elasticity\n\nAuthors: Anders Logg and Hans Petter Langtangen\n\nAnalysis of structures is one of "
},
{
"path": "chapter2/linearelasticity_code.ipynb",
"chars": 11812,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Implementation\\n\",\n \"Author: J"
},
{
"path": "chapter2/linearelasticity_code.py",
"chars": 7802,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter2/navierstokes.md",
"chars": 8728,
"preview": "# The Navier-Stokes equations\nAuthors: Anders Logg and Hans Petter Langtangen\n\nMinor modifications: Jørgen S. Dokken\n\nIn"
},
{
"path": "chapter2/nonlinpoisson.md",
"chars": 1751,
"preview": "# A nonlinear Poisson equation\nAuthors: Anders Logg and Hans Petter Langtangen\n\nWe shall now address how to solve nonlin"
},
{
"path": "chapter2/nonlinpoisson_code.ipynb",
"chars": 9427,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Implementation\\n\",\n \"\\n\",\n "
},
{
"path": "chapter2/nonlinpoisson_code.py",
"chars": 6440,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter2/ns_code1.ipynb",
"chars": 25589,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Test problem 1: Channel flow (Poi"
},
{
"path": "chapter2/ns_code1.py",
"chars": 18431,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter2/ns_code2.ipynb",
"chars": 30823,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"id\": \"0\",\n \"metadata\": {},\n \"source\": [\n \"# Test problem 2: Flo"
},
{
"path": "chapter2/ns_code2.py",
"chars": 21445,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter2/pointvalues_lv4",
"chars": 1436063,
"preview": "# timestep time x y type deriv value x y type deriv value ...\n0 0.0000000000E+00 1.50000E-01 1.99999E-01 3 0 0.000000000"
},
{
"path": "chapter2/singular_poisson.ipynb",
"chars": 12210,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"id\": \"0\",\n \"metadata\": {},\n \"source\": [\n \"# Singular Poisson pr"
},
{
"path": "chapter2/singular_poisson.py",
"chars": 7249,
"preview": "# ---\n# jupyter:\n# jupytext:\n# cell_metadata_filter: -all\n# formats: ipynb,py:light\n# text_representation:"
},
{
"path": "chapter3/component_bc.ipynb",
"chars": 9525,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Component-wise Dirichlet BC\\n\",\n "
},
{
"path": "chapter3/component_bc.py",
"chars": 5808,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter3/em.ipynb",
"chars": 18791,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Electromagnetics example\\n\",\n "
},
{
"path": "chapter3/em.py",
"chars": 13767,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter3/multiple_dirichlet.ipynb",
"chars": 6269,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Setting multiple Dirichlet condit"
},
{
"path": "chapter3/multiple_dirichlet.py",
"chars": 4198,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter3/neumann_dirichlet_code.ipynb",
"chars": 8561,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {\n \"tags\": []\n },\n \"source\": [\n \"# Combining Diri"
},
{
"path": "chapter3/neumann_dirichlet_code.py",
"chars": 5910,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter3/robin_neumann_dirichlet.ipynb",
"chars": 14982,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Setting multiple Dirichlet, Neuma"
},
{
"path": "chapter3/robin_neumann_dirichlet.py",
"chars": 10434,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter3/subdomains.ipynb",
"chars": 18746,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Defining subdomains for different"
},
{
"path": "chapter3/subdomains.py",
"chars": 12452,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter3/wire.ipe",
"chars": 7972,
"preview": "<?xml version=\"1.0\"?>\n<!DOCTYPE ipe SYSTEM \"ipe.dtd\">\n<ipe version=\"70107\" creator=\"Ipe 7.1.10\">\n<info created=\"D:202012"
},
{
"path": "chapter4/compiler_parameters.ipynb",
"chars": 7730,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"id\": \"0\",\n \"metadata\": {},\n \"source\": [\n \"# JIT options and vis"
},
{
"path": "chapter4/compiler_parameters.py",
"chars": 5079,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter4/convergence.ipynb",
"chars": 12436,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Error control: Computing converge"
},
{
"path": "chapter4/convergence.py",
"chars": 8651,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter4/mixed_poisson.ipynb",
"chars": 24104,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"id\": \"0\",\n \"metadata\": {},\n \"source\": [\n \"# Mixed Poisson with "
},
{
"path": "chapter4/mixed_poisson.py",
"chars": 16296,
"preview": "# ---\n# jupyter:\n# jupytext:\n# cell_metadata_filter: -all\n# formats: ipynb,py:light\n# text_representation:"
},
{
"path": "chapter4/newton-solver.ipynb",
"chars": 16974,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"id\": \"0\",\n \"metadata\": {},\n \"source\": [\n \"# Custom Newton solve"
},
{
"path": "chapter4/newton-solver.py",
"chars": 10404,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "chapter4/solvers.ipynb",
"chars": 8643,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Solver configuration\\n\",\n \"Aut"
},
{
"path": "chapter4/solvers.py",
"chars": 5855,
"preview": "# ---\n# jupyter:\n# jupytext:\n# formats: ipynb,py:light\n# text_representation:\n# extension: .py\n# f"
},
{
"path": "docker/Dockerfile",
"chars": 1988,
"preview": "# Execute from root of repo as: docker buildx build --platform=linux/arm64,linux/amd64 -f docker/Dockerfile . --progress"
},
{
"path": "fem.md",
"chars": 7505,
"preview": "# An overview of the FEniCS Project\n\nThe FEniCS project is a research and software project aimed at creating mathematica"
},
{
"path": "index.ipynb",
"chars": 3135,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"id\": \"0\",\n \"metadata\": {},\n \"source\": [\n \"# The FEniCSx tutoria"
},
{
"path": "jupyter_book.code-workspace",
"chars": 41,
"preview": "{\n\t\"folders\": [\n\t\t{\n\t\t\t\"path\": \".\"\n\t\t}\n\t}"
},
{
"path": "pyproject.toml",
"chars": 1062,
"preview": "[build-system]\nrequires = [\"setuptools>=64.4.0\", \"wheel\", \"pip>=22.3\", \"poetry-core\"]\nbuild-backend = \"setuptools.build_"
},
{
"path": "references.bib",
"chars": 5957,
"preview": "@book{Langtangen_Mardal_FEM_2019,\n author = {Langtangen, Hans Petter\n and Mardal, Kent-Andre},\n title"
},
{
"path": "tox.ini",
"chars": 44,
"preview": "[flake8]\nmax-line-length = 120\nignore = W503"
}
]
About this extraction
This page contains the full source code of the jorgensd/dolfinx-tutorial GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 88 files (2.8 MB), approximately 728.8k tokens, and a symbol index with 66 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.