├── 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 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](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 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](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 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](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 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](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 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](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 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](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 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](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 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](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 | "\"Open" 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 | "\"Open" 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 | } --------------------------------------------------------------------------------