├── Problem8_HPC
├── update.png
├── meshim3D.png
├── accumulate.png
├── distribute.png
├── README.md
├── pbs_submit
├── poisson.py
└── load.sh
├── notes
└── fenicsx23_notes_RFA_IB.pdf
├── Problem6_Darcy
├── README.md
└── Darcy.py
├── Problem5_Navier-Stokes
├── README.md
└── navier_stokes.py
├── Problem2_Transient
├── README.md
├── utils.py
└── heat_eq.py
├── Problem3_Elasticity
├── README.md
├── Elasticity.py
└── Elasticity.ipynb
├── Problem1_Poisson
├── README.md
└── Poisson_basic.py
├── Problem4_Thermo-Elasticity
├── README.md
└── ThermoElastic.ipynb
├── Problem7_Helmholtz
├── readme.md
├── mesh_generation.py
├── plane_wave.py
├── utils.py
└── helmholtz.py
└── README.md
/Problem8_HPC/update.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IgorBaratta/FEniCSxCourse/HEAD/Problem8_HPC/update.png
--------------------------------------------------------------------------------
/Problem8_HPC/meshim3D.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IgorBaratta/FEniCSxCourse/HEAD/Problem8_HPC/meshim3D.png
--------------------------------------------------------------------------------
/Problem8_HPC/accumulate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IgorBaratta/FEniCSxCourse/HEAD/Problem8_HPC/accumulate.png
--------------------------------------------------------------------------------
/Problem8_HPC/distribute.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IgorBaratta/FEniCSxCourse/HEAD/Problem8_HPC/distribute.png
--------------------------------------------------------------------------------
/notes/fenicsx23_notes_RFA_IB.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IgorBaratta/FEniCSxCourse/HEAD/notes/fenicsx23_notes_RFA_IB.pdf
--------------------------------------------------------------------------------
/Problem6_Darcy/README.md:
--------------------------------------------------------------------------------
1 | # Darcy flow
2 |
3 |
4 | [](https://colab.research.google.com/github/IgorBaratta/FEniCSxCourse/blob/ICMC23/Problem6_Darcy/Darcy.ipynb)
5 |
--------------------------------------------------------------------------------
/Problem8_HPC/README.md:
--------------------------------------------------------------------------------
1 | # DOLFINx in Parallel with MPI
2 |
3 | [](https://colab.research.google.com/github/IgorBaratta/FEniCSxCourse/blob/ICMC23/Problem8_HPC/HPC.ipynb)
--------------------------------------------------------------------------------
/Problem5_Navier-Stokes/README.md:
--------------------------------------------------------------------------------
1 | # Navier-stokes problem
2 |
3 | [](https://colab.research.google.com/github/IgorBaratta/FEniCSxCourse/blob/ICMC23/Problem5_Navier-Stokes/navier_stokes.ipynb)
4 |
--------------------------------------------------------------------------------
/Problem2_Transient/README.md:
--------------------------------------------------------------------------------
1 | # Time-dependent heat equation
2 | As a first extension of the previous chapter's Poisson problem, we consider the time-dependent heat equation or the time-dependent diffusion equation. The Poisson equation, which describes the stationary distribution of heat in a body, is naturally extended to a time-dependent problem.
3 |
4 | [](https://colab.research.google.com/github/IgorBaratta/FEniCSxCourse/blob/ICMC23/Problem2_Transient/heat_eq.ipynb)
5 |
--------------------------------------------------------------------------------
/Problem8_HPC/pbs_submit:
--------------------------------------------------------------------------------
1 | # This is a script for submitting a job to a PBS (Portable Batch System) queue on a cluster.
2 | # The script requests 2 nodes with 40 CPUs each and specifies the walltime limit of 1 hour for the job.
3 |
4 | #PBS -N JobPar
5 | #PBS -l select=2:ncpus=40:nodetype=n40
6 | #PBS -l walltime=1:00:00
7 | #PBS -m ae
8 | #PBS -M igorbaratta@gmail.com
9 |
10 | WORK_DIR=~/FEniCSxCourse/Problem8_HPC
11 | source $WORK_DIR/load.sh
12 | mpirun -np 40 --hostfile $PBS_NODEFILE python3 $WORK_DIR/poisson.py --num_dofs 10000
13 |
14 |
--------------------------------------------------------------------------------
/Problem3_Elasticity/README.md:
--------------------------------------------------------------------------------
1 | # Linear Elasticity
2 |
3 | In this tutorial we
4 |
5 | 1. Present an implementation of the finite element discretization of the Navier-Poisson elastostatic problem
6 | 2. Create a non-trivial geometry and impose essential boundary conditions on a vector field
7 | 3. Visualize the solution using Paraview
8 | 4. Perform some postprocessing of the solution.
9 |
10 | [](https://colab.research.google.com/github/IgorBaratta/FEniCSxCourse/blob/ICMC23/Problem3_Elasticity/Elasticity.ipynb)
11 |
--------------------------------------------------------------------------------
/Problem1_Poisson/README.md:
--------------------------------------------------------------------------------
1 | # Poisson Problem
2 |
3 | In this first tutorial we
4 |
5 | 1. Present a basic implementation of the finite element solution of Poisson's problem
6 | 2. Import all necessary libraries to solve the problem with the FEniCSx platform
7 | 3. Solve the problem with different grid refinements
8 | 4. Visualize the solution using Paraview
9 | 5. Perform some postprocessing
10 |
11 |
12 |
13 | [](https://colab.research.google.com/github/IgorBaratta/FEniCSxCourse/blob/main/Problem1_Poisson/Poisson_basic.ipynb)
--------------------------------------------------------------------------------
/Problem4_Thermo-Elasticity/README.md:
--------------------------------------------------------------------------------
1 | # Thermo-Elasticity
2 |
3 | In this tutorial we will combine two of our previous solvers to model the thermo-elastic deformation of a bimaterial, i.e., a specimen made of two materials with different thermal expansion coefficients. The idea is to solve a problem involving two fields governed by different EDPs, namely, the temperature field $T$ and the displacement field $\mathbf{u}$.
4 |
5 | [](https://colab.research.google.com/github/IgorBaratta/FEniCSxCourse/blob/ICMC23/Problem4_Thermo-Elasticity/ThermoElastic.ipynb)
--------------------------------------------------------------------------------
/Problem7_Helmholtz/readme.md:
--------------------------------------------------------------------------------
1 | # Scattering of a plane wave by a penetrable circle
2 |
3 | This tutorial illustrates how to:
4 |
5 | * Solve PDEs with complex-valued fields,
6 | * Import and use high-order meshes from Gmsh
7 | * Compute the scattering of a plane wave by a homogeneous dielectric obstacle
8 | * Employ an "absorbing boundary conditions" to truncate the domain
9 | * Compare the approximation to the analytical solution
10 |
11 | [](https://colab.research.google.com/github/IgorBaratta/FEniCSxCourse/blob/ICMC23/Problem7_Helmholtz/helmholtz.ipynb)
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FEniCSx - Course
2 |
3 |
4 | ## Description
5 |
6 | This repository contains the python scripts and jupyter notebooks to be used in the FEniCSx course to be held at ICMC-USP, São Carlos, SP 30/01-03/02/2023.
7 |
8 | To clone the repository:
9 |
10 | ```
11 | git clone https://github.com/IgorBaratta/FEniCSxCourse.git -b ICMC23
12 | cd FEniCSxCourse/
13 | ```
14 |
15 |
16 | ## Lectures
17 |
18 | - [ ] Lecture 1: Introduction
19 | * [Poisson's problem](Problem1_Poisson/README.md)
20 | - [ ] Lecture 2: Classical 2nd order problems
21 | * [Transient heat conduction](Problem2_Transient/README.md)
22 | * [Elasticity](Problem3_Elasticity/README.md)
23 | - [ ] Lecture 3:
24 | * [Mixed problems](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file)
25 | - [ ] Lecture 4: Advanced topics
26 | * [Darcy's flow](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file)
27 | * [Helmholtz's equation](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file)
28 | - [ ] Lecture 5: High Performance Computing
29 | * [3D Poisson's problem - Preconditioners](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line)
30 |
--------------------------------------------------------------------------------
/Problem7_Helmholtz/mesh_generation.py:
--------------------------------------------------------------------------------
1 | from mpi4py import MPI
2 |
3 | try:
4 | import gmsh
5 | except ModuleNotFoundError:
6 | import sys
7 | print("This demo requires gmsh to be installed")
8 | sys.exit(0)
9 |
10 |
11 | def generate_mesh(filename: str, radius: int, length: int, h_elem: int, order: int, verbose: bool = False):
12 | if MPI.COMM_WORLD.rank == 0:
13 |
14 | gmsh.initialize()
15 | gmsh.model.add("helmholtz_domain")
16 | gmsh.option.setNumber("General.Terminal", verbose)
17 | # Set the mesh size
18 | gmsh.option.setNumber("Mesh.CharacteristicLengthMax", 0.75*h_elem)
19 |
20 | # Add scatterers
21 | c1 = gmsh.model.occ.addCircle(0.0, 0.0, 0.0, radius)
22 | gmsh.model.occ.addCurveLoop([c1], tag=c1)
23 | gmsh.model.occ.addPlaneSurface([c1], tag=c1)
24 |
25 | # Add domain
26 | r0 = gmsh.model.occ.addRectangle(-length/2, -length/2, 0.0, length, length)
27 | inclusive_rectangle, _ = gmsh.model.occ.fragment([(2, r0)], [(2, c1)])
28 |
29 | gmsh.model.occ.synchronize()
30 |
31 | # Add physical groups
32 | gmsh.model.addPhysicalGroup(2, [c1], tag=1)
33 | gmsh.model.addPhysicalGroup(2, [r0], tag=2)
34 |
35 | # Generate mesh
36 | gmsh.model.mesh.generate(2)
37 | gmsh.model.mesh.setOrder(order)
38 | gmsh.model.mesh.optimize("HighOrder")
39 | gmsh.write(filename)
40 |
41 | gmsh.finalize()
42 |
--------------------------------------------------------------------------------
/Problem7_Helmholtz/plane_wave.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import dolfinx
3 | import ufl
4 | from mpi4py import MPI
5 | from dolfinx.io import XDMFFile
6 |
7 | p = 2
8 | k0 = 10 * np.pi
9 |
10 | mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 100, 100)
11 | element = ufl.FiniteElement("Lagrange", ufl.triangle, p)
12 | n = ufl.FacetNormal(mesh)
13 |
14 | # Definition of function space
15 | V = dolfinx.fem.FunctionSpace(mesh, element)
16 |
17 |
18 | def incoming_wave(x):
19 | d = np.cos(theta) * x[0] + np.sin(theta) * x[1]
20 | return np.exp(1.0j * k0 * d)
21 |
22 |
23 | # Incoming wave
24 | theta = 0
25 | ui = dolfinx.fem.Function(V)
26 | ui.interpolate(incoming_wave)
27 | g = ufl.dot(ufl.grad(ui), n) + 1j * k0 * ui
28 |
29 | # Define variational problem
30 | u = ufl.TrialFunction(V)
31 | v = ufl.TestFunction(V)
32 |
33 | # Weak Form
34 | a = ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx \
35 | - k0**2 * ufl.inner(u, v) * ufl.dx \
36 | + 1j * k0 * ufl.inner(u, v) * ufl.ds
37 | L = ufl.inner(g, v) * ufl.ds
38 |
39 | opt = {"ksp_type": "preonly", "pc_type": "lu"}
40 | problem = dolfinx.fem.petsc.LinearProblem(a, L, petsc_options=opt)
41 | uh = problem.solve()
42 | uh.name = "u"
43 |
44 | # with XDMFFile(MPI.COMM_WORLD, "out.xdmf", "w") as file:
45 | # file.write_mesh(mesh)
46 | # file.write_function(uh)
47 |
48 |
49 | error = uinc - uh
50 | M = ufl.inner(error, error) * ufl.dx
51 | error_norm = dolfinx.fem.assemble_scalar(dolfinx.fem.form(M))
52 | print(error_norm)
--------------------------------------------------------------------------------
/Problem2_Transient/utils.py:
--------------------------------------------------------------------------------
1 | from matplotlib import pyplot
2 | from dolfinx import plot
3 | import pyvista
4 |
5 |
6 | def plot_mesh(mesh, cell_values=None, filename="file.html"):
7 | comm = mesh.comm
8 | if comm.size > 1:
9 | return
10 | pyvista.start_xvfb()
11 | grid = pyvista.UnstructuredGrid(*plot.create_vtk_mesh(mesh))
12 | plotter = pyvista.Plotter(notebook=True)
13 |
14 |
15 |
16 | if cell_values is not None:
17 | min_ = cell_values.x.array.min()
18 | max_ = cell_values.x.array.max()
19 | grid.cell_data["cell_values"] = cell_values.x.array
20 | viridis = pyplot.cm.get_cmap("viridis", 25)
21 | plotter.add_mesh(grid, cmap=viridis, show_edges=True, clim=[min_, max_])
22 | else:
23 | plotter.add_mesh(grid, show_edges=True)
24 |
25 | plotter.camera.zoom(2.0)
26 | plotter.view_xy()
27 | plotter.export_html(filename, backend="pythreejs")
28 | plotter.close()
29 |
30 |
31 | def plot_function(uh, filename):
32 | pyvista.start_xvfb(0.5)
33 | plotter = pyvista.Plotter(notebook=False, off_screen=True)
34 | V = uh.function_space
35 | topology, cells, geometry = plot.create_vtk_mesh(V.mesh)
36 | grid = pyvista.UnstructuredGrid(topology, cells, geometry)
37 | grid.point_data["uh"] = uh.x.array
38 | viridis = pyplot.cm.get_cmap("viridis", 25)
39 |
40 | min_ = uh.x.array.min()
41 | max_ = uh.x.array.max()
42 |
43 | plotter.add_mesh(grid, show_edges=True, lighting=False,
44 | cmap=viridis, clim=[min_, max_])
45 | plotter.camera.zoom(2.0)
46 | plotter.view_xy()
47 | plotter.export_html(filename, backend="pythreejs")
48 | plotter.close()
49 |
50 |
51 | def create_gif(uh, filename, clim):
52 | pyvista.start_xvfb(0.5)
53 | plotter = pyvista.Plotter(notebook=False, off_screen=True)
54 | plotter.open_gif(filename)
55 | V = uh.function_space
56 | topology, cells, geometry = plot.create_vtk_mesh(V)
57 | grid = pyvista.UnstructuredGrid(topology, cells, geometry)
58 | grid.point_data["uh"] = uh.x.array
59 | viridis = pyplot.cm.get_cmap("viridis", 25)
60 |
61 | plotter.add_mesh(grid, show_edges=True, lighting=False, cmap=viridis, clim=clim)
62 | plotter.camera.zoom(2.0)
63 | plotter.view_xy()
64 |
65 | return plotter
66 |
--------------------------------------------------------------------------------
/Problem7_Helmholtz/utils.py:
--------------------------------------------------------------------------------
1 | from matplotlib import pyplot
2 | from dolfinx import plot
3 | import pyvista
4 | from scipy.special import jv, hankel1
5 | import numpy as np
6 |
7 | pyvista.set_plot_theme("paraview")
8 |
9 |
10 | def plot_mesh(mesh, cell_values=None, filename="file.html"):
11 | pyvista.start_xvfb()
12 | grid = pyvista.UnstructuredGrid(*plot.create_vtk_mesh(mesh))
13 | plotter = pyvista.Plotter(notebook=True)
14 |
15 | if cell_values is not None:
16 | min_ = cell_values.x.array.min()
17 | max_ = cell_values.x.array.max()
18 | grid.cell_data["cell_values"] = cell_values.x.array.real
19 | viridis = pyplot.cm.get_cmap("viridis", 25)
20 | plotter.add_mesh(grid, cmap=viridis,
21 | show_edges=True, clim=[min_, max_])
22 | else:
23 | plotter.add_mesh(grid, show_edges=True)
24 |
25 | plotter.camera.zoom(2.0)
26 | plotter.view_xy()
27 | plotter.export_html(filename, backend="pythreejs")
28 | plotter.close()
29 |
30 |
31 | def penetrable_circle(k0, k1, rad, plot_grid):
32 | # Compute
33 | points = np.vstack((plot_grid[0].ravel(), plot_grid[1].ravel()))
34 | fem_xx = points[0, :]
35 | fem_xy = points[1, :]
36 | r = np.sqrt(fem_xx * fem_xx + fem_xy * fem_xy)
37 | theta = np.arctan2(fem_xy, fem_xx)
38 | npts = np.size(fem_xx, 0)
39 | a = rad
40 |
41 | n_terms = np.max([100, np.int(55 + (k0 * a)**1.01)])
42 |
43 | Nx = plot_grid.shape[1]
44 | Ny = plot_grid.shape[2]
45 |
46 | u_inc = np.exp(1j * k0 * fem_xx)
47 | n_int = np.where(r < a)
48 | n_ext = np.where(r >= a)
49 | u_inc[n_int] = 0.0
50 | u_plot = u_inc.reshape(Nx, Ny)
51 |
52 | u_int = np.zeros(npts, dtype=np.complex128)
53 | u_ext = np.zeros(npts, dtype=np.complex128)
54 | n = np.arange(-n_terms, n_terms+1)
55 |
56 | bessel_k0 = jv(n, k0 * rad)
57 | bessel_k1 = jv(n, k1 * rad)
58 | hankel_k0 = hankel1(n, k0 * rad)
59 |
60 | bessel_deriv_k0 = jv(n-1, k0 * rad) - n/(k0 * rad) * jv(n, k0 * rad)
61 | bessel_deriv_k1 = jv(n-1, k1 * rad) - n/(k1 * rad) * jv(n, k1 * rad)
62 |
63 | hankel_deriv_k0 = n/(k0 * rad) * hankel_k0 - hankel1(n+1, k0 * rad)
64 |
65 | a_n = (1j**n) * (k1 * bessel_deriv_k1 * bessel_k0 -
66 | k0 * bessel_k1 * bessel_deriv_k0) / \
67 | (k0 * hankel_deriv_k0 * bessel_k1 -
68 | k1 * bessel_deriv_k1 * hankel_k0)
69 | b_n = (a_n * hankel_k0 + (1j**n) * bessel_k0) / bessel_k1
70 |
71 | # compute u_ext += a_n * hankel1(n, k0 * r) * np.exp(1j * n_ * theta)
72 | k0r = np.tile(k0*r, (a_n.size, 1))
73 | n_mat = np.repeat(n, r.size).reshape((a_n.size, r.size))
74 | M = np.diag(a_n)@hankel1(n_mat, k0r)
75 | u = np.exp(1j * np.outer(n, theta))
76 | u_ext += np.sum(M*u, axis=0)
77 |
78 | # u_int += b_n * jv(n_, k1 * r) * np.exp(1j * n_ * theta)
79 | k1r = np.tile(k1*r, (b_n.size, 1))
80 | n_mat = np.repeat(n, r.size).reshape((a_n.size, r.size))
81 | M = np.diag(b_n)@jv(n_mat, k1r)
82 | u = np.exp(1j * np.outer(n, theta))
83 | u_int += np.sum(M*u, axis=0)
84 |
85 | u_int[n_ext] = 0.0
86 | u_ext[n_int] = 0.0
87 | u_sc = u_int + u_ext
88 | u_scat = u_sc.reshape(Nx, Ny)
89 | u_tot = u_scat + u_plot
90 |
91 | return u_tot
92 |
--------------------------------------------------------------------------------
/Problem8_HPC/poisson.py:
--------------------------------------------------------------------------------
1 | from mpi4py import MPI
2 | from petsc4py import PETSc
3 | import dolfinx
4 | from dolfinx.fem.petsc import LinearProblem
5 | import ufl
6 | import numpy
7 | import argparse
8 |
9 |
10 | def create_mesh(comm, target_dofs, strong_scaling):
11 | """Create a mesh such that the target number of dofs is achieved.
12 |
13 | Parameters
14 | ----------
15 | comm : object
16 | Communication object used to specify the number of processes in the simulation.
17 | target_dofs : int
18 | Target number of degrees of freedom (dofs) for the mesh.
19 | The number of dofs represents the number of unknown variables in the simulation.
20 | strong_scaling : bool
21 | Boolean flag indicating whether the simulation is using strong scaling.
22 | Strong scaling means that the number of dofs is kept constant while the number of processes is increased.
23 |
24 | Returns
25 | -------
26 | mesh : object
27 | The created mesh using the `create_unit_cube` function with the specified number of cells
28 | in each direction and the communication object.
29 |
30 | """
31 |
32 | # Get number of processes
33 | num_processes = comm.size
34 |
35 | # If strong_scaling is true, the target total dofs is simply equal to target_dofs.
36 | # If strong_scaling is false, the target total dofs is equal to target_dofs
37 | # multiplied by the number of processes.
38 | N = target_dofs if strong_scaling else target_dofs * num_processes
39 |
40 | # Computes the number of cells in each direction of the mesh by starting with
41 | # Nc = [1, 1, 1] and incrementing each dimension until the total number of cells
42 | # is greater than or equal to the target total dofs.
43 | Nc = numpy.array([1, 1, 1], dtype=int)
44 | while numpy.prod(Nc+1) < N:
45 | for i in range(len(Nc)):
46 | Nc[i] = Nc[i]+1
47 | if numpy.prod(Nc+1) >= N:
48 | break
49 |
50 | return dolfinx.mesh.create_unit_cube(comm, Nc[0], Nc[1], Nc[2])
51 |
52 |
53 | def poisson_solver(ndofs: int, petsc_options: dict, strong_scaling: bool = False):
54 |
55 | """
56 | Solves the Poisson equation using a finite element method.
57 |
58 | Parameters
59 | ----------
60 | ndofs : int
61 | The target number of degrees of freedom.
62 | petsc_options : dict
63 | Dictionary of PETSc solver options.
64 | strong_scaling : bool, optional
65 | Whether to enable strong scaling or not, by default False.
66 |
67 | Returns
68 | -------
69 | num_dofs : int
70 | The number of degrees of freedom.
71 | solver_time : float
72 | The time taken by the solver to complete.
73 | num_iterations : int
74 | The number of iterations taken by the solver.
75 |
76 | """
77 |
78 | comm = MPI.COMM_WORLD
79 | mesh = create_mesh(comm, ndofs, strong_scaling)
80 | tdim = mesh.topology.dim
81 | mesh.topology.create_connectivity(tdim - 1, tdim)
82 |
83 | V = dolfinx.fem.FunctionSpace(mesh, ("Lagrange", 1))
84 | u = ufl.TrialFunction(V)
85 | v = ufl.TestFunction(V)
86 |
87 | # Retrieves the indices of the exterior facets (boundary faces)
88 | bndry_facets = dolfinx.mesh.exterior_facet_indices(mesh.topology)
89 | # Computes the degrees of freedom (DOFs) corresponding to the exterior facets (boundary faces) of the mesh
90 | bndry_dofs = dolfinx.fem.locate_dofs_topological(V, tdim - 1, bndry_facets)
91 | zero = dolfinx.fem.Constant(mesh, 0.0)
92 | # creates the boundary condition (BC) object
93 | bcs = [dolfinx.fem.dirichletbc(zero, bndry_dofs, V)]
94 |
95 | a = ufl.inner(ufl.grad(u), ufl.grad(v))*ufl.dx
96 |
97 | x = ufl.SpatialCoordinate(mesh)
98 | f = 1 - x[0]**2
99 | L = f*v*ufl.dx
100 |
101 | uh = dolfinx.fem.Function(V)
102 | problem = LinearProblem(a, L, u=uh, bcs=bcs, petsc_options=petsc_options)
103 |
104 | with dolfinx.common.Timer("Solver") as t:
105 | problem.solve()
106 |
107 | num_dofs = V.dofmap.index_map.size_global
108 | if comm.rank == 0:
109 | print(f"Number of degrees of freedom: {num_dofs}", flush=True)
110 | print(f"Solver time: {t.elapsed()[0]}", flush=True)
111 | print(f"Number of iterations: {problem.solver.its}", flush=True)
112 | print(f"Number of MPI processes: {comm.size}", flush=True)
113 | print(f"Solver: {petsc_options}", flush=True)
114 |
115 | return num_dofs, t.elapsed()[0], problem.solver.its
116 |
117 |
118 | # Solver configuration
119 | lu_solver = {"ksp_type": "preonly",
120 | "pc_type": "lu"}
121 |
122 | cg_nopre = {"ksp_type": "cg",
123 | "pc_type": "none",
124 | "ksp_rtol": 1e-7}
125 |
126 | multigrid = {"ksp_type": "cg",
127 |
128 | "pc_type": "hypre",
129 | "pc_hypre_type": "boomeramg",
130 | "pc_hypre_boomeramg_strong_threshold": 0.7,
131 | "pc_hypre_boomeramg_agg_nl": 4,
132 | "pc_hypre_boomeramg_agg_num_paths": 2,
133 | "ksp_rtol": 1e-7}
134 |
135 | solvers = {"lu": lu_solver, "cg": cg_nopre, "mg": multigrid}
136 | scaling = {"strong": True, "weak": False}
137 |
138 | if __name__ == "__main__":
139 | parser = argparse.ArgumentParser(
140 | description='Code to test the parallel performance of DOLFINx and the underlying linear solvers.')
141 | parser.add_argument('--num_dofs', type=int,
142 | help='Number of degrees-of-freedom: total (in case of strong scaling) or per process (for weak scaling).')
143 | parser.add_argument('--solver', type=str, default="cg",
144 | help='Solver to use', choices=['lu', 'cg', 'mg'])
145 | parser.add_argument('--scaling_type', type=str, default="strong",
146 | help='Scaling type: strong (fixed problem size) or weak (fixed problem size per process)', choices=['strong', 'weak'])
147 |
148 |
149 | args = parser.parse_args()
150 | num_dofs = args.num_dofs
151 | solver = solvers[args.solver]
152 | strong_scaling = scaling[args.scaling_type]
153 | poisson_solver(num_dofs, solver, strong_scaling)
154 |
--------------------------------------------------------------------------------
/Problem8_HPC/load.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # make module visible to user
4 | module use --append /home/ibaratta/spack/share/spack/modules/linux-centos7-ivybridge/
5 |
6 | # pkgconf@1.8.0%gcc@12.2.0 build_system=autotools arch=linux-centos7-ivybridge
7 | module load pkgconf-1.8.0-gcc-12.2.0-r6oblgz
8 | # ncurses@6.3%gcc@12.2.0~symlinks+termlib abi=none build_system=autotools arch=linux-centos7-ivybridge
9 | module load ncurses-6.3-gcc-12.2.0-rpsk3qf
10 | # ca-certificates-mozilla@2022-10-11%gcc@12.2.0 build_system=generic arch=linux-centos7-ivybridge
11 | module load ca-certificates-mozilla-2022-10-11-gcc-12.2.0-cmymec7
12 | # berkeley-db@18.1.40%gcc@12.2.0+cxx~docs+stl build_system=autotools patches=26090f4,b231fcc arch=linux-centos7-ivybridge
13 | module load berkeley-db-18.1.40-gcc-12.2.0-pt426z7
14 | # libiconv@1.16%gcc@12.2.0 build_system=autotools libs=shared,static arch=linux-centos7-ivybridge
15 | module load libiconv-1.16-gcc-12.2.0-q3hwmrg
16 | # diffutils@3.8%gcc@12.2.0 build_system=autotools arch=linux-centos7-ivybridge
17 | module load diffutils-3.8-gcc-12.2.0-pxo34ea
18 | # bzip2@1.0.8%gcc@12.2.0~debug~pic+shared build_system=generic arch=linux-centos7-ivybridge
19 | module load bzip2-1.0.8-gcc-12.2.0-il3l5em
20 | # readline@8.1.2%gcc@12.2.0 build_system=autotools arch=linux-centos7-ivybridge
21 | module load readline-8.1.2-gcc-12.2.0-yqvtl6d
22 | # gdbm@1.23%gcc@12.2.0 build_system=autotools arch=linux-centos7-ivybridge
23 | module load gdbm-1.23-gcc-12.2.0-ifwajuc
24 | # zlib@1.2.13%gcc@12.2.0+optimize+pic+shared build_system=makefile arch=linux-centos7-ivybridge
25 | module load zlib-1.2.13-gcc-12.2.0-u7kjh4e
26 | # perl@5.36.0%gcc@12.2.0+cpanm+shared+threads build_system=generic arch=linux-centos7-ivybridge
27 | module load perl-5.36.0-gcc-12.2.0-s6exf4r
28 | # openssl@1.1.1s%gcc@12.2.0~docs~shared build_system=generic certs=mozilla arch=linux-centos7-ivybridge
29 | module load openssl-1.1.1s-gcc-12.2.0-r4kpfmo
30 | # cmake@3.25.1%gcc@12.2.0~doc+ncurses+ownlibs~qt build_system=generic build_type=Release arch=linux-centos7-ivybridge
31 | module load cmake-3.25.1-gcc-12.2.0-gd2obex
32 | # cmake@3.25.0%gcc@12.2.0~doc+ncurses+ownlibs~qt build_system=generic build_type=Release arch=linux-centos7-ivybridge
33 | module load cmake-3.25.0-gcc-12.2.0-ytweddi
34 | # openblas@0.3.21%gcc@12.2.0~bignuma~consistent_fpcsr+fortran~ilp64+locking+pic+shared build_system=makefile patches=d3d9b15 symbol_suffix=none threads=none arch=linux-centos7-ivybridge
35 | module load openblas-0.3.21-gcc-12.2.0-txw66pt
36 | # fenics-basix@main%gcc@12.2.0~ipo build_system=cmake build_type=RelWithDebInfo arch=linux-centos7-ivybridge
37 | module load fenics-basix-main-gcc-12.2.0-4pv4agf
38 | # boost@1.80.0%gcc@12.2.0~atomic~chrono~clanglibcpp~container~context~contract~coroutine~date_time~debug~exception~fiber+filesystem~graph~graph_parallel~icu~iostreams~json~locale~log~math~mpi+multithreaded~nowide~numpy~pic+program_options~python~random~regex~serialization+shared~signals~singlethreaded~stacktrace~system~taggedlayout~test~thread+timer~type_erasure~versionedlayout~wave build_system=generic cxxstd=98 patches=a440f96 visibility=hidden arch=linux-centos7-ivybridge
39 | module load boost-1.80.0-gcc-12.2.0-wox5fe7
40 | # fenics-ufcx@main%gcc@12.2.0~ipo build_system=cmake build_type=RelWithDebInfo arch=linux-centos7-ivybridge
41 | module load fenics-ufcx-main-gcc-12.2.0-6wp3mxa
42 | # cpio@2.13%gcc@12.2.0 build_system=autotools arch=linux-centos7-ivybridge
43 | module load cpio-2.13-gcc-12.2.0-hjdxdm5
44 | # intel-mpi@2019.10.317%gcc@12.2.0~external-libfabric build_system=generic arch=linux-centos7-ivybridge
45 | module load intel-mpi-2019.10.317-gcc-12.2.0-hkevula
46 | # hdf5@1.12.2%gcc@12.2.0~cxx~fortran~hl~ipo~java+mpi+shared~szip~threadsafe+tools api=default build_system=cmake build_type=RelWithDebInfo arch=linux-centos7-ivybridge
47 | module load hdf5-1.12.2-gcc-12.2.0-xcb4knj
48 | # ncurses@6.4%gcc@12.2.0~symlinks+termlib abi=none build_system=autotools arch=linux-centos7-ivybridge
49 | module load ncurses-6.4-gcc-12.2.0-2qnwagm
50 | # libiconv@1.17%gcc@12.2.0 build_system=autotools libs=shared,static arch=linux-centos7-ivybridge
51 | module load libiconv-1.17-gcc-12.2.0-3wqatjk
52 | # diffutils@3.8%gcc@12.2.0 build_system=autotools arch=linux-centos7-ivybridge
53 | module load diffutils-3.8-gcc-12.2.0-l2hwwac
54 | # bzip2@1.0.8%gcc@12.2.0~debug~pic+shared build_system=generic arch=linux-centos7-ivybridge
55 | module load bzip2-1.0.8-gcc-12.2.0-iuerx6x
56 | # readline@8.2%gcc@12.2.0 build_system=autotools patches=bbf97f1 arch=linux-centos7-ivybridge
57 | module load readline-8.2-gcc-12.2.0-uxbbhvt
58 | # gdbm@1.23%gcc@12.2.0 build_system=autotools arch=linux-centos7-ivybridge
59 | module load gdbm-1.23-gcc-12.2.0-axfuzom
60 | # perl@5.36.0%gcc@12.2.0+cpanm+open+shared+threads build_system=generic arch=linux-centos7-ivybridge
61 | module load perl-5.36.0-gcc-12.2.0-qxjwymq
62 | # openssl@1.1.1s%gcc@12.2.0~docs~shared build_system=generic certs=mozilla arch=linux-centos7-ivybridge
63 | module load openssl-1.1.1s-gcc-12.2.0-rhymar3
64 | # cmake@3.25.1%gcc@12.2.0~doc+ncurses+ownlibs~qt build_system=generic build_type=Release arch=linux-centos7-ivybridge
65 | module load cmake-3.25.1-gcc-12.2.0-duodk5g
66 | # metis@5.1.0%gcc@12.2.0~gdb~int64~ipo~real64+shared build_system=cmake build_type=RelWithDebInfo patches=4991da9,93a7903,b1225da arch=linux-centos7-ivybridge
67 | module load metis-5.1.0-gcc-12.2.0-nk5hibg
68 | # parmetis@4.0.3%gcc@12.2.0~gdb~int64~ipo+shared build_system=cmake build_type=RelWithDebInfo patches=4f89253,50ed208,704b84f arch=linux-centos7-ivybridge
69 | module load parmetis-4.0.3-gcc-12.2.0-sfknekz
70 | # hypre@2.27.0%gcc@12.2.0~complex~cuda~debug~fortran~gptune~int64~internal-superlu~mixedint+mpi~openmp~rocm+shared~superlu-dist~sycl~umpire~unified-memory build_system=autotools arch=linux-centos7-ivybridge
71 | module load hypre-2.27.0-gcc-12.2.0-fivr6dj
72 | # libmd@1.0.4%gcc@12.2.0 build_system=autotools arch=linux-centos7-ivybridge
73 | module load libmd-1.0.4-gcc-12.2.0-2femkdy
74 | # libbsd@0.11.5%gcc@12.2.0 build_system=autotools arch=linux-centos7-ivybridge
75 | module load libbsd-0.11.5-gcc-12.2.0-hku6h5m
76 | # expat@2.5.0%gcc@12.2.0+libbsd build_system=autotools arch=linux-centos7-ivybridge
77 | module load expat-2.5.0-gcc-12.2.0-jinadu4
78 | # xz@5.2.7%gcc@12.2.0~pic build_system=autotools libs=shared,static arch=linux-centos7-ivybridge
79 | module load xz-5.2.7-gcc-12.2.0-si5ysnr
80 | # libxml2@2.10.3%gcc@12.2.0~python build_system=autotools arch=linux-centos7-ivybridge
81 | module load libxml2-2.10.3-gcc-12.2.0-cx4flod
82 | # pigz@2.7%gcc@12.2.0 build_system=makefile arch=linux-centos7-ivybridge
83 | module load pigz-2.7-gcc-12.2.0-c3ixfw2
84 | # zstd@1.5.2%gcc@12.2.0+programs build_system=makefile compression=none libs=shared,static arch=linux-centos7-ivybridge
85 | module load zstd-1.5.2-gcc-12.2.0-a6vxsh5
86 | # tar@1.34%gcc@12.2.0 build_system=autotools zip=pigz arch=linux-centos7-ivybridge
87 | module load tar-1.34-gcc-12.2.0-aagu52g
88 | # gettext@0.21.1%gcc@12.2.0+bzip2+curses+git~libunistring+libxml2+tar+xz build_system=autotools arch=linux-centos7-ivybridge
89 | module load gettext-0.21.1-gcc-12.2.0-cm2o636
90 | # libffi@3.4.3%gcc@12.2.0 build_system=autotools arch=linux-centos7-ivybridge
91 | module load libffi-3.4.3-gcc-12.2.0-vhyufgj
92 | # libxcrypt@4.4.33%gcc@12.2.0~obsolete_api build_system=autotools arch=linux-centos7-ivybridge
93 | module load libxcrypt-4.4.33-gcc-12.2.0-avqvbg4
94 | # sqlite@3.40.0%gcc@12.2.0+column_metadata+dynamic_extensions+fts~functions+rtree build_system=autotools arch=linux-centos7-ivybridge
95 | module load sqlite-3.40.0-gcc-12.2.0-53fphsv
96 | # util-linux-uuid@2.38.1%gcc@12.2.0 build_system=autotools arch=linux-centos7-ivybridge
97 | module load util-linux-uuid-2.38.1-gcc-12.2.0-gdbxwwi
98 | # python@3.10.8%gcc@12.2.0+bz2+crypt+ctypes+dbm~debug+libxml2+lzma~nis~optimizations+pic+pyexpat+pythoncmd+readline+shared+sqlite3+ssl~tkinter+uuid+zlib build_system=generic patches=0d98e93,7d40923,f2fd060 arch=linux-centos7-ivybridge
99 | module load python-3.10.8-gcc-12.2.0-aysx3jq
100 | # petsc@3.18.2%gcc@12.2.0~X~batch~cgns~complex~cuda~debug+double~exodusii~fftw~fortran~giflib+hdf5~hpddm~hwloc+hypre~int64~jpeg~knl~kokkos~libpng~libyaml~memkind+metis~mkl-pardiso~mmg~moab~mpfr+mpi~mumps~openmp~p4est~parmmg~ptscotch~random123~rocm~saws+shared~strumpack~suite-sparse~tetgen~trilinos~valgrind build_system=generic clanguage=C arch=linux-centos7-ivybridge
101 | module load petsc-3.18.2-gcc-12.2.0-2ixtwez
102 | # pugixml@1.11.4%gcc@12.2.0~ipo+pic+shared build_system=cmake build_type=RelWithDebInfo arch=linux-centos7-ivybridge
103 | module load pugixml-1.11.4-gcc-12.2.0-4etxgqs
104 | # fenics-dolfinx@main%gcc@12.2.0~adios2~ipo~slepc build_system=cmake build_type=RelWithDebInfo partitioners=parmetis arch=linux-centos7-ivybridge
105 | module load fenics-dolfinx-main-gcc-12.2.0-cv2wm7n
106 | # py-pip@22.2.2%gcc@12.2.0 build_system=generic arch=linux-centos7-ivybridge
107 | module load py-pip-22.2.2-gcc-12.2.0-ldxsyev
108 | # py-setuptools@63.0.0%gcc@12.2.0 build_system=generic arch=linux-centos7-ivybridge
109 | module load py-setuptools-63.0.0-gcc-12.2.0-crhpacx
110 | # py-wheel@0.37.1%gcc@12.2.0 build_system=generic arch=linux-centos7-ivybridge
111 | module load py-wheel-0.37.1-gcc-12.2.0-m5kmvdk
112 | # py-pycparser@2.21%gcc@12.2.0 build_system=python_pip arch=linux-centos7-ivybridge
113 | module load py-pycparser-2.21-gcc-12.2.0-ikbml6c
114 | # py-cffi@1.15.1%gcc@12.2.0 build_system=python_pip arch=linux-centos7-ivybridge
115 | module load py-cffi-1.15.1-gcc-12.2.0-hvla6u3
116 | # py-cython@0.29.32%gcc@12.2.0 build_system=python_pip arch=linux-centos7-ivybridge
117 | module load py-cython-0.29.32-gcc-12.2.0-lbmln2y
118 | # py-numpy@1.24.1%gcc@12.2.0+blas+lapack build_system=python_pip patches=873745d arch=linux-centos7-ivybridge
119 | module load py-numpy-1.24.1-gcc-12.2.0-kfxy4x7
120 | # libxml2@2.10.3%gcc@12.2.0~python build_system=autotools arch=linux-centos7-ivybridge
121 | module load libxml2-2.10.3-gcc-12.2.0-euikbud
122 | # tar@1.34%gcc@12.2.0 build_system=autotools zip=pigz arch=linux-centos7-ivybridge
123 | module load tar-1.34-gcc-12.2.0-phwjhv6
124 | # gettext@0.21.1%gcc@12.2.0+bzip2+curses+git~libunistring+libxml2+tar+xz build_system=autotools arch=linux-centos7-ivybridge
125 | module load gettext-0.21.1-gcc-12.2.0-mxfeajh
126 | # libxcrypt@4.4.33%gcc@12.2.0~obsolete_api build_system=autotools arch=linux-centos7-ivybridge
127 | module load libxcrypt-4.4.33-gcc-12.2.0-dxw2tsl
128 | # sqlite@3.40.0%gcc@12.2.0+column_metadata+dynamic_extensions+fts~functions+rtree build_system=autotools arch=linux-centos7-ivybridge
129 | module load sqlite-3.40.0-gcc-12.2.0-refyjuo
130 | # python@3.10.8%gcc@12.2.0+bz2+crypt+ctypes+dbm~debug+libxml2+lzma~nis~optimizations+pic+pyexpat+pythoncmd+readline+shared+sqlite3+ssl~tkinter+uuid+zlib build_system=generic patches=0d98e93,7d40923,f2fd060 arch=linux-centos7-ivybridge
131 | module load python-3.10.8-gcc-12.2.0-biuz4ql
132 | # re2c@2.2%gcc@12.2.0 build_system=generic arch=linux-centos7-ivybridge
133 | module load re2c-2.2-gcc-12.2.0-tnimm53
134 | # ninja@1.11.1%gcc@12.2.0+re2c build_system=generic arch=linux-centos7-ivybridge
135 | module load ninja-1.11.1-gcc-12.2.0-ljsgm5g
136 | # py-pybind11@2.10.1%gcc@12.2.0~ipo build_system=cmake build_type=RelWithDebInfo arch=linux-centos7-ivybridge
137 | module load py-pybind11-2.10.1-gcc-12.2.0-24ke7tz
138 | # py-fenics-basix@main%gcc@12.2.0 build_system=python_pip arch=linux-centos7-ivybridge
139 | module load py-fenics-basix-main-gcc-12.2.0-lvnvd2x
140 | # py-fenics-ufl@main%gcc@12.2.0 build_system=python_pip arch=linux-centos7-ivybridge
141 | module load py-fenics-ufl-main-gcc-12.2.0-b23g5qv
142 | # py-fenics-ffcx@main%gcc@12.2.0 build_system=python_pip arch=linux-centos7-ivybridge
143 | module load py-fenics-ffcx-main-gcc-12.2.0-tx5c7km
144 | # py-mpi4py@3.1.4%gcc@12.2.0 build_system=python_pip arch=linux-centos7-ivybridge
145 | module load py-mpi4py-3.1.4-gcc-12.2.0-dwjrfpy
146 | # py-petsc4py@3.18.2%gcc@12.2.0+mpi build_system=python_pip patches=d344e0e arch=linux-centos7-ivybridge
147 | module load py-petsc4py-3.18.2-gcc-12.2.0-ejqaogn
148 | # py-fenics-dolfinx@main%gcc@12.2.0 build_system=python_pip arch=linux-centos7-ivybridge
149 | module load py-fenics-dolfinx-main-gcc-12.2.0-tiyyy53
150 |
--------------------------------------------------------------------------------
/Problem7_Helmholtz/helmholtz.py:
--------------------------------------------------------------------------------
1 | # # +
2 | # try:
3 | # import gmsh
4 | # except ImportError:
5 | # !wget "https://github.com/fem-on-colab/fem-on-colab.github.io/raw/7f220b6/releases/gmsh-install.sh" -O "/tmp/gmsh-install.sh" && bash "/tmp/gmsh-install.sh"
6 | # import gmsh
7 |
8 | # try:
9 | # import dolfinx
10 | # except ImportError:
11 | # !wget "https://fem-on-colab.github.io/releases/fenicsx-install-complex.sh" -O "/tmp/fenicsx-install.sh" && bash "/tmp/fenicsx-install.sh"
12 | # import dolfinx
13 |
14 | # try:
15 | # import pyvista
16 | # except ImportError:
17 | # !pip install -q piglet pyvirtualdisplay ipyvtklink pyvista panel pythreejs
18 | # !apt-get -qq install xvfb
19 | # import pyvista
20 |
21 | # !wget "https://raw.githubusercontent.com/IgorBaratta/FEniCSxCourse/main/Problem7_Helmholtz/utils.py"
22 | # !wget "https://raw.githubusercontent.com/IgorBaratta/FEniCSxCourse/main/Problem7_Helmholtz/mesh_generation.py"
23 |
24 | # # -
25 |
26 | # # The Helmholtz equation
27 | #
28 | # In this tutorial, we will learn:
29 | # - How to solve PDEs with complex-valued fields,
30 | # - How to import and use high-order meshes from Gmsh,
31 | # - How to use high order discretizations,
32 | # - How to use UFL expressions.
33 |
34 | # ## Problem statement
35 | #
36 | # The Helmholtz equation can be used to model the scattering of time-harmonic waves by finite and infinite obstacles.
37 | # We will solve the Helmholtz equation subject to a first order absorbing boundary condition:
38 | # $$
39 | # \begin{align*}
40 | # \Delta u + k^2 u &= 0 && \text{in } \Omega,\\
41 | # \nabla u \cdot \mathbf{n} - \mathrm{j}ku &= g && \text{on } \partial\Omega,
42 | # \end{align*}
43 | # $$
44 | # where $k$ is a piecewise constant wavenumber, $\mathrm{j}=\sqrt{-1}$, and $g$ is the boundary source term computed as
45 | # $$g = \nabla u_\text{inc} \cdot \mathbf{n} - \mathrm{j}ku_\text{inc}$$
46 |
47 | # To derive the weak form for the Helmholtz's equation, we first multiply both sides
48 | # of the equation with the complex conjugate of a sufficiently smooth arbitrary test
49 | # function $v$, integrate by parts in $\Omega$, the domain of interest, and after
50 | # applying the divergence theorem, we find
51 | # $$
52 | # \begin{align*}
53 | # -\int_\Omega \nabla u \cdot \nabla \bar{v} ~ dx + \int_\Omega k^2 u \,\bar{v} ~ dx + \int_{\partial \Omega} \left(\nabla u \cdot \mathbf{n} \right) \bar{v} ~ ds = \int_\Omega f \, \bar{v} ~ dx.
54 | # \end{align*}
55 | # $$
56 |
57 | # Assuming that $u$ is a classical solution of our original equation with suitable
58 | # boundary conditions, it is also a solution of the weak form for any $v \in C_0^1(\Omega)$,
59 | # nevertheless with a reduced smoothness requirement.
60 | # If $\Omega \in \mathbb{R}^d, \, d = 1, 2, 3 \,$, then the natural space for the
61 | # weak solution and the test functions $v$ is the Sobolev space
62 | # $\mathcal{H}^1 (\Omega)$, given by
63 | # \begin{equation}
64 | # \mathcal{H}^1(\Omega) := \{ u: \Omega \rightarrow \mathbb{C}|\, u \in L^2(\Omega),\, \partial_{x_i}u\in L^2(\Omega), 1\leq i \leq d \}.
65 | # \end{equation}
66 |
67 |
68 | # Assuming that the test function $v$ vanishes on $\Gamma_D$, where
69 | # the solution $u$ is known, we arrive at the following variational problem:
70 | #
71 | # Find $u \in V$ such that
72 | # $$
73 | # \begin{align*}
74 | # -\int_\Omega \nabla u \cdot \nabla \bar{v} ~ dx + \int_\Omega k^2 u \,\bar{v}~ dx + \int_{\partial \Omega / \Gamma_D} \left(\nabla u \cdot \mathbf{n} \right) \bar{v} ~ ds = \int_\Omega f \, \bar{v}~ dx \qquad \forall v \in \widehat{V}.
75 | # \end{align*}
76 | # $$
77 | #
78 | # Standard Galerkin finite element solutions with low-order piecewise polynomials differ significantly
79 | # from the best approximation, due to spurious dispersion in the computation, unless the mesh is sufficiently refined.
80 | # This phenomenon, related to the indefiniteness of the Helmholtz operator is known as the pollution effect.
81 | #
82 |
83 |
84 | # +
85 | from utils import penetrable_circle
86 | from dolfinx import geometry
87 | import matplotlib.pyplot as plt
88 | import dolfinx.cpp as _cpp
89 | from dolfinx.io import XDMFFile, VTXWriter
90 | from mpi4py import MPI
91 |
92 | # utils for plotting and generating mesh
93 | from utils import plot_mesh
94 | from mesh_generation import generate_mesh
95 |
96 | # Auxiliary libraries
97 | import IPython
98 | import numpy as np
99 |
100 | # Import dolfinx modules and ufl
101 | import dolfinx
102 | from dolfinx.io import gmshio
103 | import ufl
104 | # -
105 |
106 | # This example is designed to be executed with complex-valued coefficients.
107 | # To be able to solve this problem, we use the complex build of PETSc.
108 |
109 | # +
110 | import sys
111 | from petsc4py import PETSc
112 |
113 | if not np.issubdtype(PETSc.ScalarType, np.complexfloating):
114 | print("This tutorial requires complex number support")
115 | sys.exit(0)
116 | else:
117 | print(f"Using {PETSc.ScalarType}.")
118 | # -
119 |
120 | # ## Defining model parameters
121 |
122 | # +
123 | # wavenumber in free space (air)
124 | k0 = 10
125 |
126 | # Corresponding wavelength
127 | lmbda = 2 * np.pi / k0
128 |
129 | # scatterer radius
130 | radius = 2*lmbda
131 |
132 | # refractive index of scatterer
133 | ref_ind = 1.0
134 |
135 | # Polynomial degree
136 | degree = 6
137 |
138 | # width of computational domain
139 | dim_x = 20*lmbda
140 |
141 | # Mesh order
142 | mesh_order = 2
143 | # -
144 |
145 | # For this problem we use a square mesh with triangular elements.
146 | # The mesh element size is h_elem, and the #elements in one dimension is n_elem
147 | h_elem = lmbda / 5
148 |
149 |
150 | # ## Interfacing with GMSH
151 | # We will use Gmsh to generate the computational domain (mesh) for this example.
152 | # As long as Gmsh has been installed (including its Python API), DOLFINx supports direct input of Gmsh models (generated on one process).
153 | # DOLFINx will then in turn distribute the mesh over all processes in the communicator passed to `dolfinx.io.gmshio.model_to_mesh`.
154 |
155 | # The function `generate_mesh` creates a Gmsh model and saves it into a `.msh` file.
156 |
157 | # +
158 |
159 | # MPI communicator
160 | comm = MPI.COMM_WORLD
161 |
162 | file_name = "domain.msh"
163 | generate_mesh(file_name, radius, dim_x, h_elem, mesh_order)
164 | # -
165 |
166 | # Now we can read the mesh from file:
167 |
168 | # +
169 | mesh, cell_tags, _ = gmshio.read_from_msh(file_name, comm, rank=0, gdim=2)
170 |
171 | num_cells = mesh.topology.index_map(2).size_global
172 | h = _cpp.mesh.h(mesh, 2, range(num_cells))
173 | print(num_cells, h.max(), h_elem)
174 | # -
175 |
176 | # ## Material parameters
177 | # In this problem, the wave number in the different parts of the domain
178 | # depends on cell markers, inputted through `cell_tags`. We use the fact that a
179 | # discontinuous Lagrange space of order 0 (cell-wise constants) has a
180 | # one-to-one mapping with the cells local to the process.
181 |
182 | # +
183 | W = dolfinx.fem.FunctionSpace(mesh, ("DG", 0))
184 | k = dolfinx.fem.Function(W)
185 | k.x.array[:] = k0
186 | k.x.array[cell_tags.find(1)] = ref_ind*k0
187 |
188 | plot_mesh(mesh, cell_values=k, filename="mesh.html")
189 | IPython.display.HTML(filename="mesh.html")
190 | # -
191 |
192 |
193 | # ## Boundary source term
194 | # $$g = \nabla u_{inc} \cdot \mathbf{n} - \mathrm{j}ku_{inc}$$
195 | # where $u_{inc} = e^{-jkx}$ the incoming wave, is a plane wave propagating
196 | # in the $x$ direction.
197 | #
198 | # Next, we define the boundary source term by using `ufl.SpatialCoordinate`.
199 | # When using this function, all quantities using this expression will be evaluated
200 | # at quadrature points.
201 |
202 | # +
203 | n = ufl.FacetNormal(mesh)
204 | x = ufl.SpatialCoordinate(mesh)
205 | uinc = ufl.exp(-1j * k0 * x[0])
206 | g = ufl.dot(ufl.grad(uinc), n) + 1j * k0 * uinc
207 | # -
208 |
209 | # ## Variational form
210 | # Next, we define the variational problem using a 6th order Lagrange space.
211 | # Note that as we are using complex valued functions, we have to use the
212 | # appropriate inner product; see DOLFINx tutorial: Complex numbers for more
213 | # information.
214 | #
215 | # Find $u \in V$ such that
216 | # $$-\int_\Omega \nabla u \cdot \nabla \bar{v} ~ dx + \int_\Omega k^2 u \,\bar{v}~ dx - j\int_{\partial \Omega} ku \bar{v} ~ ds = \int_{\partial \Omega} g \, \bar{v}~ ds \qquad \forall v \in \widehat{V}.$$
217 |
218 | # +
219 | element = ufl.FiniteElement("Lagrange", mesh.ufl_cell(), degree)
220 | V = dolfinx.fem.FunctionSpace(mesh, element)
221 |
222 | u = ufl.TrialFunction(V)
223 | v = ufl.TestFunction(V)
224 | # -
225 | # +
226 | a = - ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx \
227 | + k**2 * ufl.inner(u, v) * ufl.dx \
228 | - 1j * k * ufl.inner(u, v) * ufl.ds
229 | L = ufl.inner(g, v) * ufl.ds
230 | # -
231 |
232 | # ## Linear solver
233 | # Next, we will solve the problem using a direct solver (LU).
234 | # Contrary to the case of elliptic problems where effective multigrid
235 | # and domain decomposition methods are readily available
236 | # (see for example PETSc documentation), the solution of $Au=b$ is less understood.
237 | # The matrix inherits many characteristics from the original equations;
238 | # it is symmetric unless non-reciprocal materials are used, and generally,
239 | # it is not positive definite nor hermitian.
240 | # +
241 | opt = {"ksp_type": "preonly", "pc_type": "lu"}
242 | problem = dolfinx.fem.petsc.LinearProblem(a, L, petsc_options=opt)
243 | uh = problem.solve()
244 | uh.name = "u"
245 | # -
246 |
247 | # ## Visualizing the solution:
248 |
249 | # ### Post-processing with Paraview
250 |
251 | # Using XDMFFile:
252 |
253 | # +
254 | # XDMF writes data to mesh nodes
255 | with XDMFFile(comm, "out.xdmf", "w") as file:
256 | file.write_mesh(mesh)
257 | file.write_function(uh)
258 | # -
259 |
260 | # # Using VTXWriter
261 |
262 | # +
263 | # with VTXWriter(comm, "out.bp", [u_abs]) as f:
264 | # f.write(0.0)
265 | # -
266 |
267 |
268 | # # Using matplotlib
269 | # # +
270 |
271 |
272 | # # Plot field and save figure
273 |
274 | # Square grid with 10 points per wavelength in each direction
275 | Nx = int(np.ceil(dim_x/lmbda * 10))
276 |
277 |
278 | extent = [-dim_x/2, dim_x/2, -dim_x/2, dim_x/2]
279 | xmin, xmax, ymin, ymax = extent
280 | plot_grid = np.mgrid[xmin:xmax:Nx * 1j, ymin:ymax:Nx * 1j]
281 | points = np.vstack((plot_grid[0].ravel(),
282 | plot_grid[1].ravel(),
283 | np.zeros(plot_grid[0].size)))
284 |
285 | #
286 | tree = geometry.BoundingBoxTree(mesh, 2)
287 | cell_candidates = geometry.compute_collisions(tree, points.T)
288 | colliding_cells = geometry.compute_colliding_cells(
289 | mesh, cell_candidates, points.T)
290 | ncells = colliding_cells.num_nodes
291 | cells = [colliding_cells.links(i)[0] for i in range(ncells)]
292 | u_total = uh.eval(points.T, cells).reshape((Nx, Nx))
293 |
294 | u_exact = penetrable_circle(k0, k0*ref_ind, radius, plot_grid)
295 | error = np.linalg.norm(np.abs(u_exact)-np.abs(u_total))/np.linalg.norm(u_exact)
296 | print('Relative error = ', error)
297 |
298 | plt.rc('font', family='serif', size=22)
299 | fig = plt.figure(figsize=(10, 10))
300 | ax = fig.gca()
301 | plt.imshow(np.fliplr(np.real(u_total)).T, extent=extent,
302 | cmap=plt.cm.get_cmap('seismic'), interpolation='spline16')
303 |
304 | # Add circle
305 | circle = plt.Circle((0., 0.), radius, color='black', fill=False)
306 | ax.add_artist(circle)
307 |
308 | plt.axis('off')
309 | plt.colorbar()
310 | fig.savefig('circle_scatter.png')
311 |
312 |
313 | # # -
314 |
315 | # # Homework:
316 |
317 | # # **Task 1**: download the files `out.xdmf` and `out.bp`.
318 | # # Why do they look so different?
319 |
320 | # # **Task 2**: create a first order Lagrange function and interpolate the solution
321 | # # into u1. Use XDMFFile and VTXWriter to visualize the solution.
322 |
323 | # # +
324 | # p1 = ufl.FiniteElement("Lagrange", mesh.ufl_cell(), 1)
325 | # V1 = dolfinx.fem.FunctionSpace(mesh, p1)
326 |
327 | # u1 = dolfinx.fem.Function(V1)
328 | # u1.interpolate(uh)
329 | # # -
330 |
331 | # # **Task 3**: Select an iterative solver and plot the solution.
332 | # # Can you explain what's happening?
333 |
--------------------------------------------------------------------------------
/Problem5_Navier-Stokes/navier_stokes.py:
--------------------------------------------------------------------------------
1 | # ---
2 | # jupyter:
3 | # jupytext:
4 | # formats: ipynb,py:light
5 | # text_representation:
6 | # extension: .py
7 | # format_name: light
8 | # format_version: '1.5'
9 | # jupytext_version: 1.14.1
10 | # kernelspec:
11 | # display_name: Python 3 (ipykernel)
12 | # language: python
13 | # name: python3
14 | # ---
15 |
16 | # # Test problem 1: Channel flow (Poiseuille flow)
17 | #
18 | # Authors: Anders Logg and Hans Petter Langtangen
19 | #
20 | # Adapted to DOLFINx by: Jørgen S. Dokken
21 | #
22 | # In this section, you will learn how to:
23 | # - Solve the Navier-Stokes problem using a splitting scheme
24 | # - Visualize functions from higher order Lagrangian spaces
25 | #
26 | # In this section, we will compute the flow between two infinite plates, so-called channel or Poiseuille flow.
27 | # As we shall see, this problem has an analytical solution.
28 | # Let $H$ be the distance between the plates and $L$ the length of the channel. There are no body forces.
29 | #
30 | # We may scale the problem first to get rid of seemingly independent physical parameters. The physics of this problem is governed by viscous effects only, in the direction perpendicular to the flow, so a time scale should be based on diffusion across the channel: $t_v=H^2/\nu$. We let $U$, some characteristic inflow velocity, be the velocity scale and $H$ the spatial scale. The pressure scale is taken as the characteristic shear stress, $\mu U/H$, since this is a primary example of shear flow. Inserting $\bar{x}=x/H, \bar{y}=y/H, \bar{z}=z/H, \bar{u}=u/U, \bar{p}=Hp/{\mu U}$, and $\bar{t}=H^2/\nu$ in the equations results in the scaled Navier-Stokes equations (dropping the bars after scaling)
31 | # $$
32 | # \begin{equation}
33 | # \frac{\partial u}{\partial t}+ \mathrm{Re} u \cdot \nabla u &= -\nabla p + \nabla^2 u,\\
34 | # \nabla \cdot u &=0.
35 | # \end{equation}
36 | # $$
37 | # A detailed derivation for scaling of the Navier-Stokes equation for a large variety of physical situations can be found in {cite}`Langtangen2016scaling` (Chapter 4.2) by Hans Petter Langtangen and Geir K. Pedersen.
38 | #
39 | # Here, $\mathrm{Re}=\rho UH/\mu$ is the Reynolds number. Because of the time and pressure scales, which are different from convection dominated fluid flow, the Reynolds number is associated with the convective term and not the viscosity term.
40 | #
41 | # The exact solution is derived by assuming $u=(u_x(x,y,z),0,0)$ with the $x$-axis pointing along the channel. Since $\nabla \cdot u = 0$, $u$ cannot be dependent on $x$.
42 | #
43 | # The physics of channel flow is also two-dimensional so we can omit the $z$-coordinate (more precisely: $\partial/\partial z = 0$). Inserting $u=(u_x, 0, 0)$ in the (scaled) governing equations gives $u_x''(y)=\frac{\partial p}{\partial x}$.
44 | # Differentiating this equation with respect to $x$ shows that
45 | # $\frac{\partial^2p}{\partial x^2}=0$ so $\partial p/\partial x$ is a constant here called $-\beta$. This is the driving force of the flow and can be specified as a known parameter in the problem. Integrating $u_x''(x,y)=-\beta$ over the width of the channel, $[0,1]$, and requiring $u=(0,0,0)$ at the channel walls, results in $u_x=\frac{1}{2}\beta y(1-y)$. The characteristic inlet velocity $U$ can be taken as the maximum inflow at $y=0.5$, implying $\beta=8$. The length of the channel, $L/H$ in the scaled model, has no impact on the result, so for simplicity we just compute on the unit square. Mathematically, the pressure must be prescribed at a point, but since $p$ does not depend on $y$, we can set $p$ to a known value, e.g. zero, along the outlet boundary $x=1$. The result is
46 | # $p(x)=8(1-x)$ and $u_x=4y(1-y)$.
47 | #
48 | # The boundary conditions can be set as $p=8$ at $x=0$, $p=0$ at $x=1$ and $u=(0,0,0)$ on the walls $y=0,1$. This defines the pressure drop and should result in unit maximum velocity at the inlet and outlet and a parabolic velocity profile without no further specifications. Note that it is only meaningful to solve the Navier-Stokes equations in 2D or 3D geometries, although the underlying mathematical problem collapses to two $1D$ problems, one for $u_x(y)$ and one for $p(x)$.
49 | #
50 | # The scaled model is not so easy to simulate using a standard Navier-Stokes solver with dimensions. However, one can argue that the convection term is zero, so the Re coefficient in front of this term in the scaled PDEs is not important and can be set to unity. In that case, setting $\rho=\mu=1$ in the original Navier-Stokes equations resembles the scaled model.
51 | #
52 | # For a specific engineering problem one wants to simulate a specific fluid and set corresponding parameters. A general solver is therefore most naturally implemented with dimensions and using the original physical parameters.
53 | # However, scaling may greatly simplify numerical simulations.
54 | # First of all, it shows that all fluids behave in the same way; it does not matter whether we have oil, gas, or water flowing between two plates, and it does not matter how fast the flow is (up to some critical value of the Reynolds number where the flow becomes unstable and transitions to a complicated turbulent flow of totally different nature.)
55 | # This means that one simulation is enough to cover all types of channel flow!
56 | # In other applications, scaling shows that it might be necessary to just set the fraction of some parameters (dimensionless numbers) rather than the parameters themselves. This simplifies exploring the input parameter space which is often the purpose of simulation. Frequently, the scaled problem is run by setting some of the input parameters with dimension to fixed values (often unity).
57 |
58 | # ## Implementation
59 | #
60 | #
61 | # As in the previous example, we load the DOLFINx module, along with the `mpi4py` module, and create the unit square mesh and define the run-time and temporal discretization
62 |
63 | # +
64 | from mpi4py import MPI
65 | from petsc4py import PETSc
66 | import numpy as np
67 | import pyvista
68 |
69 | from dolfinx.fem import Constant,Function, FunctionSpace, assemble_scalar, dirichletbc, form, locate_dofs_geometrical
70 | from dolfinx.fem.petsc import assemble_matrix, assemble_vector, apply_lifting, create_vector, set_bc
71 | from dolfinx.io import XDMFFile
72 | from dolfinx.mesh import create_unit_square
73 | from dolfinx.plot import create_vtk_mesh
74 | from ufl import (FacetNormal, FiniteElement, Identity,TestFunction, TrialFunction, VectorElement,
75 | div, dot, ds, dx, inner, lhs, nabla_grad, rhs, sym)
76 |
77 | mesh = create_unit_square(MPI.COMM_WORLD, 10, 10)
78 | t = 0
79 | T = 10
80 | num_steps = 500
81 | dt = T/num_steps
82 | # -
83 |
84 | # As opposed to the previous demos, we will create our two function spaces using the `ufl` element definitions as input
85 |
86 | v_cg2 = VectorElement("CG", mesh.ufl_cell(), 2)
87 | s_cg1 = FiniteElement("CG", mesh.ufl_cell(), 1)
88 | V = FunctionSpace(mesh, v_cg2)
89 | Q = FunctionSpace(mesh, s_cg1)
90 |
91 | # The first space `V` is a vector valued function space for the velocity, while `Q` is a scalar valued function space for pressure. We use piecewise quadratic elements for the velocity and piecewise linear elements for the pressure. When creating the vector finite element, the dimension of the vector element will be set to the geometric dimension of the mesh. One can easily create vector-valued function spaces with other dimensions by adding the keyword argument dim, i.e.
92 | # `v_cg = ufl.VectorElement("CG", mesh.ufl_cell(), 2, dim=10)`.
93 | #
94 | # ```{admonition} Stable finite element spaces for the Navier-Stokes equation
95 | # It is well-known that certain finite element spaces are not *stable* for the Navier-Stokes equations, or even for the simpler Stokes equation. The prime example of an unstable pair of finite element spaces is to use first order degree continuous piecewise polynomials for both the velocity and the pressure.
96 | # Using an unstable pair of spaces typically results in a solution with *spurious* (unwanted, non-physical) oscillations in the pressure solution. The simple remedy is to use continuous piecewise quadratic elements for the velocity and continuous piecewise linear elements for the pressure. Together, these elements form the so-called *Taylor-Hood* element. Spurious oscillations may occur also for splitting methods if an unstable element pair is used.
97 | #
98 | # Since we have two different function spaces, we need to create two sets of trial and test functions:
99 |
100 | u = TrialFunction(V)
101 | v = TestFunction(V)
102 | p = TrialFunction(Q)
103 | q = TestFunction(Q)
104 |
105 |
106 | # As we have seen in [Linear elasticity problem](./linearelasticity_code) we can use Python-functions to create the different Dirichlet conditions. For this problem, we have three Dirichlet condition: First, we will set $u=0$ at the walls of the channel, that is at $y=0$ and $y=1$. In this case, we will use `dolfinx.fem.locate_dofs_geometrical`
107 |
108 | def walls(x):
109 | return np.logical_or(np.isclose(x[1],0), np.isclose(x[1],1))
110 | wall_dofs = locate_dofs_geometrical(V, walls)
111 | u_noslip = np.array((0,) * mesh.geometry.dim, dtype=PETSc.ScalarType)
112 | bc_noslip = dirichletbc(u_noslip, wall_dofs, V)
113 |
114 |
115 | # Second, we will set $p=8$ at the inflow ($x=0$)
116 |
117 | def inflow(x):
118 | return np.isclose(x[0], 0)
119 | inflow_dofs = locate_dofs_geometrical(Q, inflow)
120 | bc_inflow = dirichletbc(PETSc.ScalarType(8), inflow_dofs, Q)
121 |
122 |
123 | # And finally, $p=0$ at the outflow ($x=1$). This will result in a pressure gradient that will accelerate the flow from the initial state with zero velocity. At the end, we collect the boundary conditions for the velocity and pressure in Python lists so we can easily access them in the following computation.
124 |
125 | def outflow(x):
126 | return np.isclose(x[0], 1)
127 | outflow_dofs = locate_dofs_geometrical(Q, outflow)
128 | bc_outflow = dirichletbc(PETSc.ScalarType(0), outflow_dofs, Q)
129 | bcu = [bc_noslip]
130 | bcp = [bc_inflow, bc_outflow]
131 |
132 | # We now move on to the definition of the three variational forms, one for each step in the IPCS scheme. Let us look at the definition of the first variational problem and the relevant parameters.
133 |
134 | u_n = Function(V)
135 | u_n.name = "u_n"
136 | U = 0.5 * (u_n + u)
137 | n = FacetNormal(mesh)
138 | f = Constant(mesh, PETSc.ScalarType((0,0)))
139 | k = Constant(mesh, PETSc.ScalarType(dt))
140 | mu = Constant(mesh, PETSc.ScalarType(1))
141 | rho = Constant(mesh, PETSc.ScalarType(1))
142 |
143 |
144 | # ```{admonition} Usage of "dolfinx.Constant"
145 | # Note that we have wrapped several parameters as constants. This is to reduce the compilation-time of the variational formulations. By wrapping them as a constant, we can change the variable
146 | # ```
147 | # The next step is to set up the variational form of the first step.
148 | # As the variational problem contains a mix of known and unknown quantities, we will use the following naming convention: `u` (mathematically $u^{n+1}$) is known as a trial function in the variational form. `u_` is the most recently computed approximation ($u^{n+1}$ available as a `Function` object), `u_n` is $u^n$, and the same convention goes for `p,p_` ($p^{n+1}$) and `p_n` (p^n).
149 |
150 | # +
151 | # Define strain-rate tensor
152 | def epsilon(u):
153 | return sym(nabla_grad(u))
154 |
155 | # Define stress tensor
156 | def sigma(u, p):
157 | return 2*mu*epsilon(u) - p*Identity(u.geometric_dimension())
158 |
159 | # Define the variational problem for the first step
160 | p_n = Function(Q)
161 | p_n.name = "p_n"
162 | F1 = rho*dot((u - u_n) / k, v)*dx
163 | F1 += rho*dot(dot(u_n, nabla_grad(u_n)), v)*dx
164 | F1 += inner(sigma(U, p_n), epsilon(v))*dx
165 | F1 += dot(p_n*n, v)*ds - dot(mu*nabla_grad(U)*n, v)*ds
166 | F1 -= dot(f, v)*dx
167 | a1 = form(lhs(F1))
168 | L1 = form(rhs(F1))
169 | # -
170 |
171 | # Note that we have used the `ufl`-functions `lhs` and `rhs` to sort out the bilinear form $a(u,v)$ and linear form $L(v)$. This is particulary convenient in longer and more complicated variational forms. With our particular discretization $a(u,v)$ `a1` is not time dependent, and only has to be assembled once, while the right hand side is dependent on the solution from the previous time step (`u_n`). Thus, we do as for the [](./heat_code), and create the matrix outside the time-loop.
172 |
173 | A1 = assemble_matrix(a1, bcs=bcu)
174 | A1.assemble()
175 | b1 = create_vector(L1)
176 |
177 | # We now set up similar variational formulations and structures for the second and third step
178 |
179 | # +
180 | # Define variational problem for step 2
181 | u_ = Function(V)
182 | a2 = form(dot(nabla_grad(p), nabla_grad(q))*dx)
183 | L2 = form(dot(nabla_grad(p_n), nabla_grad(q))*dx - (rho/k)*div(u_)*q*dx)
184 | A2 = assemble_matrix(a2, bcs=bcp)
185 | A2.assemble()
186 | b2 = create_vector(L2)
187 |
188 | # Define variational problem for step 3
189 | p_ = Function(Q)
190 | a3 = form(rho*dot(u, v)*dx)
191 | L3 = form(rho*dot(u_, v)*dx - k*dot(nabla_grad(p_ - p_n), v)*dx)
192 | A3 = assemble_matrix(a3)
193 | A3.assemble()
194 | b3 = create_vector(L3)
195 | # -
196 |
197 | # As we have create all the linear structures for the problem, we can now create a solver for each of them using PETSc. We can therefore customize the solution strategy for each step. For the tentative velocity step and pressure correction step, we will use the Stabilized version of BiConjugate Gradient to solve the linear system, and using algebraic multigrid for preconditioning. For the last step, the velocity update, we use a conjugate gradient method with successive over relaxation, Gauss Seidel (SOR) preconditioning.
198 |
199 | # +
200 | # Solver for step 1
201 | solver1 = PETSc.KSP().create(mesh.comm)
202 | solver1.setOperators(A1)
203 | solver1.setType(PETSc.KSP.Type.BCGS)
204 | pc1 = solver1.getPC()
205 | pc1.setType(PETSc.PC.Type.HYPRE)
206 | pc1.setHYPREType("boomeramg")
207 |
208 | # Solver for step 2
209 | solver2 = PETSc.KSP().create(mesh.comm)
210 | solver2.setOperators(A2)
211 | solver2.setType(PETSc.KSP.Type.BCGS)
212 | pc2 = solver2.getPC()
213 | pc2.setType(PETSc.PC.Type.HYPRE)
214 | pc2.setHYPREType("boomeramg")
215 |
216 | # Solver for step 3
217 | solver3 = PETSc.KSP().create(mesh.comm)
218 | solver3.setOperators(A3)
219 | solver3.setType(PETSc.KSP.Type.CG)
220 | pc3 = solver3.getPC()
221 | pc3.setType(PETSc.PC.Type.SOR)
222 | # -
223 |
224 | # We prepare output files for the velocity and pressure data, and write the mesh and initial conditions to file
225 |
226 | xdmf = XDMFFile(mesh.comm, "poiseuille.xdmf", "w")
227 | xdmf.write_mesh(mesh)
228 | xdmf.write_function(u_n, t)
229 | xdmf.write_function(p_n, t)
230 |
231 | # We also interpolate the analytical solution into our function-space and create a variational formulation for the $L^2$-error.
232 | #
233 |
234 | # +
235 | import numpy as np
236 | def u_exact(x):
237 | values = np.zeros((2, x.shape[1]), dtype=PETSc.ScalarType)
238 | values[0] = 4*x[1]*(1.0 - x[1])
239 | return values
240 | u_ex = Function(V)
241 | u_ex.interpolate(u_exact)
242 |
243 | L2_error = form(dot(u_ - u_ex, u_ - u_ex)*dx)
244 | # -
245 |
246 | # The next step is to create the loop over time. Note that we for all three steps only have to assemble the right hand side and apply the boundary condition using lifting.
247 |
248 | # + tags=[]
249 | for i in range(num_steps):
250 | # Update current time step
251 | t += dt
252 |
253 | # Step 1: Tentative veolcity step
254 | with b1.localForm() as loc_1:
255 | loc_1.set(0)
256 | assemble_vector(b1, L1)
257 | apply_lifting(b1, [a1], [bcu])
258 | b1.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
259 | set_bc(b1, bcu)
260 | solver1.solve(b1, u_.vector)
261 | u_.x.scatter_forward()
262 |
263 | # Step 2: Pressure corrrection step
264 | with b2.localForm() as loc_2:
265 | loc_2.set(0)
266 | assemble_vector(b2, L2)
267 | apply_lifting(b2, [a2], [bcp])
268 | b2.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
269 | set_bc(b2, bcp)
270 | solver2.solve(b2, p_.vector)
271 | p_.x.scatter_forward()
272 |
273 | # Step 3: Velocity correction step
274 | with b3.localForm() as loc_3:
275 | loc_3.set(0)
276 | assemble_vector(b3, L3)
277 | b3.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
278 | solver3.solve(b3, u_.vector)
279 | u_.x.scatter_forward()
280 | # Update variable with solution form this time step
281 | u_n.x.array[:] = u_.x.array[:]
282 | p_n.x.array[:] = p_.x.array[:]
283 |
284 | # Write solutions to file
285 | xdmf.write_function(u_n, t)
286 | xdmf.write_function(p_n, t)
287 |
288 | # Compute error at current time-step
289 | error_L2 = np.sqrt(mesh.comm.allreduce(assemble_scalar(L2_error), op=MPI.SUM))
290 | error_max = mesh.comm.allreduce(np.max(u_.vector.array - u_ex.vector.array), op=MPI.MAX)
291 | # Print error only every 20th step and at the last step
292 | if (i % 20 == 0) or (i == num_steps - 1):
293 | print(f"Time {t:.2f}, L2-error {error_L2:.2e}, Max error {error_max:.2e}")
294 | # Close xmdf file
295 | xdmf.close()
296 | # -
297 |
298 | # ## Verification
299 | # As for the previous problems we compute the error at each degree of freedom and the $L^2(\Omega)$-error. We start with the initial condition $u=(0,0)$. We have not specified the initial condition explicitly, and FEniCSx will initialize all `Function`s including `u_n` and `u_` to zero. Since the exact solution is quadratic, we expect to reach machine precision within finite time. For our implementation, we observe that the error quickly approaches zero, and is of order $10^{-6}$ at $T=10
300 | #
301 | # ## Visualization of vectors
302 | # We have already looked at how to plot higher order functions and vector functions. In this section we will look at how to visualize vector functions with glyphs, instead of warping the mesh.
303 |
304 | # +
305 | pyvista.set_jupyter_backend("pythreejs")
306 | topology, cell_types, geometry = create_vtk_mesh(V)
307 | values = np.zeros((geometry.shape[0], 3), dtype=np.float64)
308 | values[:, :len(u_n)] = u_n.x.array.real.reshape((geometry.shape[0], len(u_n)))
309 |
310 | # Create a point cloud of glyphs
311 | function_grid = pyvista.UnstructuredGrid(topology, cell_types, geometry)
312 | function_grid["u"] = values
313 | glyphs = function_grid.glyph(orient="u", factor=0.2)
314 |
315 | # Create a pyvista-grid for the mesh
316 | grid = pyvista.UnstructuredGrid(*create_vtk_mesh(mesh, mesh.topology.dim))
317 |
318 | # Create plotter
319 | plotter = pyvista.Plotter()
320 | plotter.add_mesh(grid, style="wireframe", color="k")
321 | plotter.add_mesh(glyphs)
322 | plotter.view_xy()
323 | if not pyvista.OFF_SCREEN:
324 | plotter.show()
325 | else:
326 | pyvista.start_xvfb()
327 | fig_as_array = plotter.screenshot("glyphs.png")
328 | # -
329 |
330 | # ## References
331 | # ```{bibliography}
332 | # :filter: docname in docnames
333 | # ```
334 |
--------------------------------------------------------------------------------
/Problem6_Darcy/Darcy.py:
--------------------------------------------------------------------------------
1 | # ---
2 | # jupyter:
3 | # jupytext:
4 | # text_representation:
5 | # extension: .py
6 | # format_name: light
7 | # format_version: '1.5'
8 | # jupytext_version: 1.14.1
9 | # kernelspec:
10 | # display_name: Python 3
11 | # language: python
12 | # name: python3
13 | # ---
14 |
15 | # + [markdown] id="Zj2JuirOgqkX"
16 | # # Mixed problem: Darcy's flow in porous media
17 | # ---
18 |
19 | # + [markdown] id="qIsdaHH2sjb0"
20 | # Let us consider Poisson's problem in mixed form, in which we have both, the flux variable $\boldsymbol{J}$ and the scalar variable $p$
21 | # (the pressure), that satisfy
22 | #
23 | # \begin{equation}
24 | # \left \{
25 | # \begin{array}{rcll}
26 | # \nabla \cdot \boldsymbol{J} & = & f & \mbox{in}~~\Omega \\
27 | # & & & \\
28 | # \boldsymbol{J} & = & -\boldsymbol{\kappa} \cdot \nabla{p} & \mbox{in}~~\Omega \\
29 | # & & & \\
30 | # p & = & p_D & \mbox{on}~~\partial{\Omega}_D \\
31 | # & & & \\
32 | # \boldsymbol{J} \cdot \check{\mathbf{n}} & = & J_N & \mbox{on}~~\partial{\Omega}_N \\
33 | # \end{array}
34 | # \right.
35 | # \end{equation}
36 | #
37 | # where $\boldsymbol{\kappa} \in [L^{\infty}(\Omega)]^{d \times d}$
38 | # is in general a full tensor. Rewrite the second equation as
39 | #
40 | # $$
41 | # \boldsymbol{\kappa}^{-1} \cdot \boldsymbol{J} + \nabla{p} = 0
42 | # $$
43 | #
44 | # This leads us to the following variational problem: Find $(\boldsymbol{J}, p) \in V_{J} \times Q$ such that
45 | #
46 | # \begin{eqnarray}
47 | # {\int_{\Omega}}{\boldsymbol{\kappa}^{-1} \cdot \boldsymbol{J} \cdot \mathbf{v}}\,dx - {\int_{\Omega}}{p\,\nabla \cdot \mathbf{v}}\,dx & = &
48 | # -{\int_{\partial{\Omega}_D}}{p_D \left (\mathbf{v} \cdot \check{\mathbf{n}}\right )}\,ds \\
49 | # {\int_{\Omega}}{q\,\nabla \cdot \boldsymbol{J}}\,dx & = & {\int_{\Omega}}{f\,q}\,dx
50 | # \end{eqnarray}
51 | # $\forall (\mathbf{v},q) \in V_0 \times Q.$
52 | #
53 | # Notice that, in contrast to the primal formulation of Poisson's problem we have been dealing until now, in the mixed form, the Neumann condition $J$ on $\Gamma_N$ appears (**strongly**) as a restriction on the trial set $V_{J}$ for $\boldsymbol{J}$ and the Dirichlet condition
54 | # $p_D$ on $\Gamma_D$ appears (**naturally**) in the variational problem.
55 | #
56 | # In abstract form we write: Find $(\mathbf{u}, p) \in V_{J} \times Q$ such that
57 | #
58 | # \begin{equation}
59 | # \left.
60 | # \begin{array}{rlll}
61 | # a(\mathbf{u},\mathbf{v}) - b(\mathbf{v},p) & = & \ell(\mathbf{v}) \\
62 | # b(\mathbf{u},q) & = & g(q)
63 | # \end{array}
64 | # \right.
65 | # \end{equation}
66 | # $\forall (\mathbf{v},q) \in V_0 \times Q$.
67 |
68 | # + id="F8zm2mocgiW4"
69 | # try:
70 | # import gmsh
71 | # except ImportError:
72 | # # !wget "https://fem-on-colab.github.io/releases/gmsh-install.sh" -O "/tmp/gmsh-install.sh" && bash "/tmp/gmsh-install.sh"
73 | # import gmsh
74 |
75 | # try:
76 | # import dolfinx
77 | # except ImportError:
78 | # # !wget "https://fem-on-colab.github.io/releases/fenicsx-install-real.sh" -O "/tmp/fenicsx-install.sh" && bash "/tmp/fenicsx-install.sh"
79 | # import dolfinx
80 |
81 | # # !wget "https://raw.githubusercontent.com/IgorBaratta/FEniCSxCourse/main/Problem6_Darcy/perm_field.dat"
82 |
83 | # + id="e6_JfgvuhIjl"
84 | from dolfinx import mesh, fem, io, plot
85 |
86 | from ufl import SpatialCoordinate, FiniteElement, MixedElement, TestFunction, TrialFunction, split, Measure, dx, ds, grad, div, dot, inner, as_vector, FacetNormal
87 |
88 | import gmsh
89 | import numpy as np
90 | from mpi4py import MPI
91 | from petsc4py.PETSc import ScalarType
92 |
93 | # + [markdown] id="71R8ed6EwoF3"
94 | # ## Finite element mesh
95 |
96 | # + [markdown] id="XuMNCOpHv2V_"
97 | # First, create the finite element mesh, we can use triangular or quadrilateral elements, just by uncommenting the appropriate flag below
98 | #
99 | # * `mesh.CellType.triangle`
100 | # * `mesh.CellType.quadrilateral`
101 | #
102 | # We also may select a structured mesh using the convinience
103 | # `dolfinx` function `mesh.create_rectangle` or a more general partitions
104 | # or the `gmsh` library as usual:
105 |
106 | # + id="ICOmBcdxjC8U"
107 | # Computational mesh
108 | Lx, Ly = 11.0/3.0, 1.0
109 | #cell_type = mesh.CellType.triangle
110 | cell_type = mesh.CellType.quadrilateral
111 |
112 | simple_mesh = False
113 |
114 | if(simple_mesh):
115 | msh = mesh.create_rectangle(comm=MPI.COMM_WORLD,
116 | points=((0.0, 0.0), (Lx, Ly)), n=(220, 60),
117 | cell_type=cell_type)
118 | else:
119 | def GenerateMesh():
120 | gmsh.initialize()
121 | proc = MPI.COMM_WORLD.rank
122 |
123 | if proc == 0:
124 | # We create one rectangle and the circular inclusion
125 | background = gmsh.model.occ.addRectangle(0, 0, 0, Lx, Ly)
126 | gmsh.model.occ.synchronize()
127 | pl = gmsh.model.addPhysicalGroup(2, [background], 1)
128 |
129 | gmsh.model.mesh.setSize(gmsh.model.getEntities(0), 0.04)
130 |
131 | if(cell_type == mesh.CellType.quadrilateral):
132 | gmsh.model.mesh.setRecombine(2, pl)
133 |
134 | gmsh.model.mesh.generate(2)
135 |
136 | msh, subdomains, boundaries = io.gmshio.model_to_mesh(gmsh.model, comm=MPI.COMM_WORLD, rank=0, gdim=2)
137 | gmsh.finalize()
138 | return msh, subdomains, boundaries
139 |
140 | msh, subdomains, boundaries = GenerateMesh()
141 |
142 | # Visualize the mesh
143 | with io.XDMFFile(MPI.COMM_WORLD, "mymesh.xdmf", "w") as xdmf:
144 | xdmf.write_mesh(msh)
145 |
146 | # Identify the four boundaries
147 | boundaries = [(1, lambda x: np.isclose(x[0], 0)),
148 | (2, lambda x: np.isclose(x[0], Lx)),
149 | (3, lambda x: np.isclose(x[1], 0)),
150 | (4, lambda x: np.isclose(x[1], Ly))]
151 |
152 | facet_indices, facet_markers = [], []
153 | fdim = msh.topology.dim - 1
154 | for (marker, locator) in boundaries:
155 | facets = mesh.locate_entities(msh, fdim, locator)
156 | facet_indices.append(facets)
157 | facet_markers.append(np.full_like(facets, marker))
158 | facet_indices = np.hstack(facet_indices).astype(np.int32)
159 | facet_markers = np.hstack(facet_markers).astype(np.int32)
160 | sorted_facets = np.argsort(facet_indices)
161 | facet_tag = mesh.meshtags(msh, fdim, facet_indices[sorted_facets], facet_markers[sorted_facets])
162 |
163 | ds = Measure("ds", domain=msh, subdomain_data=facet_tag)
164 |
165 | if(False):
166 | one = fem.Constant(msh, 1.0)
167 | for i in [1, 2, 3, 4]:
168 | length_form = fem.form( one*ds(i) )
169 | lengthside = fem.assemble_scalar(length_form)
170 | print(lengthside)
171 |
172 | # + [markdown] id="zdm6uNXywuGB"
173 | # ## Highly heterogeneous coefficient
174 | #
175 | # In this tutorial we will see how to load some data to define the permeability field. In this case we consider the isotropic
176 | # case, i.e.,
177 | #
178 | # $$
179 | # \boldsymbol{\kappa}(\mathbf{x}) = \kappa(\mathbf{x})\,\mathbf{I}_{d\times d}
180 | # $$
181 | #
182 | # We will solve the famous **SPE10** benchmark
183 | # used in reservoir engineering. We load the data contained in file `perm_field.dat` as follows
184 |
185 | # + id="NmU7siTKtytB"
186 | if(cell_type == mesh.CellType.triangle):
187 | el_kappa = FiniteElement("DG", msh.ufl_cell(), 0)
188 | else:
189 | el_kappa = FiniteElement("DQ", msh.ufl_cell(), 0)
190 |
191 | K = fem.FunctionSpace(msh, el_kappa)
192 | kappax = fem.Function(K)
193 | invkappax = fem.Function(K)
194 |
195 | # Physical size of global domain SPE10
196 | Lx, Ly = 11.0/3.0, 1.0
197 | Nx, Ny = 220, 60
198 | Dx, Dy = Lx/Nx, Ly/Ny
199 |
200 | # Read the data
201 | table = open('perm_field.dat', 'r')
202 | lista = table.readlines()
203 | table.close()
204 |
205 | kx_array = np.zeros(shape=(Nx,Ny))
206 | for i in range(Nx):
207 | for j in range(Ny):
208 | k = Nx*j + i
209 | kx_array[i,j] = float(lista[k].split()[0])
210 |
211 | # + [markdown] id="RSjmvJPhy_xK"
212 | # Notice how the field is highly heterogeneous
213 |
214 | # + colab={"base_uri": "https://localhost:8080/", "height": 200} executionInfo={"elapsed": 1119, "status": "ok", "timestamp": 1674237751735, "user": {"displayName": "Roberto Federico Ausas", "userId": "01910242568345374894"}, "user_tz": 180} id="xL1OPt_Y14_9" outputId="ee094ecf-c9c2-4a3e-9c01-fa6f984396f5"
215 | import matplotlib.pyplot as plt
216 | plt.figure(figsize=(33/3, 3))
217 | plt.imshow(np.log(kx_array[:,:].T), origin="leftbottom", cmap='gist_rainbow')
218 | plt.colorbar()
219 | plt.show()
220 |
221 |
222 | # + [markdown] id="B-9-NBavzZ_J"
223 | # Now, we must assign this values to a function belonging to an appropriate
224 | # finite element space. To that end we will use as in other ocassions an elementwise constant function. Notice that for triangular and quadrilateral cells we must properly declare the corresponding element type:
225 | #
226 | # * `DG` for triangular elements
227 | # * `DQ` for quadrilateral elements
228 | #
229 | # and finally interpolate the data to the function representing the permeability ${\kappa}$, which can be efficiently done in the following way:
230 |
231 | # + colab={"base_uri": "https://localhost:8080/", "height": 17} executionInfo={"elapsed": 326, "status": "ok", "timestamp": 1674237777339, "user": {"displayName": "Roberto Federico Ausas", "userId": "01910242568345374894"}, "user_tz": 180} id="8myvziiczMSn" outputId="fdbfd930-1873-43c0-9945-4d1b4cc61702"
232 | def EvalKappa(x):
233 | icellf = 0.99999999*x[0]/Dx
234 | jcellf = 0.99999999*x[1]/Dy
235 | icell = icellf.astype(int)
236 | jcell = jcellf.astype(int)
237 | return kx_array[icell,jcell]
238 |
239 | kappax.interpolate(EvalKappa)
240 |
241 | # Visualize in Paraview
242 | kappax.name = "kappa"
243 | with io.XDMFFile(msh.comm, "kappa.xdmf", "w") as xdmf:
244 | xdmf.write_mesh(msh)
245 | xdmf.write_function(kappax)
246 |
247 | from google.colab import files
248 | files.download('kappa.xdmf')
249 | files.download('kappa.h5')
250 |
251 | # + [markdown] id="ZmUZ-49T4WEH"
252 | # ## Mixed variational formulation
253 | #
254 | # We first notice that alternatively, the problem can be written by defining a unique bilinear and linear form by substracting the two equations:
255 | #
256 | # Find $(\mathbf{u},p) \in V_{J} \times Q$ such that
257 | # \begin{equation}
258 | # \underbrace{{a}(\mathbf{u},\mathbf{v}) - b(\mathbf{v},p) - b(\mathbf{u},q)}_{B\left ((\mathbf{u},p), (\mathbf{v},q) \right ) } = \underbrace{\ell(\mathbf{v}) - g(q)}_{L \left ((\mathbf{v},q)\right )}
259 | # \end{equation}
260 | # $\forall (\mathbf{v},q) \in V_0 \times Q$,
261 | #
262 | # so the problem is defined in the product space $W_J = V_{J} \times Q$ as
263 | #
264 | # Find $(\mathbf{u},p) \in W$ such that
265 | # \begin{equation}
266 | # B\left ((\mathbf{u},p), (\mathbf{v},q) \right ) = L \left ((\mathbf{v},q)\right )~~~\forall (\mathbf{v},q) \in W_0
267 | # \end{equation}
268 | #
269 | # and this is how we are actually defining the variational problem in
270 | # `dolfinx`.
271 | #
272 | # Now, the proper functional setting for this problem is, for
273 | # the velocity space:
274 | #
275 | # $$
276 | # V = H(\mbox{div},\Omega) =
277 | # \{\mathbf{v} \in [L^2(\Omega)]^d,~~\nabla \cdot \mathbf{v} \in L^2(\Omega) \}
278 | # $$
279 | #
280 | # $$
281 | # V_J = \{\mathbf{v} \in V,~~\mathbf{v}\cdot \check{\mathbf{n}} = J~\mbox{on}~\partial{\Omega}_N \}
282 | # $$
283 | #
284 | # and the pressure space:
285 | #
286 | # $$
287 | # Q = L^2(\Omega)
288 | # $$
289 | #
290 | # Popular discrete subspaces are the Raviart-Thomas (`RT` or `RTCF`) and Brezzi-Douglas-Marini (`BDM` or `BDMCF`) spaces wich are available in `dolfinx`, such that we ensure the velocity has continuous normal
291 | # component. These are used in combination with
292 | # spaces of discontinuous functions for the pressure field, so as to end
293 | # up with a stable formulation:
294 |
295 | # + id="c1NRDg4Troes"
296 | degree_u = 1
297 | degree_p = 0
298 |
299 | if(cell_type == mesh.CellType.triangle):
300 | el_u = FiniteElement("RT", msh.ufl_cell(), degree_u)
301 | el_p = FiniteElement("DG", msh.ufl_cell(), degree_p)
302 | else:
303 | el_u = FiniteElement("RTCF", msh.ufl_cell(), degree_u)
304 | el_p = FiniteElement("DQ", msh.ufl_cell(), degree_p)
305 |
306 | # Define the mixed element
307 | el_m = MixedElement([el_u , el_p])
308 | # and the corresponding global space
309 | W = fem.FunctionSpace(msh, el_m)
310 |
311 | # + [markdown] id="VkqstkpYs_EH" endofcell="--"
312 | # Recalling, the bilinear and linear forms are:
313 | #
314 | # $$
315 | # B((\mathbf{u},\mathbf{v}), (p,q)) =
316 | # \int_{\Omega}{\kappa}^{-1} \boldsymbol{J} \cdot \mathbf{v}\,dx -
317 | # {\int_{\Omega}}{p\,\nabla \cdot \mathbf{v}}\,dx
318 | # -
319 | # {\int_{\Omega}}{q\,\nabla \cdot \mathbf{u}}\,dx
320 | # $$
321 | #
322 | # $$
323 | # L((\mathbf{v}, q)) = {\int_{\partial{\Omega}_D}}{p_D \left (\mathbf{v} \cdot \check{\mathbf{n}} \right )}\,ds
324 | # - {\int_{\Omega}}{f\,q}\,dx
325 | # $$
326 | #
327 | # --
328 |
329 | # + id="Zsip_Tw9xlFG"
330 | TrialF = TrialFunction(W)
331 | TestF = TestFunction(W)
332 | (u, p) = split(TrialF)
333 | (v, q) = split(TestF)
334 |
335 | # Source term
336 | f = fem.Constant(msh, 0.0)
337 |
338 | pleft = fem.Constant(msh, 100.0)
339 | pright = fem.Constant(msh, 0.0)
340 |
341 | # Define variational form
342 | n = FacetNormal(msh)
343 | B = ((1.0/kappax)*inner(u, v) - div(v)*p - div(u)*q) * dx
344 | L = f * q * dx - pleft*dot(v,n)*ds(1) - pright*dot(v,n)*ds(2)
345 |
346 |
347 | # + [markdown] id="AwvgJEALyHUt"
348 | # and finally, the **essential** boundary conditions (this take some work):
349 |
350 | # + id="UoPkexO9lpvw"
351 | # Define some function, such that g.n = J (the flux data)
352 | def g(x, n):
353 | return (np.zeros_like(x[0]), np.zeros_like(x[0]))
354 |
355 | W0, U_to_W = W.sub(0).collapse()
356 | w_bc = fem.Function(W0)
357 |
358 | sdim = msh.topology.dim
359 | top_facets = mesh.locate_entities_boundary(msh, sdim-1, lambda x: np.isclose(x[1], Ly))
360 | top_cells = mesh.compute_incident_entities(msh, top_facets, sdim-1, sdim)
361 | w_bc.interpolate(lambda x: g(x, (0,1)), top_cells)
362 |
363 | bottom_facets = mesh.locate_entities_boundary(msh, sdim-1, lambda x: np.isclose(x[1], 0))
364 | bottom_cells = mesh.compute_incident_entities(msh, bottom_facets, sdim-1, sdim)
365 | w_bc.interpolate(lambda x: g(x, (0,1)), bottom_cells)
366 |
367 | w_bc.x.scatter_forward()
368 |
369 | bc_dofs = fem.locate_dofs_topological((W.sub(0), W0), sdim-1, np.hstack([top_facets, bottom_facets]))
370 | bcs = [fem.dirichletbc(w_bc, bc_dofs, W.sub(0))]
371 |
372 | # + [markdown] id="8JKM68y9E9Xz"
373 | # The discrete algebraic problem that emanates from this variational formulation has
374 | # the typical **saddle point** structure, i.e.,
375 | #
376 | # $$
377 | # \begin{bmatrix}
378 | # A & B \\
379 | # B^{\intercal} & 0
380 | # \end{bmatrix}
381 | # \begin{bmatrix}
382 | # \mathsf{U} \\
383 | # \mathsf{P}
384 | # \end{bmatrix}
385 | # =
386 | # \begin{bmatrix}
387 | # \mathsf{F} \\
388 | # \mathsf{G}
389 | # \end{bmatrix}
390 | # $$
391 | #
392 | # where $\mathsf{X} = [\mathsf{U}, \mathsf{P}]^{\intercal}$
393 | # is the global vector of velocity and pressure unknowns.
394 | # Solving this problem by means of iterative methods, which is mandatory
395 | # for large systems, generally in the 3D case, can be quite challenging,
396 | # requiring efficient **preconditioners**. In our 2D case we can solve using direct methods:
397 |
398 | # + colab={"base_uri": "https://localhost:8080/"} executionInfo={"elapsed": 2779, "status": "ok", "timestamp": 1674237943742, "user": {"displayName": "Roberto Federico Ausas", "userId": "01910242568345374894"}, "user_tz": 180} id="1jueYKwWFBWN" outputId="d4b7022c-2fac-4f99-964f-bda477863691"
399 | wh = fem.Function(W)
400 | petsc_opts={"ksp_type": "preonly", "pc_type": "lu", "pc_factor_mat_solver_type": "superlu", "ksp_monitor": None}
401 | problem = fem.petsc.LinearProblem(B, L, bcs=bcs, u=wh, petsc_options=petsc_opts)
402 | problem.solve()
403 | (uh,ph) = wh.split()
404 |
405 | # Verification
406 | inflow = fem.assemble_scalar(fem.form(dot(uh,n)*ds(1)))
407 | outflow = fem.assemble_scalar(fem.form(dot(uh,n)*ds(2)))
408 | bottomflow = fem.assemble_scalar(fem.form(dot(uh,n)*ds(3)))
409 | topflow = fem.assemble_scalar(fem.form(dot(uh,n)*ds(4)))
410 | print("\nInflow =%f\nOutflow=%f\nTopflow=%f\nBotflow=%f" %(inflow, outflow, topflow, bottomflow))
411 |
412 | # + [markdown] id="zrt8iHpK1Eze"
413 | # Since in this case the source term $f = 0$, the inflow must be equal to the outflow, a fact that the discrete formulation is able to capture.
414 | # To visualize the velocity field in `Paraview`, we must first interpolate onto a space of vector functions, e.g., a `DG` space:
415 |
416 | # + colab={"base_uri": "https://localhost:8080/", "height": 17} executionInfo={"elapsed": 773, "status": "ok", "timestamp": 1674238058895, "user": {"displayName": "Roberto Federico Ausas", "userId": "01910242568345374894"}, "user_tz": 180} id="v0nahLzIx42k" outputId="8b18e2a7-1c35-4ef7-a2a3-79af8323493e"
417 | if(cell_type == mesh.CellType.triangle):
418 | BDM_out = fem.VectorFunctionSpace(msh, ("DG", 1))
419 | else:
420 | BDM_out = fem.VectorFunctionSpace(msh, ("DQ", 1))
421 | u_out = fem.Function(BDM_out)
422 | u_out.interpolate(uh)
423 | u_out.name = "Velocity"
424 | with io.XDMFFile(msh.comm, "vel.xdmf", "w") as xdmf:
425 | xdmf.write_mesh(msh)
426 | xdmf.write_function(u_out)
427 |
428 | ph.name = "Pressure"
429 | with io.XDMFFile(msh.comm, "pres.xdmf", "w") as xdmf:
430 | xdmf.write_mesh(msh)
431 | xdmf.write_function(ph)
432 |
433 | from google.colab import files
434 | files.download('vel.xdmf')
435 | files.download('vel.h5')
436 | files.download('pres.xdmf')
437 | files.download('pres.h5')
438 |
439 |
440 | # + [markdown] id="wMIeE0biH00Y"
441 | # ## Homework 6
442 | #
443 | # Solve the same problem using the primal formulation
444 | # we have seen in our first lecture and compare the results.
445 | # Compute the mass balance and observe how it fails as compared to
446 | # the mixed formulation.
447 |
448 | # +
449 | def SolvePoissonPrimal(msh):
450 |
451 | V = fem.FunctionSpace(msh, ("CG", 1))
452 |
453 | nunks = V.dofmap.index_map.size_global
454 |
455 | dofsL = fem.locate_dofs_geometrical(V, lambda x: np.isclose(x[0], 0))
456 | dofsR = fem.locate_dofs_geometrical(V, lambda x: np.isclose(x[0], Lx))
457 | bcs = [fem.dirichletbc(ScalarType(100.0), dofsL, V), fem.dirichletbc(ScalarType(0.0), dofsR, V)]
458 |
459 | p, q = TrialFunction(V), TestFunction(V)
460 |
461 | #kappax = fem.Constant(msh, 1.0)
462 | a = inner(kappax*grad(p), grad(q)) * dx
463 | f = 0.0
464 | source = fem.Constant(msh, f)
465 | L = source * q * dx
466 | problem = fem.petsc.LinearProblem(a, L, bcs=bcs, petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
467 | ph = problem.solve()
468 |
469 | ds = Measure("ds", domain=msh, subdomain_data=facet_tag)
470 |
471 | Jh = -kappax*grad(ph)
472 | n = FacetNormal(msh)
473 | inflow = fem.assemble_scalar(fem.form(dot(Jh,n)*ds(1)))
474 | outflow = fem.assemble_scalar(fem.form(dot(Jh,n)*ds(2)))
475 | bottomflow = fem.assemble_scalar(fem.form(dot(Jh,n)*ds(3)))
476 | topflow = fem.assemble_scalar(fem.form(dot(Jh,n)*ds(4)))
477 | print("\nInflow =%f\nOutflow=%f\nTopflow=%f\nBotflow=%f" %(inflow, outflow, topflow, bottomflow))
478 |
479 | return ph
480 |
481 | phprimal = SolvePoissonPrimal(msh)
482 |
483 | phprimal.name = "Pressure"
484 | with io.XDMFFile(msh.comm, "presprimal.xdmf", "w") as xdmf:
485 | xdmf.write_mesh(msh)
486 | xdmf.write_function(phprimal)
487 |
--------------------------------------------------------------------------------
/Problem2_Transient/heat_eq.py:
--------------------------------------------------------------------------------
1 | # # Solving a time-dependent problem
2 |
3 | #
4 | # This notebook shows how to solve a transient problem using DOLFINx,
5 | # namely the diffusion problem, the simplest extension of the Poisson problem.
6 | #
7 | # The transient or unsteady version of the previous problems includes an additional term involving the rate
8 | # of change of the physical quantity of interest.
9 | # For the heat conduction problem the equation reads
10 | # $$
11 | # \begin{align*}
12 | # a(\mathbf{x},t)\,\dfrac{\partial{u}(\boldsymbol{x},t)}{\partial{t}} -\nabla \cdot \left ( \mu(\boldsymbol{x},t) \nabla{u}(\boldsymbol{x},t) \right) & = f(\boldsymbol{x},t) & \boldsymbol{x} \in \Omega,~~t \in [0,T] \\
13 | # & & & \\
14 | # u(\boldsymbol{x},t) & = g(\boldsymbol{x},t) & \boldsymbol{x} \in \partial{\Omega},~~t \in [0,T] \\
15 | # & & & \\
16 | # u(\boldsymbol{x},0) & = u_0(\boldsymbol{x}) & \boldsymbol{x} \in \partial{\Omega} &
17 | # \end{align*}
18 | # $$
19 | #
20 | # Where $u$, the temperature distribution, varies with space and time $u(\boldsymbol{x}, t)$. \
21 | # $u_D$ is a prescribed function at the boundary $\partial\Omega_\text{D}$. \
22 | # And $u_0$ is the initial temperature distribution.
23 | #
24 | # In general, to solve time-dependent PDEs using the finite element method, we first discretize
25 | # the time derivative using a finite difference scheme which yelds a recursive series of stationary
26 | # problems. We can then convert each stationary problem into a variational problem.
27 |
28 | # ## Time discretization
29 | # A backward Euler scheme can be used to approximate the time derivative.
30 | # $$
31 | # \begin{align*}
32 | # a \cdot \frac{u_{n} - u_{n-1}}{\Delta t} - \nabla \cdot (\mu \nabla u_{n}) = f_{n}
33 | # \end{align*}
34 | # $$
35 | # We may reorder the semi-discrete equation so that the left-hand side contains the terms with
36 | # the unknown $u_{n}$ and the right-hand side contains computed terms only.
37 | # $$
38 | # \begin{align*}
39 | # a \, u_{n} - \Delta t \nabla \, \cdot (\mu\nabla u_{n}) = \Delta t f_{n} + a \, u_{n-1}
40 | # \end{align*}
41 | # $$
42 | #
43 |
44 | # ## Variational formulation
45 | # As usual we find the weak by multiplying our semi-discrete equation by a sufficiently
46 | # regular test function $v$ and applying integration by parts. \
47 | # At time-step $n$ the weak the variational problem reads:
48 | #
49 | # Find $u_n \in V(\Omega)$ such that
50 | # $$
51 | # \begin{align*}
52 | # \int_{\Omega} a\, u_n v \,dx + \Delta t
53 | # \int_{\Omega}{ \mu \nabla u_n \cdot \nabla v}\,dx
54 | # = \Delta t \int_{\Omega} f \cdot v \, dx + \int_{\Omega}
55 | # a\, u_{n-1} \cdot v \, dx
56 | # \end{align*}
57 | # $$
58 |
59 | # Again, for this problem, the natural choice is a space of scalar-valued continuous functions,
60 | # that are element-wise polynomials of degree $k$
61 | # $$
62 | # V(\mathcal{T}_h) = V_h = \{v \in H ^ 1(\Omega),
63 | # ~v | _E \in P_k(E) \, \forall E \in \mathcal{T}_h\}
64 | # $$
65 | # Which in DOLFINx can be declared as:
66 | #
67 | # V = fem.FunctionSpace(mesh, ("Lagrange", k))
68 |
69 | # ## Test problem - Manufactured Solution
70 | # We construct first a test problem for which we can easily check the answer. \
71 | # In this tutorial we create a test problem with a linear variation in time because we
72 | # know our first-order time-stepping scheme is exact for linear functions.
73 | #
74 | # $$
75 | # \begin{align*}
76 | # u(\boldsymbol{x}, t) = c_0 x_0^2 + c_1 x_0 + c_3t
77 | # \end{align*}
78 | # $$
79 | # which produces a function whose computed values at the nodes are exact regardless
80 | # of element size and $\Delta t$, as long as the mesh is partitioned uniformly.
81 | # Then we insert the exact solution into the strong form of our pde.
82 | # With simple manipulations we realize that the source should be:
83 | # $$
84 | # \begin{align*}
85 | # f = c_2 - 2c_0
86 | # \end{align*}
87 | # $$
88 | # Also, at the boundaries we have:
89 | # $$
90 | # \begin{align*}
91 | # T_D(\boldsymbol{x}, t) = c_0 x_0^2 + c_1 x_0 + c_2t
92 | # \end{align*}
93 | # $$
94 |
95 | # ## Implementation
96 | #
97 | # The first step is to import all modules and packages we are going to use:
98 |
99 | # +
100 | # Auxiliary packages
101 | from petsc4py import PETSc # Linear algebra backend
102 | from mpi4py import MPI # MPI
103 | import numpy
104 |
105 | # Import dolfinx and ufl modules
106 | from dolfinx import geometry
107 | from dolfinx import fem
108 | from dolfinx import io
109 | from dolfinx import la
110 | from dolfinx.mesh import (CellType, create_unit_square, locate_entities, exterior_facet_indices)
111 | from ufl import TestFunction, TrialFunction, dx, inner, grad
112 |
113 | # Convenience functions for plotting on jupyter notebooks
114 | from utils import plot_mesh
115 | import IPython
116 | # -
117 |
118 | # ### Creating a distributed computational domain (mesh)
119 |
120 | # To create a simple computational domain in DOLFINx, we use the mesh generation utilities in `dolfinx.mesh`.
121 | # In this module, we have the tools to build rectangles of triangular or quadrilateral elements and boxes
122 | # of tetrahedral or hexahedral elements. We start by creating a unit square:
123 |
124 | comm = MPI.COMM_WORLD
125 | mesh = create_unit_square(comm, 10, 10, CellType.triangle)
126 | # Compute conductivities between facets and cells
127 | mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim)
128 |
129 |
130 | # ### Visualizing mesh
131 | # We have provided a few convenience functions for plotting on jupyter notebooks.
132 |
133 | plot_mesh(mesh, filename="mesh.html")
134 | IPython.display.HTML(filename="mesh.html")
135 |
136 | # ### Handling time-dependent functions expressions
137 | #
138 | # First we define the constants we use in our program:
139 |
140 | # +
141 | a = fem.Constant(1.0)
142 | mu = fem.Constant(1.0)
143 | c0, c1, c2 = 1.0, 2.0, 0.5
144 | dt = fem.Constant(mesh, 0.1)
145 | # -
146 |
147 | # We can now define the source term as a Constant:
148 | # $$
149 | # \begin{align*}
150 | # f = c_2 - 2c_0
151 | # \end{align*}
152 | # $$
153 |
154 | f = fem.Constant(mesh, c2 - 2*c0)
155 | print(type(f))
156 |
157 | # We can now define an expression that be used to compute $u$, $u_0$ and $u_D$.
158 | # Once we have this expression we can compute the coefficients of a function $u \in V_h$
159 | # by interpolation.
160 |
161 | # +
162 | from functools import partial
163 |
164 | def expression(t, x):
165 | return c0*x[0]**2 + c1*x[0] + c2*t
166 |
167 | V = fem.FunctionSpace(mesh, ("Lagrange", 1))
168 | u0 = fem.Function(V)
169 | u0.interpolate(partial(expression, 0))
170 |
171 | u0.name = "Temperature0"
172 | with io.XDMFFile(MPI.COMM_WORLD, "t0.xdmf", "w") as xdmf:
173 | xdmf.write_mesh(mesh)
174 | xdmf.write_function(u0)
175 | # -
176 |
177 | # ## Setting up a variational problem
178 |
179 | V = fem.FunctionSpace(mesh, ("Lagrange", 1))
180 | u = TrialFunction(V)
181 | v = TestFunction(V)
182 |
183 | # The variational form can be written in UFL syntax:
184 | # $$
185 | # \begin{align*}
186 | # a(u, v) = \int_{\Omega} u v \,dx + \Delta t \int_{\Omega}{\nabla u \cdot \nabla v}\,dx
187 | # \end{align*}
188 | # $$
189 |
190 | a = inner(u, v) * dx + dt * inner(grad(u), grad(v)) * dx
191 | a = fem.form(a) # JIT compilation
192 |
193 | # $$
194 | # \begin{align*}
195 | # L(v) = \Delta t \int_{\Omega} f \cdot v \, dx + \int_{\Omega} u_{n-1} \cdot v \, dx
196 | # \end{align*}
197 | # $$
198 |
199 | L = dt * inner(f, v) * dx + inner(u0, v) * dx
200 |
201 | # Note that we are using `u` for the solution that we seek at time step `n``
202 | # and `u0`` for time step `n-1``.
203 |
204 | # To give the user freedom to set boundary conditions on single degrees of freedom,
205 | # the function `dolfinx.fem.dirichletbc` takes in the list of degrees of freedom(DOFs) as input.\
206 | # The DOFs on the boundary can be obtained in many ways: DOLFINx supplies a few convenience functions,
207 | # such as `dolfinx.fem.locate_dofs_topological` and `dolfinx.fem.locate_dofs_geometrical`.
208 | # DOLFINx also has convenience functions to obtain a list of all boundary facets.
209 |
210 | # +
211 | # Create Dirichlet function
212 | u_D = fem.Function(V)
213 | u_D.interpolate(partial(expression, 0))
214 |
215 | # Define Dirichlet bc
216 |
217 | # Get boundary facets
218 | bndry_facets = exterior_facet_indices(mesh.topology)
219 |
220 | # Locate degrees of freedom on those facets
221 | tdim = mesh.topology.dim
222 | bndry_dofs = fem.locate_dofs_topological(V, tdim - 1, bndry_facets)
223 |
224 | # Create list of Dirichlet BC
225 | bcs = [fem.dirichletbc(u_D, bndry_dofs)]
226 | # -
227 |
228 | # ### Setting up a time dependent solver
229 | #
230 | # As the left hand side of our problem(the matrix) is time independent, we would like avoid
231 | # re-assembling it at every time step.We assemble the matrix once outside the temporal loop.
232 |
233 | A = fem.petsc.assemble_matrix(a, bcs=bcs)
234 | A.assemble()
235 |
236 | # Next, we can generate the integration kernel for the right hand side(RHS),
237 | # and create the RHS vector `b` that we will assembled into at each time step.
238 |
239 | b = fem.Function(V)
240 | L = fem.form(L) # JIT compilation
241 |
242 | # We next create the PETSc KSP(Krylov subspace method) solver, and set it to solve using an
243 | # [algebraic multigrid method](https: // hypre.readthedocs.io/en/latest/solvers-boomeramg.html).
244 |
245 | # Define Solver
246 | solver = PETSc.KSP().create(comm)
247 | solver.setOperators(A)
248 | solver.setType(PETSc.KSP.Type.CG)
249 | pc = solver.getPC()
250 | pc.setType(PETSc.PC.Type.HYPRE)
251 | pc.setHYPREType("boomeramg")
252 |
253 | # ### Plotting a time dependent problem
254 |
255 | # As we are solving a time dependent problem, we would like to create a time dependent animation
256 | # of the solution. The first step is to create an output file where we are going to save the
257 | # solution at each time step:
258 |
259 | # Open file, keep it
260 | file = io.XDMFFile(MPI.COMM_WORLD, "temperature.xdmf", "w")
261 | file.write_mesh(mesh)
262 |
263 |
264 | # ## Solving a time dependent problem
265 | #
266 | # We are now ready to solve the time dependent problem. At each time step, we need to:
267 | # 1. Update the time dependent boundary condition and source
268 | # 2. Reassemble the right hand side vector $b$ that depends on $u_{n-1}$
269 | # 3. Apply boundary conditions to $b$
270 | # 4. Solve linear problem $AU = b$
271 | # 5. Update current solution, $u_{n-1} = u_n$
272 |
273 | # +
274 | u = fem.Function(V)
275 | u.name = "temperature"
276 |
277 | t = 0
278 | t_max = 100*dt.value
279 | while t < t_max:
280 | t += dt.value
281 | print(f"t = {t:.2f} s")
282 |
283 | # Update boundary condition
284 | u_D.interpolate(partial(expression, t))
285 |
286 | # Assemble RHS
287 | b.x.array[:] = 0
288 | fem.petsc.assemble_vector(b.vector, L)
289 |
290 | # Apply boundary condition
291 | fem.petsc.apply_lifting(b.vector, [a], [bcs])
292 | b.x.scatter_reverse(la.ScatterMode.add)
293 | fem.petsc.set_bc(b.vector, bcs)
294 |
295 | # Solve linear problem
296 | u.x.array[:] = 0
297 | solver.solve(b.vector, u.vector)
298 | u.x.scatter_forward()
299 |
300 | # Update un
301 | u0.x.array[:] = u.x.array
302 |
303 | # Save solution at time step t
304 | file.write_function(T, t=t)
305 |
306 | # Now we can close the file
307 | file.close()
308 | # -
309 |
310 | # ### What's the temperature at an arbitrary point $\boldsymbol{x}_p$?
311 | # Remember that
312 | # $$
313 | # u_h(\boldsymbol{x}) = \sum_{i=1}^{dim{V_h}}{u_j \psi_j(\boldsymbol{x}})
314 | # $$
315 | #
316 | # Once we find the values of of coefficients, its just a matter of evaluation the basis functions
317 | # at $\boldsymbol{x}_p$.
318 |
319 | # +
320 | # Given an arbitrary point "xp"
321 | xp = numpy.array([0.5, 0.5, 0.0], dtype=numpy.float64)
322 |
323 | # We first compute the cells that it belongs to
324 | bb_tree = geometry.BoundingBoxTree(mesh, mesh.topology.dim)
325 | cell_candidates = geometry.compute_collisions(bb_tree, p)
326 | cells = geometry.compute_colliding_cells(mesh, cell_candidates, xp)
327 |
328 | # Given a list of cells it's easy and "efficient" to compute T(p)
329 | values = u.eval(xp, cells[0])
330 | print(f"Temperature at point {xp} is {values[0]}")
331 | # -
332 |
333 | # # Homework 2:
334 | # **TASK 1**: The above time-stepping loop does not include any
335 | # comparison of numerical and exact solutions, which is required
336 | # to validate the implementation. In the previous section we've
337 | # learned how to compute the L2 and H1 error norms.
338 | #
339 | # Use the knowledge gained in the previous section to calculate both
340 | # the L2 and H1 error norms for $t=t_{max}$.
341 |
342 | # +
343 | # Solution goes here:
344 | # -
345 |
346 |
347 | # In this homework we are going to tackle a more physically realistic problem.
348 | # Given the temperature outside a room, we shall compute the temperature inside
349 | # the room, and find the appropriate insulation material.
350 | #
351 | # Without internal sources the strong form of our problem reads:
352 | # $$
353 | # \begin{align*}
354 | # \rho C_p\frac{\partial T}{\partial t} &= \nabla \cdot (\kappa \nabla T) & & \text{in } \, \Omega, \\
355 | # T(\boldsymbol{x}, t) &= T_D = T_R + T_a \cdot sin(\omega t) & & \,\partial\Omega_\text{D}, \\
356 | # T(\boldsymbol{x}, t=0) &= T_0 & & \text{in } \, \Omega,
357 | # \end{align*}
358 | # $$
359 | # where $k$ is the thermal conductivity, $\rho$ is the density and $C_p$ is the
360 | # is specific heat capacity.
361 | #
362 | # In this example, we assume computational domain is divided into
363 | # two domains $\Omega_{air}$ and $\Omega_{wall}$. Within each domain
364 | # we can define the thermal diffusivity of the medium $\alpha$:
365 | # $$
366 | # \begin{align*}
367 | # \alpha = \frac{k}{\rho C_p}
368 | # \end{align*}
369 | # $$
370 | #
371 | # Examples of thermal diffusivity for four different materials:
372 | # $$\alpha_{air} = 19 \cdot 10^{-6} \, m^2/s$$
373 | # $$\alpha_{glass} = 0.34 \cdot 10^{-6} \, m^2/s$$
374 | # $$\alpha_{brick} = 0.27 \cdot 10^{-6} \, m^2/s$$
375 | # $$\alpha_{wood} = 0.082 \cdot 10^{-6} \, m^2/s$$
376 | # $$\alpha_{steel} = 11.72 \cdot 10^{-6} \, m^2/s$$
377 | #
378 | # Given unit square room $[1m \times 1m]$, $0.1 m$ thick walls, and outside
379 | # temperature given by $T_D$, which material should be used for the wall
380 | # such that the temperature at the center of the room never drops below
381 | # $10^oC$?
382 |
383 | # **TASK 1**: Complete following code with the diffusivity of the wall
384 | # (chosen from the above values). Then visualize the material distribution.
385 |
386 | # +
387 | def wall(x):
388 | vertical = (x[0] <= 0.1) | (x[0] >= 0.9)
389 | horizontal = (x[1] <= 0.1) | (x[1] >= 0.9)
390 | return horizontal | vertical
391 |
392 |
393 | comm = MPI.COMM_WORLD
394 | mesh = create_unit_square(comm, 20, 20, CellType.quadrilateral)
395 |
396 | cells = locate_entities(mesh, mesh.topology.dim, wall)
397 | DG = fem.FunctionSpace(mesh, ("DG", 0))
398 |
399 | # Thermal diffusivity
400 | alpha = fem.Function(DG)
401 | alpha.x.array[:] = 19e-6
402 |
403 | # Edit here: diffusivity of wall
404 | alpha.x.array[cells] = 0.37e-6 # wood
405 |
406 | plot_mesh(mesh, cell_values=alpha, filename="alpha.html")
407 | IPython.display.HTML(filename="alpha.html")
408 | # -
409 |
410 |
411 | # **Task 2**: create a python function that defines the outside temperature
412 | # $$T_{out} = -5 + 15 \cdot sin(\omega t)$$
413 | # where $\omega = \pi/(24\cdot60\cdot60)$.
414 | # Plot the temperature for a period of 24 hours.
415 |
416 | # +
417 | period = 60*60*24 # seconds in a day
418 | def outside_temperature(t, x):
419 | # Edit this function
420 | temperature = 0
421 | return numpy.full_like(x[0], temperature)
422 |
423 | # Task: Plot the hourly temperature during a day:
424 | temp = numpy.zeros(24)
425 | time = numpy.zeros(24)
426 | for i in range(temp.size):
427 | time[i] = i*60*60
428 | temp[i] = outside_temperature(time[i], [0, 0])
429 |
430 | import matplotlib.pyplot as plt
431 | plt.plot(time, temp)
432 | plt.xlabel('Time (hour)', fontsize=20)
433 | plt.ylabel('Temperature ($^oC$)', fontsize=20)
434 | plt.savefig("temperature.png")
435 |
436 |
437 | # +
438 | # Reference solution
439 | period = 60*60*24 # seconds in a day
440 |
441 | def outside_temperature(t, x):
442 | omega = numpy.pi/period
443 | t = t % period
444 | temperature = 15 * numpy.sin(omega*t) - 5
445 | return numpy.full_like(x[0], temperature)
446 |
447 | # Task: Plot the hourly temperature during a day:
448 | temp = numpy.zeros(24)
449 | time = numpy.zeros(24)
450 | for i in range(temp.size):
451 | t = i*60*60
452 | time[i] = i
453 | temp[i] = outside_temperature(t, [0, 0])
454 |
455 | import matplotlib.pyplot as plt
456 | plt.plot(time, temp)
457 | plt.xlabel('Time (hour)', fontsize=20)
458 | plt.ylabel('Temperature ($^oC$)', fontsize=20)
459 |
460 |
461 | # -
462 |
463 | # **Task 3**: Modify the following thermal solver so that it returns
464 | # the temperature inside the room at each time step.
465 | # Save the solution at each time-step and visualize with paraview.
466 |
467 | def thermal_solver(alpha, intial_temp=15, time_step=900.0):
468 | mesh = alpha.function_space.mesh
469 |
470 | # Create function space
471 | V = fem.FunctionSpace(mesh, ("Lagrange", 1))
472 |
473 | # Define trial and test functions
474 | T = TrialFunction(V)
475 | v = TestFunction(V)
476 |
477 | # Initial condition
478 | T0 = fem.Function(V)
479 | T0.interpolate(lambda x: numpy.full_like(x[0], intial_temp))
480 |
481 | # Time step (15 min - 900 seconds)
482 | dt = fem.Constant(mesh, time_step)
483 |
484 | # Bilinear form
485 | a = inner(T, v) * dx + dt * alpha * inner(grad(T), grad(v)) * dx
486 | a = fem.form(a)
487 |
488 | # Linear form
489 | L = inner(T0, v) * dx
490 | L = fem.form(L) # JIT compilation
491 |
492 |
493 | # Define dirichlet bc at t=0
494 | temp0 = partial(outside_temperature, 0)
495 | T_D = fem.Function(V)
496 | T_D.interpolate(temp0)
497 |
498 | # Create boundary condition
499 | tdim = mesh.topology.dim
500 | mesh.topology.create_connectivity(tdim - 1, tdim)
501 | bndry_facets = exterior_facet_indices(mesh.topology)
502 | bndry_dofs = fem.locate_dofs_topological(V, tdim - 1, bndry_facets)
503 | bcs = [fem.dirichletbc(T_D, bndry_dofs)]
504 |
505 | # Assemble matrix A and RHS b
506 | A = fem.petsc.assemble_matrix(a, bcs=bcs)
507 | A.assemble()
508 | b = fem.Function(V)
509 |
510 | solver = PETSc.KSP().create(comm)
511 | solver.setOperators(A)
512 | solver.setType(PETSc.KSP.Type.CG)
513 | pc = solver.getPC()
514 | pc.setType(PETSc.PC.Type.HYPRE)
515 | pc.setHYPREType("boomeramg")
516 |
517 | T = fem.Function(V)
518 | T.interpolate(T0)
519 |
520 | # Check against standard table value
521 | # Create bounding box for function evaluation
522 | bb_tree = geometry.BoundingBoxTree(mesh, mesh.topology.dim)
523 | p = numpy.array([0.5, 0.5, 0.0], dtype=numpy.float64)
524 | cell_candidates = geometry.compute_collisions(bb_tree, p)
525 | cells = geometry.compute_colliding_cells(mesh, cell_candidates, p)
526 |
527 | value = T.eval(p, cells[0])
528 | print(f"Room temperature {value}")
529 |
530 | t = 0
531 | num_days = 1
532 | t_max = period * num_days
533 |
534 | # Array to store temperature
535 | temperature = [value]
536 | time = [t]
537 |
538 | while t < t_max:
539 | time.append(t)
540 | t += dt.value
541 |
542 | # Update boundary condition
543 | T_D.interpolate(partial(outside_temperature, t))
544 |
545 | # Assemble RHS
546 | b.x.array[:] = 0
547 | fem.petsc.assemble_vector(b.vector, L)
548 |
549 | # Apply boundary condition
550 | fem.petsc.apply_lifting(b.vector, [a], [bcs])
551 | # b.x.scatter_reverse(ScatterMode.add)
552 | fem.petsc.set_bc(b.vector, bcs)
553 |
554 | # Solve linear problem
555 | solver.solve(b.vector, T.vector)
556 | T.x.scatter_forward()
557 |
558 | value = T.eval(p, cells[0])
559 | print(f"Room temperature {value[0]} at {t/(60*60)} h")
560 |
561 | # Update un
562 | T0.x.array[:] = T.x.array
563 |
564 | return temperature, time
565 |
566 | # **Task 4**: Which material should we use for insulation?
567 | # Modify the alpha value on the wall and plot temperature
568 | # vs time inside the room.
569 |
570 | temperature, time = thermal_solver(alpha)
571 | assert len(temperature) == len(time)
572 |
--------------------------------------------------------------------------------
/Problem3_Elasticity/Elasticity.py:
--------------------------------------------------------------------------------
1 | # ---
2 | # jupyter:
3 | # jupytext:
4 | # text_representation:
5 | # extension: .py
6 | # format_name: light
7 | # format_version: '1.5'
8 | # jupytext_version: 1.14.1
9 | # kernelspec:
10 | # display_name: Python 3
11 | # name: python3
12 | # ---
13 |
14 | # + [markdown] id="HlwXpyx788Vo"
15 | # # Solid mechanics: Linear elasticity
16 | # ---
17 |
18 | # + [markdown] id="sUiY4ZupvRWL"
19 | # ## Introduction
20 |
21 | # + [markdown] id="04Q-hHB9299K"
22 | # In this tutorial we
23 | #
24 | # 1. Present an implementation of the finite element discretization
25 | # of the Navier-Poisson elastostatic problem
26 | # 2. Create a non-trivial geometry and impose essential boundary conditions
27 | # on a vector field
28 | # 3. Visualize the solution using Paraview
29 | # 4. Perform some postprocessing of the solution.
30 | #
31 | # **Mathematical formulation:**
32 | #
33 | # \begin{equation}
34 | # \left \{
35 | # \begin{array}{rcll}
36 | # -\nabla \cdot \boldsymbol{\sigma} (\mathbf{u}) & = & \mathbf{f} & \mbox{in}~\Omega \\
37 | # & & & \\
38 | # \mathbf{u} & = & \mathbf{g} & \mbox{on}~\Gamma_{\mathbf{u}} \\
39 | # & & & \\
40 | # \boldsymbol{\sigma} \cdot \check{\mathbf{n}} & = & \boldsymbol{\mathcal{F}} & \mbox{on}~\Gamma_{\boldsymbol{\mathcal{F}}}
41 | # \end{array}
42 | # \right.
43 | # \end{equation}
44 | # where the stress tensor is
45 | #
46 | # $$
47 | # \boldsymbol{\sigma} = 2\mu\, \boldsymbol{\varepsilon}({\mathbf{u}})+ \lambda \left ( \nabla\cdot\mathbf{u} \right )\, \mathbf{I}_{d \times d}
48 | # $$
49 | #
50 | # where $d$ is the spatial dimension, $\mathbf{I}_{d \times d}$ is
51 | # the identity tensor and the deformation tensor is defined by
52 | #
53 | # $$
54 | # \boldsymbol{\varepsilon}({\mathbf{u}}) = \frac12 (\nabla{\mathbf{u}} + \nabla^{\intercal}{\mathbf{u}})
55 | # $$
56 | #
57 | # Introducing the space of kinematically admissible motions
58 | #
59 | # $$
60 | # V_{\mathbf{g}} = \{\mathbf{v} \in \left [ H^1(\Omega) \right ]^d,~\mathbf{v} = \mathbf{g}~\mbox{on}~\Gamma_D\}
61 | # $$
62 | #
63 | # and applying the principle of virtual work, the variational formulation is obtained: Find $\mathbf{u} \in V_{\mathbf{g}}$ such that
64 | #
65 | # \begin{eqnarray}
66 | # \underbrace{\int_{\Omega}{\left [2\,\mu \boldsymbol{\varepsilon}(\mathbf{u}) : \boldsymbol{\varepsilon}(\mathbf{v})
67 | # # + \lambda\, (\nabla \cdot \mathbf{u})\,(\nabla \cdot \mathbf{v}) \right ]\,dx}}_{a(\mathbf{u},\mathbf{v})} =
68 | # \underbrace{\int_{\Omega}{\mathbf{f}\cdot \mathbf{v}}\,dx +
69 | # \int_{\Gamma_{\boldsymbol{\mathcal{F}}}}{\boldsymbol{\mathcal{F}} \cdot \mathbf{v}}\,ds}_{\ell(\mathbf{v})}
70 | # \end{eqnarray}
71 | # $\forall \mathbf{v} \in V_{\mathbf{0}}$.
72 | #
73 | # Finally, recall that the discrete version of this problem follows from applying the Galerkin method: Find $\mathbf{u}_h \in V_{h\mathbf{g}} \subset V_{\mathbf{g}}(\Omega)$ such that
74 | #
75 | # \begin{equation}
76 | # a(\mathbf{u}_h,\mathbf{v}_h) = \ell(\mathbf{v}_h)~~ \forall \mathbf{v}_h \in V_{h\mathbf{0}}
77 | # \end{equation}
78 |
79 | # + [markdown] id="7TeY3nHjqzod"
80 | # ## Initialization
81 |
82 | # + [markdown] id="KV_eRNQpHfOv"
83 | # As in previous tutorials, we import all necessary libraries, namely, `gmsh`, `dolfinx` and `ufl`
84 |
85 | # + id="69Xzz1wQx-Nd"
86 | try:
87 | import gmsh
88 | except ImportError:
89 | # !wget "https://github.com/fem-on-colab/fem-on-colab.github.io/raw/7f220b6/releases/gmsh-install.sh" -O "/tmp/gmsh-install.sh" && bash "/tmp/gmsh-install.sh"
90 | import gmsh
91 |
92 | try:
93 | import dolfinx
94 | except ImportError:
95 | # !wget "https://github.com/fem-on-colab/fem-on-colab.github.io/raw/7f220b6/releases/fenicsx-install-real.sh" -O "/tmp/fenicsx-install.sh" && bash "/tmp/fenicsx-install.sh"
96 | import dolfinx
97 |
98 | try:
99 | import pyvista
100 | except ImportError:
101 | # !pip install - q piglet pyvirtualdisplay ipyvtklink pyvista panel
102 | # !apt-get - qq install xvfb
103 | import pyvista
104 |
105 | # + id="ExTIMkkrxi-H"
106 | from dolfinx import mesh, fem, io, plot
107 | from ufl import SpatialCoordinate, TestFunction, TrialFunction, Measure, Identity, div, dx, ds, grad, nabla_grad, inner, sym, tr, sqrt, as_vector, FacetNormal
108 |
109 | import numpy as np
110 | from mpi4py import MPI
111 | from petsc4py.PETSc import ScalarType
112 |
113 |
114 | # + [markdown] id="DmgHdjHcaPJF"
115 | # Now, we create a mesh using
116 |
117 | # + id="fDoVkR60ydkP" colab={"resources": {"http://localhost:8080/jupyter-threejs.js": {"data": "CjwhRE9DVFlQRSBodG1sPgo8aHRtbCBsYW5nPWVuPgogIDxtZXRhIGNoYXJzZXQ9dXRmLTg+CiAgPG1ldGEgbmFtZT12aWV3cG9ydCBjb250ZW50PSJpbml0aWFsLXNjYWxlPTEsIG1pbmltdW0tc2NhbGU9MSwgd2lkdGg9ZGV2aWNlLXdpZHRoIj4KICA8dGl0bGU+RXJyb3IgNDA0IChOb3QgRm91bmQpISExPC90aXRsZT4KICA8c3R5bGU+CiAgICAqe21hcmdpbjowO3BhZGRpbmc6MH1odG1sLGNvZGV7Zm9udDoxNXB4LzIycHggYXJpYWwsc2Fucy1zZXJpZn1odG1se2JhY2tncm91bmQ6I2ZmZjtjb2xvcjojMjIyO3BhZGRpbmc6MTVweH1ib2R5e21hcmdpbjo3JSBhdXRvIDA7bWF4LXdpZHRoOjM5MHB4O21pbi1oZWlnaHQ6MTgwcHg7cGFkZGluZzozMHB4IDAgMTVweH0qID4gYm9keXtiYWNrZ3JvdW5kOnVybCgvL3d3dy5nb29nbGUuY29tL2ltYWdlcy9lcnJvcnMvcm9ib3QucG5nKSAxMDAlIDVweCBuby1yZXBlYXQ7cGFkZGluZy1yaWdodDoyMDVweH1we21hcmdpbjoxMXB4IDAgMjJweDtvdmVyZmxvdzpoaWRkZW59aW5ze2NvbG9yOiM3Nzc7dGV4dC1kZWNvcmF0aW9uOm5vbmV9YSBpbWd7Ym9yZGVyOjB9QG1lZGlhIHNjcmVlbiBhbmQgKG1heC13aWR0aDo3NzJweCl7Ym9keXtiYWNrZ3JvdW5kOm5vbmU7bWFyZ2luLXRvcDowO21heC13aWR0aDpub25lO3BhZGRpbmctcmlnaHQ6MH19I2xvZ297YmFja2dyb3VuZDp1cmwoLy93d3cuZ29vZ2xlLmNvbS9pbWFnZXMvbG9nb3MvZXJyb3JwYWdlL2Vycm9yX2xvZ28tMTUweDU0LnBuZykgbm8tcmVwZWF0O21hcmdpbi1sZWZ0Oi01cHh9QG1lZGlhIG9ubHkgc2NyZWVuIGFuZCAobWluLXJlc29sdXRpb246MTkyZHBpKXsjbG9nb3tiYWNrZ3JvdW5kOnVybCgvL3d3dy5nb29nbGUuY29tL2ltYWdlcy9sb2dvcy9lcnJvcnBhZ2UvZXJyb3JfbG9nby0xNTB4NTQtMngucG5nKSBuby1yZXBlYXQgMCUgMCUvMTAwJSAxMDAlOy1tb3otYm9yZGVyLWltYWdlOnVybCgvL3d3dy5nb29nbGUuY29tL2ltYWdlcy9sb2dvcy9lcnJvcnBhZ2UvZXJyb3JfbG9nby0xNTB4NTQtMngucG5nKSAwfX1AbWVkaWEgb25seSBzY3JlZW4gYW5kICgtd2Via2l0LW1pbi1kZXZpY2UtcGl4ZWwtcmF0aW86Mil7I2xvZ297YmFja2dyb3VuZDp1cmwoLy93d3cuZ29vZ2xlLmNvbS9pbWFnZXMvbG9nb3MvZXJyb3JwYWdlL2Vycm9yX2xvZ28tMTUweDU0LTJ4LnBuZykgbm8tcmVwZWF0Oy13ZWJraXQtYmFja2dyb3VuZC1zaXplOjEwMCUgMTAwJX19I2xvZ297ZGlzcGxheTppbmxpbmUtYmxvY2s7aGVpZ2h0OjU0cHg7d2lkdGg6MTUwcHh9CiAgPC9zdHlsZT4KICA8YSBocmVmPS8vd3d3Lmdvb2dsZS5jb20vPjxzcGFuIGlkPWxvZ28gYXJpYS1sYWJlbD1Hb29nbGU+PC9zcGFuPjwvYT4KICA8cD48Yj40MDQuPC9iPiA8aW5zPlRoYXTigJlzIGFuIGVycm9yLjwvaW5zPgogIDxwPiAgPGlucz5UaGF04oCZcyBhbGwgd2Uga25vdy48L2lucz4K", "ok": false, "headers": [["content-length", "1449"], ["content-type", "text/html; charset=utf-8"]], "status": 404, "status_text": ""}}, "base_uri": "https://localhost:8080/", "height": 521} executionInfo={"status": "ok", "timestamp": 1674214789414, "user_tz": 0, "elapsed": 4195, "user": {"displayName": "Igor Baratta", "userId": "07060436923309155079"}} outputId="a661d772-2e29-4b70-f83d-469a5e6844f7"
118 | def GenerateMesh():
119 |
120 | gmsh.initialize()
121 | proc = MPI.COMM_WORLD.rank
122 | if proc == 0:
123 | lc = 0.05
124 | Db = 0.4
125 | Hb = 0.4
126 | Hp = 6*Hb
127 | R = 3*Hb
128 | TT = np.sqrt(R*R - 4*Hb*Hb)
129 |
130 | gmsh.model.geo.addPoint(0, 0, 0, lc, 1)
131 | gmsh.model.geo.addPoint(Db, 0, 0, lc, 2)
132 | gmsh.model.geo.addPoint(Db, Hb, 0, 0.5*lc, 3)
133 | gmsh.model.geo.addPoint(TT+Db, 3*Hb, 0, lc, 4)
134 | gmsh.model.geo.addPoint(Db, 5*Hb, 0, lc, 5)
135 | gmsh.model.geo.addPoint(Db, 6*Hb, 0, 0.5*lc, 6)
136 | gmsh.model.geo.addPoint(0, 6*Hb, 0, lc, 7)
137 | gmsh.model.geo.addPoint(0, 3*Hb, 0, 0.1*lc, 8)
138 | gmsh.model.geo.addPoint(TT+Db-R, 3*Hb, 0, 0.1*lc, 9)
139 |
140 | gmsh.model.geo.addLine(1, 2, 1)
141 | gmsh.model.geo.addLine(2, 3, 2)
142 |
143 | gmsh.model.geo.addCircleArc(3, 4, 9, 3)
144 | gmsh.model.geo.addCircleArc(9, 4, 5, 4)
145 |
146 | gmsh.model.geo.addLine(5, 6, 5)
147 | gmsh.model.geo.addLine(6, 7, 6)
148 | gmsh.model.geo.addLine(7, 8, 7)
149 | gmsh.model.geo.addLine(8, 1, 8)
150 |
151 | gmsh.model.geo.addCurveLoop([1, 2, 3, 4, 5, 6, 7, 8], 1)
152 | gmsh.model.geo.addPlaneSurface([1], 1)
153 | gmsh.model.geo.synchronize()
154 | # Tag the whole boundary with 101
155 | gmsh.model.addPhysicalGroup(1, [1, 2, 3, 4, 5, 6, 7, 8], 101)
156 | # Tag the top boundary with 100
157 | gmsh.model.addPhysicalGroup(1, [6], 100)
158 | ps = gmsh.model.addPhysicalGroup(2, [1])
159 | gmsh.model.setPhysicalName(2, ps, "My surface")
160 | gmsh.model.geo.synchronize()
161 |
162 | gmsh.option.setNumber("Mesh.Algorithm", 6)
163 | gmsh.model.mesh.generate(2)
164 | msh, subdomains, boundaries = io.gmshio.model_to_mesh(gmsh.model, comm=MPI.COMM_WORLD, rank=0, gdim=2)
165 | gmsh.finalize()
166 | return msh, subdomains, boundaries
167 |
168 | msh, subdomains, boundaries = GenerateMesh()
169 |
170 | with io.XDMFFile(MPI.COMM_WORLD, "body.xdmf", "w") as xdmf:
171 | xdmf.write_mesh(msh)
172 |
173 | import IPython
174 |
175 | def plot_mesh(mesh, filename="file.html"):
176 | pyvista.start_xvfb()
177 | grid = pyvista.UnstructuredGrid(*plot.create_vtk_mesh(mesh))
178 | plotter = pyvista.Plotter(notebook=True, window_size=[500,500])
179 | plotter.add_mesh(grid, show_edges=True)
180 | plotter.camera.zoom(4.0)
181 | plotter.view_xy()
182 | plotter.export_html(filename, backend="pythreejs")
183 | plotter.close()
184 |
185 |
186 | plot_mesh(msh, "mesh.html")
187 | IPython.display.HTML(filename="mesh.html")
188 |
189 | # + [markdown] id="GHEnrW5_dXPQ"
190 | # ## Finite element solution
191 |
192 | # + [markdown] id="_w-zEZ7fdloa"
193 | # We must create now the discrete function space associated to the mesh $\mathcal{T}_h$. As in previous examples a natural choice is a space of continuous vector functions, whose components are elementwise polynomials of degree $k$
194 | #
195 | # $$
196 | # V(\mathcal{T}_h) = V_h = \{\mathbf{v} \in [H^1(\Omega)]^d,~\mathbf{v}|_E \in [P_k(E)]^d \, \forall E \in \mathcal{T}_h\}
197 | # $$
198 | #
199 | # which is done in `dolfinx` using
200 |
201 | # + id="35oDBR1Oeusx"
202 | degree = 1
203 | V = fem.VectorFunctionSpace(msh, ("CG", 1))
204 |
205 | # + [markdown] id="paubzY5efWjl"
206 | # As usual, setting the boundary conditions is the step that takes more work. We must identify the degrees of freedom on the boundary and set accordingly.
207 | # For the problem at hand we will consider the following conditions
208 | #
209 | # \begin{eqnarray}
210 | # \mathbf{u} & = & (0,0)^{\intercal} ~~\mbox{in}~~\Gamma_{\mbox{bottom}} \\
211 | # & & \\
212 | # u_x & = & 0~~\mbox{in}~~\Gamma_{\mbox{left}}
213 | # \end{eqnarray}
214 | #
215 | # These conditions ensure that **rigid body** motions (rotations and translations) are totally restricted.
216 |
217 | # + id="fKejVTE43cnd"
218 | u_bottom = ScalarType((0.0, 0.0))
219 | ux_left = ScalarType(0.0)
220 |
221 | # For the left boundary, just restrict u_x
222 | sdim = msh.topology.dim
223 | fdim = sdim - 1
224 | facets_left = mesh.locate_entities_boundary(msh, fdim, lambda x: np.isclose(x[0], 0.0))
225 | dofsL = fem.locate_dofs_topological(V.sub(0), fdim, facets_left)
226 |
227 | # For the bottom restrict everything
228 | dofsB = fem.locate_dofs_geometrical(V, lambda x: np.isclose(x[1], 0.0))
229 | bcs = [fem.dirichletbc(u_bottom, dofsB, V), fem.dirichletbc(ux_left, dofsL, V.sub(0))]
230 |
231 | # + [markdown] id="UJgkqWKgf0A2"
232 | # As for the natural bounday conditions, on the top wall we will apply
233 | # the following surface force distribution
234 | #
235 | # $$
236 | # \boldsymbol{\mathcal{F}} = (0,-0.1)^{\intercal}
237 | # $$
238 | # so as to impose a compressive load. The rest of the boundary is traction free and the body forces are considered to be negligible,
239 | #
240 | # $$
241 | # \boldsymbol{\mathcal{F}} = (0,0)^{\intercal},~~~\mathbf{f} = (0,0)^{\intercal}
242 | # $$
243 | #
244 | # so, we can finally define the bilinear and linear forms and write the variational formulation of the elastostatic problem
245 | #
246 | # $$
247 | # a(\mathbf{u},\mathbf{v}) = \int_{\Omega}{\left [2\mu \,\boldsymbol{\varepsilon}(\mathbf{u}) : \boldsymbol{\varepsilon}(\mathbf{v})
248 | # # + \lambda\, (\nabla \cdot \mathbf{u})\,(\nabla \cdot \mathbf{v}) \right ]\,dx}
249 | # $$
250 | #
251 | # and
252 | #
253 | # $$
254 | # \ell(\mathbf{v}) = \int_{\Omega}{\mathbf{f}\cdot \mathbf{v}}\,dx +
255 | # \int_{\Gamma_{\boldsymbol{\mathcal{F}}}}{\boldsymbol{\mathcal{F}} \cdot \mathbf{v}}\,ds
256 | # $$
257 |
258 | # + colab={"base_uri": "https://localhost:8080/"} id="Q3F5qNVkfynZ" executionInfo={"status": "ok", "timestamp": 1674217195349, "user_tz": 0, "elapsed": 1198, "user": {"displayName": "Igor Baratta", "userId": "07060436923309155079"}} outputId="84451108-b66e-4437-a38e-b7d1af7e5712"
259 | # The rest of the boundary is traction free, except for the top in which we apply a surface force distribution
260 |
261 | # surface force
262 | F = fem.Constant(msh, ScalarType( (0.0, -0.1) ) )
263 |
264 | # Body force
265 | f = fem.Constant(msh, ScalarType( (0.0, 0.0) ) )
266 |
267 | # Constitutive parameters
268 | E, nu = 10.0, 0.3
269 | mu = E/(2.0*(1.0 + nu))
270 | lamb = E*nu/((1.0 + nu)*(1.0 - 2.0*nu))
271 |
272 | u, v = TrialFunction(V), TestFunction(V)
273 |
274 | def epsilon(u):
275 | return 0.5*(nabla_grad(u) + nabla_grad(u).T)
276 |
277 | def sigma(u):
278 | return lamb*div(u)*Identity(sdim) + 2*mu*epsilon(u)
279 |
280 | x = SpatialCoordinate(msh)
281 |
282 | ds = Measure("ds")(subdomain_data=boundaries)
283 |
284 | a = inner(sigma(u), epsilon(v)) * dx
285 | L = inner(f, v)*dx + inner(F,v)*ds(100)
286 |
287 | petsc_opts={"ksp_type": "preonly", "pc_type": "lu", "pc_factor_mat_solver_type": "mumps", "ksp_monitor": None}
288 | problem = fem.petsc.LinearProblem(a, L, bcs=bcs, petsc_options=petsc_opts)
289 | uh = problem.solve()
290 |
291 | # + [markdown] id="wT8b_QXub5pO"
292 | # ## Visualization and postprocessing
293 |
294 | # + [markdown] id="pZ0T61UH6TNW"
295 | # Let us write the solution for visualization in `Paraview` as we have done in the previous examples
296 |
297 | # + id="saVLTaLwfKoO"
298 | uh.name = "displacement"
299 | with io.XDMFFile(MPI.COMM_WORLD, "displacement.xdmf", "w") as xdmf:
300 | xdmf.write_mesh(msh)
301 | xdmf.write_function(uh)
302 |
303 | from google.colab import files
304 | files.download('displacement.xdmf')
305 | files.download('displacement.h5')
306 |
307 |
308 | # + [markdown] id="4FiWe7UsbwGD"
309 | # ## Homework 3
310 |
311 | # + [markdown] id="mP251voMb2Tn"
312 | # 1. **Von Mises stresses**
313 | #
314 | # Given the deviatoric stresses
315 | # $$
316 | # \boldsymbol{s} = \boldsymbol{\sigma}(\mathbf{u}_h) - \frac{\mbox{tr}(\boldsymbol{\sigma}(\mathbf{u}_h))}{d}\boldsymbol{I}_{d\times d}
317 | # $$
318 | #
319 | # Compute the **scalar** quantity known as the Von Mises stresses
320 | # defined as the second invariant of the deviatoric stresses:
321 | #
322 | # $$
323 | # \sigma_V = \sqrt{\frac32\boldsymbol{s}:\boldsymbol{s}}
324 | # $$
325 | #
326 | # where $:$ stands dor the double contraction or scalar product between matrizes.
327 | # This quantity is used by engineers to detect the critical parts of the structure.
328 | #
329 | # Implement in `dolfinx`. For visualization of results, interpolate $\sigma_V$ onto a space of elementwise constant functions (a `DG` space of order 0) as we have introduced before
330 | #
331 | # $$
332 | # Q_h = \{v \in L^2(\Omega),~v|_E \in P_0(E) \, \forall E \in \mathcal{T}_h\}
333 | # $$
334 | #
335 | # Follow the next guidelines:
336 | #
337 | # s = sigma(uh) - ...
338 | # sigmaV = sqrt(...)
339 | #
340 | # Q = fem.FunctionSpace(msh, ("DG", 0))
341 | # vM_expr = fem.Expression(sigmaV, Q.element.interpolation_points())
342 | # vonMises = fem.Function(Q)
343 | # vonMises.interpolate(vM_expr)
344 | #
345 | # stresses.name = "von_Mises"
346 | # with io.XDMFFile(msh.comm, "vonmises.xdmf", "w") as file:
347 | # file.write_mesh(msh)
348 | # file.write_function(vonMises)
349 | #
350 | # from google.colab import files
351 | # files.download('vonmises.xdmf')
352 | # files.download('vonmises.h5')
353 | #
354 | # Notice the use of the `interpolate` method to assign to each element the corresponding value of $\sigma_V$.
355 |
356 | # + [markdown] id="GU-xKNZxBTlS"
357 | # 2. **OPTIONAL: 3D problem**
358 | #
359 | # Consider the 3D version of the previous problem which is shown
360 | # in the figure below. This mesh can be created with the function `GenerateMesh3D()`.
361 | #
362 | # Implement the necessary changes to solve
363 | # the problem with the following boundary conditions
364 | #
365 | # \begin{eqnarray}
366 | # \mathbf{u} & = & (0,0,0)^{\intercal} ~~\mbox{in}~~\Gamma_{\mbox{bottom}} \nonumber \\
367 | # & & \nonumber \\
368 | # \mathbf{u} & = & (0,0,-0.1)^{\intercal} ~~\mbox{in}~~\Gamma_{\mbox{top}} \nonumber
369 | # \end{eqnarray}
370 | #
371 | # whereas the rest of the boundary remains traction free
372 | #
373 | # $$
374 | # \boldsymbol{\mathcal{F}} = (0, 0, 0)^{\intercal}
375 | # $$
376 |
377 | # + id="Q3k93hsUilE7"
378 | def GenerateMesh3D():
379 | gmsh.initialize()
380 | proc = MPI.COMM_WORLD.rank
381 | if proc == 0:
382 |
383 | lc = 0.025
384 | Db = 0.4
385 | Hb = 0.4
386 | global Hp
387 | Hp = 6*Hb
388 | R = 3*Hb
389 | TT = np.sqrt(R*R - 4*Hb*Hb)
390 |
391 | gmsh.model.geo.addPoint(0, 0, 0, lc, 1)
392 | gmsh.model.geo.addPoint(Db, 0, 0, lc, 2)
393 | gmsh.model.geo.addPoint(Db, Hb, 0, 0.5*lc, 3)
394 | gmsh.model.geo.addPoint(TT+Db, 3*Hb, 0, lc, 4)
395 | gmsh.model.geo.addPoint(Db, 5*Hb, 0, lc, 5)
396 | gmsh.model.geo.addPoint(Db, 6*Hb, 0, 0.5*lc, 6)
397 | gmsh.model.geo.addPoint(0, 6*Hb, 0, lc, 7)
398 | gmsh.model.geo.addPoint(0, 3*Hb, 0, 0.1*lc, 8)
399 | gmsh.model.geo.addPoint(TT+Db-R, 3*Hb, 0, 0.1*lc, 9)
400 |
401 | gmsh.model.geo.addLine(1, 2, 1)
402 | gmsh.model.geo.addLine(2, 3, 2)
403 | gmsh.model.geo.addCircleArc(3, 4, 9, 3)
404 | gmsh.model.geo.addCircleArc(9, 4, 5, 4)
405 | gmsh.model.geo.addLine(5, 6, 5)
406 | gmsh.model.geo.addLine(6, 7, 6)
407 | gmsh.model.geo.addLine(7, 8, 7)
408 | gmsh.model.geo.addLine(8, 1, 8)
409 |
410 | gmsh.model.geo.addCurveLoop([1, 2, 3, 4, 5, 6, 7, 8], 1)
411 | gmsh.model.geo.addPlaneSurface([1], 1)
412 | gmsh.model.geo.synchronize()
413 | gmsh.model.addPhysicalGroup(1, [1, 2, 3, 4, 5, 6, 7, 8], 101)
414 | ps = gmsh.model.addPhysicalGroup(2, [1])
415 | gmsh.model.setPhysicalName(2, ps, "My surface 1")
416 |
417 | gmsh.model.geo.addPoint(-Db, 0, 0, lc, 10)
418 | gmsh.model.geo.addPoint(-Db, Hb, 0, 0.5*lc, 11)
419 | gmsh.model.geo.addPoint(-(TT+Db), 3*Hb, 0, lc, 12)
420 | gmsh.model.geo.addPoint(-Db, 5*Hb, 0, lc, 13)
421 | gmsh.model.geo.addPoint(-Db, 6*Hb, 0, 0.5*lc, 14)
422 | gmsh.model.geo.addPoint(-(TT+Db-R), 3*Hb, 0, 0.1*lc, 15)
423 |
424 | gmsh.model.geo.addLine(1, 8, 9)
425 | gmsh.model.geo.addLine(8, 7, 10)
426 | gmsh.model.geo.addLine(7, 14, 11)
427 | gmsh.model.geo.addLine(14, 13, 12)
428 | gmsh.model.geo.addCircleArc(13, 12, 15, 13)
429 | gmsh.model.geo.addCircleArc(15, 12, 11, 14)
430 | gmsh.model.geo.addLine(11, 10, 15)
431 | gmsh.model.geo.addLine(10, 1, 16)
432 |
433 | gmsh.model.geo.addCurveLoop([9, 10, 11, 12, 13, 14, 15, 16], 2)
434 | gmsh.model.geo.addPlaneSurface([2], 2)
435 | gmsh.model.geo.synchronize()
436 | gmsh.model.addPhysicalGroup(1, [9, 10, 11, 12, 13, 14, 15, 16], 103)
437 | ps = gmsh.model.addPhysicalGroup(2, [2])
438 |
439 | gmsh.model.setPhysicalName(2, ps, "My surface 2")
440 | gmsh.model.geo.synchronize()
441 |
442 | ov1 = gmsh.model.geo.revolve([(2, 1)], 0, 0, 0, 0, 1, 0, -np.pi / 2)
443 | ov2 = gmsh.model.geo.revolve([(2, 1)], 0, 0, 0, 0, 1, 0, np.pi / 2)
444 | ov3 = gmsh.model.geo.revolve([(2, 2)], 0, 0, 0, 0, 1, 0, -np.pi / 2)
445 | ov4 = gmsh.model.geo.revolve([(2, 2)], 0, 0, 0, 0, 1, 0, np.pi / 2)
446 | gmsh.model.geo.synchronize()
447 |
448 | gmsh.model.addPhysicalGroup(3, [ov1[1][1]], 105)
449 | gmsh.model.addPhysicalGroup(3, [ov2[1][1]], 106)
450 | gmsh.model.addPhysicalGroup(3, [ov3[1][1]], 107)
451 | gmsh.model.addPhysicalGroup(3, [ov4[1][1]], 108)
452 | gmsh.model.geo.synchronize()
453 |
454 | gmsh.option.setNumber("Mesh.Algorithm", 2)
455 | gmsh.model.mesh.generate(3)
456 | #gmsh.write("./3dcorpo.msh")
457 | #gmsh.write("foo.geo_unrolled")
458 | msh, subdomains, boundaries = io.gmshio.model_to_mesh(gmsh.model, comm=MPI.COMM_WORLD, rank=0, gdim=3)
459 | gmsh.finalize()
460 | return msh, subdomains, boundaries
461 |
462 | msh, subdomains, boundaries = GenerateMesh3D()
463 |
464 | with io.XDMFFile(MPI.COMM_WORLD, "3Dbody.xdmf", "w") as xdmf:
465 | xdmf.write_mesh(msh)
466 |
467 |
468 | # + colab={"resources": {"http://localhost:8080/jupyter-threejs.js": {"data": "CjwhRE9DVFlQRSBodG1sPgo8aHRtbCBsYW5nPWVuPgogIDxtZXRhIGNoYXJzZXQ9dXRmLTg+CiAgPG1ldGEgbmFtZT12aWV3cG9ydCBjb250ZW50PSJpbml0aWFsLXNjYWxlPTEsIG1pbmltdW0tc2NhbGU9MSwgd2lkdGg9ZGV2aWNlLXdpZHRoIj4KICA8dGl0bGU+RXJyb3IgNDA0IChOb3QgRm91bmQpISExPC90aXRsZT4KICA8c3R5bGU+CiAgICAqe21hcmdpbjowO3BhZGRpbmc6MH1odG1sLGNvZGV7Zm9udDoxNXB4LzIycHggYXJpYWwsc2Fucy1zZXJpZn1odG1se2JhY2tncm91bmQ6I2ZmZjtjb2xvcjojMjIyO3BhZGRpbmc6MTVweH1ib2R5e21hcmdpbjo3JSBhdXRvIDA7bWF4LXdpZHRoOjM5MHB4O21pbi1oZWlnaHQ6MTgwcHg7cGFkZGluZzozMHB4IDAgMTVweH0qID4gYm9keXtiYWNrZ3JvdW5kOnVybCgvL3d3dy5nb29nbGUuY29tL2ltYWdlcy9lcnJvcnMvcm9ib3QucG5nKSAxMDAlIDVweCBuby1yZXBlYXQ7cGFkZGluZy1yaWdodDoyMDVweH1we21hcmdpbjoxMXB4IDAgMjJweDtvdmVyZmxvdzpoaWRkZW59aW5ze2NvbG9yOiM3Nzc7dGV4dC1kZWNvcmF0aW9uOm5vbmV9YSBpbWd7Ym9yZGVyOjB9QG1lZGlhIHNjcmVlbiBhbmQgKG1heC13aWR0aDo3NzJweCl7Ym9keXtiYWNrZ3JvdW5kOm5vbmU7bWFyZ2luLXRvcDowO21heC13aWR0aDpub25lO3BhZGRpbmctcmlnaHQ6MH19I2xvZ297YmFja2dyb3VuZDp1cmwoLy93d3cuZ29vZ2xlLmNvbS9pbWFnZXMvbG9nb3MvZXJyb3JwYWdlL2Vycm9yX2xvZ28tMTUweDU0LnBuZykgbm8tcmVwZWF0O21hcmdpbi1sZWZ0Oi01cHh9QG1lZGlhIG9ubHkgc2NyZWVuIGFuZCAobWluLXJlc29sdXRpb246MTkyZHBpKXsjbG9nb3tiYWNrZ3JvdW5kOnVybCgvL3d3dy5nb29nbGUuY29tL2ltYWdlcy9sb2dvcy9lcnJvcnBhZ2UvZXJyb3JfbG9nby0xNTB4NTQtMngucG5nKSBuby1yZXBlYXQgMCUgMCUvMTAwJSAxMDAlOy1tb3otYm9yZGVyLWltYWdlOnVybCgvL3d3dy5nb29nbGUuY29tL2ltYWdlcy9sb2dvcy9lcnJvcnBhZ2UvZXJyb3JfbG9nby0xNTB4NTQtMngucG5nKSAwfX1AbWVkaWEgb25seSBzY3JlZW4gYW5kICgtd2Via2l0LW1pbi1kZXZpY2UtcGl4ZWwtcmF0aW86Mil7I2xvZ297YmFja2dyb3VuZDp1cmwoLy93d3cuZ29vZ2xlLmNvbS9pbWFnZXMvbG9nb3MvZXJyb3JwYWdlL2Vycm9yX2xvZ28tMTUweDU0LTJ4LnBuZykgbm8tcmVwZWF0Oy13ZWJraXQtYmFja2dyb3VuZC1zaXplOjEwMCUgMTAwJX19I2xvZ297ZGlzcGxheTppbmxpbmUtYmxvY2s7aGVpZ2h0OjU0cHg7d2lkdGg6MTUwcHh9CiAgPC9zdHlsZT4KICA8YSBocmVmPS8vd3d3Lmdvb2dsZS5jb20vPjxzcGFuIGlkPWxvZ28gYXJpYS1sYWJlbD1Hb29nbGU+PC9zcGFuPjwvYT4KICA8cD48Yj40MDQuPC9iPiA8aW5zPlRoYXTigJlzIGFuIGVycm9yLjwvaW5zPgogIDxwPiAgPGlucz5UaGF04oCZcyBhbGwgd2Uga25vdy48L2lucz4K", "ok": false, "headers": [["content-length", "1449"], ["content-type", "text/html; charset=utf-8"]], "status": 404, "status_text": "Not Found"}}, "base_uri": "https://localhost:8080/", "height": 521, "output_embedded_package_id": "1JBILJemJOX0MmjyS1PzZWctK2RfTmc2S"} id="cuB8nG5u9jxi" executionInfo={"status": "ok", "timestamp": 1673879005435, "user_tz": 180, "elapsed": 25460, "user": {"displayName": "Roberto Federico Ausas", "userId": "01910242568345374894"}} outputId="1cc87655-b567-41b8-cde3-93192a979b46"
469 | plot_mesh(msh, "mesh.html")
470 | IPython.display.HTML(filename="mesh.html")
471 |
--------------------------------------------------------------------------------
/Problem3_Elasticity/Elasticity.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "provenance": [],
7 | "include_colab_link": true
8 | },
9 | "kernelspec": {
10 | "name": "python3",
11 | "display_name": "Python 3"
12 | },
13 | "language_info": {
14 | "name": "python"
15 | }
16 | },
17 | "cells": [
18 | {
19 | "cell_type": "markdown",
20 | "metadata": {
21 | "id": "view-in-github",
22 | "colab_type": "text"
23 | },
24 | "source": [
25 | "
"
26 | ]
27 | },
28 | {
29 | "cell_type": "markdown",
30 | "source": [
31 | "# Solid mechanics: Linear elasticity\n",
32 | "---"
33 | ],
34 | "metadata": {
35 | "id": "HlwXpyx788Vo"
36 | }
37 | },
38 | {
39 | "cell_type": "markdown",
40 | "source": [
41 | "## Introduction"
42 | ],
43 | "metadata": {
44 | "id": "sUiY4ZupvRWL"
45 | }
46 | },
47 | {
48 | "cell_type": "markdown",
49 | "source": [
50 | "In this tutorial we\n",
51 | "\n",
52 | "1. Present an implementation of the finite element discretization\n",
53 | "of the Navier-Poisson elastostatic problem\n",
54 | "2. Create a non-trivial geometry and impose essential boundary conditions\n",
55 | "on a vector field\n",
56 | "3. Visualize the solution using Paraview\n",
57 | "4. Perform some postprocessing of the solution.\n",
58 | "\n",
59 | "**Mathematical formulation:**\n",
60 | "\n",
61 | "\\begin{equation}\n",
62 | "\\left \\{\n",
63 | "\\begin{array}{rcll}\n",
64 | "-\\nabla \\cdot \\boldsymbol{\\sigma} (\\mathbf{u}) & = & \\mathbf{f} & \\mbox{in}~\\Omega \\\\\n",
65 | "& & & \\\\\n",
66 | "\\mathbf{u} & = & \\mathbf{g} & \\mbox{on}~\\Gamma_{\\mathbf{u}} \\\\\n",
67 | "& & & \\\\\n",
68 | "\\boldsymbol{\\sigma} \\cdot \\check{\\mathbf{n}} & = & \\boldsymbol{\\mathcal{F}} & \\mbox{on}~\\Gamma_{\\boldsymbol{\\mathcal{F}}}\n",
69 | "\\end{array}\n",
70 | "\\right.\n",
71 | "\\end{equation}\n",
72 | "where the stress tensor is\n",
73 | "\n",
74 | "$$\n",
75 | "\\boldsymbol{\\sigma} = 2\\mu\\, \\boldsymbol{\\varepsilon}({\\mathbf{u}})+ \\lambda \\left ( \\nabla\\cdot\\mathbf{u} \\right )\\, \\mathbf{I}_{d \\times d}\n",
76 | "$$\n",
77 | " \n",
78 | "where $d$ is the spatial dimension, $\\mathbf{I}_{d \\times d}$ is \n",
79 | "the identity tensor and the deformation tensor is defined by\n",
80 | "\n",
81 | "$$\n",
82 | "\\boldsymbol{\\varepsilon}({\\mathbf{u}}) = \\frac12 (\\nabla{\\mathbf{u}} + \\nabla^{\\intercal}{\\mathbf{u}})\n",
83 | "$$\n",
84 | "\n",
85 | "Introducing the space of kinematically admissible motions\n",
86 | "\n",
87 | "$$\n",
88 | "V_{\\mathbf{g}} = \\{\\mathbf{v} \\in \\left [ H^1(\\Omega) \\right ]^d,~\\mathbf{v} = \\mathbf{g}~\\mbox{on}~\\Gamma_D\\}\n",
89 | "$$\n",
90 | "\n",
91 | "and applying the principle of virtual work, the variational formulation is obtained: Find $\\mathbf{u} \\in V_{\\mathbf{g}}$ such that\n",
92 | "\n",
93 | "\\begin{eqnarray}\n",
94 | "\\underbrace{\\int_{\\Omega}{\\left [2\\,\\mu \\boldsymbol{\\varepsilon}(\\mathbf{u}) : \\boldsymbol{\\varepsilon}(\\mathbf{v})\n",
95 | "+ \\lambda\\, (\\nabla \\cdot \\mathbf{u})\\,(\\nabla \\cdot \\mathbf{v}) \\right ]\\,dx}}_{a(\\mathbf{u},\\mathbf{v})} =\n",
96 | " \\underbrace{\\int_{\\Omega}{\\mathbf{f}\\cdot \\mathbf{v}}\\,dx +\n",
97 | "\\int_{\\Gamma_{\\boldsymbol{\\mathcal{F}}}}{\\boldsymbol{\\mathcal{F}} \\cdot \\mathbf{v}}\\,ds}_{\\ell(\\mathbf{v})}\n",
98 | "\\end{eqnarray}\n",
99 | "$\\forall \\mathbf{v} \\in V_{\\mathbf{0}}$.\n",
100 | "\n",
101 | "Finally, recall that the discrete version of this problem follows from applying the Galerkin method: Find $\\mathbf{u}_h \\in V_{h\\mathbf{g}} \\subset V_{\\mathbf{g}}(\\Omega)$ such that\n",
102 | "\n",
103 | "\\begin{equation}\n",
104 | "a(\\mathbf{u}_h,\\mathbf{v}_h) = \\ell(\\mathbf{v}_h)~~ \\forall \\mathbf{v}_h \\in V_{h\\mathbf{0}} \n",
105 | "\\end{equation}"
106 | ],
107 | "metadata": {
108 | "id": "04Q-hHB9299K"
109 | }
110 | },
111 | {
112 | "cell_type": "markdown",
113 | "source": [
114 | "## Initialization"
115 | ],
116 | "metadata": {
117 | "id": "7TeY3nHjqzod"
118 | }
119 | },
120 | {
121 | "cell_type": "markdown",
122 | "source": [
123 | "As in previous tutorials, we import all necessary libraries, namely, `gmsh`, `dolfinx` and `ufl`"
124 | ],
125 | "metadata": {
126 | "id": "KV_eRNQpHfOv"
127 | }
128 | },
129 | {
130 | "cell_type": "code",
131 | "execution_count": null,
132 | "metadata": {
133 | "id": "69Xzz1wQx-Nd"
134 | },
135 | "outputs": [],
136 | "source": [
137 | "try:\n",
138 | " import gmsh\n",
139 | "except ImportError:\n",
140 | " !wget \"https://fem-on-colab.github.io/releases/gmsh-install.sh\" -O \"/tmp/gmsh-install.sh\" && bash \"/tmp/gmsh-install.sh\"\n",
141 | " import gmsh\n",
142 | " \n",
143 | "try:\n",
144 | " import dolfinx\n",
145 | "except ImportError:\n",
146 | " !wget \"https://fem-on-colab.github.io/releases/fenicsx-install-real.sh\" -O \"/tmp/fenicsx-install.sh\" && bash \"/tmp/fenicsx-install.sh\"\n",
147 | " import dolfinx\n",
148 | "\n",
149 | "try:\n",
150 | " import pyvista\n",
151 | "except ImportError:\n",
152 | " !pip install - q piglet pyvirtualdisplay ipyvtklink pyvista panel\n",
153 | " !apt-get - qq install xvfb\n",
154 | " import pyvista"
155 | ]
156 | },
157 | {
158 | "cell_type": "code",
159 | "source": [
160 | "from dolfinx import mesh, fem, io, plot\n",
161 | "from ufl import SpatialCoordinate, TestFunction, TrialFunction, Measure, Identity, div, dx, ds, grad, nabla_grad, inner, sym, tr, sqrt, as_vector, FacetNormal\n",
162 | "\n",
163 | "import numpy as np\n",
164 | "from mpi4py import MPI\n",
165 | "from petsc4py.PETSc import ScalarType"
166 | ],
167 | "metadata": {
168 | "id": "ExTIMkkrxi-H"
169 | },
170 | "execution_count": null,
171 | "outputs": []
172 | },
173 | {
174 | "cell_type": "markdown",
175 | "source": [
176 | "Now, we create a mesh using "
177 | ],
178 | "metadata": {
179 | "id": "DmgHdjHcaPJF"
180 | }
181 | },
182 | {
183 | "cell_type": "code",
184 | "source": [
185 | "def GenerateMesh():\n",
186 | " \n",
187 | " gmsh.initialize()\n",
188 | " proc = MPI.COMM_WORLD.rank\n",
189 | " if proc == 0:\n",
190 | " lc = 0.05\n",
191 | " Db = 0.4\n",
192 | " Hb = 0.4\n",
193 | " Hp = 6*Hb\n",
194 | " R = 3*Hb\n",
195 | " TT = np.sqrt(R*R - 4*Hb*Hb)\n",
196 | " \n",
197 | " gmsh.model.geo.addPoint(0, 0, 0, lc, 1)\n",
198 | " gmsh.model.geo.addPoint(Db, 0, 0, lc, 2)\n",
199 | " gmsh.model.geo.addPoint(Db, Hb, 0, 0.5*lc, 3)\n",
200 | " gmsh.model.geo.addPoint(TT+Db, 3*Hb, 0, lc, 4)\n",
201 | " gmsh.model.geo.addPoint(Db, 5*Hb, 0, lc, 5)\n",
202 | " gmsh.model.geo.addPoint(Db, 6*Hb, 0, 0.5*lc, 6)\n",
203 | " gmsh.model.geo.addPoint(0, 6*Hb, 0, lc, 7)\n",
204 | " gmsh.model.geo.addPoint(0, 3*Hb, 0, 0.1*lc, 8)\n",
205 | " gmsh.model.geo.addPoint(TT+Db-R, 3*Hb, 0, 0.1*lc, 9)\n",
206 | " \n",
207 | " gmsh.model.geo.addLine(1, 2, 1)\n",
208 | " gmsh.model.geo.addLine(2, 3, 2)\n",
209 | "\n",
210 | " gmsh.model.geo.addCircleArc(3, 4, 9, 3)\n",
211 | " gmsh.model.geo.addCircleArc(9, 4, 5, 4)\n",
212 | " \n",
213 | " gmsh.model.geo.addLine(5, 6, 5)\n",
214 | " gmsh.model.geo.addLine(6, 7, 6)\n",
215 | " gmsh.model.geo.addLine(7, 8, 7)\n",
216 | " gmsh.model.geo.addLine(8, 1, 8)\n",
217 | " \n",
218 | " gmsh.model.geo.addCurveLoop([1, 2, 3, 4, 5, 6, 7, 8], 1)\n",
219 | " gmsh.model.geo.addPlaneSurface([1], 1)\n",
220 | " gmsh.model.geo.synchronize()\n",
221 | " # Tag the whole boundary with 101\n",
222 | " gmsh.model.addPhysicalGroup(1, [1, 2, 3, 4, 5, 6, 7, 8], 101)\n",
223 | " # Tag the top boundary with 100\n",
224 | " gmsh.model.addPhysicalGroup(1, [6], 100)\n",
225 | " ps = gmsh.model.addPhysicalGroup(2, [1])\n",
226 | " gmsh.model.setPhysicalName(2, ps, \"My surface\") \n",
227 | " gmsh.model.geo.synchronize()\n",
228 | " \n",
229 | " gmsh.option.setNumber(\"Mesh.Algorithm\", 6)\n",
230 | " gmsh.model.mesh.generate(2)\n",
231 | " msh, subdomains, boundaries = io.gmshio.model_to_mesh(gmsh.model, comm=MPI.COMM_WORLD, rank=0, gdim=2)\n",
232 | " gmsh.finalize()\n",
233 | " return msh, subdomains, boundaries\n",
234 | "\n",
235 | "msh, subdomains, boundaries = GenerateMesh()\n",
236 | "\n",
237 | "with io.XDMFFile(MPI.COMM_WORLD, \"body.xdmf\", \"w\") as xdmf:\n",
238 | " xdmf.write_mesh(msh)\n",
239 | "\n",
240 | "import IPython\n",
241 | "\n",
242 | "def plot_mesh(mesh, filename=\"file.html\"):\n",
243 | " pyvista.start_xvfb()\n",
244 | " grid = pyvista.UnstructuredGrid(*plot.create_vtk_mesh(mesh))\n",
245 | " plotter = pyvista.Plotter(notebook=True, window_size=[500,500])\n",
246 | " plotter.add_mesh(grid, show_edges=True)\n",
247 | " plotter.camera.zoom(4.0)\n",
248 | " plotter.view_xy()\n",
249 | " plotter.export_html(filename, backend=\"pythreejs\")\n",
250 | " plotter.close()\n",
251 | "\n",
252 | "\n",
253 | "plot_mesh(msh, \"mesh.html\")\n",
254 | "IPython.display.HTML(filename=\"mesh.html\")"
255 | ],
256 | "metadata": {
257 | "id": "fDoVkR60ydkP"
258 | },
259 | "execution_count": null,
260 | "outputs": []
261 | },
262 | {
263 | "cell_type": "markdown",
264 | "source": [
265 | "## Finite element solution"
266 | ],
267 | "metadata": {
268 | "id": "GHEnrW5_dXPQ"
269 | }
270 | },
271 | {
272 | "cell_type": "markdown",
273 | "source": [
274 | "We must create now the discrete function space associated to the mesh $\\mathcal{T}_h$. As in previous examples a natural choice is a space of continuous vector functions, whose components are elementwise polynomials of degree $k$\n",
275 | "\n",
276 | "$$\n",
277 | "V(\\mathcal{T}_h) = V_h = \\{\\mathbf{v} \\in [H^1(\\Omega)]^d,~\\mathbf{v}|_E \\in [P_k(E)]^d \\, \\forall E \\in \\mathcal{T}_h\\}\n",
278 | "$$\n",
279 | "\n",
280 | "which is done in `dolfinx` using"
281 | ],
282 | "metadata": {
283 | "id": "_w-zEZ7fdloa"
284 | }
285 | },
286 | {
287 | "cell_type": "code",
288 | "source": [
289 | "degree = 1\n",
290 | "V = fem.VectorFunctionSpace(msh, (\"CG\", 1))"
291 | ],
292 | "metadata": {
293 | "id": "35oDBR1Oeusx"
294 | },
295 | "execution_count": null,
296 | "outputs": []
297 | },
298 | {
299 | "cell_type": "markdown",
300 | "source": [
301 | "As usual, setting the boundary conditions is the step that takes more work. We must identify the degrees of freedom on the boundary and set accordingly. \n",
302 | "For the problem at hand we will consider the following conditions\n",
303 | "\n",
304 | "\\begin{eqnarray}\n",
305 | "\\mathbf{u} & = & (0,0)^{\\intercal} ~~\\mbox{in}~~\\Gamma_{\\mbox{bottom}} \\\\\n",
306 | "& & \\\\\n",
307 | "u_x & = & 0~~\\mbox{in}~~\\Gamma_{\\mbox{left}}\n",
308 | "\\end{eqnarray}\n",
309 | "\n",
310 | "These conditions ensure that **rigid body** motions (rotations and translations) are totally restricted."
311 | ],
312 | "metadata": {
313 | "id": "paubzY5efWjl"
314 | }
315 | },
316 | {
317 | "cell_type": "code",
318 | "source": [
319 | "u_bottom = ScalarType((0.0, 0.0))\n",
320 | "ux_left = ScalarType(0.0)\n",
321 | "\n",
322 | "# For the left boundary, just restrict u_x\n",
323 | "sdim = msh.topology.dim\n",
324 | "fdim = sdim - 1\n",
325 | "facets_left = mesh.locate_entities_boundary(msh, fdim, lambda x: np.isclose(x[0], 0.0))\n",
326 | "dofsL = fem.locate_dofs_topological(V.sub(0), fdim, facets_left)\n",
327 | "\n",
328 | "# For the bottom restrict everything\n",
329 | "dofsB = fem.locate_dofs_geometrical(V, lambda x: np.isclose(x[1], 0.0))\n",
330 | "bcs = [fem.dirichletbc(u_bottom, dofsB, V), fem.dirichletbc(ux_left, dofsL, V.sub(0))]"
331 | ],
332 | "metadata": {
333 | "id": "fKejVTE43cnd"
334 | },
335 | "execution_count": null,
336 | "outputs": []
337 | },
338 | {
339 | "cell_type": "markdown",
340 | "source": [
341 | "As for the natural bounday conditions, on the top wall we will apply \n",
342 | "the following surface force distribution\n",
343 | "\n",
344 | "$$\n",
345 | "\\boldsymbol{\\mathcal{F}} = (0,-0.1)^{\\intercal}\n",
346 | "$$\n",
347 | "so as to impose a compressive load. The rest of the boundary is traction free and the body forces are considered to be negligible, \n",
348 | "\n",
349 | "$$\n",
350 | "\\boldsymbol{\\mathcal{F}} = (0,0)^{\\intercal},~~~\\mathbf{f} = (0,0)^{\\intercal}\n",
351 | "$$\n",
352 | "\n",
353 | "so, we can finally define the bilinear and linear forms and write the variational formulation of the elastostatic problem\n",
354 | "\n",
355 | "$$\n",
356 | "a(\\mathbf{u},\\mathbf{v}) = \\int_{\\Omega}{\\left [2\\mu \\,\\boldsymbol{\\varepsilon}(\\mathbf{u}) : \\boldsymbol{\\varepsilon}(\\mathbf{v})\n",
357 | "+ \\lambda\\, (\\nabla \\cdot \\mathbf{u})\\,(\\nabla \\cdot \\mathbf{v}) \\right ]\\,dx}\n",
358 | "$$\n",
359 | "\n",
360 | "and\n",
361 | "\n",
362 | "$$\n",
363 | "\\ell(\\mathbf{v}) = \\int_{\\Omega}{\\mathbf{f}\\cdot \\mathbf{v}}\\,dx +\n",
364 | "\\int_{\\Gamma_{\\boldsymbol{\\mathcal{F}}}}{\\boldsymbol{\\mathcal{F}} \\cdot \\mathbf{v}}\\,ds\n",
365 | "$$"
366 | ],
367 | "metadata": {
368 | "id": "UJgkqWKgf0A2"
369 | }
370 | },
371 | {
372 | "cell_type": "code",
373 | "source": [
374 | "# The rest of the boundary is traction free, except for the top in which we apply a surface force distribution\n",
375 | "\n",
376 | "# surface force\n",
377 | "F = fem.Constant(msh, ScalarType( (0.0, -0.1) ) )\n",
378 | "\n",
379 | "# Body force\n",
380 | "f = fem.Constant(msh, ScalarType( (0.0, 0.0) ) )\n",
381 | "\n",
382 | "# Constitutive parameters\n",
383 | "E, nu = 10.0, 0.3\n",
384 | "mu = E/(2.0*(1.0 + nu))\n",
385 | "lamb = E*nu/((1.0 + nu)*(1.0 - 2.0*nu))\n",
386 | "\n",
387 | "u, v = TrialFunction(V), TestFunction(V)\n",
388 | "\n",
389 | "def epsilon(u):\n",
390 | " return 0.5*(nabla_grad(u) + nabla_grad(u).T)\n",
391 | "\n",
392 | "def sigma(u):\n",
393 | " return lamb*div(u)*Identity(sdim) + 2*mu*epsilon(u)\n",
394 | "\n",
395 | "x = SpatialCoordinate(msh)\n",
396 | "\n",
397 | "ds = Measure(\"ds\")(subdomain_data=boundaries)\n",
398 | "\n",
399 | "a = inner(sigma(u), epsilon(v)) * dx\n",
400 | "L = inner(f, v)*dx + inner(F,v)*ds(100)\n",
401 | "\n",
402 | "petsc_opts={\"ksp_type\": \"preonly\", \"pc_type\": \"lu\", \"pc_factor_mat_solver_type\": \"mumps\", \"ksp_monitor\": None}\n",
403 | "problem = fem.petsc.LinearProblem(a, L, bcs=bcs, petsc_options=petsc_opts)\n",
404 | "uh = problem.solve()"
405 | ],
406 | "metadata": {
407 | "id": "Q3F5qNVkfynZ"
408 | },
409 | "execution_count": null,
410 | "outputs": []
411 | },
412 | {
413 | "cell_type": "markdown",
414 | "source": [
415 | "## Visualization and postprocessing"
416 | ],
417 | "metadata": {
418 | "id": "wT8b_QXub5pO"
419 | }
420 | },
421 | {
422 | "cell_type": "markdown",
423 | "source": [
424 | "Let us write the solution for visualization in `Paraview` as we have done in the previous examples"
425 | ],
426 | "metadata": {
427 | "id": "pZ0T61UH6TNW"
428 | }
429 | },
430 | {
431 | "cell_type": "code",
432 | "source": [
433 | "uh.name = \"displacement\"\n",
434 | "with io.XDMFFile(MPI.COMM_WORLD, \"displacement.xdmf\", \"w\") as xdmf:\n",
435 | " xdmf.write_mesh(msh)\n",
436 | " xdmf.write_function(uh)\n",
437 | "\n",
438 | "from google.colab import files\n",
439 | "files.download('displacement.xdmf') \n",
440 | "files.download('displacement.h5')"
441 | ],
442 | "metadata": {
443 | "id": "saVLTaLwfKoO"
444 | },
445 | "execution_count": null,
446 | "outputs": []
447 | },
448 | {
449 | "cell_type": "markdown",
450 | "source": [
451 | "## Homework 3"
452 | ],
453 | "metadata": {
454 | "id": "4FiWe7UsbwGD"
455 | }
456 | },
457 | {
458 | "cell_type": "markdown",
459 | "source": [
460 | "1. **Von Mises stresses**\n",
461 | "\n",
462 | "Given the deviatoric stresses \n",
463 | "$$\n",
464 | "\\boldsymbol{s} = \\boldsymbol{\\sigma}(\\mathbf{u}_h) - \\frac{\\mbox{tr}(\\boldsymbol{\\sigma}(\\mathbf{u}_h))}{d}\\boldsymbol{I}_{d\\times d}\n",
465 | "$$\n",
466 | "\n",
467 | "Compute the **scalar** quantity known as the Von Mises stresses\n",
468 | "defined as the second invariant of the deviatoric stresses:\n",
469 | "\n",
470 | "$$\n",
471 | "\\sigma_V = \\sqrt{\\frac32\\boldsymbol{s}:\\boldsymbol{s}}\n",
472 | "$$\n",
473 | "\n",
474 | "where $:$ stands dor the double contraction or scalar product between matrizes.\n",
475 | "This quantity is used by engineers to detect the critical parts of the structure.\n",
476 | "\n",
477 | "Implement in `dolfinx`. For visualization of results, interpolate $\\sigma_V$ onto a space of elementwise constant functions (a `DG` space of order 0) as we have introduced before\n",
478 | "\n",
479 | "$$\n",
480 | "Q_h = \\{v \\in L^2(\\Omega),~v|_E \\in P_0(E) \\, \\forall E \\in \\mathcal{T}_h\\}\n",
481 | "$$\n",
482 | "\n",
483 | "Follow the next guidelines:\n",
484 | "\n",
485 | " s = sigma(uh) - ...\n",
486 | " sigmaV = sqrt(...)\n",
487 | "\n",
488 | " Q = fem.FunctionSpace(msh, (\"DG\", 0))\n",
489 | " vM_expr = fem.Expression(sigmaV, Q.element.interpolation_points())\n",
490 | " vonMises = fem.Function(Q)\n",
491 | " vonMises.interpolate(vM_expr)\n",
492 | "\n",
493 | " stresses.name = \"von_Mises\"\n",
494 | " with io.XDMFFile(msh.comm, \"vonmises.xdmf\", \"w\") as file:\n",
495 | " file.write_mesh(msh)\n",
496 | " file.write_function(vonMises)\n",
497 | "\n",
498 | " from google.colab import files\n",
499 | " files.download('vonmises.xdmf') \n",
500 | " files.download('vonmises.h5')\n",
501 | "\n",
502 | "Notice the use of the `interpolate` method to assign to each element the corresponding value of $\\sigma_V$."
503 | ],
504 | "metadata": {
505 | "id": "mP251voMb2Tn"
506 | }
507 | },
508 | {
509 | "cell_type": "markdown",
510 | "source": [
511 | "2. **OPTIONAL: 3D problem**\n",
512 | "\n",
513 | "Consider the 3D version of the previous problem which is shown\n",
514 | "in the figure below. This mesh can be created with the function `GenerateMesh3D()`. \n",
515 | "\n",
516 | "Implement the necessary changes to solve\n",
517 | "the problem with the following boundary conditions\n",
518 | "\n",
519 | "\\begin{eqnarray}\n",
520 | "\\mathbf{u} & = & (0,0,0)^{\\intercal} ~~\\mbox{in}~~\\Gamma_{\\mbox{bottom}} \\nonumber \\\\\n",
521 | "& & \\nonumber \\\\\n",
522 | "\\mathbf{u} & = & (0,0,-0.1)^{\\intercal} ~~\\mbox{in}~~\\Gamma_{\\mbox{top}} \\nonumber\n",
523 | "\\end{eqnarray}\n",
524 | "\n",
525 | "whereas the rest of the boundary remains traction free \n",
526 | "\n",
527 | "$$\n",
528 | "\\boldsymbol{\\mathcal{F}} = (0, 0, 0)^{\\intercal}\n",
529 | "$$"
530 | ],
531 | "metadata": {
532 | "id": "GU-xKNZxBTlS"
533 | }
534 | },
535 | {
536 | "cell_type": "code",
537 | "source": [
538 | "def GenerateMesh3D():\n",
539 | " gmsh.initialize()\n",
540 | " proc = MPI.COMM_WORLD.rank\n",
541 | " if proc == 0:\n",
542 | "\n",
543 | " lc = 0.025\n",
544 | " Db = 0.4\n",
545 | " Hb = 0.4\n",
546 | " global Hp\n",
547 | " Hp = 6*Hb\n",
548 | " R = 3*Hb\n",
549 | " TT = np.sqrt(R*R - 4*Hb*Hb)\n",
550 | " \n",
551 | " gmsh.model.geo.addPoint(0, 0, 0, lc, 1)\n",
552 | " gmsh.model.geo.addPoint(Db, 0, 0, lc, 2)\n",
553 | " gmsh.model.geo.addPoint(Db, Hb, 0, 0.5*lc, 3)\n",
554 | " gmsh.model.geo.addPoint(TT+Db, 3*Hb, 0, lc, 4)\n",
555 | " gmsh.model.geo.addPoint(Db, 5*Hb, 0, lc, 5)\n",
556 | " gmsh.model.geo.addPoint(Db, 6*Hb, 0, 0.5*lc, 6)\n",
557 | " gmsh.model.geo.addPoint(0, 6*Hb, 0, lc, 7)\n",
558 | " gmsh.model.geo.addPoint(0, 3*Hb, 0, 0.1*lc, 8)\n",
559 | " gmsh.model.geo.addPoint(TT+Db-R, 3*Hb, 0, 0.1*lc, 9)\n",
560 | " \n",
561 | " gmsh.model.geo.addLine(1, 2, 1)\n",
562 | " gmsh.model.geo.addLine(2, 3, 2)\n",
563 | " gmsh.model.geo.addCircleArc(3, 4, 9, 3)\n",
564 | " gmsh.model.geo.addCircleArc(9, 4, 5, 4)\n",
565 | " gmsh.model.geo.addLine(5, 6, 5)\n",
566 | " gmsh.model.geo.addLine(6, 7, 6)\n",
567 | " gmsh.model.geo.addLine(7, 8, 7)\n",
568 | " gmsh.model.geo.addLine(8, 1, 8)\n",
569 | " \n",
570 | " gmsh.model.geo.addCurveLoop([1, 2, 3, 4, 5, 6, 7, 8], 1)\n",
571 | " gmsh.model.geo.addPlaneSurface([1], 1)\n",
572 | " gmsh.model.geo.synchronize()\n",
573 | " gmsh.model.addPhysicalGroup(1, [1, 2, 3, 4, 5, 6, 7, 8], 101)\n",
574 | " ps = gmsh.model.addPhysicalGroup(2, [1])\n",
575 | " gmsh.model.setPhysicalName(2, ps, \"My surface 1\")\n",
576 | "\n",
577 | " gmsh.model.geo.addPoint(-Db, 0, 0, lc, 10)\n",
578 | " gmsh.model.geo.addPoint(-Db, Hb, 0, 0.5*lc, 11)\n",
579 | " gmsh.model.geo.addPoint(-(TT+Db), 3*Hb, 0, lc, 12)\n",
580 | " gmsh.model.geo.addPoint(-Db, 5*Hb, 0, lc, 13)\n",
581 | " gmsh.model.geo.addPoint(-Db, 6*Hb, 0, 0.5*lc, 14)\n",
582 | " gmsh.model.geo.addPoint(-(TT+Db-R), 3*Hb, 0, 0.1*lc, 15)\n",
583 | " \n",
584 | " gmsh.model.geo.addLine(1, 8, 9)\n",
585 | " gmsh.model.geo.addLine(8, 7, 10)\n",
586 | " gmsh.model.geo.addLine(7, 14, 11)\n",
587 | " gmsh.model.geo.addLine(14, 13, 12)\n",
588 | " gmsh.model.geo.addCircleArc(13, 12, 15, 13)\n",
589 | " gmsh.model.geo.addCircleArc(15, 12, 11, 14)\n",
590 | " gmsh.model.geo.addLine(11, 10, 15)\n",
591 | " gmsh.model.geo.addLine(10, 1, 16)\n",
592 | " \n",
593 | " gmsh.model.geo.addCurveLoop([9, 10, 11, 12, 13, 14, 15, 16], 2)\n",
594 | " gmsh.model.geo.addPlaneSurface([2], 2)\n",
595 | " gmsh.model.geo.synchronize()\n",
596 | " gmsh.model.addPhysicalGroup(1, [9, 10, 11, 12, 13, 14, 15, 16], 103)\n",
597 | " ps = gmsh.model.addPhysicalGroup(2, [2])\n",
598 | "\n",
599 | " gmsh.model.setPhysicalName(2, ps, \"My surface 2\")\n",
600 | " gmsh.model.geo.synchronize()\n",
601 | "\n",
602 | " ov1 = gmsh.model.geo.revolve([(2, 1)], 0, 0, 0, 0, 1, 0, -np.pi / 2)\n",
603 | " ov2 = gmsh.model.geo.revolve([(2, 1)], 0, 0, 0, 0, 1, 0, np.pi / 2)\n",
604 | " ov3 = gmsh.model.geo.revolve([(2, 2)], 0, 0, 0, 0, 1, 0, -np.pi / 2)\n",
605 | " ov4 = gmsh.model.geo.revolve([(2, 2)], 0, 0, 0, 0, 1, 0, np.pi / 2)\n",
606 | " gmsh.model.geo.synchronize()\n",
607 | "\n",
608 | " gmsh.model.addPhysicalGroup(3, [ov1[1][1]], 105)\n",
609 | " gmsh.model.addPhysicalGroup(3, [ov2[1][1]], 106)\n",
610 | " gmsh.model.addPhysicalGroup(3, [ov3[1][1]], 107)\n",
611 | " gmsh.model.addPhysicalGroup(3, [ov4[1][1]], 108)\n",
612 | " gmsh.model.geo.synchronize()\n",
613 | " \n",
614 | " gmsh.option.setNumber(\"Mesh.Algorithm\", 2)\n",
615 | " gmsh.model.mesh.generate(3)\n",
616 | " #gmsh.write(\"./3dcorpo.msh\")\n",
617 | " #gmsh.write(\"foo.geo_unrolled\")\n",
618 | " msh, subdomains, boundaries = io.gmshio.model_to_mesh(gmsh.model, comm=MPI.COMM_WORLD, rank=0, gdim=3)\n",
619 | " gmsh.finalize()\n",
620 | " return msh, subdomains, boundaries\n",
621 | "\n",
622 | "msh, subdomains, boundaries = GenerateMesh3D()\n",
623 | "\n",
624 | "with io.XDMFFile(MPI.COMM_WORLD, \"3Dbody.xdmf\", \"w\") as xdmf:\n",
625 | " xdmf.write_mesh(msh)\n"
626 | ],
627 | "metadata": {
628 | "id": "Q3k93hsUilE7"
629 | },
630 | "execution_count": null,
631 | "outputs": []
632 | },
633 | {
634 | "cell_type": "code",
635 | "source": [
636 | "plot_mesh(msh, \"mesh.html\")\n",
637 | "IPython.display.HTML(filename=\"mesh.html\")"
638 | ],
639 | "metadata": {
640 | "id": "cuB8nG5u9jxi"
641 | },
642 | "execution_count": null,
643 | "outputs": []
644 | }
645 | ]
646 | }
--------------------------------------------------------------------------------
/Problem1_Poisson/Poisson_basic.py:
--------------------------------------------------------------------------------
1 | # ---
2 | # jupyter:
3 | # jupytext:
4 | # text_representation:
5 | # extension: .py
6 | # format_name: light
7 | # format_version: '1.5'
8 | # jupytext_version: 1.14.1
9 | # kernelspec:
10 | # display_name: Python 3
11 | # name: python3
12 | # ---
13 |
14 | # + [markdown] id="HlwXpyx788Vo"
15 | # # Poisson's problem
16 | # ---
17 |
18 | # + [markdown] id="sUiY4ZupvRWL"
19 | # ## Introduction
20 |
21 | # + [markdown] id="04Q-hHB9299K"
22 | # In this first tutorial we
23 | #
24 | # 1. Present a basic implementation of the finite element solution of Poisson's problem
25 | # 2. Import all necessary libraries to solve the problem with the FEniCSx platform
26 | # 3. Solve the problem with different grid refinements
27 | # 4. Visualize the solution using Paraview
28 | # 5. Perform some postprocessing
29 | #
30 | #
31 | #
32 | # **Mathematical formulation:**
33 | #
34 | # Let us recall the mathematical formulation of the Poisson's problem with
35 | # Dirichlet and Neumann boundary conditions. In differential form: Find ${u}$ such that
36 | #
37 | # \begin{equation}
38 | # \left \{
39 | # \begin{array}{rcll}
40 | # -\nabla \cdot [ \mu(\mathbf{x}) {u}(\mathbf{x})] & = & f(\mathbf{x}) & \mbox{in}~\Omega \\
41 | # && \\
42 | # {u}(\mathbf{x}) & = & g_D(\mathbf{x}) & \mbox{in}~\partial\Omega_D \\
43 | # && \\
44 | # -\mu(\mathbf{x}) \nabla{u}(\mathbf{x})\cdot\check{\mathbf{n}} & = & g_N(\mathbf{x}) & \mbox{in}~\partial\Omega_N \\
45 | # \end{array}
46 | # \right.
47 | # \end{equation}
48 | #
49 | # or by multiplying by a sufficiently regular test function $v$ and
50 | # applying integration by parts. in variational form: Find $u \in V_g(\Omega)$ such that
51 | #
52 | # \begin{equation}
53 | # \underbrace{{\int_{\Omega}}{\mu(\mathbf{x})\,\nabla{u}(\mathbf{x})\cdot \nabla{v}(\mathbf{x})}\,dx}_{a(u,v)} =
54 | # \underbrace{\int_{\Omega}{f(\mathbf{x})\,v(\mathbf{x})}\,dx
55 | # -\int_{\partial\Omega_N}{g_N(\mathbf{x})\,v(\mathbf{x})}\,ds}_{\ell(v)}~~~\forall v \in V_0(\Omega)
56 | # \end{equation}
57 | #
58 | # where $a(\cdot,\cdot)$ is a bilinear form and
59 | # $\ell(\cdot)$ is a linear form and the space $V_g$ is
60 | #
61 | # $$
62 | # V_g = \{v \in H^1(\Omega),~~v(\mathbf{x}) = g_D(\mathbf{x})~\forall \mathbf{x} \in \partial{\Omega}_D \}
63 | # $$
64 | #
65 | # Finally, recall that the discrete version of this problem follows from applying the Galerkin method: Find $u_h \in V_{hg} \subset V_g(\Omega)$ such that
66 | #
67 | # \begin{equation}
68 | # a(u_h,v_h) = \ell(v_h)~~ \forall v_h \in V_{h0}
69 | # \end{equation}
70 |
71 | # + [markdown] id="7TeY3nHjqzod"
72 | # ## Initialization
73 |
74 | # + [markdown] id="KV_eRNQpHfOv"
75 | # The first step is to import all necessary libraries. In particular, we must import
76 | # the [`FEniCSx`](https://fenicsproject.org/) library, which can be done now in Colab thanks to the efforts of the
77 | # [`FEM on Colab`](https://fem-on-colab.github.io/).
78 | # Notice that the first time the library is imported, the system may take a while. Following times are expected to be faster.
79 |
80 | # # + id="69Xzz1wQx-Nd"
81 | # try:
82 | # import gmsh
83 | # except ImportError:
84 | # # !wget "https://github.com/fem-on-colab/fem-on-colab.github.io/raw/7f220b6/releases/gmsh-install.sh" -O "/tmp/gmsh-install.sh" && bash "/tmp/gmsh-install.sh"
85 | # import gmsh
86 |
87 | # try:
88 | # import dolfinx
89 | # except ImportError:
90 | # # !wget "https://github.com/fem-on-colab/fem-on-colab.github.io/raw/7f220b6/releases/fenicsx-install-real.sh" -O "/tmp/fenicsx-install.sh" && bash "/tmp/fenicsx-install.sh"
91 | # import dolfinx
92 |
93 | # try:
94 | # import pyvista
95 | # except ImportError:
96 | # # !pip install - q piglet pyvirtualdisplay ipyvtklink pyvista panel
97 | # # !apt-get - qq install xvfb
98 | # import pyvista
99 |
100 | # + [markdown] id="UIbgSCf5tHec"
101 | # Once the `DOLFINx` package (the main library of the `FEniCSx` project) is installed, we must import some of its modules.
102 | #
103 | # Relevant `DOLFINx` modules:
104 | # - `dolfinx.mesh`: Classes and functions related to the computational domain
105 | # - `dolfinx.fem`: Finite element method functionality
106 | # - `dolfinx.io`: Input/Output (read/write) functionality
107 | # - `dolfinx.plot`: Convenience functions for exporting plotting data
108 |
109 | # + id="5WQGfKrctD5f"
110 | import pyvista
111 | from dolfinx import mesh, fem, io, plot
112 |
113 | # + [markdown] id="4r-qfD9RuJNr"
114 | # We must also import the [`Unified Form Language`](https://github.com/FEniCS/ufl/) (`UFL`) library
115 | # which provides us a specific language for writting variational formulations as well as the required mathematical operators
116 |
117 | # + id="nA6Sl0gjuHvH"
118 | from ufl import SpatialCoordinate, TestFunction, TrialFunction, Measure, dx, ds, grad, inner, as_vector, FacetNormal
119 |
120 | # + [markdown] id="VXCVESjwu6xf"
121 | # Finally, we import some auxiliary libraries:
122 |
123 | # + id="9lcr-ZVFtApl"
124 | import numpy as np
125 | from mpi4py import MPI
126 | from petsc4py.PETSc import ScalarType
127 |
128 | # + [markdown] id="1QQ6ZyybvDrU"
129 | # ## Problem definition
130 | #
131 |
132 | # + [markdown] id="FSbBTX6aH4Zw"
133 | # The first thing is to define the computational domain and parts of the boundary on which the Dirichlet conditions are to be applied:
134 |
135 | # + id="D8311BM_zrO0"
136 | # Computational mesh
137 | Lx, Ly = 1.0, 1.0
138 | msh = mesh.create_rectangle(comm=MPI.COMM_WORLD,
139 | points=((0.0, 0.0), (Lx, Ly)), n=(64, 64),
140 | cell_type=mesh.CellType.triangle)
141 |
142 | # + colab={"resources": {"http://localhost:8080/jupyter-threejs.js": {"data": "CjwhRE9DVFlQRSBodG1sPgo8aHRtbCBsYW5nPWVuPgogIDxtZXRhIGNoYXJzZXQ9dXRmLTg+CiAgPG1ldGEgbmFtZT12aWV3cG9ydCBjb250ZW50PSJpbml0aWFsLXNjYWxlPTEsIG1pbmltdW0tc2NhbGU9MSwgd2lkdGg9ZGV2aWNlLXdpZHRoIj4KICA8dGl0bGU+RXJyb3IgNDA0IChOb3QgRm91bmQpISExPC90aXRsZT4KICA8c3R5bGU+CiAgICAqe21hcmdpbjowO3BhZGRpbmc6MH1odG1sLGNvZGV7Zm9udDoxNXB4LzIycHggYXJpYWwsc2Fucy1zZXJpZn1odG1se2JhY2tncm91bmQ6I2ZmZjtjb2xvcjojMjIyO3BhZGRpbmc6MTVweH1ib2R5e21hcmdpbjo3JSBhdXRvIDA7bWF4LXdpZHRoOjM5MHB4O21pbi1oZWlnaHQ6MTgwcHg7cGFkZGluZzozMHB4IDAgMTVweH0qID4gYm9keXtiYWNrZ3JvdW5kOnVybCgvL3d3dy5nb29nbGUuY29tL2ltYWdlcy9lcnJvcnMvcm9ib3QucG5nKSAxMDAlIDVweCBuby1yZXBlYXQ7cGFkZGluZy1yaWdodDoyMDVweH1we21hcmdpbjoxMXB4IDAgMjJweDtvdmVyZmxvdzpoaWRkZW59aW5ze2NvbG9yOiM3Nzc7dGV4dC1kZWNvcmF0aW9uOm5vbmV9YSBpbWd7Ym9yZGVyOjB9QG1lZGlhIHNjcmVlbiBhbmQgKG1heC13aWR0aDo3NzJweCl7Ym9keXtiYWNrZ3JvdW5kOm5vbmU7bWFyZ2luLXRvcDowO21heC13aWR0aDpub25lO3BhZGRpbmctcmlnaHQ6MH19I2xvZ297YmFja2dyb3VuZDp1cmwoLy93d3cuZ29vZ2xlLmNvbS9pbWFnZXMvbG9nb3MvZXJyb3JwYWdlL2Vycm9yX2xvZ28tMTUweDU0LnBuZykgbm8tcmVwZWF0O21hcmdpbi1sZWZ0Oi01cHh9QG1lZGlhIG9ubHkgc2NyZWVuIGFuZCAobWluLXJlc29sdXRpb246MTkyZHBpKXsjbG9nb3tiYWNrZ3JvdW5kOnVybCgvL3d3dy5nb29nbGUuY29tL2ltYWdlcy9sb2dvcy9lcnJvcnBhZ2UvZXJyb3JfbG9nby0xNTB4NTQtMngucG5nKSBuby1yZXBlYXQgMCUgMCUvMTAwJSAxMDAlOy1tb3otYm9yZGVyLWltYWdlOnVybCgvL3d3dy5nb29nbGUuY29tL2ltYWdlcy9sb2dvcy9lcnJvcnBhZ2UvZXJyb3JfbG9nby0xNTB4NTQtMngucG5nKSAwfX1AbWVkaWEgb25seSBzY3JlZW4gYW5kICgtd2Via2l0LW1pbi1kZXZpY2UtcGl4ZWwtcmF0aW86Mil7I2xvZ297YmFja2dyb3VuZDp1cmwoLy93d3cuZ29vZ2xlLmNvbS9pbWFnZXMvbG9nb3MvZXJyb3JwYWdlL2Vycm9yX2xvZ28tMTUweDU0LTJ4LnBuZykgbm8tcmVwZWF0Oy13ZWJraXQtYmFja2dyb3VuZC1zaXplOjEwMCUgMTAwJX19I2xvZ297ZGlzcGxheTppbmxpbmUtYmxvY2s7aGVpZ2h0OjU0cHg7d2lkdGg6MTUwcHh9CiAgPC9zdHlsZT4KICA8YSBocmVmPS8vd3d3Lmdvb2dsZS5jb20vPjxzcGFuIGlkPWxvZ28gYXJpYS1sYWJlbD1Hb29nbGU+PC9zcGFuPjwvYT4KICA8cD48Yj40MDQuPC9iPiA8aW5zPlRoYXTigJlzIGFuIGVycm9yLjwvaW5zPgogIDxwPiAgPGlucz5UaGF04oCZcyBhbGwgd2Uga25vdy48L2lucz4K", "ok": false, "headers": [["content-length", "1449"], ["content-type", "text/html; charset=utf-8"]], "status": 404, "status_text": "Not Found"}}, "base_uri": "https://localhost:8080/", "height": 521} id="EnCsm0xxr7mf" outputId="1f905982-bc8e-445c-bac3-7a9576d34470"
143 | import IPython
144 |
145 | def plot_mesh(mesh, cell_values=None, filename="file.html"):
146 | pyvista.start_xvfb()
147 | grid = pyvista.UnstructuredGrid(*plot.create_vtk_mesh(mesh))
148 | plotter = pyvista.Plotter(notebook=True, window_size=[500,500])
149 |
150 | if cell_values is not None:
151 | min_ = cell_values.x.array.min()
152 | max_ = cell_values.x.array.max()
153 | grid.cell_data["cell_values"] = cell_values.x.array
154 | viridis = plt.cm.get_cmap("viridis", 25)
155 | plotter.add_mesh(grid, cmap=viridis, show_edges=True, clim=[min_, max_])
156 | else:
157 | plotter.add_mesh(grid, show_edges=True)
158 |
159 | plotter.camera.zoom(2.0)
160 | plotter.view_xy()
161 | plotter.export_html(filename, backend="pythreejs")
162 | plotter.close()
163 |
164 |
165 | plot_mesh(msh, cell_values = None, filename="mesh.html")
166 | IPython.display.HTML(filename="mesh.html")
167 |
168 | # + [markdown] id="qApYWLPxCN8m"
169 | # ## Finite element solution
170 |
171 | # + [markdown] id="i6qqhaC5zsf4"
172 | # We must create now the discrete function space associated to a conforming finite element partition $\mathcal{T}_h$ of $\Omega$, ***the mesh***, in which the solution is sought.
173 | #
174 | # For this problem, the natural choice is a space of continuous functions, that are elementwise polynomials of degree $k$
175 | #
176 | # $$
177 | # V(\mathcal{T}_h) = V_h = \{v \in H^1(\Omega),~v|_E \in P_k(E) \, \forall E \in \mathcal{T}_h\}
178 | # $$
179 | #
180 |
181 | # + id="PQj_dwidz8JT"
182 | degree = 1
183 | V = fem.FunctionSpace(msh, ("CG", degree))
184 |
185 | # + [markdown] id="oihaJl_u_VL3"
186 | # Above, `CG` refers to Continuous Galerkin, which is just another name for the widely used *Lagrange* spaces in the FEM, so, the following would be equivalent:
187 | #
188 | # V = fem.FunctionSpace(msh, ("Lagrange", degree))
189 | #
190 | # For the $P_1$ Lagrangian space, the dofs are simply the function values
191 | # at the vertices of the triangulation, a discrete function
192 | # $\mathbf{u}_h\in V_h$ is written as
193 | #
194 | # $$
195 | # u_h = \sum_{i=1}^{dim{V_h}}{u_j \psi_j}
196 | # $$
197 | # where $\{\psi_j\}_{j=1}^{dim{V_h}}$ are a set of basis functions for $V_h$, so the global vector of unknowns $\boldsymbol{\mathsf{U}} \in \mathbb{R}^{dimV_h}$.
198 | #
199 | # ---
200 | #
201 | # Now, we identify the parts of the boundary on which
202 | # Dirichlet conditions are given and define the corresponding objects. In `FEniCSx` we can identify degress of freedom $u_j$'s located on the boundary:
203 | #
204 | # * geometrically, or
205 | # * topologically
206 | #
207 | # in this case, we use geometric detection:
208 |
209 | # + id="ujpVWn5K_Txw"
210 | # Boundary values
211 | uleft = 100.0
212 | uright = 1.0
213 |
214 | # Identify the degrees of freedom to which the values are to be applied
215 | dofsL = fem.locate_dofs_geometrical(V, lambda x: np.isclose(x[0], 0))
216 | dofsR = fem.locate_dofs_geometrical(V, lambda x: np.isclose(x[0], Lx))
217 |
218 | # Define the Dirichlet type BCs as a list
219 | bcs = [fem.dirichletbc(ScalarType(uleft), dofsL, V), fem.dirichletbc(ScalarType(uright), dofsR, V)]
220 |
221 |
222 | # + [markdown] id="mMfiPE4_Ag6u"
223 | # Finally, we can write the variational formulation of our problem and solve, recalling
224 | #
225 | # $$
226 | # a(u,v) = \int_{\Omega}{\mu\,\nabla{u}\cdot \nabla{v}}\,dx
227 | # $$
228 | # and, if we take $g_N = 0$, we naturally have
229 | # $$
230 | # \ell(v) = \int_{\Omega}{f\,v}\,dx
231 | # $$
232 |
233 | # + id="uMjbmwHTz-fQ"
234 | # Variational formulation
235 |
236 | # Trial and test functions
237 | u = TrialFunction(V)
238 | v = TestFunction(V)
239 |
240 | # Bilinear form
241 | mu = fem.Constant(msh, 1.0)
242 | a = inner(mu*grad(u), grad(v)) * dx
243 |
244 | # Linear form
245 | f = 1.0
246 | source = fem.Constant(msh, f)
247 | L = source * v * dx
248 |
249 | # Solve the problem
250 | problem = fem.petsc.LinearProblem(a, L, bcs=bcs, petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
251 | uh = problem.solve()
252 |
253 | # + [markdown] id="i4VCoJqTCBR3"
254 | # ## Visualization and postprocessing
255 |
256 | # + id="PIvCos9RdzFy" colab={"base_uri": "https://localhost:8080/", "height": 17} outputId="ff309c11-85bf-4587-efe1-9d652a9c1b2b"
257 | uh.name = "Temperature"
258 | with io.XDMFFile(MPI.COMM_WORLD, "temperature.xdmf", "w") as xdmf:
259 | xdmf.write_mesh(msh)
260 | xdmf.write_function(uh)
261 |
262 | from google.colab import files
263 | files.download('temperature.xdmf')
264 | files.download('temperature.h5')
265 |
266 | # + [markdown] id="61HnZa0R4qG_"
267 | # A typical verification is to compute the relevant error norms, which in this case are the $L^2(\Omega)$ error norm:
268 | #
269 | # $$
270 | # e_{L^2(\Omega)} = \sqrt{ \int_{\Omega}{(u - u_h)^2}\,dx}
271 | # $$
272 | # and the $H^1(\Omega)$ error norm
273 | # $$
274 | # e_{H^1(\Omega)} = \sqrt{ \int_{\Omega}{\left [ (u - u_h)^2 + \nabla{(u-u_h)} \cdot \nabla{(u-u_h)}\right ] }\,dx }
275 | # $$
276 | #
277 | # As a result of taking zero Neumann boundary conditions on the top
278 | # and bottom walls, the exact solution to this problem, with $\mu$ and $f$ constants, ends up being dependent only on the $x$ coordinate, i.e., we are actually solving the problem
279 | #
280 | # $$
281 | # -\dfrac{d^2u}{dx^2} = f,~~u(0) = u_l,~~u(L_x) = u_r
282 | # $$
283 | #
284 | # whose solution is
285 | #
286 | # $$
287 | # u = u_l + \left ( \dfrac{u_r - u_l}{L_x} + \frac12 \, f \, L_x \right ) x - \frac12 f x^2
288 | # $$
289 |
290 | # + id="nDl2pc7R0hJE" colab={"base_uri": "https://localhost:8080/"} outputId="05de8aa7-8c90-4244-eb9f-25f150739979"
291 | # We need the coordinates to create a ufl expression
292 | x = SpatialCoordinate(msh)
293 |
294 | # to define the exact solution
295 | uex = lambda x: uleft + ( (uright - uleft)/Lx + 0.5*f*Lx )*x[0] - 0.5*f*x[0]**2
296 |
297 | # and the error
298 | eh = uh - uex(x)
299 |
300 | # Now compute the L2 and H1 error norms
301 | eL2form = fem.form( eh**2*dx )
302 | eH1form = fem.form( inner(grad(eh),grad(eh))*dx )
303 |
304 | errorL2 = fem.assemble_scalar(eL2form)
305 | errorH1 = errorL2 + fem.assemble_scalar(eH1form)
306 |
307 | print(" |-L2error=", np.sqrt(errorL2))
308 | print(" |-H1error=", np.sqrt(errorH1))
309 |
310 |
311 | # + [markdown] id="nmKoR4CJag-l"
312 | # ## A sanity check: Convergence rates
313 | #
314 | # How do we know the numerical solution is actually correct?
315 | #
316 | # We can compute the rate of convergence of the numerical method and compare to the expected asymptotic value.
317 | #
318 | # $$
319 | # \|u - u_h \|_{L^2(\Omega)} \le C h^{p+1}
320 | # $$
321 | #
322 | # $$
323 | # \|u - u_h \|_{H^1(\Omega)} \le C h^{p}
324 | # $$
325 | #
326 | # In order to facilitate the computation, we first
327 | # encapsulate the whole resolution into a single python function that
328 | # takes as arguments the desired refinement and degree of the
329 | # polynomial space and returns the $L^2(\Omega)$ and $H^1(\Omega)$-error norms, i.e.
330 | #
331 |
332 | # + id="f7gnZHIWauU8"
333 | def SolvePoisson(msh):
334 | degree = 1
335 | V = fem.FunctionSpace(msh, ("CG", degree))
336 |
337 | nunks = V.dofmap.index_map.size_global
338 | print("Solving: Characteristic mesh size h=%f (%d elements, %d unknowns)" %(Lx/N, 2*N*N, nunks))
339 |
340 | dofsL = fem.locate_dofs_geometrical(V, lambda x: np.isclose(x[0], 0))
341 | dofsR = fem.locate_dofs_geometrical(V, lambda x: np.isclose(x[0], Lx))
342 | bcs = [fem.dirichletbc(ScalarType(uleft), dofsL, V), fem.dirichletbc(ScalarType(uright), dofsR, V)]
343 |
344 | u, v = TrialFunction(V), TestFunction(V)
345 | mu = fem.Constant(msh, 1.0)
346 | a = inner(mu*grad(u), grad(v)) * dx
347 | f = 1.0
348 | source = fem.Constant(msh, f)
349 | L = source * v * dx
350 | problem = fem.petsc.LinearProblem(a, L, bcs=bcs, petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
351 | uh = problem.solve()
352 |
353 | uex = lambda x: uleft + ( (uright - uleft)/Lx + 0.5*f*Lx )*x[0] - 0.5*f*x[0]**2
354 | x = SpatialCoordinate(msh)
355 | eh = uh - uex(x)
356 | eL2form = fem.form( eh**2*dx )
357 | eH1form = fem.form( inner(grad(eh),grad(eh))*dx )
358 | errorL2 = fem.assemble_scalar(eL2form)
359 | errorH1 = errorL2 + fem.assemble_scalar(eH1form)
360 |
361 | return np.sqrt(errorL2), np.sqrt(errorH1)
362 |
363 |
364 | # + id="BnbhloHEfjLk" colab={"base_uri": "https://localhost:8080/"} outputId="4edccaf3-1c99-490f-e068-37c2cdef0614"
365 | # Make a loop over the meshes
366 | Lx = Ly = 1.0
367 | degree = 1
368 | eL2, eH1, h = [], [], []
369 | for N in [10, 20, 40, 80, 160, 320]:
370 | msh = mesh.create_rectangle(comm=MPI.COMM_WORLD,
371 | points=((0.0, 0.0), (Lx, Ly)), n=(N, N),
372 | cell_type=mesh.CellType.triangle)
373 |
374 | errorL2, errorH1 = SolvePoisson(msh)
375 | eL2.append(errorL2)
376 | eH1.append(errorH1)
377 | h.append(Lx/N)
378 |
379 | # + id="al_JJ-QNgSws" colab={"base_uri": "https://localhost:8080/", "height": 406} outputId="7f45b0c1-53f2-4a30-fd7c-812e9aa41acd"
380 | import matplotlib.pyplot as plt
381 | plt.figure(figsize= (9,6))
382 | plt.rcParams['font.size'] = '16'
383 | plt.axes(facecolor = "lightgray")
384 | plt.xlabel('$h$',fontsize=20)
385 | plt.ylabel('Error', fontsize=20)
386 | l = plt.plot(h, eL2,'-or', h, eH1, '-ob')
387 | plt.setp(l, 'markersize', 10)
388 | plt.setp(l, 'markerfacecolor', 'white')
389 | plt.legend(['$e_{L^2}$', '$e_{H1}$'])
390 | plt.loglog(base=10)
391 | plt.grid()
392 | plt.show()
393 |
394 | # + [markdown] id="y_5pTXZmbLq1"
395 | # # A more complex geometry: Poisson's multimaterial problem
396 |
397 | # + [markdown] id="UpCJrtyUbSys"
398 | # We will use a more advance tool, the `gmsh` library to create a square domain with some internal inclusions, such that
399 | #
400 | # \begin{equation}
401 | # \mu(\mathbf{x}) = \left\{
402 | # \begin{array}{r l l l }
403 | # \mu_B & \mbox{if} & \mathbf{x} \in \Omega_{B} = \bigcup_{i=1}^{n_b} {\omega_i}, ~
404 | # \omega_i = \{ \mathbf{x} \in \Omega, \parallel \mathbf{x} - \mathbf{x}_c^i \parallel < r_i \} \\
405 | # ~\\
406 | # \mu_A & \mbox{if} & \mathbf{x} \in \Omega_A = \Omega \setminus \Omega_{B}
407 | # \end{array}
408 | # \right.
409 | # \end{equation}
410 | #
411 | # To that end we must import the library first by doing:
412 | #
413 |
414 | # + id="rQ3IHYkoCP33" colab={"resources": {"http://localhost:8080/jupyter-threejs.js": {"data": "CjwhRE9DVFlQRSBodG1sPgo8aHRtbCBsYW5nPWVuPgogIDxtZXRhIGNoYXJzZXQ9dXRmLTg+CiAgPG1ldGEgbmFtZT12aWV3cG9ydCBjb250ZW50PSJpbml0aWFsLXNjYWxlPTEsIG1pbmltdW0tc2NhbGU9MSwgd2lkdGg9ZGV2aWNlLXdpZHRoIj4KICA8dGl0bGU+RXJyb3IgNDA0IChOb3QgRm91bmQpISExPC90aXRsZT4KICA8c3R5bGU+CiAgICAqe21hcmdpbjowO3BhZGRpbmc6MH1odG1sLGNvZGV7Zm9udDoxNXB4LzIycHggYXJpYWwsc2Fucy1zZXJpZn1odG1se2JhY2tncm91bmQ6I2ZmZjtjb2xvcjojMjIyO3BhZGRpbmc6MTVweH1ib2R5e21hcmdpbjo3JSBhdXRvIDA7bWF4LXdpZHRoOjM5MHB4O21pbi1oZWlnaHQ6MTgwcHg7cGFkZGluZzozMHB4IDAgMTVweH0qID4gYm9keXtiYWNrZ3JvdW5kOnVybCgvL3d3dy5nb29nbGUuY29tL2ltYWdlcy9lcnJvcnMvcm9ib3QucG5nKSAxMDAlIDVweCBuby1yZXBlYXQ7cGFkZGluZy1yaWdodDoyMDVweH1we21hcmdpbjoxMXB4IDAgMjJweDtvdmVyZmxvdzpoaWRkZW59aW5ze2NvbG9yOiM3Nzc7dGV4dC1kZWNvcmF0aW9uOm5vbmV9YSBpbWd7Ym9yZGVyOjB9QG1lZGlhIHNjcmVlbiBhbmQgKG1heC13aWR0aDo3NzJweCl7Ym9keXtiYWNrZ3JvdW5kOm5vbmU7bWFyZ2luLXRvcDowO21heC13aWR0aDpub25lO3BhZGRpbmctcmlnaHQ6MH19I2xvZ297YmFja2dyb3VuZDp1cmwoLy93d3cuZ29vZ2xlLmNvbS9pbWFnZXMvbG9nb3MvZXJyb3JwYWdlL2Vycm9yX2xvZ28tMTUweDU0LnBuZykgbm8tcmVwZWF0O21hcmdpbi1sZWZ0Oi01cHh9QG1lZGlhIG9ubHkgc2NyZWVuIGFuZCAobWluLXJlc29sdXRpb246MTkyZHBpKXsjbG9nb3tiYWNrZ3JvdW5kOnVybCgvL3d3dy5nb29nbGUuY29tL2ltYWdlcy9sb2dvcy9lcnJvcnBhZ2UvZXJyb3JfbG9nby0xNTB4NTQtMngucG5nKSBuby1yZXBlYXQgMCUgMCUvMTAwJSAxMDAlOy1tb3otYm9yZGVyLWltYWdlOnVybCgvL3d3dy5nb29nbGUuY29tL2ltYWdlcy9sb2dvcy9lcnJvcnBhZ2UvZXJyb3JfbG9nby0xNTB4NTQtMngucG5nKSAwfX1AbWVkaWEgb25seSBzY3JlZW4gYW5kICgtd2Via2l0LW1pbi1kZXZpY2UtcGl4ZWwtcmF0aW86Mil7I2xvZ297YmFja2dyb3VuZDp1cmwoLy93d3cuZ29vZ2xlLmNvbS9pbWFnZXMvbG9nb3MvZXJyb3JwYWdlL2Vycm9yX2xvZ28tMTUweDU0LTJ4LnBuZykgbm8tcmVwZWF0Oy13ZWJraXQtYmFja2dyb3VuZC1zaXplOjEwMCUgMTAwJX19I2xvZ297ZGlzcGxheTppbmxpbmUtYmxvY2s7aGVpZ2h0OjU0cHg7d2lkdGg6MTUwcHh9CiAgPC9zdHlsZT4KICA8YSBocmVmPS8vd3d3Lmdvb2dsZS5jb20vPjxzcGFuIGlkPWxvZ28gYXJpYS1sYWJlbD1Hb29nbGU+PC9zcGFuPjwvYT4KICA8cD48Yj40MDQuPC9iPiA8aW5zPlRoYXTigJlzIGFuIGVycm9yLjwvaW5zPgogIDxwPiAgPGlucz5UaGF04oCZcyBhbGwgd2Uga25vdy48L2lucz4K", "ok": false, "headers": [["content-length", "1449"], ["content-type", "text/html; charset=utf-8"]], "status": 404, "status_text": "Not Found"}}, "base_uri": "https://localhost:8080/", "height": 521} outputId="abdc7c3f-89c7-4c4e-e981-d3612f0acdf2"
415 | ninclusions = 3
416 | Lx = 2.0
417 | Ly = 2.0
418 | R1 = 0.25
419 | R2 = 0.15
420 | R3 = 0.25
421 |
422 | def GenerateMesh():
423 |
424 | gmsh.initialize()
425 | proc = MPI.COMM_WORLD.rank
426 |
427 | mass1 = np.pi*R1**2
428 | mass2 = np.pi*R2**2
429 | mass3 = np.pi*R3**2
430 | mass_inc = mass1 + mass2 + mass3
431 |
432 | if proc == 0:
433 | # We create one rectangle and the circular inclusion
434 | background = gmsh.model.occ.addRectangle(0, 0, 0, Lx, Ly)
435 | inclusion1 = gmsh.model.occ.addDisk(0.5, 1.0, 0, R1, R1)
436 | inclusion2 = gmsh.model.occ.addDisk(1.0, 1.5, 0, R2, R2)
437 | inclusion3 = gmsh.model.occ.addDisk(1.5, 1.0, 0, R3, R3)
438 | gmsh.model.occ.synchronize()
439 | all_inclusions = [(2, inclusion1)]
440 | all_inclusions.extend([(2, inclusion2)])
441 | all_inclusions.extend([(2, inclusion3)])
442 | whole_domain = gmsh.model.occ.fragment([(2, background)], all_inclusions)
443 | gmsh.model.occ.synchronize()
444 |
445 | background_surfaces = []
446 | other_surfaces = []
447 | for domain in whole_domain[0]:
448 | com = gmsh.model.occ.getCenterOfMass(domain[0], domain[1])
449 | mass = gmsh.model.occ.getMass(domain[0], domain[1])
450 | #print(mass, com)
451 | # Identify the square by its mass
452 | if np.isclose(mass, (Lx*Ly - mass_inc)):
453 | gmsh.model.addPhysicalGroup(domain[0], [domain[1]], 0)
454 | background_surfaces.append(domain)
455 | elif np.isclose(np.linalg.norm(com), np.sqrt((0.5)**2 + (1.0)**2)):
456 | gmsh.model.addPhysicalGroup(domain[0], [domain[1]], 1)
457 | other_surfaces.append(domain)
458 | elif np.isclose(np.linalg.norm(com), np.sqrt((1.0)**2 + (1.5)**2)) and com[1] > 1.0:
459 | gmsh.model.addPhysicalGroup(domain[0], [domain[1]], 2)
460 | other_surfaces.append(domain)
461 | elif np.isclose(np.linalg.norm(com), np.sqrt((1.5)**2 + (1.0)**2)):
462 | gmsh.model.addPhysicalGroup(domain[0], [domain[1]], 3)
463 | other_surfaces.append(domain)
464 |
465 | # Tag the left and right boundaries
466 | left = []
467 | right = []
468 | for line in gmsh.model.getEntities(dim=1):
469 | com = gmsh.model.occ.getCenterOfMass(line[0], line[1])
470 | if np.isclose(com[0], 0.0):
471 | left.append(line[1])
472 | if np.isclose(com[0], Lx):
473 | right.append(line[1])
474 | gmsh.model.addPhysicalGroup(1, left, 3)
475 | gmsh.model.addPhysicalGroup(1, right,1)
476 |
477 | gmsh.model.mesh.setSize(gmsh.model.getEntities(0), 0.05)
478 | gmsh.model.mesh.generate(2)
479 |
480 | msh, subdomains, boundaries = io.gmshio.model_to_mesh(gmsh.model, comm=MPI.COMM_WORLD, rank=0, gdim=2)
481 | gmsh.finalize()
482 | return msh, subdomains, boundaries
483 |
484 | msh, subdomains, boundaries = GenerateMesh()
485 |
486 | with io.XDMFFile(MPI.COMM_WORLD, "mymesh.xdmf", "w") as xdmf:
487 | xdmf.write_mesh(msh)
488 |
489 | plot_mesh(msh, cell_values=None, filename="mesh.html")
490 | IPython.display.HTML(filename="mesh.html")
491 |
492 |
493 | # + [markdown] id="soJmXnpLc1h-"
494 | # In order to define the diffusivity as a piecewise constant function we must introduce a `DG` space:
495 | #
496 | #
497 | # $$
498 | # Q_h = \{v \in L^2(\Omega),~v|_E \in P_0(E) \, \forall E \in \mathcal{T}_h\}
499 | # $$
500 | #
501 | # which is done in `dolfinx` as
502 |
503 | # + id="TCFR6CQxdcwI" colab={"resources": {"http://localhost:8080/jupyter-threejs.js": {"data": "CjwhRE9DVFlQRSBodG1sPgo8aHRtbCBsYW5nPWVuPgogIDxtZXRhIGNoYXJzZXQ9dXRmLTg+CiAgPG1ldGEgbmFtZT12aWV3cG9ydCBjb250ZW50PSJpbml0aWFsLXNjYWxlPTEsIG1pbmltdW0tc2NhbGU9MSwgd2lkdGg9ZGV2aWNlLXdpZHRoIj4KICA8dGl0bGU+RXJyb3IgNDA0IChOb3QgRm91bmQpISExPC90aXRsZT4KICA8c3R5bGU+CiAgICAqe21hcmdpbjowO3BhZGRpbmc6MH1odG1sLGNvZGV7Zm9udDoxNXB4LzIycHggYXJpYWwsc2Fucy1zZXJpZn1odG1se2JhY2tncm91bmQ6I2ZmZjtjb2xvcjojMjIyO3BhZGRpbmc6MTVweH1ib2R5e21hcmdpbjo3JSBhdXRvIDA7bWF4LXdpZHRoOjM5MHB4O21pbi1oZWlnaHQ6MTgwcHg7cGFkZGluZzozMHB4IDAgMTVweH0qID4gYm9keXtiYWNrZ3JvdW5kOnVybCgvL3d3dy5nb29nbGUuY29tL2ltYWdlcy9lcnJvcnMvcm9ib3QucG5nKSAxMDAlIDVweCBuby1yZXBlYXQ7cGFkZGluZy1yaWdodDoyMDVweH1we21hcmdpbjoxMXB4IDAgMjJweDtvdmVyZmxvdzpoaWRkZW59aW5ze2NvbG9yOiM3Nzc7dGV4dC1kZWNvcmF0aW9uOm5vbmV9YSBpbWd7Ym9yZGVyOjB9QG1lZGlhIHNjcmVlbiBhbmQgKG1heC13aWR0aDo3NzJweCl7Ym9keXtiYWNrZ3JvdW5kOm5vbmU7bWFyZ2luLXRvcDowO21heC13aWR0aDpub25lO3BhZGRpbmctcmlnaHQ6MH19I2xvZ297YmFja2dyb3VuZDp1cmwoLy93d3cuZ29vZ2xlLmNvbS9pbWFnZXMvbG9nb3MvZXJyb3JwYWdlL2Vycm9yX2xvZ28tMTUweDU0LnBuZykgbm8tcmVwZWF0O21hcmdpbi1sZWZ0Oi01cHh9QG1lZGlhIG9ubHkgc2NyZWVuIGFuZCAobWluLXJlc29sdXRpb246MTkyZHBpKXsjbG9nb3tiYWNrZ3JvdW5kOnVybCgvL3d3dy5nb29nbGUuY29tL2ltYWdlcy9sb2dvcy9lcnJvcnBhZ2UvZXJyb3JfbG9nby0xNTB4NTQtMngucG5nKSBuby1yZXBlYXQgMCUgMCUvMTAwJSAxMDAlOy1tb3otYm9yZGVyLWltYWdlOnVybCgvL3d3dy5nb29nbGUuY29tL2ltYWdlcy9sb2dvcy9lcnJvcnBhZ2UvZXJyb3JfbG9nby0xNTB4NTQtMngucG5nKSAwfX1AbWVkaWEgb25seSBzY3JlZW4gYW5kICgtd2Via2l0LW1pbi1kZXZpY2UtcGl4ZWwtcmF0aW86Mil7I2xvZ297YmFja2dyb3VuZDp1cmwoLy93d3cuZ29vZ2xlLmNvbS9pbWFnZXMvbG9nb3MvZXJyb3JwYWdlL2Vycm9yX2xvZ28tMTUweDU0LTJ4LnBuZykgbm8tcmVwZWF0Oy13ZWJraXQtYmFja2dyb3VuZC1zaXplOjEwMCUgMTAwJX19I2xvZ297ZGlzcGxheTppbmxpbmUtYmxvY2s7aGVpZ2h0OjU0cHg7d2lkdGg6MTUwcHh9CiAgPC9zdHlsZT4KICA8YSBocmVmPS8vd3d3Lmdvb2dsZS5jb20vPjxzcGFuIGlkPWxvZ28gYXJpYS1sYWJlbD1Hb29nbGU+PC9zcGFuPjwvYT4KICA8cD48Yj40MDQuPC9iPiA8aW5zPlRoYXTigJlzIGFuIGVycm9yLjwvaW5zPgogIDxwPiAgPGlucz5UaGF04oCZcyBhbGwgd2Uga25vdy48L2lucz4K", "ok": false, "headers": [["content-length", "1449"], ["content-type", "text/html; charset=utf-8"]], "status": 404, "status_text": "Not Found"}}, "base_uri": "https://localhost:8080/", "height": 521} outputId="7ce90f5b-be8f-45b4-edf3-e4100f4142de"
504 | def SetDifus(msh, subdomains, muA, muB):
505 | Q = fem.FunctionSpace(msh, ("DG", 0))
506 | muh = fem.Function(Q)
507 |
508 | # Identify cells with different tags
509 | cells_background = subdomains.indices[subdomains.values == 0]
510 | cells_inc = subdomains.indices[subdomains.values >= 1]
511 |
512 | # Set mu = muA in the background cells
513 | muh.x.array[cells_background] = np.full(len(cells_background), muA)
514 |
515 | # Set mu = muB in the inclusion celss
516 | muh.x.array[cells_inc] = np.full(len(cells_inc), muB)
517 |
518 | return muh
519 |
520 | # Test the function
521 | muA, muB = 1.0, 10000.0
522 | muh = SetDifus(msh, subdomains, muA, muB)
523 | muh.name = "mu"
524 | with io.XDMFFile(MPI.COMM_WORLD, "mu.xdmf", "w") as xdmf:
525 | xdmf.write_mesh(msh)
526 | xdmf.write_function(muh)
527 |
528 | plot_mesh(msh, cell_values=muh, filename="muvalues.html")
529 | IPython.display.HTML(filename="muvalues.html")
530 |
531 |
532 | # + id="jYw9C1HtQxhU" colab={"base_uri": "https://localhost:8080/", "height": 70} outputId="b57752fb-43af-471d-bfd6-77b41492fba5"
533 | def SolvePoissonMulti(msh, subdomains, muA, muB):
534 |
535 | dx = Measure("dx")(subdomain_data=subdomains)
536 | ds = Measure("ds")(subdomain_data=boundaries)
537 |
538 | # Sanity check
539 | if(False):
540 | # Compute the length and area for verification
541 | one = fem.Constant(msh, 1.0)
542 | for i in [1, 3]:
543 | length_form = fem.form( one*ds(i) )
544 | lengthside = fem.assemble_scalar(length_form)
545 | print(lengthside)
546 |
547 | total = 0.0
548 | for i in range(4):
549 | area_form = fem.form( one*dx(i) )
550 | area = fem.assemble_scalar(area_form)
551 | total += area
552 | print('Individual area of %d surface= %f' %(i,area))
553 | print('Total area=', total)
554 |
555 | degree = 1
556 | V = fem.FunctionSpace(msh, ("CG", degree))
557 | dofsL = fem.locate_dofs_geometrical(V, lambda x: np.isclose(x[0], 0))
558 | dofsR = fem.locate_dofs_geometrical(V, lambda x: np.isclose(x[0], Lx))
559 | bcs = [fem.dirichletbc(ScalarType(uleft), dofsL, V), fem.dirichletbc(ScalarType(uright), dofsR, V)]
560 |
561 | u, v = TrialFunction(V), TestFunction(V)
562 |
563 | muh = SetDifus(msh, subdomains, muA, muB)
564 |
565 | a = inner(muh*grad(u), grad(v)) * dx
566 | source = fem.Constant(msh, 0.0)
567 | L = source * v * dx
568 | problem = fem.petsc.LinearProblem(a, L, bcs=bcs, petsc_options={"ksp_type": "preonly", "pc_type": "lu", "pc_factor_matsolver_type": "mumps"})
569 | uh = problem.solve()
570 | print('Solving problem with %d unknowns' %(V.dofmap.index_map.size_global))
571 |
572 | return uh
573 |
574 | # Test the function
575 | uh = SolvePoissonMulti(msh, subdomains, muA, muB)
576 | uh.name = "Temperature"
577 | with io.XDMFFile(MPI.COMM_WORLD, "inclusions.xdmf", "w") as xdmf:
578 | xdmf.write_mesh(msh)
579 | xdmf.write_function(uh)
580 |
581 | from google.colab import files
582 | files.download('inclusions.xdmf')
583 | files.download('inclusions.h5')
584 |
585 | # + [markdown] id="BEV7LMmONHw9"
586 | # ---
587 | # # Homework 1
588 |
589 | # + [markdown] id="6R_uRiMWthbe"
590 | # ## Multimaterial problem
591 | #
592 | # ### Effective diffusivity
593 | #
594 | # Taking $f = 0$, compute and the effective thermal diffusivity of the system
595 | # as function of $\mu_B/\mu_A$
596 | #
597 | # \begin{equation}
598 | # \mu_{\mbox{eff}} = \frac{|q|/L_y}{|u_l - u_r|/L_x} \nonumber
599 | # \end{equation}
600 | #
601 | # where the amount of heat entering the system is given by
602 | #
603 | # \begin{equation}
604 | # q = \int_{ \Gamma_{\mbox{left}}}{\mu_A \nabla{u}_h \cdot \check{\mathbf{e}}_1}\,ds
605 | # \end{equation}
606 | #
607 | # Complete the following code:
608 | #
609 | # muA = 1.0
610 | # mueff = []
611 | # n = FacetNormal(msh)
612 | # ds = Measure("ds")(subdomain_data=boundaries)
613 | # muBvals = [1e-2, 1e-1, 1e0, 1e1, 1e2, 1e3, 1e4]
614 | # for muB in muBvals:
615 | # uh = SolvePoissonMulti(msh, subdomains, muA, muB)
616 | # q = ...
617 | # mueff.append(...)
618 |
619 | # + id="3-w-2P6vAGVS" colab={"base_uri": "https://localhost:8080/", "height": 409} outputId="d18a5a16-0a2b-418d-b554-6115480aa7ed"
620 | import matplotlib.pyplot as plt
621 | plt.figure(figsize= (9,6))
622 | plt.rcParams['font.size'] = '16'
623 | plt.axes(facecolor = "lightgray")
624 | plt.xlabel('$\mu_B/\mu_A$',fontsize=20)
625 | plt.ylabel('$\mu_{eff}$', fontsize=20)
626 | l = plt.plot(muBvals, mueff, '-or')
627 | plt.setp(l, 'markersize', 10)
628 | plt.setp(l, 'markerfacecolor', 'white')
629 | plt.semilogx(base=10)
630 | plt.grid()
631 | plt.show()
632 |
633 | # + [markdown] id="bqPQXdPy5LQb"
634 | # ### Average temperature of inclusions
635 | #
636 | # Let consider $\mu_B \gg \mu_A$, such that the temperature
637 | # on each circular region is nearly uniform. Implement the computation of
638 | # the average temperature on each inclusion, i.e.,
639 | # \begin{equation}
640 | # \langle T_i \rangle = \frac{1}{|\omega_i|} \, \int_{\omega_i}{u(\mathbf{x})}\,d{x}
641 | # \end{equation}
642 | # in which $|\omega_i|$ stands for the the area of region $\omega_i$.
643 | #
644 | # Complete the following code:
645 | #
646 | # dx = Measure("dx")(subdomain_data=subdomains)
647 | # one = Constant(msh, 1.0)
648 | # Tincav = []
649 | # for k in range(ninclusions):
650 | # area = assemble_scalar(fem.form(one * dx(k+1)))
651 | # Tinc = assemble_scalar(...)
652 | # Tincav.append(...)
653 |
--------------------------------------------------------------------------------
/Problem4_Thermo-Elasticity/ThermoElastic.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "id": "view-in-github",
7 | "colab_type": "text"
8 | },
9 | "source": [
10 | "
"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "metadata": {
16 | "id": "HlwXpyx788Vo"
17 | },
18 | "source": [
19 | "# Thermo-Elastic deformation\n",
20 | "---"
21 | ]
22 | },
23 | {
24 | "cell_type": "markdown",
25 | "metadata": {
26 | "id": "fJgZcRiQFgTB"
27 | },
28 | "source": [
29 | "In this tutorial we will combine two of our previous solvers to model the thermo-elastic deformation of a bimaterial, i.e., a specimen made of two materials with different thermal expansion coefficients. The idea is to solve a problem involving two fields governed by different EDPs, namely, the temperature field $T$ and the displacement field $\\mathbf{u}$.\n",
30 | "\n",
31 | "**Mathematical formulation:**\n",
32 | "\n",
33 | "Let us first recall the stationary **heat conduction equation** that governs the thermal problem\n",
34 | "\n",
35 | "\\begin{equation}\n",
36 | "\\left \\{\n",
37 | "\\begin{array}{rcll}\n",
38 | "-\\nabla \\cdot ( \\kappa \\, \\nabla{T}) & = & s & \\mbox{in}~\\Omega \\\\\n",
39 | "&& \\\\\n",
40 | "{T} & = & T_D & \\mbox{in}~\\partial\\Omega_D \\\\\n",
41 | "&& \\\\\n",
42 | "-\\kappa \\nabla{T}\\cdot\\check{\\mathbf{n}} & = & J_N & \\mbox{in}~\\partial\\Omega_N \\\\\n",
43 | "\\end{array}\n",
44 | "\\right.\n",
45 | "\\end{equation}\n",
46 | "\n",
47 | "where $s(\\mathbf{x})$ denotes the source term, $\\kappa$ the thermal diffusivity,\n",
48 | "$T_D$ the Dirichlet data and $J_N$ the Neumann data."
49 | ]
50 | },
51 | {
52 | "cell_type": "markdown",
53 | "metadata": {
54 | "id": "zlG7byqqFsly"
55 | },
56 | "source": [
57 | "As for the **elastostatic** problem that governs the deformation of the specimen, we have\n",
58 | "\n",
59 | "\\begin{equation}\n",
60 | "\\left \\{\n",
61 | "\\begin{array}{rcll}\n",
62 | "-\\nabla \\cdot \\boldsymbol{\\sigma} (\\mathbf{u},T) & = & \\mathbf{f} & \\mbox{in}~\\Omega \\\\\n",
63 | "& & & \\\\\n",
64 | "\\mathbf{u} & = & \\mathbf{g} & \\mbox{on}~\\Gamma_{\\mathbf{u}} \\\\\n",
65 | "& & & \\\\\n",
66 | "\\boldsymbol{\\sigma} \\cdot \\check{\\mathbf{n}} & = & \\boldsymbol{\\mathcal{F}} & \\mbox{on}~\\Gamma_{\\boldsymbol{\\mathcal{F}}}\n",
67 | "\\end{array}\n",
68 | "\\right.\n",
69 | "\\end{equation}\n",
70 | "\n",
71 | "where $\\mathbf{f}$ is the body force, $\\mathbf{g}$ the prescribed displacement, $\\boldsymbol{\\mathcal{F}}$ the surface force distribution.\n"
72 | ]
73 | },
74 | {
75 | "cell_type": "markdown",
76 | "metadata": {
77 | "id": "kxGxUGVwF-Im"
78 | },
79 | "source": [
80 | "The stress tensor is\n",
81 | "\n",
82 | "$$\n",
83 | "\\boldsymbol{\\sigma} = 2\\mu\\, \\boldsymbol{\\varepsilon}^{elas}({\\mathbf{u}})+ \\lambda \\, \\mbox{tr}(\\boldsymbol{\\varepsilon}^{elas}({\\mathbf{u}}))\\, \\mathbf{I}_{d}\n",
84 | "$$\n",
85 | " \n",
86 | "\n",
87 | " \n",
88 | "where $d$ is the spatial dimension, $\\mathbf{I}_{d}$ is the identity matrix and `tr` stands for the trace of a tensor, i.e.\n",
89 | "\n",
90 | "$$\n",
91 | "\\mbox{tr}(\\boldsymbol{\\varepsilon}^{elas}({\\mathbf{u}})) = \n",
92 | "\\sum_{k=1}^d{\\varepsilon}^{elas}_{kk}\n",
93 | "$$\n",
94 | "\n",
95 | "The difference now is that we have an additional contribution to the\n",
96 | "strain tensor\n",
97 | "\n",
98 | "$$\n",
99 | "\\boldsymbol{\\varepsilon}({\\mathbf{u}}) =\n",
100 | "\\frac12 (\\nabla{\\mathbf{u}} + \\nabla^{\\intercal}{\\mathbf{u}}) = \n",
101 | " \\boldsymbol{\\varepsilon}^{elas}({\\mathbf{u}}) + \\alpha(\\mathbf{x})\\, (T - T_0)\\,\\mathbf{I}_{d}\n",
102 | "$$\n",
103 | "\n",
104 | "where $T_0$ is a reference temperature at which no thermal deformation\n",
105 | "exists, that we assume to be equal to $0$.\n",
106 | "\n",
107 | "$~$\n",
108 | "\n",
109 | "---"
110 | ]
111 | },
112 | {
113 | "cell_type": "markdown",
114 | "metadata": {
115 | "id": "QreUwsXm5mhB"
116 | },
117 | "source": [
118 | "To implement the finite element solution, what matter to us is the **variational formulation of the coupled problem** given above, which is obtained by combining the two previously seen formulations for the Poisson's problem and the elasticity problem: Find $\\mathbf{u} \\in U_{\\mathbf{g}}$ and $T \\in V_{T}$ such that\n",
119 | "\n",
120 | "\\begin{eqnarray}\n",
121 | "a_{elas}((\\mathbf{u},T), \\mathbf{v}) = \\int_{\\Omega}{\\boldsymbol{\\sigma}(\\mathbf{u},T) : \\boldsymbol{\\varepsilon}(\\mathbf{v}) \\,dx} & = & \\\\\n",
122 | "& = & \\int_{\\Omega}{\\mathbf{f}\\cdot \\mathbf{v}}\\,dx +\n",
123 | "\\int_{\\Gamma_{\\boldsymbol{\\mathcal{F}}}}{\\boldsymbol{\\mathcal{F}} \\cdot \\mathbf{v}}\\,ds = \\ell_{elas}(\\mathbf{v}) ~~~\\forall \\mathbf{v} \\in U_{\\mathbf{0}}\\\\\n",
124 | "& & \\\\\n",
125 | "a_{th}(T,r) = {\\int_{\\Omega}}{\\kappa\\,\\nabla{T} \\cdot \\nabla{r}}\\,dx & = & \\\\\n",
126 | "& = &\\int_{\\Omega}{s\\,r}\\,dx - \\int_{\\partial\\Omega_N}{J_N\\,r}\\,ds = \\ell_{th}(r)~~~\\forall r \\in V_0\n",
127 | "\\end{eqnarray}\n",
128 | "\n",
129 | "$~$\n",
130 | "\n",
131 | "Of course, we can add everything together to obtain: Find $(\\mathbf{u}, T) \\in W = U_{\\mathbf{g}} \\times V_T$ such that \n",
132 | "\n",
133 | "$$\n",
134 | "a\\left ( (\\mathbf{u}, T), (\\mathbf{v}, r) \\right) = L((\\mathbf{v}, r))\n",
135 | "$$\n",
136 | "\n",
137 | "$\\forall (\\mathbf{v}, r) \\in U_{\\mathbf{0}} \\times V_0$ with\n",
138 | "\n",
139 | "\\begin{eqnarray}\n",
140 | "a\\left ( (\\mathbf{u}, T), (\\mathbf{v}, r) \\right) & = & \n",
141 | "a_{elas}((\\mathbf{u},T), \\mathbf{v}) + a_{th}(T,r)\\\\\n",
142 | "&& \\\\\n",
143 | "L((\\mathbf{v}, r)) & = & \\ell_{elas}(\\mathbf{v}) + \\ell_{th}(r)\n",
144 | "\\end{eqnarray}"
145 | ]
146 | },
147 | {
148 | "cell_type": "markdown",
149 | "metadata": {
150 | "id": "n1X0WfrwGXVe"
151 | },
152 | "source": [
153 | "The discrete problem follows by taking finite dimensional subspaces $U_h$ and\n",
154 | "$V_h$, which leads to the following discrete system\n",
155 | "\n",
156 | "$$\n",
157 | "\\begin{bmatrix}\n",
158 | "A_{\\mathbf{u}\\mathbf{u}} & B_{\\mathbf{u}T} \\\\\n",
159 | "0 & C_{TT}\n",
160 | "\\end{bmatrix} \n",
161 | "\\begin{bmatrix}\n",
162 | "\\mathsf{U} \\\\\n",
163 | "\\mathsf{T}\n",
164 | "\\end{bmatrix} \n",
165 | "= \n",
166 | "\\begin{bmatrix}\n",
167 | "\\mathsf{F} \\\\\n",
168 | "\\mathsf{G}\n",
169 | "\\end{bmatrix} \n",
170 | "$$\n",
171 | "\n",
172 | "where $\\boldsymbol{\\mathsf{X}} = [\\mathsf{U}, \\mathsf{T}]^{\\intercal}$\n",
173 | "is the global vector of displacement and temperature unknowns."
174 | ]
175 | },
176 | {
177 | "cell_type": "markdown",
178 | "metadata": {
179 | "id": "xkmZ4WVpHbNh"
180 | },
181 | "source": [
182 | "It is import to remark that, since the temperature field does not depend on the displacement $\\mathbf{u}$, we could easily eliminate $T$ and substitute in the elasticity problem, i.e.,\n",
183 | "\n",
184 | "$$\n",
185 | "A_{\\mathbf{u}\\mathbf{u}} \\mathsf{U} = \\mathsf{F} - B_{\\mathbf{u}T} C_{TT}^{-1} \\mathsf{G}\n",
186 | "$$\n",
187 | "\n",
188 | "Nevertheless, for didactical reasons, in this tutorial we solve the problem\n",
189 | "in monolithic form, so as to determine both fields at once and illustrate the use of **mixed function spaces**. Moreover, with this implementation, the extension to handle problems with a stronger coupling is expected to be easier.\n",
190 | "A classical situation is when we include the influence of the volumetric changes into the energy balance in the transient case, i.e.,\n",
191 | "\n",
192 | "$$\n",
193 | "\\rho C_p \\dfrac{\\partial{T}}{\\partial{t}} - \\nabla \\cdot (\\kappa \\nabla{T}) + T_0 \\, \\alpha \\,\\mbox{tr}\\left (\\dfrac{\\partial{{\\boldsymbol{\\varepsilon}}}}{\\partial{t}} \\right ) = s\n",
194 | "$$\n",
195 | "\n",
196 | "also, notice that the thermal conductivity $\\kappa$ can depend on the current\n",
197 | "level of stresses in the material.\n"
198 | ]
199 | },
200 | {
201 | "cell_type": "markdown",
202 | "metadata": {
203 | "id": "LTbwyveXrl-A"
204 | },
205 | "source": [
206 | "## Initialization"
207 | ]
208 | },
209 | {
210 | "cell_type": "markdown",
211 | "metadata": {
212 | "id": "NHeWFMTqrrAf"
213 | },
214 | "source": [
215 | "As usual be begin by importing the necessary packages:"
216 | ]
217 | },
218 | {
219 | "cell_type": "code",
220 | "execution_count": null,
221 | "metadata": {
222 | "id": "BBQFPERYCiPG"
223 | },
224 | "outputs": [],
225 | "source": [
226 | "try:\n",
227 | " import gmsh\n",
228 | "except ImportError:\n",
229 | " !wget \"https://fem-on-colab.github.io/releases/gmsh-install.sh\" -O \"/tmp/gmsh-install.sh\" && bash \"/tmp/gmsh-install.sh\"\n",
230 | " import gmsh\n",
231 | " \n",
232 | "try:\n",
233 | " import dolfinx\n",
234 | "except ImportError:\n",
235 | " !wget \"https://fem-on-colab.github.io/releases/fenicsx-install-real.sh\" -O \"/tmp/fenicsx-install.sh\" && bash \"/tmp/fenicsx-install.sh\"\n",
236 | " import dolfinx\n",
237 | "\n",
238 | "try:\n",
239 | " import pyvista\n",
240 | "except ImportError:\n",
241 | " !pip install - q piglet pyvirtualdisplay ipyvtklink pyvista panel\n",
242 | " !apt-get - qq install xvfb\n",
243 | " import pyvista"
244 | ]
245 | },
246 | {
247 | "cell_type": "code",
248 | "execution_count": null,
249 | "metadata": {
250 | "id": "1wztNG1oD7JD"
251 | },
252 | "outputs": [],
253 | "source": [
254 | "from dolfinx import mesh, fem, io, plot\n",
255 | "\n",
256 | "from ufl import sin, SpatialCoordinate, FiniteElement, VectorElement, MixedElement, TestFunction, TrialFunction, split, Identity, Measure, dx, ds, grad, nabla_grad, div, dot, inner, tr, as_vector, FacetNormal\n",
257 | "\n",
258 | "import numpy as np\n",
259 | "from mpi4py import MPI\n",
260 | "from petsc4py.PETSc import ScalarType\n",
261 | "import matplotlib.pyplot as plt"
262 | ]
263 | },
264 | {
265 | "cell_type": "markdown",
266 | "metadata": {
267 | "id": "wI0o0n2-vdmt"
268 | },
269 | "source": [
270 | "## Geometry"
271 | ]
272 | },
273 | {
274 | "cell_type": "markdown",
275 | "metadata": {
276 | "id": "nMlsTfrqH2HL"
277 | },
278 | "source": [
279 | "The aim is to create a rectangular mesh of the computational domain $\\Omega = [0,L_x] \\times [0, L_y]$. The domain will be divided into two regions, namely, the bottom and the top part\n",
280 | "\n",
281 | "$$\n",
282 | "\\Omega = \\Omega_B \\cup \\Omega_T\n",
283 | "$$\n",
284 | "\n",
285 | "where $\\Omega_B = [0,L_x] \\times [0, L_y/2]$ and $\\Omega_T = [0,L_x] \\times [L_y/2, L_y]$ each having different material properties."
286 | ]
287 | },
288 | {
289 | "cell_type": "code",
290 | "execution_count": null,
291 | "metadata": {
292 | "id": "71ykMgxElw2I"
293 | },
294 | "outputs": [],
295 | "source": [
296 | "Lx = 1.0\n",
297 | "Ly = 0.05\n",
298 | "widthlay = 0.5*Ly\n",
299 | "lengthlay = Lx\n",
300 | "\n",
301 | "def GenerateMesh():\n",
302 | "\n",
303 | " gmsh.initialize()\n",
304 | " proc = MPI.COMM_WORLD.rank\n",
305 | "\n",
306 | " if proc == 0:\n",
307 | " gmsh.model.occ.addRectangle(0, 0, 0, lengthlay, widthlay, tag=1)\n",
308 | " gmsh.model.occ.addRectangle(0, widthlay, 0, lengthlay, widthlay, tag=2)\n",
309 | " # We fuse the two rectangles and keep the interface between them \n",
310 | " gmsh.model.occ.fragment([(2,1)],[(2,2)])\n",
311 | " gmsh.model.occ.synchronize()\n",
312 | " \n",
313 | " # Mark the top (2) and bottom (1) rectangle\n",
314 | " top, bottom = None, None\n",
315 | " for surface in gmsh.model.getEntities(dim=2):\n",
316 | " com = gmsh.model.occ.getCenterOfMass(surface[0], surface[1])\n",
317 | " if np.allclose(com, [0.5*Lx, 0.5*widthlay, 0]):\n",
318 | " bottom = surface[1]\n",
319 | " else:\n",
320 | " top = surface[1]\n",
321 | "\n",
322 | " gmsh.model.addPhysicalGroup(2, [bottom], 1)\n",
323 | " gmsh.model.addPhysicalGroup(2, [top], 2)\n",
324 | "\n",
325 | " # Tag the left and right boundaries\n",
326 | " left = []\n",
327 | " right = []\n",
328 | " for line in gmsh.model.getEntities(dim=1):\n",
329 | " com = gmsh.model.occ.getCenterOfMass(line[0], line[1])\n",
330 | " if np.isclose(com[0], 0.0):\n",
331 | " left.append(line[1])\n",
332 | " if np.isclose(com[0], Lx):\n",
333 | " right.append(line[1])\n",
334 | " gmsh.model.addPhysicalGroup(1, left, 3)\n",
335 | " gmsh.model.addPhysicalGroup(1, right,1)\n",
336 | "\n",
337 | " gmsh.model.mesh.setSize(gmsh.model.getEntities(0), 0.005)\n",
338 | " gmsh.model.mesh.generate(2) \n",
339 | " \n",
340 | " msh, subdomains, boundaries = io.gmshio.model_to_mesh(gmsh.model, comm=MPI.COMM_WORLD, rank=0, gdim=2)\n",
341 | " \n",
342 | " gmsh.finalize()\n",
343 | "\n",
344 | " return msh, subdomains, boundaries\n",
345 | "\n",
346 | "msh, subdomains, boundaries = GenerateMesh()\n",
347 | "\n",
348 | "with io.XDMFFile(MPI.COMM_WORLD, \"mymesh.xdmf\", \"w\") as xdmf:\n",
349 | " xdmf.write_mesh(msh)\n",
350 | "\n",
351 | "import IPython\n",
352 | "\n",
353 | "def plot_mesh(mesh, cell_values=None, filename=\"file.html\"):\n",
354 | " pyvista.start_xvfb()\n",
355 | " grid = pyvista.UnstructuredGrid(*plot.create_vtk_mesh(mesh))\n",
356 | " plotter = pyvista.Plotter(notebook=True, window_size=[500,500])\n",
357 | "\n",
358 | " if cell_values is not None:\n",
359 | " min_ = cell_values.x.array.min()\n",
360 | " max_ = cell_values.x.array.max()\n",
361 | " grid.cell_data[\"cell_values\"] = cell_values.x.array\n",
362 | " viridis = plt.cm.get_cmap(\"viridis\", 25)\n",
363 | " plotter.add_mesh(grid, cmap=viridis, show_edges=True, clim=[min_, max_])\n",
364 | " else:\n",
365 | " plotter.add_mesh(grid, show_edges=True)\n",
366 | " \n",
367 | " plotter.camera.zoom(2.0)\n",
368 | " plotter.view_xy()\n",
369 | " plotter.export_html(filename, backend=\"pythreejs\")\n",
370 | " plotter.close()\n",
371 | "\n",
372 | "\n",
373 | "plot_mesh(msh, cell_values = None, filename=\"mesh.html\")\n",
374 | "IPython.display.HTML(filename=\"mesh.html\")"
375 | ]
376 | },
377 | {
378 | "cell_type": "markdown",
379 | "metadata": {
380 | "id": "rBp5D1sxvmgS"
381 | },
382 | "source": [
383 | "## Finite element spaces"
384 | ]
385 | },
386 | {
387 | "cell_type": "markdown",
388 | "metadata": {
389 | "id": "eUv5jYdoKd1s"
390 | },
391 | "source": [
392 | "The solution $(\\mathbf{u}, T)$ belongs to the product space \n",
393 | "$W_h = U_h \\times V_h$, $~~U_h \\subset [H^1(\\Omega)]^d$ and \n",
394 | "$V_h \\subset H^1(\\Omega)$. Since we are working with conforming \n",
395 | "finite element formulations, natural choices for the discrete spaces\n",
396 | "$U_h$ and $V_h$ are:\n",
397 | "\n",
398 | "$$\n",
399 | "U_h = U(\\mathcal{T}_h) = \\{\\mathbf{v} \\in [H^1(\\Omega)]^d,~\\mathbf{v}|_E \\in [P_k(E)]^d \\, \\forall E \\in \\mathcal{T}_h\\}\n",
400 | "$$\n",
401 | "\n",
402 | "and \n",
403 | "\n",
404 | "$$\n",
405 | "V_h = V(\\mathcal{T}_h) = \\{v \\in H^1(\\Omega),~v|_E \\in P_k(E) \\, \\forall E \\in \\mathcal{T}_h\\}\n",
406 | "$$\n",
407 | "\n",
408 | "In particular we will use polynomials of degree $1$, which is the most economical option for this case. In order to implement a monolithic formulation of the problem at hand, we need to create a mixed function space in `dolfinx` as follows "
409 | ]
410 | },
411 | {
412 | "cell_type": "code",
413 | "execution_count": null,
414 | "metadata": {
415 | "id": "3qziWIPdG1R-"
416 | },
417 | "outputs": [],
418 | "source": [
419 | "# Create an element for each field\n",
420 | "\n",
421 | "degree_u = 1\n",
422 | "el_u = VectorElement(\"CG\", msh.ufl_cell(), degree_u)\n",
423 | "\n",
424 | "degree_T = 1\n",
425 | "el_T = FiniteElement(\"CG\", msh.ufl_cell(), degree_T)\n",
426 | "\n",
427 | "# the mixed element \n",
428 | "el_m = MixedElement([el_u , el_T])\n",
429 | "\n",
430 | "# and the corresponding global space of functions\n",
431 | "W = fem.FunctionSpace(msh, el_m)\n",
432 | "\n",
433 | "# Finally, declare a space of piecewise constant functions for later use\n",
434 | "Q = fem.FunctionSpace(msh, (\"DG\", 0))"
435 | ]
436 | },
437 | {
438 | "cell_type": "markdown",
439 | "metadata": {
440 | "id": "qyAEs8cfvqC9"
441 | },
442 | "source": [
443 | "## Boundary conditions"
444 | ]
445 | },
446 | {
447 | "cell_type": "markdown",
448 | "metadata": {
449 | "id": "WI27-9wLM9zT"
450 | },
451 | "source": [
452 | "Setting up the boundary conditions take some extra work, since we\n",
453 | "have to distinguish those BCs for the displacement field and for\n",
454 | "the temperature field, namely,\n",
455 | "\n",
456 | "* For the displacement field we totally restrict the left side\n",
457 | "\n",
458 | "$$\n",
459 | "\\mathbf{u}(x=0) = [0,0]^{\\intercal}\n",
460 | "$$\n",
461 | "\n",
462 | "* For the temperature field we impose:\n",
463 | "\n",
464 | "$$\n",
465 | "T(x=0) = T_l,~~~T(x=L_x) = T_r\n",
466 | "$$\n",
467 | "\n",
468 | "* The rest of the boundary is traction free and adiabatic, so we take\n",
469 | "\n",
470 | "$$\n",
471 | "\\boldsymbol{\\mathcal{F}} = [0,0]^{\\intercal},~~~g_N = 0\n",
472 | "$$\n",
473 | "\n",
474 | "although other boundary conditions are certainly possible. \n",
475 | "\n",
476 | "\n",
477 | "In order to proceed with the implementation, recall that the essential BCs have to be provided as a list of `fem.dirichletbc` objects, but first, we have\n",
478 | "to identify the associated degrees of freedom `dofs` to which the boundary\n",
479 | "value must be apply:"
480 | ]
481 | },
482 | {
483 | "cell_type": "code",
484 | "execution_count": null,
485 | "metadata": {
486 | "id": "8FADRDLFMu-4"
487 | },
488 | "outputs": [],
489 | "source": [
490 | "# Start an empty list for the Dirichlet BCs\n",
491 | "bcs = []\n",
492 | "sdim = msh.topology.dim\n",
493 | "\n",
494 | "# Identify the facets\n",
495 | "left_facets = boundaries.indices[boundaries.values == 3]\n",
496 | "right_facets = boundaries.indices[boundaries.values == 1]\n",
497 | "\n",
498 | "#---------------------------------\n",
499 | "# BCs for the displacement field\n",
500 | "\n",
501 | "U, _ = W.sub(0).collapse()\n",
502 | "bc_dofs_ul = fem.locate_dofs_topological((W.sub(0), U), sdim-1, left_facets)\n",
503 | "\n",
504 | "# Auxiliary function: Nao ha uma forma mais facil!!!\n",
505 | "def g(x):\n",
506 | " return (np.zeros_like(x[0]), np.zeros_like(x[0]))\n",
507 | "w_bc = fem.Function(U)\n",
508 | "w_bc.interpolate(lambda x: g(x))\n",
509 | "bcs.append(fem.dirichletbc(w_bc, bc_dofs_ul, W.sub(0)))\n",
510 | "\n",
511 | "#---------------------------------\n",
512 | "# BCs for the temperature field\n",
513 | "\n",
514 | "V, _ = W.sub(1).collapse()\n",
515 | "\n",
516 | "bc_dofs_Tl = fem.locate_dofs_topological((W.sub(1), V), sdim-1, [left_facets])\n",
517 | "bc_dofs_Tr = fem.locate_dofs_topological((W.sub(1), V), sdim-1, [right_facets])\n",
518 | "\n",
519 | "# Boundary values\n",
520 | "Tleft = 0.0\n",
521 | "Tright = 10.0\n",
522 | "\n",
523 | "# Auxiliary function\n",
524 | "def g(x, Temp):\n",
525 | " return (Temp*np.ones_like(x[0]))\n",
526 | "\n",
527 | "w_bcl = fem.Function(V)\n",
528 | "w_bcl.interpolate(lambda x: g(x, Tleft))\n",
529 | "bcs.append(fem.dirichletbc(w_bcl, bc_dofs_Tl, W.sub(1)))\n",
530 | "\n",
531 | "w_bcr = fem.Function(V)\n",
532 | "w_bcr.interpolate(lambda x: g(x, Tright))\n",
533 | "bcs.append(fem.dirichletbc(w_bcr, bc_dofs_Tr, W.sub(1)))\n"
534 | ]
535 | },
536 | {
537 | "cell_type": "markdown",
538 | "metadata": {
539 | "id": "9bj4jFpXvtl1"
540 | },
541 | "source": [
542 | "## Material parameters"
543 | ]
544 | },
545 | {
546 | "cell_type": "markdown",
547 | "metadata": {
548 | "id": "-Lacko3OuuQW"
549 | },
550 | "source": [
551 | "Finally, let us recall the different terms in the variational formulation\n",
552 | "\n",
553 | "\n",
554 | "For simplicity we will assume both materials have the same elastic parameters\n",
555 | "that we take as, in terms of the Young's modulus and Poisson ration\n",
556 | "\n",
557 | "$$\n",
558 | "E = 10,~~~~\\nu = 0.3, \n",
559 | "$$\n",
560 | "\n",
561 | "\n",
562 | "although they have different thermal expansion coefficients, so as to illustrate the behavior of a bimetal bar, i.e.,\n",
563 | "\n",
564 | "$$\n",
565 | "\\alpha(\\mathbf{x}) = \\left \\{\n",
566 | "\\begin{array}{rcl}\n",
567 | "\\alpha_B = 10^{-3} & \\mbox{if} & y < \\frac12 L_y \\\\\n",
568 | "\\alpha_T = 10^{-5} & \\mbox{if} & y >= \\frac12 L_y\n",
569 | "\\end{array}\n",
570 | "\\right.\n",
571 | "$$\n",
572 | "\n",
573 | "and a thermal conductivity\n",
574 | "\n",
575 | "$$\n",
576 | "\\kappa = 1\n",
577 | "$$\n"
578 | ]
579 | },
580 | {
581 | "cell_type": "code",
582 | "execution_count": null,
583 | "metadata": {
584 | "id": "53_vI63Tdstp"
585 | },
586 | "outputs": [],
587 | "source": [
588 | "# Elastic constitutive parameters\n",
589 | "E, nu = 10.0, 0.3\n",
590 | "mu = E/(2.0*(1.0 + nu))\n",
591 | "lamb = E*nu/((1.0 + nu)*(1.0 - 2.0*nu))\n",
592 | "\n",
593 | "# Thermal conductivity\n",
594 | "kappa = fem.Constant(msh, ScalarType(1.0))\n",
595 | "\n",
596 | "# Thermal expansion\n",
597 | "def SetAlpha(msh, subdomains, alphaB, alphaT):\n",
598 | " \n",
599 | " alpha = fem.Function(Q)\n",
600 | "\n",
601 | " # Identify elements with different tags\n",
602 | " cells_layerB = subdomains.indices[subdomains.values == 1]\n",
603 | " cells_layerT = subdomains.indices[subdomains.values == 2]\n",
604 | " \n",
605 | " alpha.x.array[cells_layerB] = np.full(len(cells_layerB), alphaB)\n",
606 | " alpha.x.array[cells_layerT] = np.full(len(cells_layerT), alphaT)\n",
607 | " \n",
608 | " return alpha\n",
609 | "\n",
610 | "alphah = SetAlpha(msh, subdomains, 1e-3, 1e-5)\n",
611 | "\n",
612 | "# Visualize in Paraview for verification\n",
613 | "alphah.name = \"alpha\"\n",
614 | "with io.XDMFFile(msh.comm, \"alpha.xdmf\", \"w\") as xdmf:\n",
615 | " xdmf.write_mesh(msh)\n",
616 | " xdmf.write_function(alphah)\n",
617 | "\n",
618 | "plot_mesh(msh, cell_values=alphah, filename=\"alphavalues.html\")\n",
619 | "IPython.display.HTML(filename=\"alphavalues.html\")"
620 | ]
621 | },
622 | {
623 | "cell_type": "markdown",
624 | "metadata": {
625 | "id": "vzlfPRrlvxZR"
626 | },
627 | "source": [
628 | "As for the body forces, we will take\n",
629 | "\n",
630 | "$$\n",
631 | "\\mathbf{f} = [0, 0]^{\\intercal}\n",
632 | "$$\n",
633 | "\n",
634 | "and heat source\n",
635 | "\n",
636 | "$$\n",
637 | "s = 100\n",
638 | "$$\n",
639 | "\n",
640 | "always assuming consistent units."
641 | ]
642 | },
643 | {
644 | "cell_type": "code",
645 | "execution_count": null,
646 | "metadata": {
647 | "id": "HRoyGDWDy-Oj"
648 | },
649 | "outputs": [],
650 | "source": [
651 | "# Body force\n",
652 | "f = fem.Constant(msh, ScalarType( (0.0, 0.0) ) )\n",
653 | "\n",
654 | "# Heat source\n",
655 | "s = fem.Constant(msh, ScalarType(100.0))"
656 | ]
657 | },
658 | {
659 | "cell_type": "markdown",
660 | "metadata": {
661 | "id": "GMz0v2Usy42O"
662 | },
663 | "source": [
664 | "## Variational formulation"
665 | ]
666 | },
667 | {
668 | "cell_type": "code",
669 | "execution_count": null,
670 | "metadata": {
671 | "id": "-AQ9TL9Mde5g"
672 | },
673 | "outputs": [],
674 | "source": [
675 | "# Strain measures\n",
676 | "def epsilon(u):\n",
677 | " return 0.5*(nabla_grad(u) + nabla_grad(u).T)\n",
678 | "\n",
679 | "def epsilon_elas(u, T):\n",
680 | " return epsilon(u) - alphah*T*Identity(sdim)\n",
681 | "\n",
682 | "def sigma(u, T):\n",
683 | " return 2*mu*epsilon_elas(u, T) + lamb*tr(epsilon_elas(u, T))*Identity(sdim)\n",
684 | "\n",
685 | "TrialF = TrialFunction(W)\n",
686 | "TestF = TestFunction(W)\n",
687 | "(u, T) = split(TrialF)\n",
688 | "(v, r) = split(TestF)\n",
689 | "\n",
690 | "# LHS: Bilinear forms\n",
691 | "a = inner(sigma(u,T), epsilon(v)) * dx\n",
692 | "a += inner(kappa*grad(T), grad(r)) * dx\n",
693 | "\n",
694 | "# RHS: Forcing terms\n",
695 | "L = inner(f, v)*dx\n",
696 | "L += s*r*dx"
697 | ]
698 | },
699 | {
700 | "cell_type": "markdown",
701 | "metadata": {
702 | "id": "xaYUxf6Ux_T4"
703 | },
704 | "source": [
705 | "\n",
706 | "Finally solve and save the solution for visualization in `Paraview`"
707 | ]
708 | },
709 | {
710 | "cell_type": "code",
711 | "execution_count": null,
712 | "metadata": {
713 | "id": "_jECduwp5USo"
714 | },
715 | "outputs": [],
716 | "source": [
717 | "# Solver options\n",
718 | "petsc_opts={\"ksp_type\": \"preonly\", \"pc_type\": \"lu\", \"pc_factor_mat_solver_type\": \"mumps\", \"ksp_monitor\": None}\n",
719 | "problem = fem.petsc.LinearProblem(a, L, bcs=bcs, petsc_options=petsc_opts)\n",
720 | "wh = problem.solve()\n",
721 | "\n",
722 | "(uh,Th) = wh.split()\n",
723 | "\n",
724 | "uh.name = \"Displacement\"\n",
725 | "with io.XDMFFile(msh.comm, \"disp.xdmf\", \"w\") as xdmf:\n",
726 | " xdmf.write_mesh(msh)\n",
727 | " xdmf.write_function(uh)\n",
728 | "\n",
729 | "Th.name = \"Temperature\"\n",
730 | "with io.XDMFFile(msh.comm, \"temp.xdmf\", \"w\") as xdmf:\n",
731 | " xdmf.write_mesh(msh)\n",
732 | " xdmf.write_function(Th)\n",
733 | "\n",
734 | "from google.colab import files\n",
735 | "files.download('disp.xdmf')\n",
736 | "files.download('disp.h5')\n",
737 | "files.download('temp.xdmf')\n",
738 | "files.download('temp.h5')"
739 | ]
740 | },
741 | {
742 | "cell_type": "markdown",
743 | "metadata": {
744 | "id": "esul4qq8x7xH"
745 | },
746 | "source": [
747 | "## Homework 5"
748 | ]
749 | },
750 | {
751 | "cell_type": "markdown",
752 | "metadata": {
753 | "id": "yHTNmqcxfr3J"
754 | },
755 | "source": [
756 | "1) Solve the case of a source term in the thermal problem given by\n",
757 | "the function\n",
758 | "\n",
759 | "$$\n",
760 | "s(\\mathbf{x}) = 100 \\, \\sin(2\\pi\\,x)\n",
761 | "$$\n",
762 | "\n",
763 | "which can be programmed as:\n",
764 | "\n",
765 | " def Source(x):\n",
766 | " return 100*sin(2*np.pi*x[0])\n",
767 | "\n",
768 | " x = SpatialCoordinate(msh)\n",
769 | " s = Source(x)\n",
770 | "\n",
771 | "Take for simplicity the same boundary conditions of the previous example.\n"
772 | ]
773 | },
774 | {
775 | "cell_type": "markdown",
776 | "metadata": {
777 | "id": "BTzbxSZFO2Jw"
778 | },
779 | "source": [
780 | "2) Consider the case in which $T_l = T_r = 10$ and $s = 0$, such that the temperature increment in the sample is simply \n",
781 | "$\\Delta{T} = 10$ (i.e., uniform temperature distribution). \n",
782 | "\n",
783 | "Program a loop over different values of $\\alpha_B$ to solve the thermoelastic problem and plot the maximum deflection\n",
784 | " of the bimetal beam as a function of $\\alpha_B$.\n",
785 | " Notice that in the previous implementation we regarded\n",
786 | " many parameters and variables, such as `alphah` as having global scope,\n",
787 | " so, you should structure the code as follows in order \n",
788 | " to be able to update $\\alpha_B$:\n",
789 | "\n",
790 | " def epsilon_elas(u, T, alpha):\n",
791 | " return epsilon(u) ...\n",
792 | "\n",
793 | " def sigma(u, T, alpha):\n",
794 | " return ...\n",
795 | "\n",
796 | " max_def = []\n",
797 | " alphas = [1e-5, 1e-4, 1e-3]\n",
798 | " for alphaB in alphas:\n",
799 | "\n",
800 | " print('Solving for alphaB=', alphaB)\n",
801 | " alphah = SetAlpha(msh, subdomains, alphaB, 1e-5)\n",
802 | "\n",
803 | " a = ...\n",
804 | " a += inner(kappa*grad(T), grad(r)) * dx\n",
805 | " .\n",
806 | " .\n",
807 | " .\n",
808 | " problem = fem.petsc.LinearProblem(a, L, bcs=bcs, petsc_options=petsc_opts)\n",
809 | " wh = problem.solve()\n",
810 | " (uh,Th) = ...\n",
811 | "\n",
812 | " max_def.append( ... )\n",
813 | " uh.name = \"Displacement\"\n",
814 | " xdmf_f.write_function(uh, alphaB)\n",
815 | "\n",
816 | "\n",
817 | "\n",
818 | "Compare to the analytical prediction based on beam theory\n",
819 | "\n",
820 | "$$\n",
821 | "\\delta_{\\max} = \\dfrac{12(\\alpha_B - \\alpha_T) \\Delta{T}\\, L_x^2}{L_y \\,K}\n",
822 | "$$\n",
823 | "\n",
824 | "where \n",
825 | "\n",
826 | "$$\n",
827 | "K = 14 + \\dfrac{E_B}{E_T} + \\dfrac{E_T}{E_B}\n",
828 | "$$\n",
829 | "\n",
830 | "where $E_B$ and $E_T$ are the Young's modulus of the\n",
831 | "bottom and the top layer of the bimetal. Since, we are \n",
832 | "assuming the same values for both, we have $K = 16$."
833 | ]
834 | }
835 | ],
836 | "metadata": {
837 | "colab": {
838 | "provenance": [],
839 | "include_colab_link": true
840 | },
841 | "kernelspec": {
842 | "display_name": "Python 3",
843 | "language": "python",
844 | "name": "python3"
845 | },
846 | "language_info": {
847 | "name": "python",
848 | "version": "3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0]"
849 | },
850 | "vscode": {
851 | "interpreter": {
852 | "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
853 | }
854 | }
855 | },
856 | "nbformat": 4,
857 | "nbformat_minor": 0
858 | }
--------------------------------------------------------------------------------