From c5825d06d9f115f161daa32dfe3a6512f18d06eb Mon Sep 17 00:00:00 2001 From: jorgensd Date: Fri, 3 Oct 2025 23:09:02 +0200 Subject: [PATCH 1/6] Update membrane code with crosslinks --- chapter1/membrane_code.ipynb | 85 ++++++++++++++++++++++-------------- chapter1/membrane_code.py | 67 ++++++++++++++++++---------- 2 files changed, 97 insertions(+), 55 deletions(-) diff --git a/chapter1/membrane_code.ipynb b/chapter1/membrane_code.ipynb index 59c3b905..1e954d94 100644 --- a/chapter1/membrane_code.ipynb +++ b/chapter1/membrane_code.ipynb @@ -11,15 +11,16 @@ "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 geometrical identifier\n", - "- Use `ufl.SpatialCoordinate` to create a spatially varying function\n", - "- Interpolate a `ufl.Expression` into an appropriate function space\n", - "- Evaluate a `dolfinx.fem.Function` at any point $x$\n", + "- Create constant boundary conditions using a {py:func}`geometrical identifier`\n", + "- Use {py:class}`ufl.SpatialCoordinate` to create a spatially varying function\n", + "- Interpolate a {py:class}`ufl-Expression` 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/). We start by importing the gmsh-module and initializing it." + "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." ] }, { @@ -39,7 +40,10 @@ "id": "2", "metadata": {}, "source": [ - "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." + "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." ] }, { @@ -58,7 +62,10 @@ "id": "4", "metadata": {}, "source": [ - "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." + "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." ] }, { @@ -77,7 +84,8 @@ "id": "6", "metadata": {}, "source": [ - "Finally, we generate the two-dimensional mesh. We set a uniform mesh size by modifying the GMSH options." + "Finally, we generate the two-dimensional mesh.\n", + "We set a uniform mesh size by modifying the GMSH options." ] }, { @@ -98,25 +106,29 @@ "metadata": {}, "source": [ "# Interfacing with GMSH in DOLFINx\n", - "We will import the GMSH-mesh directly from GMSH into DOLFINx via the `dolfinx.io.gmsh` interface.\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` which takes in a `gmsh.model`\n", " and returns a {py:class}`dolfinx.io.gmsh.MeshData` object.\n", "2. {py:func}`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 `MeshData` object will contain a `dolfinx.mesh.Mesh`, under the attribute `mesh`.\n", + "The {py:class}`MeshData` object will contain a {py:class}`dolfinx.mesh.Mesh`,\n", + "under the attribute {py:attr}`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 `MeshData` object can also contain tags for all other `PhysicalGroups` that has been added to the mesh,\n", - "that being `vertex_tags`, `edge_tags`, `facet_tags` and `cell_tags`.\n", + "The {py:class}`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`, {py:attr}`facet_tags`,\n", + "{py:attr}`ridge_tags` and\n", + "{py:attr}`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 `model_to_mesh` will then load the mesh on the specified rank,\n", + "The {py:func}`model_to_mesh` will then load the mesh on the specified rank,\n", "and distribute it to the communicator using a mesh partitioner." ] }, @@ -165,7 +177,8 @@ "metadata": {}, "source": [ "## Defining a spatially varying load\n", - "The right hand side pressure function is represented using `ufl.SpatialCoordinate` and two constants, one for $\\beta$ and one for $R_0$." + "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$." ] }, { @@ -176,16 +189,8 @@ "outputs": [], "source": [ "import ufl\n", - "from dolfinx import default_scalar_type" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "14", - "metadata": {}, - "outputs": [], - "source": [ + "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", @@ -194,11 +199,15 @@ }, { "cell_type": "markdown", - "id": "15", + "id": "9f059bd4", "metadata": {}, "source": [ "## Create a Dirichlet boundary condition using geometrical conditions\n", - "The next step is to create the homogeneous boundary condition. As opposed to the [first tutorial](./fundamentals_code.ipynb) we will use `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$." + "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` 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$." ] }, { @@ -223,7 +232,9 @@ "id": "17", "metadata": {}, "source": [ - "As our Dirichlet condition is homogeneous (`u=0` on the whole boundary), we can initialize the `dolfinx.fem.dirichletbc` with a constant value, the degrees of freedom and the function space to apply the boundary condition on." + "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`." ] }, { @@ -271,8 +282,13 @@ "id": "21", "metadata": {}, "source": [ - "## Interpolation of a `ufl`-expression\n", - "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 `dolfinx.Expression`. The expression takes in any `ufl`-expression, and a set of points on the reference element. We will use the interpolation points of the space we want to interpolate in to.\n", + "## 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`.\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`\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." ] }, @@ -313,7 +329,7 @@ }, { "cell_type": "markdown", - "id": "25", + "id": "55b28bcd", "metadata": {}, "source": [ "Extract topology from mesh and create pyvista mesh" @@ -331,11 +347,14 @@ ] }, { - "cell_type": "markdown", - "id": "27", + "cell_type": "code", + "execution_count": null, + "id": "2d31e0df", "metadata": {}, + "outputs": [], "source": [ - "Set deflection values and add it to plotter" + "print(\"HEELo\")\n", + "# Set deflection values and add it to plotterss" ] }, { diff --git a/chapter1/membrane_code.py b/chapter1/membrane_code.py index 310522a7..0e66bb3e 100644 --- a/chapter1/membrane_code.py +++ b/chapter1/membrane_code.py @@ -19,15 +19,16 @@ # 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 geometrical identifier -# - Use `ufl.SpatialCoordinate` to create a spatially varying function -# - Interpolate a `ufl.Expression` into an appropriate function space -# - Evaluate a `dolfinx.fem.Function` at any point $x$ +# - Create constant boundary conditions using a {py:func}`geometrical identifier` +# - Use {py:class}`ufl.SpatialCoordinate` to create a spatially varying function +# - Interpolate a {py:class}`ufl-Expression` 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. +# 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 @@ -35,42 +36,53 @@ 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. +# 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. +# 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. +# 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 `dolfinx.io.gmsh` interface. +# 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` which takes in a `gmsh.model` # and returns a {py:class}`dolfinx.io.gmsh.MeshData` object. # 2. {py:func}`read_from_msh` which takes in a path to a `.msh`-file # and returns a {py:class}`dolfinx.io.gmsh.MeshData` object. # -# The `MeshData` object will contain a `dolfinx.mesh.Mesh`, under the attribute `mesh`. +# The {py:class}`MeshData` object will contain a {py:class}`dolfinx.mesh.Mesh`, +# under the attribute {py:attr}`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 `MeshData` object can also contain tags for all other `PhysicalGroups` that has been added to the mesh, -# that being `vertex_tags`, `edge_tags`, `facet_tags` and `cell_tags`. +# The {py:class}`MeshData` object can also contain tags for +# all other `PhysicalGroups` that has been added to the mesh, that being +# {py:attr}`cell_tags`, {py:attr}`facet_tags`, +# {py:attr}`ridge_tags` and +# {py:attr}`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 `model_to_mesh` will then load the mesh on the specified rank, +# The {py:func}`model_to_mesh` will then load the mesh on the specified rank, # and distribute it to the communicator using a mesh partitioner. # + @@ -95,8 +107,10 @@ # - # ## Defining a spatially varying load -# The right hand side pressure function is represented using `ufl.SpatialCoordinate` and two constants, one for $\beta$ and one for $R_0$. +# 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 @@ -104,9 +118,14 @@ 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 `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$. +# 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` 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 @@ -119,7 +138,9 @@ def on_boundary(x): 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 `dolfinx.fem.dirichletbc` with a constant value, the degrees of freedom and the function space to apply the boundary condition on. +# 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) @@ -139,8 +160,13 @@ def on_boundary(x): ) 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 `dolfinx.Expression`. The expression takes in any `ufl`-expression, and a set of points on the reference element. We will use the interpolation points of the space we want to interpolate in to. +# ## 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`. +# The expression takes in any UFL-expression, and a set of points on the reference element. +# We will use the {py:attr}`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)) @@ -155,15 +181,12 @@ def on_boundary(x): from dolfinx.plot import vtk_mesh import pyvista -pyvista.start_xvfb(0.1) -# - - # Extract topology from mesh and create pyvista mesh topology, cell_types, x = vtk_mesh(V) grid = pyvista.UnstructuredGrid(topology, cell_types, x) -# Set deflection values and add it to plotter +# Set deflection values and add it to plotterss # + grid.point_data["u"] = uh.x.array From 88282306716089329ff22a060d43c328698dac14 Mon Sep 17 00:00:00 2001 From: jorgensd Date: Sun, 5 Oct 2025 18:53:22 +0200 Subject: [PATCH 2/6] Ruff format --- chapter2/ns_code1.py | 1 + chapter4/compiler_parameters.py | 1 + index.ipynb | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/chapter2/ns_code1.py b/chapter2/ns_code1.py index 4f3f8557..627a4e30 100644 --- a/chapter2/ns_code1.py +++ b/chapter2/ns_code1.py @@ -371,6 +371,7 @@ def sigma(u, p): # We also interpolate the analytical solution into our function-space and create a variational formulation for the $L^2$-error. # + # + def u_exact(x): values = np.zeros((2, x.shape[1]), dtype=PETSc.ScalarType) diff --git a/chapter4/compiler_parameters.py b/chapter4/compiler_parameters.py index 529d825e..6536b670 100644 --- a/chapter4/compiler_parameters.py +++ b/chapter4/compiler_parameters.py @@ -49,6 +49,7 @@ # Next we generate a general function to assemble the mass matrix for a unit cube. Note that we use `dolfinx.fem.form` to compile the variational form. # For codes using `dolfinx.fem.petsc.LinearProblem`, you can supply `jit_options` as a keyword argument. + def compile_form(space: str, degree: int, jit_options: Dict): N = 10 mesh = create_unit_cube(MPI.COMM_WORLD, N, N, N) diff --git a/index.ipynb b/index.ipynb index 1cce4752..92713239 100644 --- a/index.ipynb +++ b/index.ipynb @@ -52,7 +52,9 @@ "source": [ "from dolfinx.fem import functionspace # Click `functionspace`\n", "from ufl import div, grad # Click `div` and `grad`\n", - "import numpy as np # Click `numpy`" + "import numpy as np # Click `numpy`\n", + "\n", + "print(functionspace, div, grad, np)" ] }, { From 4456d37aad321ef053b283a0f7fa1d0686792b03 Mon Sep 17 00:00:00 2001 From: jorgensd Date: Sun, 5 Oct 2025 18:54:51 +0200 Subject: [PATCH 3/6] Remove debug print --- chapter1/membrane_code.py | 1 - chapter2/ns_code1.ipynb | 8 ++++++-- chapter4/compiler_parameters.ipynb | 8 ++++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/chapter1/membrane_code.py b/chapter1/membrane_code.py index 8f89fe7b..9cd46c42 100644 --- a/chapter1/membrane_code.py +++ b/chapter1/membrane_code.py @@ -185,7 +185,6 @@ def on_boundary(x): topology, cell_types, x = vtk_mesh(V) grid = pyvista.UnstructuredGrid(topology, cell_types, x) -print("HEELo") # Set deflection values and add it to plotterss # + diff --git a/chapter2/ns_code1.ipynb b/chapter2/ns_code1.ipynb index 63e093e8..2ca7bdc1 100644 --- a/chapter2/ns_code1.ipynb +++ b/chapter2/ns_code1.ipynb @@ -501,7 +501,9 @@ "cell_type": "code", "execution_count": null, "id": "26", - "metadata": {}, + "metadata": { + "lines_to_end_of_cell_marker": 2 + }, "outputs": [], "source": [ "from pathlib import Path\n", @@ -516,7 +518,9 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "lines_to_next_cell": 2 + }, "source": [ "We also interpolate the analytical solution into our function-space and create a variational formulation for the $L^2$-error.\n" ] diff --git a/chapter4/compiler_parameters.ipynb b/chapter4/compiler_parameters.ipynb index 7b58b736..39feeb91 100644 --- a/chapter4/compiler_parameters.ipynb +++ b/chapter4/compiler_parameters.ipynb @@ -23,7 +23,9 @@ "cell_type": "code", "execution_count": null, "id": "1", - "metadata": {}, + "metadata": { + "lines_to_end_of_cell_marker": 2 + }, "outputs": [], "source": [ "import pandas as pd\n", @@ -46,7 +48,9 @@ { "cell_type": "markdown", "id": "2", - "metadata": {}, + "metadata": { + "lines_to_next_cell": 2 + }, "source": [ "Next we generate a general function to assemble the mass matrix for a unit cube. Note that we use `dolfinx.fem.form` to compile the variational form.\n", "For codes using `dolfinx.fem.petsc.LinearProblem`, you can supply `jit_options` as a keyword argument." From e95d75be9ac5c233260e133e8a0f4a4f5beee414 Mon Sep 17 00:00:00 2001 From: jorgensd Date: Sun, 5 Oct 2025 18:55:00 +0200 Subject: [PATCH 4/6] Remove pritn --- chapter1/membrane_code.ipynb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/chapter1/membrane_code.ipynb b/chapter1/membrane_code.ipynb index e6f0a71c..acf3b05e 100644 --- a/chapter1/membrane_code.ipynb +++ b/chapter1/membrane_code.ipynb @@ -345,14 +345,11 @@ ] }, { - "cell_type": "code", - "execution_count": null, - "id": "2d31e0df", + "cell_type": "markdown", + "id": "b2b183e2", "metadata": {}, - "outputs": [], "source": [ - "print(\"HEELo\")\n", - "# Set deflection values and add it to plotterss" + "Set deflection values and add it to plotterss" ] }, { From 4d08f00c2ce74222edc26d84cc9c5dc177845da4 Mon Sep 17 00:00:00 2001 From: jorgensd Date: Sun, 5 Oct 2025 19:05:30 +0200 Subject: [PATCH 5/6] Finalize membrane --- chapter1/membrane_code.ipynb | 50 +++++++++++++++++++++++++---------- chapter1/membrane_code.py | 51 +++++++++++++++++++++++++----------- 2 files changed, 72 insertions(+), 29 deletions(-) diff --git a/chapter1/membrane_code.ipynb b/chapter1/membrane_code.ipynb index acf3b05e..6b1a8e14 100644 --- a/chapter1/membrane_code.ipynb +++ b/chapter1/membrane_code.ipynb @@ -330,7 +330,7 @@ "id": "55b28bcd", "metadata": {}, "source": [ - "Extract topology from mesh and create pyvista mesh" + "Extract topology from mesh and create {py:class}`pyvista.UnstructuredGrid`" ] }, { @@ -349,7 +349,7 @@ "id": "b2b183e2", "metadata": {}, "source": [ - "Set deflection values and add it to plotterss" + "Set deflection values and add it to plotter" ] }, { @@ -405,7 +405,8 @@ "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 finite element functions $u$ and $p$ at these points." + "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." ] }, { @@ -428,9 +429,15 @@ "id": "33", "metadata": {}, "source": [ - "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$.\n", - "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.\n", - "This is efficiently done by creating a bounding box tree of the cells of the mesh, allowing a quick recursive search through the mesh entities." + "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` and\n", + "{py:func}`pressure.eval` to obtain the set of values for all the points." ] }, { @@ -502,7 +523,8 @@ "id": "39", "metadata": {}, "source": [ - "As we now have an array of coordinates and two arrays of function values, we can use `matplotlib` to plot them" + "As we now have an array of coordinates and two arrays of function values,\n", + "we can use {py:mod}`matplotlib` to plot them" ] }, { @@ -533,7 +555,7 @@ "id": "41", "metadata": {}, "source": [ - "If executed in parallel as a python file, we save a plot per processor" + "If executed in parallel as a Python file, we save a plot per processor" ] }, { diff --git a/chapter1/membrane_code.py b/chapter1/membrane_code.py index 9cd46c42..e4dcf5c5 100644 --- a/chapter1/membrane_code.py +++ b/chapter1/membrane_code.py @@ -180,12 +180,12 @@ def on_boundary(x): from dolfinx.plot import vtk_mesh import pyvista -# Extract topology from mesh and create pyvista mesh +# 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 plotterss +# Set deflection values and add it to plotter # + grid.point_data["u"] = uh.x.array @@ -215,7 +215,8 @@ def on_boundary(x): # ## 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. +# 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) @@ -224,9 +225,15 @@ def on_boundary(x): 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 bounding box tree of the cells of the mesh, allowing a quick recursive search through the mesh entities. +# 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` and +# {py:func}`pressure.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 `matplotlib` to plot them +# As we now have an array of coordinates and two arrays of function values, +# we can use {py:mod}`matplotlib` to plot them # + import matplotlib.pyplot as plt @@ -278,7 +299,7 @@ def on_boundary(x): plt.legend() # - -# If executed in parallel as a python file, we save a plot per processor +# 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") From 6005abd1226966ad8544c12407e80f989b7d439c Mon Sep 17 00:00:00 2001 From: jorgensd Date: Sun, 5 Oct 2025 19:57:22 +0200 Subject: [PATCH 6/6] Last set of links + matplotlib intersphinx --- _config.yml | 23 ++++++++++++----------- chapter1/membrane_code.ipynb | 10 +++++----- chapter1/membrane_code.py | 10 +++++----- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/_config.yml b/_config.yml index 2f4c9428..32047621 100644 --- a/_config.yml +++ b/_config.yml @@ -42,19 +42,20 @@ sphinx: # 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] + 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.ext.autodoc" + - "sphinx.ext.intersphinx" - "sphinx_codeautolink" parse: diff --git a/chapter1/membrane_code.ipynb b/chapter1/membrane_code.ipynb index 6b1a8e14..fcc6945a 100644 --- a/chapter1/membrane_code.ipynb +++ b/chapter1/membrane_code.ipynb @@ -462,10 +462,10 @@ "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 `links(i)`.\n", + "`i`th point by calling {py:meth}`links(i)`.\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.select_colliding_cells`,\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", @@ -502,8 +502,8 @@ "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:func}`uh.eval` and\n", - "{py:func}`pressure.eval` to obtain the set of values for all the points." + "We can then call {py:meth}`uh.eval` and\n", + "{py:meth}`pressure.eval` to obtain the set of values for all the points." ] }, { @@ -524,7 +524,7 @@ "metadata": {}, "source": [ "As we now have an array of coordinates and two arrays of function values,\n", - "we can use {py:mod}`matplotlib` to plot them" + "we can use {py:mod}`matplotlib` to plot them" ] }, { diff --git a/chapter1/membrane_code.py b/chapter1/membrane_code.py index e4dcf5c5..09a58d8f 100644 --- a/chapter1/membrane_code.py +++ b/chapter1/membrane_code.py @@ -246,10 +246,10 @@ def on_boundary(x): # 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 `links(i)`. +# `i`th point by calling `links(i)`. # 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.select_colliding_cells`, +# {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, @@ -272,15 +272,15 @@ def on_boundary(x): 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:func}`uh.eval` and -# {py:func}`pressure.eval` to obtain the set of values for all the points. +# We can then call {py:meth}`uh.eval` and +# {py:meth}`pressure.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` to plot them +# we can use {py:mod}`matplotlib` to plot them # + import matplotlib.pyplot as plt