{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Function approximation by finite elements\n", "<div id=\"ch:approx:fe\"></div>\n", "\n", "The purpose of this chapter is to use the ideas from the previous\n", "chapter on how to approximate functions, but the basis functions are now\n", "of finite element type.\n", "\n", "# Finite element basis functions\n", "<div id=\"fem:approx:fe\"></div>\n", "\n", "The specific basis functions exemplified in previous chapter are in general nonzero on the entire domain\n", "$\\Omega$, as can be seen in [Figure](#fem:approx:fe:fig:u:sin), where\n", "we plot two sinusoidal basis functions $\\psi_0(x)=\\sin\\frac{1}{2}\\pi x$ and\n", "$\\psi_1(x)=\\sin 2\\pi x$ together with the sum $u(x)=4\\psi_0(x) -\n", "\\frac{1}{2}\\psi_1(x)$. We shall now turn our attention to basis functions\n", "that have *compact support*, meaning that they are nonzero on a small\n", "portion of $\\Omega$ only. Moreover, we shall restrict the functions to\n", "be *piecewise polynomials*. This means that the domain is split into\n", "subdomains and each basis function is a polynomial on one or more of\n", "these subdomains, see [Figure](#fem:approx:fe:fig:u:fe) for a sketch\n", "involving locally defined hat functions that make\n", "$u=\\sum_jc_j{\\psi}_j$ piecewise linear. At the boundaries between\n", "subdomains, one normally just forces continuity of $u$, so that when\n", "connecting two polynomials from two subdomains, the derivative becomes\n", "discontinuous. This type of basis functions is fundamental in the\n", "finite element method. (One may wonder why continuity of derivatives\n", "is not desired, and it is, but it turns out to be mathematically\n", "challenging in 2D and 3D, and it is not strictly needed.)\n", "\n", "<!-- dom:FIGURE: [fig/u_example_sin.png, width=600] A function resulting from a weighted sum of two sine basis functions. <div id=\"fem:approx:fe:fig:u:sin\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:fig:u:sin\"></div>\n", "\n", "<p>A function resulting from a weighted sum of two sine basis functions.</p>\n", "<img src=\"fig/u_example_sin.png\" width=600>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "<!-- dom:FIGURE: [fig/u_example_P1.png, width=600] A function resulting from a weighted sum of three local piecewise linear (hat) functions. <div id=\"fem:approx:fe:fig:u:fe\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:fig:u:fe\"></div>\n", "\n", "<p>A function resulting from a weighted sum of three local piecewise linear (hat) functions.</p>\n", "<img src=\"fig/u_example_P1.png\" width=600>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "We first introduce the concepts of elements and nodes in a simplistic fashion.\n", "Later, we shall generalize the concept\n", "of an element, which is a necessary step before treating a wider class of\n", "approximations within the family of finite element methods.\n", "The generalization is also compatible with\n", "the concepts used in the [FEniCS](http://fenicsproject.org) finite\n", "element software.\n", "\n", "## Elements and nodes\n", "<div id=\"fem:approx:fe:def:elements:nodes\"></div>\n", "\n", "Let $u$ and $f$ be defined on an interval $\\Omega$. We divide $\\Omega$\n", "into $N_e$ non-overlapping subintervals $\\Omega^{(e)}$,\n", "$e=0,\\ldots,N_e-1$:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto32\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "\\Omega = \\Omega^{(0)}\\cup \\cdots \\cup \\Omega^{(N_e)}{\\thinspace .} \\label{_auto32} \\tag{68}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We shall for now refer to $\\Omega^{(e)}$ as an *element*, identified\n", "by the unique number $e$. On each element we introduce a set of\n", "points called *nodes*. For now we assume that the nodes are uniformly\n", "spaced throughout the element and that the boundary points of the\n", "elements are also nodes. The nodes are given numbers both within an\n", "element and in the global domain. These are referred to as *local* and\n", "*global* node numbers, respectively. Local nodes are numbered with an\n", "index $r=0,\\ldots,d$, while the $N_n$ global nodes are numbered as\n", "$i=0,\\ldots,N_n-1$. [Figure](#fem:approx:fe:def:elements:nodes:fig:P1) shows nodes as small\n", "circular disks and element boundaries as small vertical lines. Global\n", "node numbers appear under the nodes, but local node numbers are not\n", "shown. Since there are two nodes in each element, the local nodes are\n", "numbered 0 (left) and 1 (right) in each element.\n", "\n", "<!-- dom:FIGURE: [fig/fe_mesh1D_P1.png, width=500 frac=0.7] Finite element mesh with 5 elements and 6 nodes. <div id=\"fem:approx:fe:def:elements:nodes:fig:P1\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:def:elements:nodes:fig:P1\"></div>\n", "\n", "<p>Finite element mesh with 5 elements and 6 nodes.</p>\n", "<img src=\"fig/fe_mesh1D_P1.png\" width=500>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "\n", "Nodes and elements uniquely define a *finite element mesh*, which is\n", "our discrete representation of the domain in the computations. A\n", "common special case is that of a *uniformly partitioned mesh* where\n", "each element has the same length and the distance between nodes is\n", "constant. [Figure](#fem:approx:fe:def:elements:nodes:fig:P1) shows\n", "an example on a uniformly partitioned mesh. The strength of the finite\n", "element method (in contrast to the finite difference method) is that\n", "it is just as easy to work with a non-uniformly partitioned mesh in 3D as a\n", "uniformly partitioned mesh in 1D.\n", "\n", "### Example\n", "\n", "On $\\Omega =[0,1]$ we may introduce two elements,\n", "$\\Omega^{(0)}=[0,0.4]$ and $\\Omega^{(1)}=[0.4,1]$. Furthermore, let us\n", "introduce three nodes per element, equally spaced within each element.\n", "[Figure](#fem:approx:fe:def:elements:nodes:fig:P2) shows the mesh\n", "with $N_e=2$ elements and $N_n=2N_e+1=5$ nodes. A node's coordinate\n", "is denoted by $x_i$, where $i$ is either a global node number or a\n", "local one. In the latter case we also need to know the element number\n", "to uniquely define the node.\n", "\n", "The three nodes in element number 0 are $x_0=0$, $x_1=0.2$, and\n", "$x_2=0.4$. The local and global node numbers are here equal. In\n", "element number 1, we have the local nodes $x_0=0.4$, $x_1=0.7$, and\n", "$x_2=1$ and the corresponding global nodes $x_2=0.4$, $x_3=0.7$, and\n", "$x_4=1$. Note that the global node $x_2=0.4$ is shared by the two\n", "elements.\n", "\n", "<!-- dom:FIGURE: [fig/fe_mesh1D_P2.png, width=500 frac=0.7] Finite element mesh with 2 elements and 5 nodes. <div id=\"fem:approx:fe:def:elements:nodes:fig:P2\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:def:elements:nodes:fig:P2\"></div>\n", "\n", "<p>Finite element mesh with 2 elements and 5 nodes.</p>\n", "<img src=\"fig/fe_mesh1D_P2.png\" width=500>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "For the purpose of implementation, we introduce two lists or arrays:\n", "`nodes` for storing the coordinates of the nodes, with the global node\n", "numbers as indices, and `elements` for holding the global node numbers\n", "in each element. By defining `elements` as a list of lists, where each\n", "sublist contains the global node numbers of one particular element,\n", "the indices of each sublist will correspond to local node numbers for\n", "that element. The `nodes` and `elements` lists for the sample mesh\n", "above take the form" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "nodes = [0, 0.2, 0.4, 0.7, 1]\n", "elements = [[0, 1, 2], [2, 3, 4]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Looking up the coordinate of, e.g., local node number 2 in element 1,\n", "is done by `nodes[elements[1][2]]` (recall that nodes and\n", "elements start their numbering at 0). The corresponding global node number\n", "is 4, so we could alternatively look up the coordinate as `nodes[4]`.\n", "\n", "The numbering of elements and nodes does not need to be regular.\n", "[Figure](#fem:approx:fe:def:elements:nodes:fig:P1:irregular) shows\n", "an example corresponding to" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "nodes = [1.5, 5.5, 4.2, 0.3, 2.2, 3.1]\n", "elements = [[2, 1], [4, 5], [0, 4], [3, 0], [5, 2]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- dom:FIGURE: [fig/fe_mesh1D_random_numbering.png, width=500 frac=0.7] Example on irregular numbering of elements and nodes. <div id=\"fem:approx:fe:def:elements:nodes:fig:P1:irregular\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:def:elements:nodes:fig:P1:irregular\"></div>\n", "\n", "<p>Example on irregular numbering of elements and nodes.</p>\n", "<img src=\"fig/fe_mesh1D_random_numbering.png\" width=500>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "## The basis functions\n", "\n", "### Construction principles\n", "\n", "Finite element basis functions are in this text recognized by\n", "the notation ${\\varphi}_i(x)$, where the index (now in the beginning)\n", "corresponds to\n", "a global node number. Since ${\\psi}_i$ is the symbol for basis\n", "functions in general in this text, the particular choice of\n", "finite element basis functions means that we take\n", "${\\psi}_i = {\\varphi}_i$.\n", "\n", "\n", "\n", "Let $i$ be the global node number corresponding to local node $r$ in\n", "element number $e$ with $d+1$ local nodes. We distinguish between\n", "*internal* nodes in an element and *shared* nodes. The latter are\n", "nodes that are shared with the neighboring elements.\n", "The finite element basis functions ${\\varphi}_i$\n", "are now defined as follows.\n", "\n", " * For an internal node, with global number $i$ and local number $r$,\n", " take ${\\varphi}_i(x)$ to be the Lagrange\n", " polynomial that is 1 at the local node $r$ and zero\n", " at all other nodes in the element.\n", " The degree of the polynomial is $d$.\n", " On all other elements, ${\\varphi}_i=0$.\n", "\n", " * For a shared node,\n", " let ${\\varphi}_i$ be made up of the Lagrange polynomial on this element\n", " that is 1 at node $i$, combined with the Lagrange polynomial over\n", " the neighboring element that is also 1 at node $i$.\n", " On all other elements, ${\\varphi}_i=0$.\n", "\n", "A visual impression of three such basis functions is given in\n", "[Figure](#fem:approx:fe:fig:P2). When solving differential equations,\n", "we need the derivatives of these basis functions as well, and the\n", "corresponding derivatives are shown in [Figure](#fem:approx:fe:fig:dP2).\n", "Note that the derivatives are highly discontinuous!\n", "In these figures,\n", "the domain $\\Omega = [0,1]$ is divided into four equal-sized elements,\n", "each having three local nodes.\n", "The element boundaries are\n", "marked by vertical dashed lines and the nodes by small circles.\n", "The function ${\\varphi}_2(x)$\n", "is composed of a quadratic Lagrange polynomial over element 0 and 1,\n", "${\\varphi}_3(x)$ corresponds to an internal node in element 1 and\n", "is therefore nonzero on this element only, while ${\\varphi}_4(x)$\n", "is like ${\\varphi}_2(x)$ composed to two Lagrange polynomials over two\n", "elements. Also observe that the basis function ${\\varphi}_i$ is zero at\n", "all nodes, except at global node number $i$.\n", "We also remark that\n", "the shape of a basis function over an element is completely determined\n", "by the coordinates of the local nodes in the element.\n", "\n", "<!-- Sometimes we refer to a Lagrange polynomial on an element $e$, which -->\n", "<!-- means the basis function ${\\varphi}_i(x)$ when $x\\in\\Omega^{(e)}$, and -->\n", "<!-- ${\\varphi}_i(x)=0$ when $x\\notin\\Omega^{(e)}$. -->\n", "\n", "\n", "<!-- dom:FIGURE: [fig/phi/mpl_fe_basis_p2_4e_lab.png, width=600 frac=1] Illustration of the piecewise quadratic basis functions associated with nodes in an element. <div id=\"fem:approx:fe:fig:P2\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:fig:P2\"></div>\n", "\n", "<p>Illustration of the piecewise quadratic basis functions associated with nodes in an element.</p>\n", "<img src=\"fig/phi/mpl_fe_basis_p2_4e_lab.png\" width=600>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "<!-- dom:FIGURE: [fig/phi/mpl_fe_dbasis_p2_4e_lab.png, width=600 frac=1] Illustration of the derivatives of the piecewise quadratic basis functions associated with nodes in an element. <div id=\"fem:approx:fe:fig:dP2\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:fig:dP2\"></div>\n", "\n", "<p>Illustration of the derivatives of the piecewise quadratic basis functions associated with nodes in an element.</p>\n", "<img src=\"fig/phi/mpl_fe_dbasis_p2_4e_lab.png\" width=600>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "### Properties of ${\\varphi}_i$\n", "\n", "The construction of basis functions according to the principles above\n", "lead to two important properties of ${\\varphi}_i(x)$. First," ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:phi:prop1\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "{\\varphi}_i(x_{j}) =\\delta_{ij},\\quad \\delta_{ij} =\n", "\\left\\lbrace\\begin{array}{ll}\n", "1, & i=j,\\\\\n", "0, & i\\neq j,\n", "\\end{array}\\right.\n", "\\label{fem:approx:fe:phi:prop1} \\tag{69}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "when $x_{j}$ is a node in the mesh with global node number $j$.\n", "The\n", "result ${\\varphi}_i(x_{j}) =\\delta_{ij}$ is obtained as\n", "the Lagrange polynomials are constructed to have\n", "exactly this property.\n", "The property also implies a convenient interpretation of $c_i$\n", "as the value of $u$ at node $i$. To show this, we expand $u$\n", "in the usual way as $\\sum_jc_j{\\psi}_j$ and choose ${\\psi}_i = {\\varphi}_i$:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "u(x_{i}) = \\sum_{j\\in{\\mathcal{I}_s}} c_j {\\psi}_j (x_{i}) =\n", "\\sum_{j\\in{\\mathcal{I}_s}} c_j {\\varphi}_j (x_{i}) = c_i {\\varphi}_i (x_{i}) = c_i\n", "{\\thinspace .}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because of this interpretation,\n", "the coefficient $c_i$ is by many named $u_i$ or $U_i$.\n", "\n", "<!-- 2DO: switch to U_j? -->\n", "\n", "Second,\n", "${\\varphi}_i(x)$ is mostly zero throughout the domain:\n", "\n", " * ${\\varphi}_i(x) \\neq 0$ only on those elements that contain global node $i$,\n", "\n", " * ${\\varphi}_i(x){\\varphi}_j(x) \\neq 0$ if and only if $i$ and $j$ are global node\n", " numbers in the same element.\n", "\n", "Since $A_{i,j}$ is the integral of\n", "${\\varphi}_i{\\varphi}_j$ it means that\n", "*most of the elements in the coefficient matrix will be zero*.\n", "We will come back to these properties and use\n", "them actively in computations to save memory and CPU time.\n", "\n", "In our example so far, each element has $d+1$ nodes, resulting in\n", "local Lagrange polynomials of degree $d$, but it is not a requirement to have\n", "the same $d$ value in each element.\n", "\n", "## Example on quadratic finite element functions\n", "\n", "Let us set up the `nodes` and `elements` lists corresponding to the\n", "mesh implied by [Figure](#fem:approx:fe:fig:P2).\n", "[Figure](#fem:approx:fe:fig:P2:mesh) sketches the mesh and the\n", "numbering. We have" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "nodes = [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0]\n", "elements = [[0, 1, 2], [2, 3, 4], [4, 5, 6], [6, 7, 8]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- dom:FIGURE: [fig/fe_mesh1D_P2_4e.png, width=500 frac=0.7] Sketch of mesh with 4 elements and 3 nodes per element. <div id=\"fem:approx:fe:fig:P2:mesh\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:fig:P2:mesh\"></div>\n", "\n", "<p>Sketch of mesh with 4 elements and 3 nodes per element.</p>\n", "<img src=\"fig/fe_mesh1D_P2_4e.png\" width=500>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "\n", "Let us explain mathematically how the basis functions are constructed\n", "according to the principles.\n", "Consider element number 1 in [Figure](#fem:approx:fe:fig:P2:mesh),\n", "$\\Omega^{(1)}=[0.25, 0.5]$, with local nodes\n", "0, 1, and 2 corresponding to global nodes 2, 3, and 4.\n", "The coordinates of these nodes are\n", "$0.25$, $0.375$, and $0.5$, respectively.\n", "We define three Lagrange\n", "polynomials on this element:\n", "\n", "1. The polynomial that is 1 at local node 1\n", " (global node 3) makes up the basis function\n", " ${\\varphi}_3(x)$ over this element,\n", " with ${\\varphi}_3(x)=0$ outside the element.\n", "\n", "2. The polynomial that is 1 at local node 0 (global node 2) is the \"right\n", " part\" of the global basis function\n", " ${\\varphi}_2(x)$. The \"left part\" of ${\\varphi}_2(x)$ consists of\n", " a Lagrange polynomial associated with local node 2 in\n", " the neighboring element $\\Omega^{(0)}=[0, 0.25]$.\n", "\n", "3. Finally, the polynomial that is 1 at local node 2 (global node 4)\n", " is the \"left part\" of the global basis function ${\\varphi}_4(x)$.\n", " The \"right part\" comes from the Lagrange polynomial that is 1 at\n", " local node 0 in the neighboring element $\\Omega^{(2)}=[0.5, 0.75]$.\n", "\n", "The specific mathematical form of the polynomials *over element* 1 is\n", "given by the formula ([56](#fem:approx:global:Lagrange:poly)):" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\begin{align*}\n", "{\\varphi}_3(x) &= \\frac{(x-0.25)(x-0.5)}{(0.375-0.25)(0.375-0.5)},\\quad x\\in\\Omega^{(1)}\\\\\n", "{\\varphi}_2(x) &= \\frac{(x-0.375)(x-0.5)}{(0.25-0.375)(0.25-0.5)},\\quad x\\in\\Omega^{(1)}\\\\\n", "{\\varphi}_4(x) &= \\frac{(x-0.25)(x-0.375)}{(0.5-0.25)(0.5-0.25)},\\quad x\\in\\Omega^{(1)} .\n", "\\end{align*}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As mentioned earlier, any global basis function ${\\varphi}_i(x)$ is zero\n", "on elements that do not contain the node with global node number $i$.\n", "Clearly, the property ([69](#fem:approx:fe:phi:prop1)) is easily\n", "verified, see for instance that ${\\varphi}_3(0.375) = 1$ while\n", "${\\varphi}_3(0.25) = 0$ and ${\\varphi}_3(0.5) = 0$.\n", "\n", "The other global functions associated with internal nodes,\n", "${\\varphi}_1$, ${\\varphi}_5$, and ${\\varphi}_7$, are all of the same shape\n", "as the drawn ${\\varphi}_3$ in [Figure](#fem:approx:fe:fig:P2), while\n", "the global basis functions associated with shared nodes have the same\n", "shape as shown ${\\varphi}_2$ and ${\\varphi}_4$. If the elements were of\n", "different length, the basis functions would be stretched according to\n", "the element size and hence be different.\n", "\n", "<!-- This was difficult to follow: -->\n", "<!-- The basis function ${\\varphi}_2(x)$, corresponding to a node on the -->\n", "<!-- boundary of element 0 and 1, is made up of two pieces: (i) the Lagrange -->\n", "<!-- polynomial on element 1 that is 1 at local node 0 (global node 2) -->\n", "<!-- and zero at all other nodes in element 1, and (ii) -->\n", "<!-- the Lagrange -->\n", "<!-- polynomial on element 1 that is 1 at local node 2 (global node 2) -->\n", "<!-- and zero at all other nodes in element 0. Outside the elements that -->\n", "<!-- share global node 2, ${\\varphi}_2(x)=0$. The same reasoning is applied to -->\n", "<!-- the construction of ${\\varphi}_4(x)$ and ${\\varphi}_6(x)$. -->\n", "\n", "\n", "## Example on linear finite element functions\n", "\n", "\n", "[Figure](#fem:approx:fe:fig:P1) shows\n", "piecewise linear basis functions ($d=1$) (with derivatives in\n", "[Figure](#fem:approx:fe:fig:dP1)). These are mathematically\n", "simpler than the quadratic functions in the previous section, and one would\n", "therefore think that it is easier to understand the linear functions\n", "first. However, linear basis functions do not involve internal nodes\n", "and are therefore a special case of the general situation. That is why\n", "we think it is better to understand the construction of quadratic functions\n", "first, which easily generalize to any $d > 2$, and then look at the\n", "special case $d=1$.\n", "\n", "<!-- dom:FIGURE: [fig/phi/mpl_fe_basis_p1_4e_lab.png, width=600 frac=1] Illustration of the piecewise linear basis functions associated with nodes in an element. <div id=\"fem:approx:fe:fig:P1\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:fig:P1\"></div>\n", "\n", "<p>Illustration of the piecewise linear basis functions associated with nodes in an element.</p>\n", "<img src=\"fig/phi/mpl_fe_basis_p1_4e_lab.png\" width=600>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "<!-- dom:FIGURE: [fig/phi/mpl_fe_dbasis_p1_4e_lab.png, width=600 frac=1] Illustration of the derivatives of piecewise linear basis functions associated with nodes in an element. <div id=\"fem:approx:fe:fig:dP1\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:fig:dP1\"></div>\n", "\n", "<p>Illustration of the derivatives of piecewise linear basis functions associated with nodes in an element.</p>\n", "<img src=\"fig/phi/mpl_fe_dbasis_p1_4e_lab.png\" width=600>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "\n", "We have the same four elements on $\\Omega = [0,1]$. Now there are no\n", "internal nodes in the elements so that all basis functions are\n", "associated with shared nodes and hence made up of two Lagrange\n", "polynomials, one from each of the two neighboring elements. For\n", "example, ${\\varphi}_1(x)$ results from the Lagrange polynomial in\n", "element 0 that is 1 at local node 1 and 0 at local node 0, combined\n", "with the Lagrange polynomial in element 1 that is 1 at local node 0\n", "and 0 at local node 1. The other basis functions are constructed\n", "similarly.\n", "\n", "Explicit mathematical formulas are needed for ${\\varphi}_i(x)$ in\n", "computations. In the piecewise linear case, the formula\n", "([56](#fem:approx:global:Lagrange:poly)) leads to" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:phi:1:formula1\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "{\\varphi}_i(x) = \\left\\lbrace\\begin{array}{ll}\n", "0, & x < x_{i-1},\\\\\n", "(x - x_{i-1})/(x_{i} - x_{i-1}),\n", "& x_{i-1} \\leq x < x_{i},\\\\\n", "1 -\n", "(x - x_{i})/(x_{i+1} - x_{i}),\n", "& x_{i} \\leq x < x_{i+1},\\\\\n", "0, & x\\geq x_{i+1}{\\thinspace .} \\end{array}\n", "\\right.\n", "\\label{fem:approx:fe:phi:1:formula1} \\tag{70}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here, $x_{j}$, $j=i-1,i,i+1$, denotes the coordinate of node $j$.\n", "For elements of equal length $h$ the formulas can be simplified to" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:phi:1:formula2\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "{\\varphi}_i(x) = \\left\\lbrace\\begin{array}{ll}\n", "0, & x < x_{i-1},\\\\\n", "(x - x_{i-1})/h,\n", "& x_{i-1} \\leq x < x_{i},\\\\\n", "1 -\n", "(x - x_{i})/h,\n", "& x_{i} \\leq x < x_{i+1},\\\\\n", "0, & x\\geq x_{i+1} .\n", "\\end{array}\n", "\\right.\n", "\\label{fem:approx:fe:phi:1:formula2} \\tag{71}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example on cubic finite element functions\n", "\n", "Piecewise cubic basis functions can be defined by introducing four\n", "nodes per element. [Figure](#fem:approx:fe:fig:P3) shows\n", "examples on ${\\varphi}_i(x)$, $i=3,4,5,6$, associated with element number 1.\n", "Note that ${\\varphi}_4$ and ${\\varphi}_5$ are nonzero only on element number 1,\n", "while\n", "${\\varphi}_3$ and ${\\varphi}_6$ are made up of Lagrange polynomials on two\n", "neighboring elements.\n", "\n", "<!-- dom:FIGURE: [fig/phi/mpl_fe_basis_p3_4e_lab.png, width=600 frac=1] Illustration of the piecewise cubic basis functions associated with nodes in an element. <div id=\"fem:approx:fe:fig:P3\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:fig:P3\"></div>\n", "\n", "<p>Illustration of the piecewise cubic basis functions associated with nodes in an element.</p>\n", "<img src=\"fig/phi/mpl_fe_basis_p3_4e_lab.png\" width=600>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "\n", "We see that all the piecewise linear basis functions have the same\n", "\"hat\" shape. They are naturally referred to as *hat functions*,\n", "also called *chapeau functions*.\n", "The piecewise quadratic functions in [Figure](#fem:approx:fe:fig:P2)\n", "are seen to be of two types. \"Rounded hats\" associated with internal\n", "nodes in the elements and some more \"sombrero\" shaped hats associated\n", "with element boundary nodes. Higher-order basis functions also have\n", "hat-like shapes, but the functions have pronounced oscillations in addition,\n", "as illustrated in [Figure](#fem:approx:fe:fig:P3).\n", "\n", "\n", "A common terminology is to speak about *linear elements* as\n", "elements with two local nodes associated with\n", "piecewise linear basis functions. Similarly, *quadratic elements* and\n", "*cubic elements* refer to piecewise quadratic or cubic functions\n", "over elements with three or four local nodes, respectively.\n", "Alternative names, frequently used in the following, are **P1** for linear\n", "elements, **P2** for quadratic elements, and so forth: P$d$ signifies\n", "degree $d$ of the polynomial basis functions.\n", "\n", "\n", "## Calculating the linear system\n", "<div id=\"fem:approx:global:linearsystem\"></div>\n", "\n", "The elements in the coefficient matrix and right-hand side are given\n", "by the formulas ([28](#fem:approx:Aij)) and ([29](#fem:approx:bi)), but\n", "now the choice of ${\\psi}_i$ is ${\\varphi}_i$. Consider P1 elements\n", "where ${\\varphi}_i(x)$ is piecewise linear. Nodes and elements numbered\n", "consecutively from left to right in a uniformly partitioned mesh imply\n", "the nodes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "x_i=i h,\\quad i=0,\\ldots,N_n-1,\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "and the elements" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto33\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "\\Omega^{(i)} = [x_{i},x_{i+1}] = [i h, (i+1)h],\\quad\n", "i=0,\\ldots,N_e-1\n", "{\\thinspace .}\n", "\\label{_auto33} \\tag{72}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We have in this case $N_e$ elements and $N_n=N_e+1$ nodes.\n", "The parameter $N$ denotes the number of unknowns in the expansion\n", "for $u$, and with the P1 elements, $N=N_n$.\n", "The domain is $\\Omega=[x_{0},x_{N}]$.\n", "The formula for ${\\varphi}_i(x)$ is given by\n", "([71](#fem:approx:fe:phi:1:formula2)) and a graphical illustration is\n", "provided in Figures [fem:approx:fe:fig:P1](#fem:approx:fe:fig:P1) and\n", "[fem:approx:fe:fig:phi:i:im1](#fem:approx:fe:fig:phi:i:im1).\n", "\n", "\n", "<!-- We clearly see -->\n", "<!-- from the figures the very important property -->\n", "<!-- ${\\varphi}_i(x){\\varphi}_j(x)\\neq 0$ if and only if $j=i-1$, $j=i$, or -->\n", "<!-- $j=i+1$, or alternatively expressed, if and only if $i$ and $j$ are -->\n", "<!-- nodes in the same element. Otherwise, ${\\varphi}_i$ and ${\\varphi}_j$ are -->\n", "<!-- too distant to have an overlap and consequently their product vanishes. -->\n", "\n", "<!-- dom:FIGURE: [fig/fe_mesh1D_phi_2_3.png, width=500 frac=0.7] Illustration of the piecewise linear basis functions corresponding to global node 2 and 3. <div id=\"fem:approx:fe:fig:phi:2:3\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:fig:phi:2:3\"></div>\n", "\n", "<p>Illustration of the piecewise linear basis functions corresponding to global node 2 and 3.</p>\n", "<img src=\"fig/fe_mesh1D_phi_2_3.png\" width=500>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "### Calculating specific matrix entries\n", "\n", "Let us calculate the specific matrix entry $A_{2,3} = \\int_\\Omega\n", "{\\varphi}_2{\\varphi}_3{\\, \\mathrm{d}x}$. [Figure](#fem:approx:fe:fig:phi:2:3)\n", "shows what ${\\varphi}_2$ and ${\\varphi}_3$ look like. We realize\n", "from this figure that the product ${\\varphi}_2{\\varphi}_3\\neq 0$\n", "only over element 2, which contains node 2 and 3.\n", "The particular formulas for ${\\varphi}_{2}(x)$ and ${\\varphi}_3(x)$ on\n", "$[x_{2},x_{3}]$ are found from ([71](#fem:approx:fe:phi:1:formula2)).\n", "The function\n", "${\\varphi}_3$ has positive slope over $[x_{2},x_{3}]$ and corresponds\n", "to the interval $[x_{i-1},x_{i}]$ in\n", "([71](#fem:approx:fe:phi:1:formula2)). With $i=3$ we get" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "{\\varphi}_3(x) = (x-x_2)/h,\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "while ${\\varphi}_2(x)$ has negative slope over $[x_{2},x_{3}]$\n", "and corresponds to setting $i=2$ in ([71](#fem:approx:fe:phi:1:formula2))," ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "{\\varphi}_2(x) = 1- (x-x_2)/h{\\thinspace .}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now easily integrate," ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "A_{2,3} = \\int_\\Omega {\\varphi}_2{\\varphi}_{3}{\\, \\mathrm{d}x} =\n", "\\int_{x_{2}}^{x_{3}}\n", "\\left(1 - \\frac{x - x_{2}}{h}\\right) \\frac{x - x_{2}}{h}\n", " {\\, \\mathrm{d}x} = \\frac{h}{6}{\\thinspace .}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The diagonal entry in the coefficient matrix becomes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "A_{2,2} =\n", "\\int_{x_{1}}^{x_{2}}\n", "\\left(\\frac{x - x_{1}}{h}\\right)^2{\\, \\mathrm{d}x} +\n", "\\int_{x_{2}}^{x_{3}}\n", "\\left(1 - \\frac{x - x_{2}}{h}\\right)^2{\\, \\mathrm{d}x}\n", "= \\frac{2h}{3}{\\thinspace .}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The entry $A_{2,1}$ has an\n", "integral that is geometrically similar to the situation in\n", "[Figure](#fem:approx:fe:fig:phi:2:3), so we get\n", "$A_{2,1}=h/6$.\n", "\n", "\n", "### Calculating a general row in the matrix\n", "\n", "We can now generalize the calculation of matrix entries to\n", "a general row number $i$. The entry\n", "$A_{i,i-1}=\\int_\\Omega{\\varphi}_i{\\varphi}_{i-1}{\\, \\mathrm{d}x}$ involves\n", "hat functions as depicted in\n", "[Figure](#fem:approx:fe:fig:phi:i:im1). Since the integral\n", "is geometrically identical to the situation with specific nodes\n", "2 and 3, we realize that $A_{i,i-1}=A_{i,i+1}=h/6$ and\n", "$A_{i,i}=2h/3$. However, we can compute the integral directly\n", "too:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\begin{align*}\n", "A_{i,i-1} &= \\int_\\Omega {\\varphi}_i{\\varphi}_{i-1}{\\, \\mathrm{d}x}\\\\\n", "&=\n", "\\underbrace{\\int_{x_{i-2}}^{x_{i-1}} {\\varphi}_i{\\varphi}_{i-1}{\\, \\mathrm{d}x}}_{{\\varphi}_i=0} +\n", "\\int_{x_{i-1}}^{x_{i}} {\\varphi}_i{\\varphi}_{i-1}{\\, \\mathrm{d}x} +\n", "\\underbrace{\\int_{x_{i}}^{x_{i+1}} {\\varphi}_i{\\varphi}_{i-1}{\\, \\mathrm{d}x}}_{{\\varphi}_{i-1}=0}\\\\\n", "&= \\int_{x_{i-1}}^{x_{i}}\n", "\\underbrace{\\left(\\frac{x - x_{i}}{h}\\right)}_{{\\varphi}_i(x)}\n", "\\underbrace{\\left(1 - \\frac{x - x_{i-1}}{h}\\right)}_{{\\varphi}_{i-1}(x)} {\\, \\mathrm{d}x} =\n", "\\frac{h}{6}\n", "{\\thinspace .}\n", "\\end{align*}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The particular formulas for ${\\varphi}_{i-1}(x)$ and ${\\varphi}_i(x)$ on\n", "$[x_{i-1},x_{i}]$ are found from ([71](#fem:approx:fe:phi:1:formula2)):\n", "${\\varphi}_i$ is the linear function with positive slope, corresponding\n", "to the interval $[x_{i-1},x_{i}]$ in\n", "([71](#fem:approx:fe:phi:1:formula2)), while $\\phi_{i-1}$ has a\n", "negative slope so the definition in interval\n", "$[x_{i},x_{i+1}]$ in ([71](#fem:approx:fe:phi:1:formula2)) must be\n", "used.\n", "\n", "<!-- dom:FIGURE: [fig/fe_mesh1D_phi_i_im1.png, width=500 frac=0.7] Illustration of two neighboring linear (hat) functions with general node numbers. <div id=\"fem:approx:fe:fig:phi:i:im1\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:fig:phi:i:im1\"></div>\n", "\n", "<p>Illustration of two neighboring linear (hat) functions with general node numbers.</p>\n", "<img src=\"fig/fe_mesh1D_phi_i_im1.png\" width=500>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "\n", "The first and last row of the coefficient matrix lead to slightly\n", "different integrals:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "A_{0,0} = \\int_\\Omega {\\varphi}_0^2{\\, \\mathrm{d}x} = \\int_{x_{0}}^{x_{1}}\n", "\\left(1 - \\frac{x-x_0}{h}\\right)^2{\\, \\mathrm{d}x} = \\frac{h}{3}{\\thinspace .}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Similarly, $A_{N,N}$ involves an integral over only one element\n", "and hence equals $h/3$.\n", "\n", "<!-- dom:FIGURE: [fig/fe_mesh1D_phi_i_f.png, width=500 frac=0.7] Right-hand side integral with the product of a basis function and the given function to approximate. <div id=\"fem:approx:fe:fig:phi:i:f\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:fig:phi:i:f\"></div>\n", "\n", "<p>Right-hand side integral with the product of a basis function and the given function to approximate.</p>\n", "<img src=\"fig/fe_mesh1D_phi_i_f.png\" width=500>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "\n", "The general formula for $b_i$,\n", "see [Figure](#fem:approx:fe:fig:phi:i:f), is now easy to set up" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:bi:formula1\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "b_i = \\int_\\Omega{\\varphi}_i(x)f(x){\\, \\mathrm{d}x}\n", "= \\int_{x_{i-1}}^{x_{i}} \\frac{x - x_{i-1}}{h} f(x){\\, \\mathrm{d}x}\n", "+ \\int_{x_{i}}^{x_{i+1}} \\left(1 - \\frac{x - x_{i}}{h}\\right) f(x)\n", "{\\, \\mathrm{d}x}{\\thinspace .}\n", "\\label{fem:approx:fe:bi:formula1} \\tag{73}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We remark that the above formula applies to internal nodes (living at the interface between two elements)\n", "and that for the nodes on the boundaries only one integral needs to be computed.\n", "\n", "We need a specific $f(x)$ function to compute these integrals.\n", "With $f(x)=x(1-x)$ and\n", "two equal-sized elements in $\\Omega=[0,1]$, one gets" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "A = \\frac{h}{6}\\left(\\begin{array}{ccc}\n", "2 & 1 & 0\\\\\n", "1 & 4 & 1\\\\\n", "0 & 1 & 2\n", "\\end{array}\\right),\\quad\n", "b = \\frac{h^2}{12}\\left(\\begin{array}{c}\n", "2 - h\\\\\n", "12 - 14h\\\\\n", "10 -17h\n", "\\end{array}\\right){\\thinspace .}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The solution becomes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "c_0 = \\frac{h^2}{6},\\quad c_1 = h - \\frac{5}{6}h^2,\\quad\n", "c_2 = 2h - \\frac{23}{6}h^2{\\thinspace .}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The resulting function" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "u(x)=c_0{\\varphi}_0(x) + c_1{\\varphi}_1(x) + c_2{\\varphi}_2(x)\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "is displayed in [Figure](#fem:approx:fe:fig:ls:P1:2:4) (left).\n", "Doubling the number of elements to four leads to the improved\n", "approximation in the right part of [Figure](#fem:approx:fe:fig:ls:P1:2:4).\n", "\n", "<!-- dom:FIGURE: [fig/fe_p1_x2_2e_4e.png, width=800 frac=1.0] Least squares approximation of a parabola using 2 (left) and 4 (right) P1 elements. <div id=\"fem:approx:fe:fig:ls:P1:2:4\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:fig:ls:P1:2:4\"></div>\n", "\n", "<p>Least squares approximation of a parabola using 2 (left) and 4 (right) P1 elements.</p>\n", "<img src=\"fig/fe_p1_x2_2e_4e.png\" width=800>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "\n", "\n", "## Assembly of elementwise computations\n", "<div id=\"fem:approx:fe:elementwise\"></div>\n", "\n", "Our integral computations so far have been straightforward. However,\n", "with higher-degree polynomials and in higher dimensions (2D and 3D),\n", "integrating in the physical domain gets increasingly complicated. Instead,\n", "integrating over one element at a time, and transforming each element\n", "to a common standardized geometry in a new reference coordinate system,\n", "is technically easier. Almost all computer codes employ a finite element\n", "algorithm that calculates the linear system by integrating over one\n", "element at a time. We shall therefore explain this algorithm next.\n", "The amount of details might be overwhelming during a first reading, but\n", "once all those details are done right, one has a general\n", "finite element algorithm that can be applied to all sorts of elements,\n", "in any space dimension, no matter how geometrically complicated the domain\n", "is.\n", "\n", "\n", "### The element matrix\n", "\n", "We start by splitting\n", "the integral over $\\Omega$ into a sum of contributions from\n", "each element:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:elementwise:Asplit\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "A_{i,j} = \\int_\\Omega{\\varphi}_i{\\varphi}_j {\\, \\mathrm{d}x} = \\sum_{e} A^{(e)}_{i,j},\\quad\n", "A^{(e)}_{i,j}=\\int_{\\Omega^{(e)}} {\\varphi}_i{\\varphi}_j {\\, \\mathrm{d}x}\n", "{\\thinspace .}\n", "\\label{fem:approx:fe:elementwise:Asplit} \\tag{74}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, $A^{(e)}_{i,j}\\neq 0$, if and only if, $i$ and $j$ are nodes in element\n", "$e$ (look at [Figure](#fem:approx:fe:fig:phi:i:im1) to realize this\n", "property, but the result also holds for all types of elements).\n", "Introduce $i=q(e,r)$ as the mapping of local node number $r$ in element\n", "$e$ to the global node number $i$. This is just a short mathematical notation\n", "for the expression `i=elements[e][r]` in a program.\n", "Let $r$ and $s$ be the local node numbers corresponding to the global\n", "node numbers $i=q(e,r)$ and\n", "$j=q(e,s)$. With $d+1$ nodes per element, all the nonzero matrix entries\n", "in $A^{(e)}_{i,j}$ arise from the integrals involving basis functions with\n", "indices corresponding to the global node numbers in element number $e$:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\int_{\\Omega^{(e)}}{\\varphi}_{q(e,r)}{\\varphi}_{q(e,s)} {\\, \\mathrm{d}x},\n", "\\quad r,s=0,\\ldots, d{\\thinspace .}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These contributions can be collected in a $(d+1)\\times (d+1)$ matrix known as\n", "the *element matrix*. Let ${I_d}=\\{0,\\ldots,d\\}$ be the valid indices\n", "of $r$ and $s$.\n", "We introduce the notation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\tilde A^{(e)} = \\{ \\tilde A^{(e)}_{r,s}\\},\\quad\n", "r,s\\in{I_d},\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "for the element matrix. For P1 elements ($d=2$) we have" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\tilde A^{(e)} = \\left\\lbrack\\begin{array}{ll}\n", "\\tilde A^{(e)}_{0,0} & \\tilde A^{(e)}_{0,1}\\\\\n", "\\tilde A^{(e)}_{1,0} & \\tilde A^{(e)}_{1,1}\n", "\\end{array}\\right\\rbrack\n", "{\\thinspace .}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "while P2 elements have a $3\\times 3$ element matrix:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\tilde A^{(e)} = \\left\\lbrack\\begin{array}{lll}\n", "\\tilde A^{(e)}_{0,0} & \\tilde A^{(e)}_{0,1} & \\tilde A^{(e)}_{0,2}\\\\\n", "\\tilde A^{(e)}_{1,0} & \\tilde A^{(e)}_{1,1} & \\tilde A^{(e)}_{1,2}\\\\\n", "\\tilde A^{(e)}_{2,0} & \\tilde A^{(e)}_{2,1} & \\tilde A^{(e)}_{2,2}\n", "\\end{array}\\right\\rbrack\n", "{\\thinspace .}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Assembly of element matrices\n", "\n", "Given the numbers $\\tilde A^{(e)}_{r,s}$,\n", "we should, according to ([74](#fem:approx:fe:elementwise:Asplit)),\n", "add the contributions to the global coefficient matrix by" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto34\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", " A_{q(e,r),q(e,s)} := A_{q(e,r),q(e,s)} + \\tilde A^{(e)}_{r,s},\\quad\n", "r,s\\in{I_d}{\\thinspace .}\n", "\\label{_auto34} \\tag{75}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This process of adding in elementwise contributions to the global matrix\n", "is called *finite element assembly* or simply *assembly*.\n", "\n", "[Figure](#fem:approx:fe:fig:assembly:2x2) illustrates how element matrices\n", "for elements with two nodes are added into the global matrix.\n", "More specifically, the figure shows how the element matrix associated with\n", "elements 1 and 2 assembled, assuming that global nodes are numbered\n", "from left to right in the domain. With regularly numbered P3 elements, where\n", "the element matrices have size $4\\times 4$, the assembly of elements 1 and 2\n", "are sketched in [Figure](#fem:approx:fe:fig:assembly:4x4).\n", "\n", "<!-- dom:FIGURE: [mov/fe_assembly_regular_2x2/fe_assembly_regular_2x2.png, width=500 frac=0.6] Illustration of matrix assembly: regularly numbered P1 elements. <div id=\"fem:approx:fe:fig:assembly:2x2\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:fig:assembly:2x2\"></div>\n", "\n", "<p>Illustration of matrix assembly: regularly numbered P1 elements.</p>\n", "<img src=\"mov/fe_assembly_regular_2x2/fe_assembly_regular_2x2.png\" width=500>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "<!-- dom:FIGURE: [mov/fe_assembly_regular_4x4/fe_assembly_regular_4x4.png, width=500 frac=0.6] Illustration of matrix assembly: regularly numbered P3 elements. <div id=\"fem:approx:fe:fig:assembly:4x4\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:fig:assembly:4x4\"></div>\n", "\n", "<p>Illustration of matrix assembly: regularly numbered P3 elements.</p>\n", "<img src=\"mov/fe_assembly_regular_4x4/fe_assembly_regular_4x4.png\" width=500>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "### Assembly of irregularly numbered elements and nodes\n", "\n", "After assembly of element matrices corresponding to regularly numbered elements\n", "and nodes are understood, it is wise to study the assembly process for\n", "irregularly numbered elements and nodes. [Figure](#fem:approx:fe:def:elements:nodes:fig:P1:irregular) shows a mesh where the `elements` array, or $q(e,r)$\n", "mapping in mathematical notation, is given as" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "elements = [[2, 1], [4, 5], [0, 4], [3, 0], [5, 2]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The associated assembly of element matrices 1 and 2 is sketched in\n", "[Figure](#fem:approx:fe:fig:assembly:irr2x2).\n", "\n", "We have created [animations](${fem_doc}/mov/fe_assembly.html) to illustrate the assembly of\n", "P1 and P3 elements with regular numbering as well as P1 elements with\n", "irregular numbering. The reader is encouraged to develop a\n", "\"geometric\" understanding of how element matrix entries are added to\n", "the global matrix. This understanding is crucial for hand\n", "computations with the finite element method.\n", "\n", "<!-- [P1 assembly movie](${fem_doc}/mov/fe_assembly_regular_2x2/index.html). -->\n", "<!-- [P3 assembly movie](${fem_doc}/mov/fe_assembly_regular_4x4/index.html). -->\n", "<!-- [P1 irregular numbering](${fem_doc}/mov/fe_assembly_irregular/index.html). -->\n", "\n", "\n", "<!-- dom:FIGURE: [mov/fe_assembly_irregular/fe_assembly_irregular.png, width=500 frac=0.6] Illustration of matrix assembly: irregularly numbered P1 elements. <div id=\"fem:approx:fe:fig:assembly:irr2x2\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:fig:assembly:irr2x2\"></div>\n", "\n", "<p>Illustration of matrix assembly: irregularly numbered P1 elements.</p>\n", "<img src=\"mov/fe_assembly_irregular/fe_assembly_irregular.png\" width=500>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "<!-- old: -->\n", "<!-- FIGURE: [fig/matrix-assembly, width=600] Illustration of matrix assembly. <div id=\"fem:approx:fe:fig:assembly\"></div> -->\n", "\n", "### The element vector\n", "\n", "The right-hand side of the linear system is also computed elementwise:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto35\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "b_i = \\int_\\Omega f(x){\\varphi}_i(x) {\\, \\mathrm{d}x} = \\sum_{e} b^{(e)}_{i},\\quad\n", "b^{(e)}_{i}=\\int_{\\Omega^{(e)}} f(x){\\varphi}_i(x){\\, \\mathrm{d}x}\n", "{\\thinspace .} \\label{_auto35} \\tag{76}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We observe that\n", "$b_i^{(e)}\\neq 0$ if and only if global node $i$ is a node in element $e$\n", "(look at [Figure](#fem:approx:fe:fig:phi:i:f) to realize this property).\n", "With $d$ nodes per element we can collect the $d+1$ nonzero contributions\n", "$b_i^{(e)}$, for $i=q(e,r)$, $r\\in{I_d}$, in an *element vector*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\tilde b_r^{(e)}=\\{ \\tilde b_r^{(e)}\\},\\quad r\\in{I_d}{\\thinspace .}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These contributions are added to the\n", "global right-hand side by an assembly process similar to that for the\n", "element matrices:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto36\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "b_{q(e,r)} := b_{q(e,r)} + \\tilde b^{(e)}_{r},\\quad\n", "r\\in{I_d}{\\thinspace .} \\label{_auto36} \\tag{77}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Mapping to a reference element\n", "<div id=\"fem:approx:fe:mapping\"></div>\n", "\n", "\n", "Instead of computing the integrals" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\tilde A^{(e)}_{r,s} = \\int_{\\Omega^{(e)}}{\\varphi}_{q(e,r)}(x){\\varphi}_{q(e,s)}(x){\\, \\mathrm{d}x}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "over some element\n", "$\\Omega^{(e)} = [x_L, x_R]$ in the physical coordinate system,\n", "it turns out that it is considerably easier and more convenient\n", "to map the element domain $[x_L, x_R]$\n", "to a standardized reference element domain $[-1,1]$ and compute all\n", "integrals over the same domain $[-1,1]$.\n", "We have now introduced\n", "$x_L$ and $x_R$ as the left and right boundary points of an arbitrary element.\n", "With a natural, regular numbering of nodes and elements from left to right\n", "through the domain, we have $x_L=x_{e}$ and $x_R=x_{e+1}$ for P1 elements.\n", "\n", "### The coordinate transformation\n", "\n", "Let $X\\in [-1,1]$ be the coordinate\n", "in the reference element. A linear mapping, also known as an affine mapping,\n", "from $X$ to $x$ can be written" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:affine:mapping\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "x = \\frac{1}{2} (x_L + x_R) + \\frac{1}{2} (x_R - x_L)X{\\thinspace .}\n", "\\label{fem:approx:fe:affine:mapping} \\tag{78}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This relation can alternatively be expressed as" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:affine:mapping2\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "x = x_m + {\\frac{1}{2}}hX,\n", "\\label{fem:approx:fe:affine:mapping2} \\tag{79}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "where we have introduced the element midpoint $x_m=(x_L+x_R)/2$ and\n", "the element length $h=x_R-x_L$.\n", "\n", "### Formulas for the element matrix and vector entries\n", "\n", "Integrating over the reference element is a matter of just changing the\n", "integration variable from $x$ to $X$. Let" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto37\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "{\\tilde{\\varphi}}_r(X) = {\\varphi}_{q(e,r)}(x(X))\n", "\\label{_auto37} \\tag{80}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "be the basis function associated with local node number $r$ in the\n", "reference element. Switching from $x$ to $X$ as integration variable,\n", "using the rules from calculus, results in" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto38\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "\\tilde A^{(e)}_{r,s} =\n", "\\int_{\\Omega^{(e)}}{\\varphi}_{q(e,r)}(x){\\varphi}_{q(e,s)}(x){\\, \\mathrm{d}x}\n", "= \\int_{-1}^1 {\\tilde{\\varphi}}_r(X){\\tilde{\\varphi}}_s(X)\\frac{{\\, \\mathrm{d}x}}{{\\, \\mathrm{d}X}}{\\, \\mathrm{d}X}\n", "{\\thinspace .}\n", "\\label{_auto38} \\tag{81}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In 2D and 3D, ${\\, \\mathrm{d}x}$ is transformed to $\\hbox{det} J{\\, \\mathrm{d}X}$, where $J$ is\n", "the Jacobian of the mapping from $x$ to $X$. In 1D,\n", "$\\hbox{det} J{\\, \\mathrm{d}X} = {\\, \\mathrm{d}x}/{\\, \\mathrm{d}X} = h/2$. To obtain a uniform\n", "notation for 1D, 2D, and 3D problems we therefore replace\n", "${\\, \\mathrm{d}x}/{\\, \\mathrm{d}X}$ by $\\det J$ now.\n", "The integration over the reference element is then written as" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:mapping:Ae\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "\\tilde A^{(e)}_{r,s}\n", "= \\int_{-1}^1 {\\tilde{\\varphi}}_r(X){\\tilde{\\varphi}}_s(X)\\det J\\,{\\, \\mathrm{d}X}\n", "\\label{fem:approx:fe:mapping:Ae} \\tag{82}\n", "{\\thinspace .}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The corresponding formula for the element vector entries becomes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:mapping:be\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "\\tilde b^{(e)}_{r} = \\int_{\\Omega^{(e)}}f(x){\\varphi}_{q(e,r)}(x){\\, \\mathrm{d}x}\n", "= \\int_{-1}^1 f(x(X)){\\tilde{\\varphi}}_r(X)\\det J\\,{\\, \\mathrm{d}X}\n", "\\label{fem:approx:fe:mapping:be} \\tag{83}\n", "{\\thinspace .}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Why reference elements?**\n", "\n", "The great advantage of using reference elements is that\n", "the formulas for the basis functions, ${\\tilde{\\varphi}}_r(X)$, are the\n", "same for all elements and independent of the element geometry\n", "(length and location in the mesh). The geometric information\n", "is \"factored out\" in the simple mapping formula and the associated\n", "$\\det J$ quantity. Also, the integration domain is the same for\n", "all elements. All these features contribute to simplify computer\n", "codes and make them more general.\n", "\n", "\n", "\n", "<!-- Since we from now on will work in the reference -->\n", "<!-- element, we need explicit mathematical formulas for the basis -->\n", "<!-- functions ${\\varphi}_i(x)$ in the reference element only, i.e., we only need -->\n", "<!-- to specify formulas for ${\\tilde{\\varphi}}_r(X)$. -->\n", "<!-- This is a very convenient simplification compared to specifying -->\n", "<!-- piecewise polynomials in the physical domain. -->\n", "<!-- , and perhaps the primary -->\n", "<!-- reason why almost all computer codes -->\n", "<!-- integrate over reference elements and assemble element -->\n", "<!-- contributions. -->\n", "\n", "### Formulas for local basis functions\n", "\n", "The ${\\tilde{\\varphi}}_r(x)$ functions are simply the Lagrange\n", "polynomials defined through the local nodes in the reference element.\n", "For $d=1$ and two nodes per element, we have the linear Lagrange\n", "polynomials" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:mapping:P1:phi0\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "{\\tilde{\\varphi}}_0(X) = \\frac{1}{2} (1 - X) ,\n", "\\label{fem:approx:fe:mapping:P1:phi0} \\tag{84}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:mapping:P1:phi1\"></div>\n", "\n", "$$\n", "\\begin{equation} \n", "{\\tilde{\\varphi}}_1(X) = \\frac{1}{2} (1 + X) .\n", "\\label{fem:approx:fe:mapping:P1:phi1} \\tag{85}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Quadratic polynomials, $d=2$, have the formulas" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:mapping:P2:phi0\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "{\\tilde{\\varphi}}_0(X) = \\frac{1}{2} (X-1)X,\n", "\\label{fem:approx:fe:mapping:P2:phi0} \\tag{86}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:mapping:P2:phi1\"></div>\n", "\n", "$$\n", "\\begin{equation} \n", "{\\tilde{\\varphi}}_1(X) = 1 - X^2,\n", "\\label{fem:approx:fe:mapping:P2:phi1} \\tag{87}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:mapping:P2:phi2\"></div>\n", "\n", "$$\n", "\\begin{equation} \n", "{\\tilde{\\varphi}}_2(X) = \\frac{1}{2} (X+1)X .\n", "\\label{fem:approx:fe:mapping:P2:phi2} \\tag{88}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In general," ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto39\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "{\\tilde{\\varphi}}_r(X) = \\prod_{s=0,s\\neq r}^d \\frac{X-X_{(s)}}{X_{(r)}-X_{(s)}},\n", "\\label{_auto39} \\tag{89}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "where $X_{(0)},\\ldots,X_{(d)}$ are the coordinates of the local nodes in\n", "the reference element.\n", "These are normally uniformly spaced: $X_{(r)} = -1 + 2r/d$,\n", "$r\\in{I_d}$.\n", "\n", "\n", "\n", "## Example on integration over a reference element\n", "<div id=\"fem:approx:fe:intg:ref\"></div>\n", "\n", "To illustrate the concepts from the previous section in a specific\n", "example, we now\n", "consider calculation of the element matrix and vector for a specific choice of\n", "$d$ and $f(x)$. A simple choice is $d=1$ (P1 elements) and $f(x)=x(1-x)$\n", "on $\\Omega =[0,1]$. We have the general expressions\n", "([82](#fem:approx:fe:mapping:Ae)) and ([83](#fem:approx:fe:mapping:be))\n", "for $\\tilde A^{(e)}_{r,s}$ and $\\tilde b^{(e)}_{r}$.\n", "Writing these out for the choices ([84](#fem:approx:fe:mapping:P1:phi0))\n", "and ([85](#fem:approx:fe:mapping:P1:phi1)), and using that $\\det J = h/2$,\n", "we can do the following calculations of the element matrix entries:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\tilde A^{(e)}_{0,0}\n", "= \\int_{-1}^1 {\\tilde{\\varphi}}_0(X){\\tilde{\\varphi}}_0(X)\\frac{h}{2} {\\, \\mathrm{d}X}\\nonumber\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:intg:ref:Ae00\"></div>\n", "\n", "$$\n", "\\begin{equation} \n", "=\\int_{-1}^1 \\frac{1}{2}(1-X)\\frac{1}{2}(1-X) \\frac{h}{2} {\\, \\mathrm{d}X} =\n", "\\frac{h}{8}\\int_{-1}^1 (1-X)^2 {\\, \\mathrm{d}X} = \\frac{h}{3},\n", "\\label{fem:approx:fe:intg:ref:Ae00} \\tag{90}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\tilde A^{(e)}_{1,0}\n", "= \\int_{-1}^1 {\\tilde{\\varphi}}_1(X){\\tilde{\\varphi}}_0(X)\\frac{h}{2} {\\, \\mathrm{d}X}\\nonumber\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:intg:ref:Ae10\"></div>\n", "\n", "$$\n", "\\begin{equation} \n", "=\\int_{-1}^1 \\frac{1}{2}(1+X)\\frac{1}{2}(1-X) \\frac{h}{2} {\\, \\mathrm{d}X} =\n", "\\frac{h}{8}\\int_{-1}^1 (1-X^2) {\\, \\mathrm{d}X} = \\frac{h}{6},\n", "\\label{fem:approx:fe:intg:ref:Ae10} \\tag{91}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto40\"></div>\n", "\n", "$$\n", "\\begin{equation} \n", "\\tilde A^{(e)}_{0,1} = \\tilde A^{(e)}_{1,0},\n", "\\label{_auto40} \\tag{92}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\tilde A^{(e)}_{1,1}\n", "= \\int_{-1}^1 {\\tilde{\\varphi}}_1(X){\\tilde{\\varphi}}_1(X)\\frac{h}{2} {\\, \\mathrm{d}X}\\nonumber\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:intg:ref:Ae11\"></div>\n", "\n", "$$\n", "\\begin{equation} \n", "=\\int_{-1}^1 \\frac{1}{2}(1+X)\\frac{1}{2}(1+X) \\frac{h}{2} {\\, \\mathrm{d}X} =\n", "\\frac{h}{8}\\int_{-1}^1 (1+X)^2 {\\, \\mathrm{d}X} = \\frac{h}{3}\n", "\\label{fem:approx:fe:intg:ref:Ae11} \\tag{93}\n", "{\\thinspace .}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The corresponding entries in the element vector becomes\n", "using ([79](#fem:approx:fe:affine:mapping2)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\tilde b^{(e)}_{0}\n", "= \\int_{-1}^1 f(x(X)){\\tilde{\\varphi}}_0(X)\\frac{h}{2} {\\, \\mathrm{d}X}\\nonumber\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "= \\int_{-1}^1 (x_m + \\frac{1}{2} hX)(1-(x_m + \\frac{1}{2} hX))\n", "\\frac{1}{2}(1-X)\\frac{h}{2} {\\, \\mathrm{d}X} \\nonumber\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:intg:ref:be0\"></div>\n", "\n", "$$\n", "\\begin{equation} \n", "= - \\frac{1}{24} h^{3} + \\frac{1}{6} h^{2} x_{m} - \\frac{1}{12} h^{2} - \\frac{1}{2} h x_{m}^{2} + \\frac{1}{2} h x_{m},\n", "\\label{fem:approx:fe:intg:ref:be0} \\tag{94}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\tilde b^{(e)}_{1}\n", "= \\int_{-1}^1 f(x(X)){\\tilde{\\varphi}}_1(X)\\frac{h}{2} {\\, \\mathrm{d}X}\\nonumber\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "= \\int_{-1}^1 (x_m + \\frac{1}{2} hX)(1-(x_m + \\frac{1}{2} hX))\n", "\\frac{1}{2}(1+X)\\frac{h}{2} {\\, \\mathrm{d}X} \\nonumber\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto41\"></div>\n", "\n", "$$\n", "\\begin{equation} \n", "= - \\frac{1}{24} h^{3} - \\frac{1}{6} h^{2} x_{m} + \\frac{1}{12} h^{2} -\n", "\\frac{1}{2} h x_{m}^{2} + \\frac{1}{2} h x_{m}\n", "{\\thinspace .}\n", "\\label{_auto41} \\tag{95}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the last two expressions we have used the element midpoint $x_m$.\n", "\n", "Integration of lower-degree polynomials above is tedious,\n", "and higher-degree polynomials involve much more algebra, but `sympy`\n", "may help. For example, we can easily calculate\n", "([90](#fem:approx:fe:intg:ref:Ae00)),\n", "([91](#fem:approx:fe:intg:ref:Ae10)),\n", "and ([94](#fem:approx:fe:intg:ref:be0)) by" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\frac{h}{3}$" ], "text/plain": [ "h/3" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import sympy as sym\n", "x, x_m, h, X = sym.symbols('x x_m h X')\n", "sym.integrate(h/8*(1-X)**2, (X, -1, 1))" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\frac{h}{6}$" ], "text/plain": [ "h/6" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sym.integrate(h/8*(1+X)*(1-X), (X, -1, 1))" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "-h**3/24 + h**2*x_m/6 - h**2/12 - h*x_m**2/2 + h*x_m/2\n" ] } ], "source": [ "x = x_m + h/2*X\n", "b_0 = sym.integrate(h/4*x*(1-x)*(1-X), (X, -1, 1))\n", "print(b_0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Implementation\n", "<div id=\"fem:approx:fe:impl\"></div>\n", "\n", "Based on the experience from the previous example, it makes sense to\n", "write some code to automate the analytical integration process for any\n", "choice of finite element basis functions. In addition, we can automate\n", "the assembly process and the solution of the linear system. Another\n", "advantage is that the code for these purposes document all details of\n", "all steps in the finite element computational machinery. The complete\n", "code can be found in the module file [`fe_approx1D.py`](src/fe_approx1D.py).\n", "\n", "\n", "## Integration\n", "<div id=\"fem:approx:fe:impl:intg\"></div>\n", "\n", "First we need a Python function for\n", "defining ${\\tilde{\\varphi}}_r(X)$ in terms of a Lagrange polynomial\n", "of degree `d`:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "import sympy as sym\n", "import numpy as np\n", "\n", "def basis(d, point_distribution='uniform', symbolic=False):\n", " \"\"\"\n", " Return all local basis function phi as functions of the\n", " local point X in a 1D element with d+1 nodes.\n", " If symbolic=True, return symbolic expressions, else\n", " return Python functions of X.\n", " point_distribution can be 'uniform' or 'Chebyshev'.\n", " \"\"\"\n", " X = sym.symbols('X')\n", " if d == 0:\n", " phi_sym = [1]\n", " else:\n", " if point_distribution == 'uniform':\n", " if symbolic:\n", "\t # Compute symbolic nodes\n", " h = sym.Rational(1, d) # node spacing\n", " nodes = [2*i*h - 1 for i in range(d+1)]\n", " else:\n", " nodes = np.linspace(-1, 1, d+1)\n", " elif point_distribution == 'Chebyshev':\n", " # Just numeric nodes\n", " nodes = Chebyshev_nodes(-1, 1, d)\n", "\n", " phi_sym = [Lagrange_polynomial(X, r, nodes)\n", " for r in range(d+1)]\n", " # Transform to Python functions\n", " phi_num = [sym.lambdify([X], phi_sym[r], modules='numpy')\n", " for r in range(d+1)]\n", " return phi_sym if symbolic else phi_num\n", "\n", "def Lagrange_polynomial(x, i, points):\n", " p = 1\n", " for k in range(len(points)):\n", " if k != i:\n", " p *= (x - points[k])/(points[i] - points[k])\n", " return p" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Observe how we construct the `phi_sym` list to be\n", "symbolic expressions for ${\\tilde{\\varphi}}_r(X)$ with `X` as a\n", "`Symbol` object from `sympy`. Also note that the\n", "`Lagrange_polynomial` function works with both symbolic and numeric variables.\n", "\n", "Now we can write the function that computes the element matrix\n", "with a list of symbolic expressions for ${\\varphi}_r$\n", "(`phi = basis(d, symbolic=True)`):" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "def element_matrix(phi, Omega_e, symbolic=True):\n", " n = len(phi)\n", " A_e = sym.zeros(n, n)\n", " X = sym.Symbol('X')\n", " if symbolic:\n", " h = sym.Symbol('h')\n", " else:\n", " h = Omega_e[1] - Omega_e[0]\n", " detJ = h/2 # dx/dX\n", " for r in range(n):\n", " for s in range(r, n):\n", " A_e[r,s] = sym.integrate(phi[r]*phi[s]*detJ, (X, -1, 1))\n", " A_e[s,r] = A_e[r,s]\n", " return A_e" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the symbolic case (`symbolic` is `True`),\n", "we introduce the element length as a symbol\n", "`h` in the computations. Otherwise, the real numerical value\n", "of the element interval `Omega_e`\n", "is used and the final matrix elements are numbers,\n", "not symbols.\n", "This functionality can be demonstrated:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1/2 - X/2, X/2 + 1/2]" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from src.fe_approx1D import *\n", "phi = basis(d=1, symbolic=True)\n", "phi" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}\\frac{h}{3} & \\frac{h}{6}\\\\\\frac{h}{6} & \\frac{h}{3}\\end{matrix}\\right]$" ], "text/plain": [ "Matrix([\n", "[h/3, h/6],\n", "[h/6, h/3]])" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "element_matrix(phi, Omega_e=[0.1, 0.2], symbolic=True)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}0.0333333333333333 & 0.0166666666666667\\\\0.0166666666666667 & 0.0333333333333333\\end{matrix}\\right]$" ], "text/plain": [ "Matrix([\n", "[0.0333333333333333, 0.0166666666666667],\n", "[0.0166666666666667, 0.0333333333333333]])" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "element_matrix(phi, Omega_e=[0.1, 0.2], symbolic=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The computation of the element vector is done by a similar\n", "procedure:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "def element_vector(f, phi, Omega_e, symbolic=True):\n", " n = len(phi)\n", " b_e = sym.zeros(n, 1)\n", " # Make f a function of X\n", " X = sym.Symbol('X')\n", " if symbolic:\n", " h = sym.Symbol('h')\n", " else:\n", " h = Omega_e[1] - Omega_e[0]\n", " x = (Omega_e[0] + Omega_e[1])/2 + h/2*X # mapping\n", " f = f.subs('x', x) # substitute mapping formula for x\n", " detJ = h/2 # dx/dX\n", " for r in range(n):\n", " b_e[r] = sym.integrate(f*phi[r]*detJ, (X, -1, 1))\n", " return b_e" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here we need to replace the symbol `x` in the expression for `f`\n", "by the mapping formula such that `f` can be integrated\n", "in terms of $X$, cf. the formula\n", "$\\tilde b^{(e)}_{r} = \\int_{-1}^1 f(x(X)){\\tilde{\\varphi}}_r(X)\\frac{h}{2}{\\, \\mathrm{d}X}$.\n", "\n", "The integration in the element matrix function involves only products\n", "of polynomials, which `sympy` can easily deal with, but for the\n", "right-hand side `sympy` may face difficulties with certain types of\n", "expressions `f`. The result of the integral is then an `Integral`\n", "object and not a number or expression\n", "as when symbolic integration is successful.\n", "It may therefore be wise to introduce a fall back to the numerical\n", "integration. The symbolic integration can also spend considerable time\n", "before reaching an unsuccessful conclusion, so we may also introduce a parameter\n", "`symbolic` to turn symbolic integration on and off:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "#DO NOT RUN THIS CELL\n", "def element_vector(f, phi, Omega_e, symbolic=True):\n", " ...\n", " if symbolic:\n", " I = sym.integrate(f*phi[r]*detJ, (X, -1, 1))\n", " if not symbolic or isinstance(I, sym.Integral):\n", " h = Omega_e[1] - Omega_e[0] # Ensure h is numerical\n", " detJ = h/2\n", " integrand = sym.lambdify([X], f*phi[r]*detJ, 'mpmath')\n", " I = mpmath.quad(integrand, [-1, 1])\n", " b_e[r] = I\n", " ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Numerical integration requires that the symbolic\n", "integrand is converted\n", "to a plain Python function (`integrand`) and that\n", "the element length `h` is a real number.\n", "\n", "\n", "## Linear system assembly and solution\n", "<div id=\"fem:approx:fe:impl:linsys\"></div>\n", "\n", "The complete algorithm\n", "for computing and assembling the elementwise contributions\n", "takes the following form" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "def assemble(nodes, elements, phi, f, symbolic=True):\n", " N_n, N_e = len(nodes), len(elements)\n", " if symbolic:\n", " A = sym.zeros(N_n, N_n)\n", " b = sym.zeros(N_n, 1) # note: (N_n, 1) matrix\n", " else:\n", " A = np.zeros((N_n, N_n))\n", " b = np.zeros(N_n)\n", " for e in range(N_e):\n", " Omega_e = [nodes[elements[e][0]], nodes[elements[e][-1]]]\n", "\n", " A_e = element_matrix(phi, Omega_e, symbolic)\n", " b_e = element_vector(f, phi, Omega_e, symbolic)\n", "\n", " for r in range(len(elements[e])):\n", " for s in range(len(elements[e])):\n", " A[elements[e][r],elements[e][s]] += A_e[r,s]\n", " b[elements[e][r]] += b_e[r]\n", " return A, b" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `nodes` and `elements` variables represent the finite\n", "element mesh as explained earlier.\n", "\n", "Given the coefficient matrix `A` and the right-hand side `b`,\n", "we can compute the coefficients $\\left\\{ {c}_j \\right\\}_{j\\in{\\mathcal{I}_s}}$ in the expansion\n", "$u(x)=\\sum_jc_j{\\varphi}_j$ as the solution vector `c` of the linear\n", "system:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#DO NOT RUN THIS CELL\n", "if symbolic:\n", " c = A.LUsolve(b)\n", "else:\n", " c = np.linalg.solve(A, b)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When `A` and `b` are `sympy` arrays,\n", "the solution procedure implied by `A.LUsolve` is symbolic.\n", "Otherwise, `A` and `b` are `numpy` arrays and a standard\n", "numerical solver is called.\n", "The symbolic version is suited for small problems only\n", "(small $N$ values) since the calculation time becomes prohibitively large\n", "otherwise. Normally, the symbolic *integration* will be more time\n", "consuming in small problems than the symbolic *solution* of the linear system.\n", "\n", "## Example on computing symbolic approximations\n", "<div id=\"fem:approx:fe:impl:ex1:symbolic\"></div>\n", "\n", "We can exemplify the use of `assemble` on the computational\n", "case from the section [Calculating the linear system](#fem:approx:global:linearsystem) with\n", "two P1 elements (linear basis functions) on the domain $\\Omega=[0,1]$.\n", "Let us first work with a symbolic element length:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}\\frac{h}{3} & \\frac{h}{6} & 0\\\\\\frac{h}{6} & \\frac{2 h}{3} & \\frac{h}{6}\\\\0 & \\frac{h}{6} & \\frac{h}{3}\\end{matrix}\\right]$" ], "text/plain": [ "Matrix([\n", "[h/3, h/6, 0],\n", "[h/6, 2*h/3, h/6],\n", "[ 0, h/6, h/3]])" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "h, x = sym.symbols('h x')\n", "nodes = [0, h, 2*h]\n", "elements = [[0, 1], [1, 2]]\n", "phi = basis(d=1, symbolic=True)\n", "f = x*(1-x)\n", "A, b = assemble(nodes, elements, phi, f, symbolic=True)\n", "A" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}- \\frac{h^{3}}{12} + \\frac{h^{2}}{6}\\\\- \\frac{7 h^{3}}{6} + h^{2}\\\\- \\frac{17 h^{3}}{12} + \\frac{5 h^{2}}{6}\\end{matrix}\\right]$" ], "text/plain": [ "Matrix([\n", "[ -h**3/12 + h**2/6],\n", "[ -7*h**3/6 + h**2],\n", "[-17*h**3/12 + 5*h**2/6]])" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "b" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}\\frac{h^{2}}{6}\\\\\\frac{12 \\left(- \\frac{35 h^{3}}{72} + \\frac{7 h^{2}}{12}\\right)}{7 h}\\\\\\frac{7 \\left(- \\frac{23 h^{3}}{21} + \\frac{4 h^{2}}{7}\\right)}{2 h}\\end{matrix}\\right]$" ], "text/plain": [ "Matrix([\n", "[ h**2/6],\n", "[12*(-35*h**3/72 + 7*h**2/12)/(7*h)],\n", "[ 7*(-23*h**3/21 + 4*h**2/7)/(2*h)]])" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c = A.LUsolve(b)\n", "c" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using interpolation instead of least squares\n", "<div id=\"fem:approx:fe:impl:ex1:collocation\"></div>\n", "\n", "As an alternative to the least squares formulation,\n", "we may compute the `c` vector based on\n", "the interpolation method from the section \"The interpolation (or collocation) principle\",\n", "using finite element basis functions.\n", "Choosing the nodes as interpolation points, the method can be written as" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "u(x_{i}) = \\sum_{j\\in{\\mathcal{I}_s}} c_j{\\varphi}_j(x_{i}) = f(x_{i}),\\quad\n", "i\\in{\\mathcal{I}_s}{\\thinspace .}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The coefficient matrix $A_{i,j}={\\varphi}_j(x_{i})$ becomes\n", "the identity matrix because basis function number $j$ vanishes\n", "at all nodes, except node $i$: ${\\varphi}_j(x_{i})=\\delta_{ij}$.\n", "Therefore, $c_i = f(x_{i})$.\n", "\n", "The associated `sympy` calculations are" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, h*(1 - h), 2*h*(1 - 2*h)]" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fn = sym.lambdify([x], f)\n", "c = [fn(xc) for xc in nodes]\n", "c" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These expressions are much simpler than those based on least squares\n", "or projection in combination with finite element basis functions.\n", "However, which of the two methods that is most appropriate for a given\n", "task is problem-dependent, so we need both methods in our toolbox.\n", "\n", "## Example on computing numerical approximations\n", "<div id=\"fem:approx:fe:impl:ex1:numeric\"></div>\n", "\n", "The numerical computations corresponding to the\n", "symbolic ones in the section [Example on computing symbolic approximations](#fem:approx:fe:impl:ex1:symbolic)\n", "(still done by `sympy` and the `assemble` function) go as follows:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[0.16666667, 0.08333333, 0. ],\n", " [0.08333333, 0.33333333, 0.08333333],\n", " [0. , 0.08333333, 0.16666667]])" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "nodes = [0, 0.5, 1]\n", "elements = [[0, 1], [1, 2]]\n", "phi = basis(d=1, symbolic=True)\n", "x = sym.Symbol('x')\n", "f = x*(1-x)\n", "A, b = assemble(nodes, elements, phi, f, symbolic=False)\n", "A" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0.03125 , 0.10416667, 0.03125 ])" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "b" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0.04166667, 0.29166667, 0.04166667])" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c = np.linalg.solve(A, b)\n", "c" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The [`fe_approx1D`](fe_approx1D.py) module contains functions for generating the\n", "`nodes` and `elements` lists for equal-sized elements with\n", "any number of nodes per element. The coordinates in `nodes`\n", "can be expressed either through the element length symbol `h`\n", "(`symbolic=True`) or by real numbers (`symbolic=False`):" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "nodes, elements = mesh_uniform(N_e=10, d=3, Omega=[0,1],\n", " symbolic=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There is also a function" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#DO NOT RUN THIS CELL\n", "def approximate(f, symbolic=False, d=1, N_e=4, filename='tmp.pdf'):" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "which computes a mesh with `N_e` elements, basis functions of\n", "degree `d`, and approximates a given symbolic expression\n", "`f` by a finite element expansion $u(x) = \\sum_jc_j{\\varphi}_j(x)$.\n", "When `symbolic` is `False`, $u(x) = \\sum_jc_j{\\varphi}_j(x)$\n", "can be computed at a (large)\n", "number of points and plotted together with $f(x)$. The construction\n", "of the pointwise function $u$ from the solution vector `c` is done\n", "elementwise by evaluating $\\sum_rc_r{\\tilde{\\varphi}}_r(X)$ at a (large)\n", "number of points in each element in the local coordinate system,\n", "and the discrete $(x,u)$ values on\n", "each element are stored in separate arrays that are finally\n", "concatenated to form a global array for $x$ and for $u$.\n", "The details are found in the `u_glob` function in\n", "[`fe_approx1D.py`](fe_approx1D.py).\n", "\n", "\n", "\n", "## The structure of the coefficient matrix\n", "<div id=\"fem:approx:fe:A:structure\"></div>\n", "\n", "Let us first see how the global matrix looks if we assemble\n", "symbolic element matrices, expressed in terms of `h`, from\n", "several elements:" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}\\frac{h}{3} & \\frac{h}{6} & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\\frac{h}{6} & \\frac{2 h}{3} & \\frac{h}{6} & 0 & 0 & 0 & 0 & 0 & 0\\\\0 & \\frac{h}{6} & \\frac{2 h}{3} & \\frac{h}{6} & 0 & 0 & 0 & 0 & 0\\\\0 & 0 & \\frac{h}{6} & \\frac{2 h}{3} & \\frac{h}{6} & 0 & 0 & 0 & 0\\\\0 & 0 & 0 & \\frac{h}{6} & \\frac{2 h}{3} & \\frac{h}{6} & 0 & 0 & 0\\\\0 & 0 & 0 & 0 & \\frac{h}{6} & \\frac{2 h}{3} & \\frac{h}{6} & 0 & 0\\\\0 & 0 & 0 & 0 & 0 & \\frac{h}{6} & \\frac{2 h}{3} & \\frac{h}{6} & 0\\\\0 & 0 & 0 & 0 & 0 & 0 & \\frac{h}{6} & \\frac{2 h}{3} & \\frac{h}{6}\\\\0 & 0 & 0 & 0 & 0 & 0 & 0 & \\frac{h}{6} & \\frac{h}{3}\\end{matrix}\\right]$" ], "text/plain": [ "Matrix([\n", "[h/3, h/6, 0, 0, 0, 0, 0, 0, 0],\n", "[h/6, 2*h/3, h/6, 0, 0, 0, 0, 0, 0],\n", "[ 0, h/6, 2*h/3, h/6, 0, 0, 0, 0, 0],\n", "[ 0, 0, h/6, 2*h/3, h/6, 0, 0, 0, 0],\n", "[ 0, 0, 0, h/6, 2*h/3, h/6, 0, 0, 0],\n", "[ 0, 0, 0, 0, h/6, 2*h/3, h/6, 0, 0],\n", "[ 0, 0, 0, 0, 0, h/6, 2*h/3, h/6, 0],\n", "[ 0, 0, 0, 0, 0, 0, h/6, 2*h/3, h/6],\n", "[ 0, 0, 0, 0, 0, 0, 0, h/6, h/3]])" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from src.fe_approx1D_v1 import mesh_symbolic\n", "d=1; N_e=8; Omega=[0,1] # 8 linear elements on [0,1]\n", "phi = basis(d)\n", "f = x*(1-x)\n", "nodes, elements = mesh_symbolic(N_e, d, Omega)\n", "A, b = assemble(nodes, elements, phi, f, symbolic=True)\n", "A" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The reader is encouraged to assemble the element matrices by hand and verify\n", "this result, as this exercise will give a hands-on understanding of\n", "what the assembly is about. In general we have a coefficient matrix that is\n", "tridiagonal:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:A:fullmat\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "A = \\frac{h}{6}\n", "\\left(\n", "\\begin{array}{cccccccccc}\n", "2 & 1 & 0\n", "&\\cdots & \\cdots & \\cdots & \\cdots & \\cdots & 0 \\\\\n", "1 & 4 & 1 & \\ddots & & & & & \\vdots \\\\\n", "0 & 1 & 4 & 1 &\n", "\\ddots & & & & \\vdots \\\\\n", "\\vdots & \\ddots & & \\ddots & \\ddots & 0 & & & \\vdots \\\\\n", "\\vdots & & \\ddots & \\ddots & \\ddots & \\ddots & \\ddots & & \\vdots \\\\\n", "\\vdots & & & 0 & 1 & 4 & 1 & \\ddots & \\vdots \\\\\n", "\\vdots & & & & \\ddots & \\ddots & \\ddots &\\ddots & 0 \\\\\n", "\\vdots & & & & &\\ddots & 1 & 4 & 1 \\\\\n", "0 &\\cdots & \\cdots &\\cdots & \\cdots & \\cdots & 0 & 1 & 2\n", "\\end{array}\n", "\\right)\n", "\\label{fem:approx:fe:A:fullmat} \\tag{96}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The structure of the right-hand side is more difficult to reveal since\n", "it involves an assembly of elementwise integrals of\n", "$f(x(X)){\\tilde{\\varphi}}_r(X)h/2$, which obviously depend on the\n", "particular choice of $f(x)$.\n", "Numerical integration can give some insight into the nature of\n", "the right-hand side. For this purpose it\n", "is easier to look at the integration in $x$ coordinates, which\n", "gives the general formula ([73](#fem:approx:fe:bi:formula1)).\n", "For equal-sized elements of length $h$, we can apply the\n", "Trapezoidal rule at the global node points to arrive at" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "b_i = h\\left( \\frac{1}{2} {\\varphi}_i(x_{0})f(x_{0}) +\n", "\\frac{1}{2} {\\varphi}_i(x_{N})f(x_{N}) + \\sum_{j=1}^{N-1}\n", "{\\varphi}_i(x_{j})f(x_{j})\\right),\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "which leads to" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto42\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "b_i =\n", "\\left\\lbrace\\begin{array}{ll}\n", "\\frac{1}{2} hf(x_i),& i=0\\hbox{ or }i=N,\\\\\n", "h f(x_i), & 1 \\leq i \\leq N-1\n", "\\end{array}\\right.\n", "\\label{_auto42} \\tag{97}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The reason for this simple formula is just that ${\\varphi}_i$ is either\n", "0 or 1 at the nodes and 0 at all but one of them.\n", "\n", "Going to P2 elements (`d=2`) leads\n", "to the element matrix" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto43\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "A^{(e)} = \\frac{h}{30}\n", "\\left(\\begin{array}{ccc}\n", "4 & 2 & -1\\\\\n", "2 & 16 & 2\\\\\n", "-1 & 2 & 4\n", "\\end{array}\\right),\n", "\\label{_auto43} \\tag{98}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "and the following global matrix, assembled here from four elements:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto44\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "A = \\frac{h}{30}\n", "\\left(\n", "\\begin{array}{ccccccccc}\n", "4 & 2 & - 1 & 0\n", " & 0 & 0 & 0 & 0 & 0\\\\\n", " 2 & 16 & 2\n", " & 0 & 0 & 0 & 0 & 0 & 0\\\\- 1 & 2 &\n", " 8 & 2 & - 1 & 0 & 0 & 0 &\n", " 0\\\\0 & 0 & 2 & 16 & 2 & 0 & 0\n", " & 0 & 0\\\\0 & 0 & - 1 & 2 & 8\n", " & 2 & - 1 & 0 & 0\\\\0 & 0 & 0 & 0 &\n", " 2 & 16 & 2 & 0 & 0\\\\0 & 0 & 0\n", " & 0 & - 1 & 2 & 8 &\n", " 2 & - 1\\\\0 & 0 & 0 & 0 & 0 & 0 &\n", " 2 & 16 & 2\\\\0 & 0 & 0 & 0 & 0\n", " & 0 & - 1 & 2 & 4\n", "\\end{array}\n", "\\right) .\n", "\\label{_auto44} \\tag{99}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In general, for $i$ odd we have the nonzero elements" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "A_{i,i-2} = -1,\\quad A_{i-1,i}=2,\\quad A_{i,i} = 8,\\quad A_{i+1,i}=2,\n", "\\quad A_{i+2,i}=-1,\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "multiplied by $h/30$, and for $i$ even we have the nonzero elements" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "A_{i-1,i}=2,\\quad A_{i,i} = 16,\\quad A_{i+1,i}=2,\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "multiplied by $h/30$. The rows with odd numbers correspond to\n", "nodes at the element boundaries and get contributions from two\n", "neighboring elements in the assembly process,\n", "while the even numbered rows correspond to\n", "internal nodes in the elements where only one element contributes\n", "to the values in the global matrix.\n", "\n", "## Applications\n", "<div id=\"fem:approx:fe:impl:ex2\"></div>\n", "\n", "With the aid of the `approximate` function in the `fe_approx1D`\n", "module we can easily investigate the quality of various finite element\n", "approximations to some given functions. [Figure](#fem:approx:fe:x9:sin)\n", "shows how linear and quadratic elements approximate the polynomial\n", "$f(x)=x(1-x)^8$ on $\\Omega =[0,1]$, using equal-sized elements.\n", "The results arise from the program" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('phi basis (reference element):\\n', [1/2 - X/2, X/2 + 1/2])\n", "('numerical integration of', 0.0429511144757271*(1/2 - X/2)*(1 - 0.142857142857143*X)**8*(0.125*X + 0.125))\n", "('numerical integration of', 0.0429511144757271*(1 - 0.142857142857143*X)**8*(0.125*X + 0.125)*(X/2 + 1/2))\n", "('numerical integration of', 0.00291038304567337*(1/2 - X/2)*(1 - 0.2*X)**8*(0.125*X + 0.375))\n", "('numerical integration of', 0.00291038304567337*(1 - 0.2*X)**8*(0.125*X + 0.375)*(X/2 + 1/2))\n", "('numerical integration of', 4.88832592964172e-5*(1/2 - X/2)*(1 - 0.333333333333333*X)**8*(0.125*X + 0.625))\n", "('numerical integration of', 4.88832592964172e-5*(1 - 0.333333333333333*X)**8*(0.125*X + 0.625)*(X/2 + 1/2))\n", "('numerical integration of', 7.45058059692383e-9*(1/2 - X/2)*(1 - X)**8*(0.125*X + 0.875))\n", "('numerical integration of', 7.45058059692383e-9*(1 - X)**8*(0.125*X + 0.875)*(X/2 + 1/2))\n", "('nodes:', [0.0, 0.25, 0.5, 0.75, 1.0])\n", "('elements:', [[0, 1], [1, 2], [2, 3], [3, 4]])\n", "('A:\\n', array([[0.08333333, 0.04166667, 0. , 0. , 0. ],\n", " [0.04166667, 0.16666667, 0.04166667, 0. , 0. ],\n", " [0. , 0.04166667, 0.16666667, 0.04166667, 0. ],\n", " [0. , 0. , 0.04166667, 0.16666667, 0.04166667],\n", " [0. , 0. , 0. , 0.04166667, 0.08333333]]))\n", "('b:\\n', array([3.99730278e-03, 6.17245568e-03, 9.15739271e-04, 2.55796644e-05,\n", " 3.37157587e-08]))\n", "[[0.08333333 0.04166667 0. 0. 0. ]\n", " [0.04166667 0.16666667 0.04166667 0. 0. ]\n", " [0. 0.04166667 0.16666667 0.04166667 0. ]\n", " [0. 0. 0.04166667 0.16666667 0.04166667]\n", " [0. 0. 0. 0.04166667 0.08333333]]\n", "('c:\\n', array([ 0.03337337, 0.02918853, -0.00198856, 0.00074345, -0.00037132]))\n", "Plain interpolation/collocation:\n", "[0.0, 0.025028228759765625, 0.001953125, 1.1444091796875e-05, 0.0]\n", "('phi basis (reference element):\\n', [-X*(1/2 - X/2), (1 - X)*(X + 1), X*(X/2 + 1/2)])\n", "('numerical integration of', -0.0250282287597656*X*(1/2 - X/2)*(1 - 0.333333333333333*X)**8*(0.25*X + 0.25))\n", "('numerical integration of', 0.0250282287597656*(1 - X)*(1 - 0.333333333333333*X)**8*(0.25*X + 0.25)*(X + 1))\n", "('numerical integration of', 0.0250282287597656*X*(1 - 0.333333333333333*X)**8*(0.25*X + 0.25)*(X/2 + 1/2))\n", "('numerical integration of', -3.814697265625e-6*X*(1/2 - X/2)*(1 - X)**8*(0.25*X + 0.75))\n", "('numerical integration of', 3.814697265625e-6*(1 - X)**9*(0.25*X + 0.75)*(X + 1))\n", "('numerical integration of', 3.814697265625e-6*X*(1 - X)**8*(0.25*X + 0.75)*(X/2 + 1/2))\n", "('nodes:', [0.0, 0.25, 0.5, 0.75, 1.0])\n", "('elements:', [[0, 1, 2], [2, 3, 4]])\n", "('A:\\n', array([[ 0.06666667, 0.03333333, -0.01666667, 0. , 0. ],\n", " [ 0.03333333, 0.26666667, 0.03333333, 0. , 0. ],\n", " [-0.01666667, 0.03333333, 0.13333333, 0.03333333, -0.01666667],\n", " [ 0. , 0. , 0.03333333, 0.26666667, 0.03333333],\n", " [ 0. , 0. , -0.01666667, 0.03333333, 0.06666667]]))\n", "('b:\\n', array([ 3.01254735e-03, 8.14196654e-03, -7.69412879e-05, 4.14299242e-05,\n", " -7.89141414e-06]))\n", "[[ 0.06666667 0.03333333 -0.01666667 0. 0. ]\n", " [ 0.03333333 0.26666667 0.03333333 0. 0. ]\n", " [-0.01666667 0.03333333 0.13333333 0.03333333 -0.01666667]\n", " [ 0. 0. 0.03333333 0.26666667 0.03333333]\n", " [ 0. 0. -0.01666667 0.03333333 0.06666667]]\n", "('c:\\n', array([ 0.03059896, 0.0272017 , -0.0039536 , 0.00084044, -0.00152699]))\n", "Plain interpolation/collocation:\n", "[0.0, 0.025028228759765625, 0.001953125, 1.1444091796875e-05, 0.0]\n", "('phi basis (reference element):\\n', [1/2 - X/2, X/2 + 1/2])\n", "('numerical integration of', 0.0372949671145761*(1/2 - X/2)*(1 - 0.0666666666666667*X)**8*(0.0625*X + 0.0625))\n", "('numerical integration of', 0.0372949671145761*(1 - 0.0666666666666667*X)**8*(0.0625*X + 0.0625)*(X/2 + 1/2))\n", "('numerical integration of', 0.0118704443011666*(1/2 - X/2)*(1 - 0.0769230769230769*X)**8*(0.0625*X + 0.1875))\n", "('numerical integration of', 0.0118704443011666*(1 - 0.0769230769230769*X)**8*(0.0625*X + 0.1875)*(X/2 + 1/2))\n", "('numerical integration of', 0.00311933226475958*(1/2 - X/2)*(1 - 0.0909090909090909*X)**8*(0.0625*X + 0.3125))\n", "('numerical integration of', 0.00311933226475958*(1 - 0.0909090909090909*X)**8*(0.0625*X + 0.3125)*(X/2 + 1/2))\n", "('numerical integration of', 0.000626412234851159*(1/2 - X/2)*(1 - 0.111111111111111*X)**8*(0.0625*X + 0.4375))\n", "('numerical integration of', 0.000626412234851159*(1 - 0.111111111111111*X)**8*(0.0625*X + 0.4375)*(X/2 + 1/2))\n", "('numerical integration of', 8.38888954604045e-5*(1/2 - X/2)*(1 - 0.142857142857143*X)**8*(0.0625*X + 0.5625))\n", "('numerical integration of', 8.38888954604045e-5*(1 - 0.142857142857143*X)**8*(0.0625*X + 0.5625)*(X/2 + 1/2))\n", "('numerical integration of', 5.6843418860808e-6*(1/2 - X/2)*(1 - 0.2*X)**8*(0.0625*X + 0.6875))\n", "('numerical integration of', 5.6843418860808e-6*(1 - 0.2*X)**8*(0.0625*X + 0.6875)*(X/2 + 1/2))\n", "('numerical integration of', 9.54751158133149e-8*(1/2 - X/2)*(1 - 0.333333333333333*X)**8*(0.0625*X + 0.8125))\n", "('numerical integration of', 9.54751158133149e-8*(1 - 0.333333333333333*X)**8*(0.0625*X + 0.8125)*(X/2 + 1/2))\n", "('numerical integration of', 1.45519152283669e-11*(1/2 - X/2)*(1 - X)**8*(0.0625*X + 0.9375))\n", "('numerical integration of', 1.45519152283669e-11*(1 - X)**8*(0.0625*X + 0.9375)*(X/2 + 1/2))\n", "('nodes:', [0.0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0])\n", "('elements:', [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8]])\n", "('A:\\n', array([[0.04166667, 0.02083333, 0. , 0. , 0. ,\n", " 0. , 0. , 0. , 0. ],\n", " [0.02083333, 0.08333333, 0.02083333, 0. , 0. ,\n", " 0. , 0. , 0. , 0. ],\n", " [0. , 0.02083333, 0.08333333, 0.02083333, 0. ,\n", " 0. , 0. , 0. , 0. ],\n", " [0. , 0. , 0.02083333, 0.08333333, 0.02083333,\n", " 0. , 0. , 0. , 0. ],\n", " [0. , 0. , 0. , 0.02083333, 0.08333333,\n", " 0.02083333, 0. , 0. , 0. ],\n", " [0. , 0. , 0. , 0. , 0.02083333,\n", " 0.08333333, 0.02083333, 0. , 0. ],\n", " [0. , 0. , 0. , 0. , 0. ,\n", " 0.02083333, 0.08333333, 0.02083333, 0. ],\n", " [0. , 0. , 0. , 0. , 0. ,\n", " 0. , 0.02083333, 0.08333333, 0.02083333],\n", " [0. , 0. , 0. , 0. , 0. ,\n", " 0. , 0. , 0.02083333, 0.04166667]]))\n", "('b:\\n', array([1.59281758e-03, 4.80897040e-03, 3.17035669e-03, 1.19522758e-03,\n", " 2.95833167e-04, 4.45846261e-05, 3.25370994e-06, 6.72828820e-08,\n", " 7.43176600e-11]))\n", "[[0.04166667 0.02083333 0. 0. 0. 0.\n", " 0. 0. 0. ]\n", " [0.02083333 0.08333333 0.02083333 0. 0. 0.\n", " 0. 0. 0. ]\n", " [0. 0.02083333 0.08333333 0.02083333 0. 0.\n", " 0. 0. 0. ]\n", " [0. 0. 0.02083333 0.08333333 0.02083333 0.\n", " 0. 0. 0. ]\n", " [0. 0. 0. 0.02083333 0.08333333 0.02083333\n", " 0. 0. 0. ]\n", " [0. 0. 0. 0. 0.02083333 0.08333333\n", " 0.02083333 0. 0. ]\n", " [0. 0. 0. 0. 0. 0.02083333\n", " 0.08333333 0.02083333 0. ]\n", " [0. 0. 0. 0. 0. 0.\n", " 0.02083333 0.08333333 0.02083333]\n", " [0. 0. 0. 0. 0. 0.\n", " 0. 0.02083333 0.04166667]]\n", "('c:\\n', array([ 1.41432377e-02, 4.81687683e-02, 2.40122679e-02, 7.95928134e-03,\n", " 1.52153070e-03, 1.54587879e-04, 1.79838379e-07, 8.70844667e-07,\n", " -4.33638709e-07]))\n", "Plain interpolation/collocation:\n", "[0.0, 0.04295111447572708, 0.025028228759765625, 0.008731149137020111, 0.001953125, 0.0002444162964820862, 1.1444091796875e-05, 5.21540641784668e-08, 0.0]\n", "('phi basis (reference element):\\n', [-X*(1/2 - X/2), (1 - X)*(X + 1), X*(X/2 + 1/2)])\n", "('numerical integration of', -0.0429511144757271*X*(1/2 - X/2)*(1 - 0.142857142857143*X)**8*(0.125*X + 0.125))\n", "('numerical integration of', 0.0429511144757271*(1 - X)*(1 - 0.142857142857143*X)**8*(0.125*X + 0.125)*(X + 1))\n", "('numerical integration of', 0.0429511144757271*X*(1 - 0.142857142857143*X)**8*(0.125*X + 0.125)*(X/2 + 1/2))\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "('numerical integration of', -0.00291038304567337*X*(1/2 - X/2)*(1 - 0.2*X)**8*(0.125*X + 0.375))\n", "('numerical integration of', 0.00291038304567337*(1 - X)*(1 - 0.2*X)**8*(0.125*X + 0.375)*(X + 1))\n", "('numerical integration of', 0.00291038304567337*X*(1 - 0.2*X)**8*(0.125*X + 0.375)*(X/2 + 1/2))\n", "('numerical integration of', -4.88832592964172e-5*X*(1/2 - X/2)*(1 - 0.333333333333333*X)**8*(0.125*X + 0.625))\n", "('numerical integration of', 4.88832592964172e-5*(1 - X)*(1 - 0.333333333333333*X)**8*(0.125*X + 0.625)*(X + 1))\n", "('numerical integration of', 4.88832592964172e-5*X*(1 - 0.333333333333333*X)**8*(0.125*X + 0.625)*(X/2 + 1/2))\n", "('numerical integration of', -7.45058059692383e-9*X*(1/2 - X/2)*(1 - X)**8*(0.125*X + 0.875))\n", "('numerical integration of', 7.45058059692383e-9*(1 - X)**9*(0.125*X + 0.875)*(X + 1))\n", "('numerical integration of', 7.45058059692383e-9*X*(1 - X)**8*(0.125*X + 0.875)*(X/2 + 1/2))\n", "('nodes:', [0.0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0])\n", "('elements:', [[0, 1, 2], [2, 3, 4], [4, 5, 6], [6, 7, 8]])\n", "('A:\\n', array([[ 0.03333333, 0.01666667, -0.00833333, 0. , 0. ,\n", " 0. , 0. , 0. , 0. ],\n", " [ 0.01666667, 0.13333333, 0.01666667, 0. , 0. ,\n", " 0. , 0. , 0. , 0. ],\n", " [-0.00833333, 0.01666667, 0.06666667, 0.01666667, -0.00833333,\n", " 0. , 0. , 0. , 0. ],\n", " [ 0. , 0. , 0.01666667, 0.13333333, 0.01666667,\n", " 0. , 0. , 0. , 0. ],\n", " [ 0. , 0. , -0.00833333, 0.01666667, 0.06666667,\n", " 0.01666667, -0.00833333, 0. , 0. ],\n", " [ 0. , 0. , 0. , 0. , 0.01666667,\n", " 0.13333333, 0.01666667, 0. , 0. ],\n", " [ 0. , 0. , 0. , 0. , -0.00833333,\n", " 0.01666667, 0.06666667, 0.01666667, -0.00833333],\n", " [ 0. , 0. , 0. , 0. , 0. ,\n", " 0. , 0.01666667, 0.13333333, 0.01666667],\n", " [ 0. , 0. , 0. , 0. , 0. ,\n", " 0. , -0.00833333, 0.01666667, 0.03333333]]))\n", "('b:\\n', array([ 8.68774183e-04, 6.25705719e-03, 2.23343396e-03, 1.62098624e-03,\n", " 7.36005378e-05, 6.32912222e-05, -6.12085516e-06, 1.09817042e-07,\n", " -2.11927626e-08]))\n", "[[ 0.03333333 0.01666667 -0.00833333 0. 0. 0.\n", " 0. 0. 0. ]\n", " [ 0.01666667 0.13333333 0.01666667 0. 0. 0.\n", " 0. 0. 0. ]\n", " [-0.00833333 0.01666667 0.06666667 0.01666667 -0.00833333 0.\n", " 0. 0. 0. ]\n", " [ 0. 0. 0.01666667 0.13333333 0.01666667 0.\n", " 0. 0. 0. ]\n", " [ 0. 0. -0.00833333 0.01666667 0.06666667 0.01666667\n", " -0.00833333 0. 0. ]\n", " [ 0. 0. 0. 0. 0.01666667 0.13333333\n", " 0.01666667 0. 0. ]\n", " [ 0. 0. 0. 0. -0.00833333 0.01666667\n", " 0.06666667 0.01666667 -0.00833333]\n", " [ 0. 0. 0. 0. 0. 0.\n", " 0.01666667 0.13333333 0.01666667]\n", " [ 0. 0. 0. 0. 0. 0.\n", " -0.00833333 0.01666667 0.03333333]]\n", "('c:\\n', array([ 1.00730338e-02, 4.29311164e-02, 2.19014662e-02, 9.23688552e-03,\n", " 1.46262429e-03, 2.89361447e-04, 1.99574625e-05, -2.36293636e-06,\n", " 5.53505093e-06]))\n", "Plain interpolation/collocation:\n", "[0.0, 0.04295111447572708, 0.025028228759765625, 0.008731149137020111, 0.001953125, 0.0002444162964820862, 1.1444091796875e-05, 5.21540641784668e-08, 0.0]\n" ] }, { "data": { "text/plain": [ "array([ 1.00730338e-02, 4.29311164e-02, 2.19014662e-02, 9.23688552e-03,\n", " 1.46262429e-03, 2.89361447e-04, 1.99574625e-05, -2.36293636e-06,\n", " 5.53505093e-06])" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import sympy as sym\n", "from src.fe_approx1D import approximate\n", "x = sym.Symbol('x')\n", "\n", "approximate(f=x*(1-x)**8, symbolic=False, d=1, N_e=4)\n", "approximate(f=x*(1-x)**8, symbolic=False, d=2, N_e=2)\n", "approximate(f=x*(1-x)**8, symbolic=False, d=1, N_e=8)\n", "approximate(f=x*(1-x)**8, symbolic=False, d=2, N_e=4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The quadratic functions are seen to be better than the linear ones for the same\n", "value of $N$, as we increase $N$. This observation has some generality:\n", "higher degree is not necessarily better on a coarse mesh, but it is as\n", "we refine the mesh and the function becomes properly resolved.\n", "\n", "\n", "<!-- dom:FIGURE: [fig/fe_p1_p2_x9_248e.png, width=800 frac=1.0] Comparison of the finite element approximations: 4 P1 elements with 5 nodes (upper left), 2 P2 elements with 5 nodes (upper right), 8 P1 elements with 9 nodes (lower left), and 4 P2 elements with 9 nodes (lower right). <div id=\"fem:approx:fe:x9:sin\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:x9:sin\"></div>\n", "\n", "<p>Comparison of the finite element approximations: 4 P1 elements with 5 nodes (upper left), 2 P2 elements with 5 nodes (upper right), 8 P1 elements with 9 nodes (lower left), and 4 P2 elements with 9 nodes (lower right).</p>\n", "<img src=\"fig/fe_p1_p2_x9_248e.png\" width=800>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "\n", "## Sparse matrix storage and solution\n", "<div id=\"fem:approx:fe:impl:sparse\"></div>\n", "\n", "\n", "Some of the examples in the preceding section took several minutes to\n", "compute, even on small meshes consisting of up to eight elements.\n", "The main explanation for slow computations is unsuccessful\n", "symbolic integration: `sympy` may use a lot of energy on\n", "integrals like $\\int f(x(X)){\\tilde{\\varphi}}_r(X)h/2 {\\, \\mathrm{d}x}$ before\n", "giving up, and the program then resorts to numerical integration.\n", "Codes that can deal with a large number of basis functions and\n", "accept flexible choices of $f(x)$ should compute all integrals\n", "numerically and replace the matrix objects from `sympy` by\n", "the far more efficient array objects from `numpy`.\n", "\n", "There is also another (potential)\n", "reason for slow code: the solution algorithm for\n", "the linear system performs much more work than necessary. Most of the\n", "matrix entries $A_{i,j}$ are zero, because $({\\varphi}_i,{\\varphi}_j)=0$\n", "unless $i$ and $j$ are nodes in the same element. In 1D problems,\n", "we do not need to store or compute with these zeros when solving the\n", "linear system, but that requires solution methods adapted to the kind\n", "of matrices produced by the finite element approximations.\n", "\n", "A matrix whose majority of entries are zeros, is known as a [sparse](https://en.wikipedia.org/wiki/Sparse_matrix) matrix.\n", "Utilizing sparsity in software dramatically decreases\n", "the storage demands and the CPU-time needed to compute the solution of\n", "the linear system. This optimization is not very critical in 1D problems\n", "where modern computers can afford computing with all the zeros in the\n", "complete square matrix, but in 2D and especially in 3D, sparse\n", "matrices are fundamental for feasible finite element computations.\n", "One of the advantageous features of the finite element method is that it\n", "produces very sparse matrices. The reason is that the basis functions\n", "have local support such that the product of two basis functions, as\n", "typically met in integrals, is mostly zero.\n", "\n", "Using a numbering of nodes and elements from left to right over a 1D\n", "domain, the assembled coefficient matrix has only a few diagonals\n", "different from zero. More precisely, $2d+1$ diagonals around the main\n", "diagonal are different from zero, where $d$ is the order of the polynomial. With a different numbering of global\n", "nodes, say a random ordering, the diagonal structure is lost, but the\n", "number of nonzero elements is unaltered. Figures\n", "[fem:approx:fe:sparsity:P1](#fem:approx:fe:sparsity:P1) and [fem:approx:fe:sparsity:P3](#fem:approx:fe:sparsity:P3)\n", "exemplify sparsity patterns.\n", "\n", "\n", "<!-- dom:FIGURE: [fig/sparsity_pattern_1D_30.png, width=800] Matrix sparsity pattern for left-to-right numbering (left) and random numbering (right) of nodes in P1 elements. <div id=\"fem:approx:fe:sparsity:P1\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:sparsity:P1\"></div>\n", "\n", "<p>Matrix sparsity pattern for left-to-right numbering (left) and random numbering (right) of nodes in P1 elements.</p>\n", "<img src=\"fig/sparsity_pattern_1D_30.png\" width=800>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "<!-- dom:FIGURE: [fig/sparsity_pattern_1DP3_30.png, width=800] Matrix sparsity pattern for left-to-right numbering (left) and random numbering (right) of nodes in P3 elements. <div id=\"fem:approx:fe:sparsity:P3\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:sparsity:P3\"></div>\n", "\n", "<p>Matrix sparsity pattern for left-to-right numbering (left) and random numbering (right) of nodes in P3 elements.</p>\n", "<img src=\"fig/sparsity_pattern_1DP3_30.png\" width=800>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "The `scipy.sparse` library supports creation of sparse matrices\n", "and linear system solution.\n", "\n", " * `scipy.sparse.diags` for matrix defined via diagonals\n", "\n", " * `scipy.sparse.dok_matrix` for matrix incrementally defined via index pairs $(i,j)$\n", "\n", "The `dok_matrix` object is most convenient for finite element computations.\n", "This sparse matrix format is called DOK, which stands for Dictionary Of Keys:\n", "the implementation is basically a dictionary (hash) with the\n", "entry indices `(i,j)` as keys.\n", "\n", "Rather than declaring `A = np.zeros((N_n, N_n))`, a DOK sparse\n", "matrix is created by" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "#DO NOT RUN THIS CELL\n", "import scipy.sparse\n", "A = scipy.sparse.dok_matrix((N_n, N_n))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When there is any need to set or add some matrix entry `i,j`, just do" ] }, { "cell_type": "code", "execution_count": 85, "metadata": {}, "outputs": [], "source": [ "#DO NOT RUN THIS CELL\n", "A[i,j] = entry\n", "# or\n", "A[i,j] += entry" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The indexing creates the matrix entry on the fly, and\n", "only the nonzero entries in the matrix will be stored.\n", "\n", "To solve a system with right-hand side `b` (one-dimensional `numpy`\n", "array) with a sparse coefficient matrix `A`, we must use some kind of\n", "a sparse linear system solver. The safest choice is a method based on\n", "sparse Gaussian elimination. One high-quality package for\n", "this purpose if [UMFPACK](https://en.wikipedia.org/wiki/UMFPACK).\n", "It is interfaced from SciPy by" ] }, { "cell_type": "code", "execution_count": 86, "metadata": {}, "outputs": [], "source": [ "#DO NOT RUN THIS CELL\n", "import scipy.sparse.linalg\n", "c = scipy.sparse.linalg.spsolve(A.tocsr(), b, use_umfpack=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The call `A.tocsr()` is not strictly needed (a warning is issued\n", "otherwise), but ensures that the solution algorithm can efficiently\n", "work with a copy of the sparse matrix in *Compressed Sparse Row* (CSR) format.\n", "\n", "An advantage of the `scipy.sparse.diags` matrix over the DOK format is\n", "that the former allows vectorized assignment to the matrix.\n", "Vectorization is possible for approximation problems when all elements\n", "are of the same type. However, when solving differential equations,\n", "vectorization may be more difficult in particular because of boundary conditions.\n", "It also appears that the DOK sparse matrix format available in the `scipy.sparse` package is fast\n", "enough even for big 1D problems on today's laptops, so the need for\n", "improving efficiency occurs only in 2D and 3D problems, but then the\n", "complexity of the mesh favors the DOK format.\n", "\n", "<!-- 2DO -->\n", "\n", "<!-- Examples to come.... -->\n", "\n", "<!-- Exercise: introduce a random numbering of global nodes; need arbitrary, sparse matrix. -->" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# A generalized element concept\n", "<div id=\"fem:approx:fe:element\"></div>\n", "\n", "\n", "So far, finite element computing has employed the `nodes` and\n", "`element` lists together with the definition of the basis functions\n", "in the reference element. Suppose we want to introduce a piecewise\n", "constant approximation with one basis function ${\\tilde{\\varphi}}_0(x)=1$ in\n", "the reference element, corresponding to a ${\\varphi}_i(x)$ function that\n", "is 1 on element number $i$ and zero on all other elements.\n", "Although we could associate the function value\n", "with a node in the middle of the elements, there are no nodes at the\n", "ends, and the previous code snippets will not work because we\n", "cannot find the element boundaries from the `nodes` list.\n", "\n", "In order to get a richer space of finite element approximations, we need\n", "to revise the simple node and element concept presented so far and\n", "introduce a more powerful terminology. Much literature employs the\n", "definition of node and element introduced in the previous sections\n", "so it is important have this knowledge, besides being a good pedagogical\n", "background for understanding the extended element concept in the following.\n", "\n", "## Cells, vertices, and degrees of freedom\n", "<div id=\"fem:approx:fe:element:terminology\"></div>\n", "\n", "\n", "We now introduce *cells* as the subdomains $\\Omega^{(e)}$ previously\n", "referred to as elements. The cell boundaries are uniquely defined in terms of *vertices*.\n", "This applies to cells in both 1D and higher dimensions.\n", "We also define a set of *degrees of freedom* (dof), which are\n", "the quantities we aim to compute. The most common type of degree\n", "of freedom is the value of the unknown function $u$ at some point.\n", "(For example, we can introduce nodes as before and say the degrees of\n", "freedom are the values of $u$ at the nodes.) The basis functions are\n", "constructed so that they equal unity for one particular degree of\n", "freedom and zero for the rest. This property ensures that when\n", "we evaluate $u=\\sum_j c_j{\\varphi}_j$ for degree of freedom number $i$,\n", "we get $u=c_i$. Integrals are performed over cells, usually by\n", "mapping the cell of interest to a *reference cell*.\n", "\n", "\n", "With the concepts of cells, vertices, and degrees of freedom we\n", "increase the decoupling of the geometry (cell, vertices) from the\n", "space of basis functions. We will associate different\n", "sets of basis functions with a cell. In 1D, all cells are intervals,\n", "while in 2D we can have cells that are triangles with straight sides,\n", "or any polygon, or in fact any two-dimensional geometry. Triangles and\n", "quadrilaterals are most common, though. The popular cell types in 3D\n", "are tetrahedra and hexahedra.\n", "\n", "## Extended finite element concept\n", "<div id=\"fem:approx:fe:element:def\"></div>\n", "\n", "\n", "The concept of a *finite element* is now\n", "\n", " * a *reference cell* in a local reference coordinate system;\n", "\n", " * a set of *basis functions* ${\\tilde{\\varphi}}_i$ defined on the cell;\n", "\n", " * a set of *degrees of freedom* that uniquely determine how basis functions\n", " from different elements are glued together across element interfaces.\n", " A common technique is to choose\n", " the basis functions such that ${\\tilde{\\varphi}}_i=1$ for degree of freedom\n", " number $i$ that is associated with nodal point\n", " $x_i$ and ${\\tilde{\\varphi}}_i=0$ for all other degrees of freedom. This\n", " technique ensures the desired continuity;\n", "\n", " * a mapping between local and global degree of freedom numbers,\n", " here called the *dof map*;\n", "\n", " * a geometric *mapping* of the reference cell onto the cell in the physical\n", " domain.\n", "\n", "There must be a geometric description of a cell. This is trivial in 1D\n", "since the cell is an interval and is described by the interval limits,\n", "here called vertices. If the cell is $\\Omega^{(e)}=[x_L,x_R]$,\n", "vertex 0 is $x_L$ and vertex 1 is $x_R$. The reference cell in 1D\n", "is $[-1,1]$ in the reference coordinate system $X$.\n", "\n", "\n", "The expansion of $u$ over one cell is often used:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto52\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "u(x) = \\tilde u(X) = \\sum_{r} c_r{\\tilde{\\varphi}}_r(X),\\quad x\\in\\Omega^{(e)},\\\n", "X\\in [-1,1],\n", "\\label{_auto52} \\tag{108}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "where the sum is taken over the numbers of the degrees of freedom and\n", "$c_r$ is the value of $u$ for degree of freedom number $r$.\n", "\n", "Our previous P1, P2, etc., elements are defined by introducing $d+1$\n", "equally spaced nodes in the reference cell, a polynomial space (P$d$) containing\n", "a complete set of polynomials of order\n", "$d$, and saying that the degrees\n", "of freedom are the $d+1$ function values at these nodes. The basis\n", "functions must be 1 at one node and 0 at the others, and the Lagrange\n", "polynomials have exactly this property. The nodes can be numbered\n", "from left to right with associated degrees of freedom that are\n", "numbered in the same way. The degree of freedom mapping becomes what\n", "was previously represented by the `elements` lists. The cell mapping\n", "is the same affine mapping ([78](#fem:approx:fe:affine:mapping)) as\n", "before.\n", "\n", "**Notice.**\n", "\n", "The extended finite element concept introduced above is quite general and\n", "has served as a successful recipe for implementing many finite element frameworks\n", "and for developing the theory behind. Here, we have seen several different examples\n", "but the exposition is most focused on 1D examples and the diversity is limited\n", "as many of the different methods in 2D and 3D collapse to the same method in 1D.\n", "The curious reader is advised to for instance look into the numerous\n", "examples of finite elements implemented in FEniCS to gain insight into the variety of methods that exists.\n", "\n", "\n", "\n", "\n", "\n", "## Implementation\n", "<div id=\"fem:approx:fe:element:impl\"></div>\n", "\n", "\n", "Implementationwise,\n", "\n", " * we replace `nodes` by `vertices`;\n", "\n", " * we introduce `cells` such that `cell[e][r]` gives the mapping\n", " from local vertex `r` in cell `e` to the global vertex number\n", " in `vertices`;\n", "\n", " * we replace `elements` by `dof_map` (the contents are the same\n", " for P$d$ elements).\n", "\n", "Consider the example from the section [Elements and nodes](#fem:approx:fe:def:elements:nodes) where $\\Omega =[0,1]$ is divided\n", "into two cells, $\\Omega^{(0)}=[0,0.4]$ and $\\Omega^{(1)}=[0.4,1]$, as\n", "depicted in [Figure](#fem:approx:fe:def:elements:nodes:fig:P2). The\n", "vertices are $[0,0.4,1]$. Local vertex 0 and 1 are $0$ and $0.4$ in\n", "cell 0 and $0.4$ and $1$ in cell 1. A P2 element means that the\n", "degrees of freedom are the value of $u$ at three equally spaced points\n", "(nodes) in each cell. The data structures become" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "vertices = [0, 0.4, 1]\n", "cells = [[0, 1], [1, 2]]\n", "dof_map = [[0, 1, 2], [2, 3, 4]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we approximate $f$ by piecewise constants, known as\n", "P0 elements, we simply\n", "introduce one point or node in an element, preferably $X=0$,\n", "and define one degree of freedom, which is the function value\n", "at this node. Moreover, we set ${\\tilde{\\varphi}}_0(X)=1$.\n", "The `cells` and `vertices` arrays remain the same, but\n", "`dof_map` is altered:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "dof_map = [[0], [1]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We use the `cells` and `vertices` lists to retrieve information\n", "on the geometry of a cell, while `dof_map` is the\n", "$q(e,r)$ mapping introduced earlier in the\n", "assembly of element matrices and vectors.\n", "For example, the `Omega_e` variable (representing the cell interval)\n", "in previous code snippets must now be computed as" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#DO NOT RUN THIS CELL\n", "Omega_e = [vertices[cells[e][0]], vertices[cells[e][1]]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The assembly is done by" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#DO NOT RUN THIS CELL\n", "A[dof_map[e][r], dof_map[e][s]] += A_e[r,s]\n", "b[dof_map[e][r]] += b_e[r]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will hereafter drop the `nodes` and `elements` arrays\n", "and work exclusively with `cells`, `vertices`, and `dof_map`.\n", "The module `fe_approx1D_numint.py` now replaces the module\n", "`fe_approx1D` and offers similar functions that work with\n", "the new concepts:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[<matplotlib.lines.Line2D at 0x1b44f299a08>]" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "<Figure size 432x288 with 1 Axes>" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "from src.fe_approx1D_numint import *\n", "x = sym.Symbol('x')\n", "f = x*(1 - x)\n", "N_e = 10\n", "vertices, cells, dof_map = mesh_uniform(N_e, d=3, Omega=[0,1])\n", "phi = [basis(len(dof_map[e])-1) for e in range(N_e)]\n", "A, b = assemble(vertices, cells, dof_map, phi, f, False)\n", "c = np.linalg.solve(A, b)\n", "# Make very fine mesh and sample u(x) on this mesh for plotting\n", "x_u, u = u_glob(c, vertices, cells, dof_map,\n", " resolution_per_element=51)\n", "plt.plot(x_u, u)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These steps are offered in the `approximate` function, which we here\n", "apply to see how well four P0 elements (piecewise constants)\n", "can approximate a parabola:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "phi basis (reference element):\n", " [[1], [1], [1], [1]]\n", "cells: [[0, 1], [1, 2], [2, 3], [3, 4]]\n", "vertices: [0.0, 0.25, 0.5, 0.75, 1.0]\n", "dof_map: [[0], [1], [2], [3]]\n", "A:\n", " [[0.25 0. 0. 0. ]\n", " [0. 0.25 0. 0. ]\n", " [0. 0. 0.25 0. ]\n", " [0. 0. 0. 0.25]]\n", "b:\n", " [0.02604167 0.05729167 0.05729167 0.02604167]\n", "c:\n", " [0.10416667 0.22916667 0.22916667 0.10416667]\n", "Plain interpolation/collocation:\n", "[0.0, 0.1875, 0.25, 0.1875, 0.0]\n" ] }, { "data": { "image/png": "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\n", "text/plain": [ "<Figure size 432x288 with 1 Axes>" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "phi basis (reference element):\n", " [[1], [1], [1], [1], [1], [1], [1], [1]]\n", "cells: [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8]]\n", "vertices: [0.0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0]\n", "dof_map: [[0], [1], [2], [3], [4], [5], [6], [7]]\n", "A:\n", " [[0.125 0. 0. 0. 0. 0. 0. 0. ]\n", " [0. 0.125 0. 0. 0. 0. 0. 0. ]\n", " [0. 0. 0.125 0. 0. 0. 0. 0. ]\n", " [0. 0. 0. 0.125 0. 0. 0. 0. ]\n", " [0. 0. 0. 0. 0.125 0. 0. 0. ]\n", " [0. 0. 0. 0. 0. 0.125 0. 0. ]\n", " [0. 0. 0. 0. 0. 0. 0.125 0. ]\n", " [0. 0. 0. 0. 0. 0. 0. 0.125]]\n", "b:\n", " [0.00716146 0.01888021 0.02669271 0.03059896 0.03059896 0.02669271\n", " 0.01888021 0.00716146]\n", "c:\n", " [0.05729167 0.15104167 0.21354167 0.24479167 0.24479167 0.21354167\n", " 0.15104167 0.05729167]\n", "Plain interpolation/collocation:\n", "[0.0, 0.109375, 0.1875, 0.234375, 0.25, 0.234375, 0.1875, 0.109375, 0.0]\n" ] }, { "data": { "image/png": "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\n", "text/plain": [ "<Figure size 432x288 with 1 Axes>" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "from src.fe_approx1D_numint import *\n", "x=sym.Symbol(\"x\")\n", "for N_e in 4, 8:\n", " approximate(x*(1-x), d=0, N_e=N_e, Omega=[0,1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Figure](#fem:approx:fe:element:impl:fig:P0:x2) shows the result.\n", "\n", "\n", "<!-- dom:FIGURE: [fig/fe_p0_x2_4e_8e.png, width=600] Approximation of a parabola by 4 (left) and 8 (right) P0 elements. <div id=\"fem:approx:fe:element:impl:fig:P0:x2\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:element:impl:fig:P0:x2\"></div>\n", "\n", "<p>Approximation of a parabola by 4 (left) and 8 (right) P0 elements.</p>\n", "<img src=\"fig/fe_p0_x2_4e_8e.png\" width=600>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "\n", "## Computing the error of the approximation\n", "<div id=\"fem:approx:fe:error\"></div>\n", "\n", "So far we have focused on computing the coefficients $c_j$ in the\n", "approximation $u(x)=\\sum_jc_j{\\varphi}_j$ as well as on plotting $u$ and\n", "$f$ for visual comparison. A more quantitative comparison needs to\n", "investigate the error $e(x)=f(x)-u(x)$. We mostly want a single number to\n", "reflect the error and use a norm for this purpose, usually the $L^2$ norm" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "||e||_{L^2} = \\left(\\int_{\\Omega} e^2 {\\, \\mathrm{d}x}\\right)^{1/2}{\\thinspace .}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since the finite element approximation is defined for all $x\\in\\Omega$,\n", "and we are interested in how $u(x)$ deviates from $f(x)$ through all\n", "the elements,\n", "we can either integrate analytically or use an accurate numerical\n", "approximation. The latter is more convenient as it is a generally\n", "feasible and simple approach. The idea is to sample $e(x)$\n", "at a large number of points in each element. The function `u_glob`\n", "in the `fe_approx1D_numint` module does this for $u(x)$ and returns\n", "an array `x` with coordinates and an array `u` with the $u$ values:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#DO NOT RUN THIS CELL\n", "x, u = u_glob(c, vertices, cells, dof_map,\n", " resolution_per_element=101)\n", "e = f(x) - u" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let us use the Trapezoidal method to approximate the integral. Because\n", "different elements may have different lengths, the `x` array may have\n", "a non-uniformly distributed set of coordinates. Also, the `u_glob`\n", "function works in an element by element fashion such that coordinates\n", "at the boundaries between elements appear twice. We therefore need\n", "to use a \"raw\" version of the Trapezoidal rule where we just add up\n", "all the trapezoids:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\int_\\Omega g(x) {\\, \\mathrm{d}x} \\approx \\sum_{j=0}^{n-1} \\frac{1}{2}(g(x_j) +\n", "g(x_{j+1}))(x_{j+1}-x_j),\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "if $x_0,\\ldots,x_n$ are all the coordinates in `x`. In\n", "vectorized Python code," ] }, { "cell_type": "code", "execution_count": 94, "metadata": {}, "outputs": [], "source": [ "#DO NOT RUN THIS CELL\n", "g_x = g(x)\n", "integral = 0.5*np.sum((g_x[:-1] + g_x[1:])*(x[1:] - x[:-1]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Computing the $L^2$ norm of the error, here named `E`, is now achieved by" ] }, { "cell_type": "code", "execution_count": 95, "metadata": {}, "outputs": [], "source": [ "#DO NOT RUN THIS CELL\n", "e2 = e**2\n", "E = np.sqrt(0.5*np.sum((e2[:-1] + e2[1:])*(x[1:] - x[:-1]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "# Finite elements in 2D and 3D\n", "\n", "Finite element approximation is particularly powerful in 2D and 3D because\n", "the method can handle a geometrically complex domain $\\Omega$ with ease.\n", "The principal idea is, as in 1D, to divide the domain into cells\n", "and use polynomials for approximating a function over a cell.\n", "Two popular cell shapes are triangles and quadrilaterals.\n", "It is common to denote finite elements on triangles and tetrahedrons as P while\n", "elements defined in terms of quadrilaterals and boxes are denoted by Q.\n", "Figures [fem:approx:fe:2D:fig:rectP1](#fem:approx:fe:2D:fig:rectP1), [fem:approx:fe:2D:fig:circP1](#fem:approx:fe:2D:fig:circP1),\n", "and [fem:approx:fe:2D:fig:rectQ1](#fem:approx:fe:2D:fig:rectQ1) provide examples. P1 elements\n", "means linear functions ($a_0 + a_1x + a_2y$) over triangles, while Q1 elements\n", "have bilinear functions ($a_0 + a_1x + a_2y + a_3xy$) over rectangular cells.\n", "Higher-order elements can easily be defined.\n", "\n", "\n", "<!-- dom:FIGURE: [fig/mesh2D_rect_P1.png, width=800] Example on 2D P1 elements. <div id=\"fem:approx:fe:2D:fig:rectP1\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:2D:fig:rectP1\"></div>\n", "\n", "<p>Example on 2D P1 elements.</p>\n", "<img src=\"fig/mesh2D_rect_P1.png\" width=800>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "<!-- dom:FIGURE: [fig/mesh2D_quarter_circle.png, width=400] Example on 2D P1 elements in a deformed geometry. <div id=\"fem:approx:fe:2D:fig:circP1\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:2D:fig:circP1\"></div>\n", "\n", "<p>Example on 2D P1 elements in a deformed geometry.</p>\n", "<img src=\"fig/mesh2D_quarter_circle.png\" width=400>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "<!-- dom:FIGURE: [fig/mesh2D_rect_Q1.png, width=400] Example on 2D Q1 elements. <div id=\"fem:approx:fe:2D:fig:rectQ1\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:2D:fig:rectQ1\"></div>\n", "\n", "<p>Example on 2D Q1 elements.</p>\n", "<img src=\"fig/mesh2D_rect_Q1.png\" width=400>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "\n", "## Basis functions over triangles in the physical domain\n", "\n", "Cells with triangular shape will be in main focus here. With the P1\n", "triangular element, $u$ is a linear function over each cell, as\n", "depicted in [Figure](#fem:approx:fe:2D:fig:femfunc), with\n", "discontinuous derivatives at the cell boundaries.\n", "\n", "<!-- dom:FIGURE: [fig/demo2D_4x3r.png, width=400] Example on scalar function defined in terms of piecewise linear 2D functions defined on triangles. <div id=\"fem:approx:fe:2D:fig:femfunc\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:2D:fig:femfunc\"></div>\n", "\n", "<p>Example on scalar function defined in terms of piecewise linear 2D functions defined on triangles.</p>\n", "<img src=\"fig/demo2D_4x3r.png\" width=400>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "We give the vertices of the cells global and local numbers as in 1D.\n", "The degrees of freedom in the P1 element are the function values at\n", "a set of nodes, which are the three vertices.\n", "The basis function ${\\varphi}_i(x,y)$ is then 1 at the vertex with global vertex\n", "number $i$ and zero at all other vertices.\n", "On an element, the three degrees of freedom uniquely determine\n", "the linear basis functions in that element, as usual.\n", "The global\n", "${\\varphi}_i(x,y)$ function is then a combination of the linear functions\n", "(planar surfaces)\n", "over all the neighboring cells\n", "that have vertex number $i$ in common. [Figure](#fem:approx:fe:2D:fig:basphi)\n", "tries to illustrate the shape of such a \"pyramid\"-like function.\n", "\n", "<!-- dom:FIGURE: [fig/demo2D_basisfunc.png, width=400] Example on a piecewise linear 2D basis function over a patch of triangles. <div id=\"fem:approx:fe:2D:fig:basphi\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:2D:fig:basphi\"></div>\n", "\n", "<p>Example on a piecewise linear 2D basis function over a patch of triangles.</p>\n", "<img src=\"fig/demo2D_basisfunc.png\" width=400>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "### Element matrices and vectors\n", "\n", "As in 1D, we split the integral over $\\Omega$ into a sum of integrals\n", "over cells. Also as in 1D, ${\\varphi}_i$ overlaps ${\\varphi}_j$\n", "(i.e., ${\\varphi}_i{\\varphi}_j\\neq 0$) if and only if\n", "$i$ and $j$ are vertices in the same cell. Therefore, the integral\n", "of ${\\varphi}_i{\\varphi}_j$ over an element is nonzero only when $i$ and $j$\n", "run over the vertex numbers in the element. These nonzero contributions\n", "to the coefficient matrix are, as in 1D, collected in an element matrix.\n", "The size of the element matrix becomes $3\\times 3$ since there are\n", "three degrees of freedom\n", "that $i$ and $j$ run over. Again, as in 1D, we number the\n", "local vertices in a cell, starting at 0, and add the entries in\n", "the element matrix into the global system matrix, exactly as in 1D.\n", "All the details and code appear below.\n", "\n", "\n", "\n", "## Basis functions over triangles in the reference cell\n", "\n", "As in 1D, we can define the basis functions and the degrees of freedom\n", "in a reference cell and then use a mapping from the reference coordinate\n", "system to the physical coordinate system.\n", "We also need a mapping of local degrees of freedom numbers to global degrees\n", "of freedom numbers.\n", "<!-- (`dof_map`). -->\n", "\n", "The reference cell in an $(X,Y)$ coordinate system has vertices\n", "$(0,0)$, $(1,0)$, and $(0,1)$, corresponding to local vertex numbers\n", "0, 1, and 2, respectively. The P1 element has linear functions\n", "${\\tilde{\\varphi}}_r(X,Y)$ as basis functions, $r=0,1,2$.\n", "Since a linear function ${\\tilde{\\varphi}}_r(X,Y)$ in 2D is of\n", "the form $C_{r,0} + C_{r,1}X + C_{r,2}Y$, and hence has three\n", "parameters $C_{r,0}$, $C_{r,1}$, and $C_{r,2}$, we need three\n", "degrees of freedom. These are in general taken as the function values at a\n", "set of nodes. For the P1 element the set of nodes is the three vertices.\n", "[Figure](#fem:approx:fe:2D:fig:P12D) displays the geometry of the\n", "element and the location of the nodes.\n", "\n", "<!-- dom:FIGURE: [fig/fenics-book/elements/P1_2d.png, width=100 frac=0.3] 2D P1 element. <div id=\"fem:approx:fe:2D:fig:P12D\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:2D:fig:P12D\"></div>\n", "\n", "<p>2D P1 element.</p>\n", "<img src=\"fig/fenics-book/elements/P1_2d.png\" width=100>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "Requiring ${\\tilde{\\varphi}}_r=1$ at node number $r$ and\n", "${\\tilde{\\varphi}}_r=0$ at the two other nodes, gives three linear equations to\n", "determine $C_{r,0}$, $C_{r,1}$, and $C_{r,2}$. The result is" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto63\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "{\\tilde{\\varphi}}_0(X,Y) = 1 - X - Y,\n", "\\label{_auto63} \\tag{122}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto64\"></div>\n", "\n", "$$\n", "\\begin{equation} \n", "{\\tilde{\\varphi}}_1(X,Y) = X,\n", "\\label{_auto64} \\tag{123}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto65\"></div>\n", "\n", "$$\n", "\\begin{equation} \n", "{\\tilde{\\varphi}}_2(X,Y) = Y\n", "\\label{_auto65} \\tag{124}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Higher-order approximations are obtained by increasing the polynomial order,\n", "adding additional nodes, and letting the degrees of freedom be\n", "function values at the nodes. [Figure](#fem:approx:fe:2D:fig:P22D)\n", "shows the location of the six nodes in the P2 element.\n", "\n", "<!-- dom:FIGURE: [fig/fenics-book/elements/P2_2d.png, width=100 frac=0.3] 2D P2 element. <div id=\"fem:approx:fe:2D:fig:P22D\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:2D:fig:P22D\"></div>\n", "\n", "<p>2D P2 element.</p>\n", "<img src=\"fig/fenics-book/elements/P2_2d.png\" width=100>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "<!-- 2DO: write up local basis funcs for P2 -->\n", "\n", "A polynomial of degree $p$ in $X$ and $Y$ has $n_p=(p+1)(p+2)/2$ terms\n", "and hence needs $n_p$ nodes. The values at the nodes constitute $n_p$\n", "degrees of freedom. The location of the nodes for\n", "${\\tilde{\\varphi}}_r$ up to degree 6 is displayed in [Figure](#fem:approx:fe:2D:fig:P162D).\n", "\n", "<!-- dom:FIGURE: [fig/fenics-book/elements/P1-6_2d.png, width=400 frac=1.0] 2D P1, P2, P3, P4, P5, and P6 elements. <div id=\"fem:approx:fe:2D:fig:P162D\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:2D:fig:P162D\"></div>\n", "\n", "<p>2D P1, P2, P3, P4, P5, and P6 elements.</p>\n", "<img src=\"fig/fenics-book/elements/P1-6_2d.png\" width=400>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "The generalization to 3D is straightforward: the reference element is a\n", "[tetrahedron](http://en.wikipedia.org/wiki/Tetrahedron)\n", "with vertices $(0,0,0)$, $(1,0,0)$, $(0,1,0)$, and $(0,0,1)$\n", "in a $X,Y,Z$ reference coordinate system. The P1 element has its degrees\n", "of freedom as four nodes, which are the four vertices, see [Figure](#fem:approx:fe:2D:fig:P1:123D). The P2 element adds additional\n", "nodes along the edges of the cell, yielding a total of 10 nodes and\n", "degrees of freedom, see\n", "[Figure](#fem:approx:fe:2D:fig:P2:123D).\n", "\n", "<!-- dom:FIGURE: [fig/fenics-book/elements/P1-1d2d3d.png, width=400 frac=1.0] P1 elements in 1D, 2D, and 3D. <div id=\"fem:approx:fe:2D:fig:P1:123D\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:2D:fig:P1:123D\"></div>\n", "\n", "<p>P1 elements in 1D, 2D, and 3D.</p>\n", "<img src=\"fig/fenics-book/elements/P1-1d2d3d.png\" width=400>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "<!-- dom:FIGURE: [fig/fenics-book/elements/P2-1d2d3d.png, width=400 frac=1.0] P2 elements in 1D, 2D, and 3D. <div id=\"fem:approx:fe:2D:fig:P2:123D\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:2D:fig:P2:123D\"></div>\n", "\n", "<p>P2 elements in 1D, 2D, and 3D.</p>\n", "<img src=\"fig/fenics-book/elements/P2-1d2d3d.png\" width=400>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "\n", "The interval in 1D, the triangle in 2D, the tetrahedron in 3D, and\n", "its generalizations to higher space dimensions are known\n", "as *simplex* cells (the geometry) or *simplex* elements (the geometry,\n", "basis functions, degrees of freedom, etc.). The plural forms\n", "[simplices](http://en.wikipedia.org/wiki/Simplex) and\n", "simplexes are\n", "also much used shorter terms when referring to this type of cells or elements.\n", "The side of a simplex is called a *face*, while the tetrahedron also\n", "has *edges*.\n", "\n", "\n", "**Acknowledgment.**\n", "Figures [fem:approx:fe:2D:fig:P12D](#fem:approx:fe:2D:fig:P12D)-[fem:approx:fe:2D:fig:P2:123D](#fem:approx:fe:2D:fig:P2:123D)\n", "are created by Anders Logg and taken from the [FEniCS book](https://launchpad.net/fenics-book): *Automated Solution of Differential Equations by the Finite Element Method*, edited by A. Logg, K.-A. Mardal, and G. N. Wells, published\n", "by [Springer](http://goo.gl/lbyVMH), 2012.\n", "\n", "\n", "\n", "## Affine mapping of the reference cell\n", "\n", "Let ${\\tilde{\\varphi}}_r^{(1)}$ denote the basis functions associated\n", "with the P1 element in 1D, 2D, or 3D, and let $\\boldsymbol{x}_{q(e,r)}$ be\n", "the physical coordinates of local vertex number $r$ in cell $e$.\n", "Furthermore,\n", "let $\\boldsymbol{X}$ be a point in the reference coordinate system corresponding\n", "to the point $\\boldsymbol{x}$ in the physical coordinate system.\n", "The affine mapping of any $\\boldsymbol{X}$ onto $\\boldsymbol{x}$ is\n", "then defined by" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:affine:map\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "\\boldsymbol{x} = \\sum_{r} {\\tilde{\\varphi}}_r^{(1)}(\\boldsymbol{X})\\boldsymbol{x}_{q(e,r)},\n", "\\label{fem:approx:fe:affine:map} \\tag{125}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "where $r$ runs over the local vertex numbers in the cell.\n", "The affine mapping essentially stretches, translates, and rotates\n", "the triangle. Straight or planar faces of the reference cell are\n", "therefore mapped onto\n", "straight or planar faces in the physical coordinate system. The mapping can\n", "be used for both P1 and higher-order elements, but note that the\n", "mapping itself always applies the P1 basis functions.\n", "\n", "<!-- dom:FIGURE: [fig/ElmT3n2D_map.png, width=400] Affine mapping of a P1 element. <div id=\"fem:approx:fe:map:fig:2DP1\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:map:fig:2DP1\"></div>\n", "\n", "<p>Affine mapping of a P1 element.</p>\n", "<img src=\"fig/ElmT3n2D_map.png\" width=400>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "\n", "## Isoparametric mapping of the reference cell\n", "\n", "\n", "Instead of using the P1 basis functions in the mapping\n", "([125](#fem:approx:fe:affine:map)),\n", "we may use the basis functions of the actual P$d$ element:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:isop:map\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "\\boldsymbol{x} = \\sum_{r} {\\tilde{\\varphi}}_r(\\boldsymbol{X})\\boldsymbol{x}_{q(e,r)},\n", "\\label{fem:approx:fe:isop:map} \\tag{126}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "where $r$ runs over all nodes, i.e., all points associated with the\n", "degrees of freedom. This is called an *isoparametric mapping*.\n", "For P1 elements it is identical to the affine mapping\n", "([125](#fem:approx:fe:affine:map)), but for higher-order elements\n", "the mapping of the straight or planar faces of the reference cell will\n", "result in a *curved* face in the physical coordinate system.\n", "For example, when we use the basis functions of the triangular P2 element\n", "in 2D in ([126](#fem:approx:fe:isop:map)), the straight faces of the\n", "reference triangle are mapped onto curved faces of parabolic shape in\n", "the physical coordinate system, see [Figure](#fem:approx:fe:map:fig:2DP2).\n", "\n", "<!-- dom:FIGURE: [fig/ElmT6n2D_map.png, width=400] Isoparametric mapping of a P2 element. <div id=\"fem:approx:fe:map:fig:2DP2\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fe:map:fig:2DP2\"></div>\n", "\n", "<p>Isoparametric mapping of a P2 element.</p>\n", "<img src=\"fig/ElmT6n2D_map.png\" width=400>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "From ([125](#fem:approx:fe:affine:map)) or\n", "([126](#fem:approx:fe:isop:map)) it is easy to realize that the\n", "vertices are correctly mapped. Consider a vertex with local number $s$.\n", "Then ${\\tilde{\\varphi}}_s=1$ at this vertex and zero at the others.\n", "This means that only one term in the sum is nonzero and $\\boldsymbol{x}=\\boldsymbol{x}_{q(e,s)}$,\n", "which is the coordinate of this vertex in the global coordinate system.\n", "\n", "\n", "## Computing integrals\n", "\n", "Let $\\tilde\\Omega^r$ denote the reference cell and $\\Omega^{(e)}$\n", "the cell in the physical coordinate system. The transformation of\n", "the integral from the physical to the reference coordinate system reads" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto66\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "\\int_{\\Omega^{(e)}}{\\varphi}_i (\\boldsymbol{x}) {\\varphi}_j (\\boldsymbol{x}) {\\, \\mathrm{d}x} =\n", "\\int_{\\tilde\\Omega^r} {\\tilde{\\varphi}}_i (\\boldsymbol{X}) {\\tilde{\\varphi}}_j (\\boldsymbol{X})\n", "\\det J\\, {\\, \\mathrm{d}X},\n", "\\label{_auto66} \\tag{127}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"_auto67\"></div>\n", "\n", "$$\n", "\\begin{equation} \n", "\\int_{\\Omega^{(e)}}{\\varphi}_i (\\boldsymbol{x}) f(\\boldsymbol{x}) {\\, \\mathrm{d}x} =\n", "\\int_{\\tilde\\Omega^r} {\\tilde{\\varphi}}_i (\\boldsymbol{X}) f(\\boldsymbol{x}(\\boldsymbol{X})) \\det J\\, {\\, \\mathrm{d}X},\n", "\\label{_auto67} \\tag{128}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "where ${\\, \\mathrm{d}x}$ means the infinitesimal area element $dx dy$ in 2D and\n", "$dx dy dz$ in 3D, with a similar\n", "definition of ${\\, \\mathrm{d}X}$. The quantity $\\det J$ is the determinant of the\n", "Jacobian of the mapping $\\boldsymbol{x}(\\boldsymbol{X})$. In 2D," ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!-- Equation labels as ordinary links -->\n", "<div id=\"fem:approx:fe:2D:mapping:J:detJ\"></div>\n", "\n", "$$\n", "\\begin{equation}\n", "J = \\left[\\begin{array}{cc}\n", "\\frac{\\partial x}{\\partial X} & \\frac{\\partial x}{\\partial Y}\\\\\n", "\\frac{\\partial y}{\\partial X} & \\frac{\\partial y}{\\partial Y}\n", "\\end{array}\\right], \\quad\n", "\\det J = \\frac{\\partial x}{\\partial X}\\frac{\\partial y}{\\partial Y}\n", "- \\frac{\\partial x}{\\partial Y}\\frac{\\partial y}{\\partial X}\n", "{\\thinspace .}\n", "\\label{fem:approx:fe:2D:mapping:J:detJ} \\tag{129}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With the affine mapping\n", "([125](#fem:approx:fe:affine:map)), $\\det J=2\\Delta$, where $\\Delta$ is\n", "the area or volume of the cell in the physical coordinate system.\n", "\n", "**Remark.**\n", "Observe that finite elements in 2D and 3D build on the same\n", "*ideas* and *concepts* as in 1D, but there is simply much\n", "more to compute because the\n", "specific mathematical formulas in 2D and 3D are more complicated\n", "and the book-keeping with dof maps also gets more complicated.\n", "The manual work is tedious, lengthy, and error-prone\n", "so automation by the computer is a must.\n", "\n", "\n", "<!-- 2DO -->\n", "<!-- First: two triangles -->\n", "<!-- vertices = [(0,0), (1,0), (0,1), (1,1)] -->\n", "<!-- cells = [[0, 1, 3], [0, 3, 2]] -->\n", "<!-- dof_map = cells -->\n", "<!-- write up affine mapping -->\n", "<!-- D is the area that sympy.Triangle can compute :-) No, do that directly! 0.5... -->\n", "<!-- rhs: choose simple f=x*y, try hand-calculation or two-step -->\n", "<!-- sympy: first integrate in y with (0,1-x) as limits, then -->\n", "<!-- integrate the result in x -->\n", "<!-- a = integrate(x*y*(1-x-y), (y, 0, 1-x)) -->\n", "<!-- b = integrate(a, (x,0,1)) -->\n", "<!-- use the same for local element matrix -->\n", "<!-- show assembly -->\n", "<!-- should have pysketcher prog for drawing 2D mesh, mark and number nodes -->\n", "<!-- and elements -->\n", "\n", "<!-- Should have example with x**8*(1-x)*y**8*(1-y) worked out, but -->\n", "<!-- need software -->\n", "\n", "<!-- Need 2D exercises -->\n", "\n", "# Implementation\n", "<div id=\"fe:approx:fenics\"></div>\n", "\n", "\n", "Our previous programs for doing 1D approximation by finite element\n", "basis function had a focus on all the small details needed to compute\n", "the solution. When going to 2D and 3D, the basic algorithms are the\n", "same, but the amount of computational detail with basis functions,\n", "reference functions, mappings, numerical integration and so on,\n", "becomes overwhelming because of all the flexibility and choices of\n", "elements. For this purpose, we *must*, except in the simplest cases\n", "with P1 elements, use some well-developed, existing computer\n", "library.\n", "## Example of approximation in 2D using FEniCS\n", "<div id=\"fem:approx:fenics:2D\"></div>\n", "\n", "Here we shall use [FEniCS](http://fenicsproject.org), which\n", "is a free, open source finite element package for advanced computations. The\n", "package can be programmed in C++ or Python. How it works is best\n", "illustrated by an example.\n", "\n", "### Mathematical problem\n", "\n", "We want to approximate the function $f(x)=2xy - x^2$ by P1 and P2 elements\n", "on $[0,2]\\times[-1,1]$ using a division into $8\\times 8$ squares, which are\n", "then divided into rectangles and then into triangles.\n", "\n", "### The code\n", "\n", "Observe that the code employs the basic concepts from 1D, but is\n", "capable of using *any* element in FEniCS on *any* mesh in *any* number of\n", "space dimensions (!)." ] }, { "cell_type": "code", "execution_count": 96, "metadata": {}, "outputs": [], "source": [ "from fenics import *\n", "\n", "def approx(f, V):\n", " \"\"\"Return Galerkin approximation to f in V.\"\"\"\n", " u = TrialFunction(V)\n", " v = TestFunction(V)\n", " a = u*v*dx\n", " L = f*v*dx\n", " u = Function(V)\n", " solve(a == L, u)\n", " return u\n", "\n", "def problem():\n", " f = Expression('2*x[0]*x[1] - pow(x[0], 2)', degree=2)\n", " mesh = RectangleMesh(Point(0,-1), Point(2,1), 8, 8)\n", "\n", " V1 = FunctionSpace(mesh, 'P', 1)\n", " u1 = approx(f, V1)\n", " u1.rename('u1', 'u1')\n", " u1_error = errornorm(f, u1, 'L2')\n", " u1_norm = norm(u1, 'L2')\n", "\n", " V2 = FunctionSpace(mesh, 'P', 2)\n", " u2 = approx(f, V2)\n", " u2.rename('u2', 'u2')\n", " u2_error = errornorm(f, u2, 'L2')\n", " u2_norm = norm(u2, 'L2')\n", "\n", " print(('L2 errors: e1=%g, e2=%g' % (u1_error, u2_error)))\n", " print(('L2 norms: n1=%g, n2=%g' % (u1_norm, u2_norm)))\n", " # Simple plotting\n", " import matplotlib.pyplot as plt\n", " plot(f, title='f', mesh=mesh)\n", " plt.show()\n", " plot(u1, title='u1')\n", " plt.show()\n", " plot(u2, title='u2')\n", " plt.show()\n", "\n", "if __name__ == '__main__':\n", " problem()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Figure](#fem:approx:fenics:2D:fig1) shows the computed `u1`. The plots of\n", "`u2` and `f` are identical and therefore not shown.\n", "The plot shows that visually the approximation is quite close to\n", "`f`, but to quantify it more precisely we simply compute the\n", "error using the function `errornorm`. The output\n", "of errors becomes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " L2 errors: e1=0.01314, e2=4.93418e-15\n", " L2 norms: n1=4.46217, n2=4.46219\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Hence, the second order approximation `u2` is able to reproduce\n", "`f` up to floating point precision, whereas the first\n", "order approximation `u1` has an error of slightly more than $\\frac{1}{3}$\\%.\n", "\n", "<!-- Remember to rotate PDF file from internal FEniCS plotting: -->\n", "<!-- pdftk dolfin_plot_1.pdf cat 1-endnorth output rotated_file.pdf -->\n", "\n", "<!-- dom:FIGURE: [fig/fenics_2D_plot_approx.png, width=400 frac=0.7] Plot of the computed approximation using Lagrange elements of second order. <div id=\"fem:approx:fenics:2D:fig1\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fenics:2D:fig1\"></div>\n", "\n", "<p>Plot of the computed approximation using Lagrange elements of second order.</p>\n", "<img src=\"fig/fenics_2D_plot_approx.png\" width=400>\n", "\n", "<!-- end figure -->\n", "\n", "\n", "\n", "\n", "### Dissection of the code\n", "\n", "The function `approx` is a general solver function for any $f$ and\n", "$V$. We define the unknown $u$ in the variational form $a=a(u,v) = \\int uv{\\, \\mathrm{d}x}$\n", "as a `TrialFunction` object and the test function $v$ as a\n", "`TestFunction` object. Then we define the variational form through\n", "the integrand `u*v*dx`. The linear form $L$ is similarly defined as\n", "`f*v*dx`. Here, `f` is an `Expression` object in FEniCS, i.e., a\n", "formula defined in terms of a C++ expression. This expression is in turn\n", "jit-compiled into a Python object for fast evaluation. With `a` and `L` defined,\n", "we re-define `u` to be a finite element function `Function`, which is\n", "now the unknown scalar field to be computed by the simple expression\n", "`solve(a == L, u)`. We remark that the above function `approx`\n", "is implemented in FEniCS (in a slightly more general fashion)\n", "in the function `project`.\n", "\n", "The `problem` function applies `approx` to solve a specific problem.\n", "\n", "### Integrating SymPy and FEniCS\n", "\n", "The definition of $f$ must be expressed in C++. This part requires\n", "two definitions: one of $f$ and one of $\\Omega$, or more precisely:\n", "the mesh (discrete $\\Omega$ divided into cells). The definition of\n", "$f$ is here expressed in C++ (it will be compiled for fast\n", "evaluation), where the independent coordinates are given by a C/C++\n", "vector `x`. This means that $x$ is `x[0]`, $y$ is `x[1]`, and $z$ is\n", "`x[2]`. Moreover, `x[0]**2` must be written as `pow(x[0], 2)` in\n", "C/C++.\n", "\n", "Fortunately, we can easily integrate SymPy and `Expression` objects,\n", "because SymPy can take a formula and translate it to C/C++ code, and\n", "then we can require a Python code to numerically evaluate the formula.\n", "Here is how we can specify `f` in SymPy and use it in FEniCS as an\n", "`Expression` object:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "-x[0]**2 + 2*x[0]*x[1]\n" ] } ], "source": [ "import sympy as sym\n", "x, y = sym.symbols('x[0] x[1]')\n", "f = 2*x*y - x**2\n", "print(f)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "-pow(x[0], 2) + 2*x[0]*x[1]\n" ] } ], "source": [ "f = sym.printing.ccode(f) # Translate to C code\n", "print(f)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here, the function `ccode` generates C code and we use\n", "`x` and `y` as placeholders for\n", "`x[0]` and `x[1]`, which represent the coordinate of\n", "a general point `x` in any dimension. The output of `ccode`\n", "can then be used directly in `Expression`.\n", "\n", "\n", "## Refined code with curve plotting\n", "<div id=\"fem:approx:fenics:2D:2\"></div>\n", "\n", "### Interpolation and projection\n", "\n", "The operation of defining `a`, `L`, and solving for a `u` is so\n", "common that it has been implemented in the FEniCS function `project`:" ] }, { "cell_type": "code", "execution_count": 99, "metadata": {}, "outputs": [], "source": [ "u = project(f, V)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So, there is no need for our `approx` function!\n", "\n", "If we want to do interpolation (or collocation) instead, we simply do" ] }, { "cell_type": "code", "execution_count": 100, "metadata": {}, "outputs": [], "source": [ "u = interpolate(f, V)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Plotting the solution along a line\n", "\n", "Having `u` and `f` available as finite element functions (`Function`\n", "objects), we can easily plot the solution along a line since FEniCS\n", "has functionality for evaluating a `Function` at arbitrary points\n", "*inside the domain*. For example, here is the code for plotting $u$ and\n", "$f$ along a line $x=\\hbox{const}$ or $y=\\hbox{const}$." ] }, { "cell_type": "code", "execution_count": 101, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "def comparison_plot2D(\n", " u, f, # Function expressions in x and y\n", " value=0.5, # x or y equals this value\n", " variation='y', # independent variable\n", " n=100, # no of intervals in plot\n", " tol=1E-8, # tolerance for points inside the domain\n", " plottitle='', # heading in plot\n", " filename='tmp', # stem of filename\n", " ):\n", " \"\"\"\n", " Plot u and f along a line in x or y dir with n intervals\n", " and a tolerance of tol for points inside the domain.\n", " \"\"\"\n", " v = np.linspace(-1+tol, 1-tol, n+1)\n", " # Compute points along specified line:\n", " points = np.array([(value, v_)\n", " if variation == 'y' else (v_, value)\n", " for v_ in v])\n", " u_values = [u(point) for point in points] # eval. Function\n", " f_values = [f(point) for point in points]\n", " plt.figure()\n", " plt.plot(v, u_values, 'r-', v, f_values, 'b--')\n", " plt.legend(['u', 'f'], loc='upper left')\n", " if variation == 'y':\n", " plt.xlabel('y'); plt.ylabel('u, f')\n", " else:\n", " plt.xlabel('x'); plt.ylabel('u, f')\n", " plt.title(plottitle)\n", " plt.savefig(filename + '.pdf')\n", " plt.savefig(filename + '.png')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Integrating plotting and computations\n", "\n", "It is now very easy to give some graphical impression of the approximations\n", "for various kinds of 2D elements.\n", "Basically, to solve the problem of approximating $f=2xy-x^2$ on $\\Omega = [-1,1]\\times [0,2]$ by P2 elements on a $2\\times 2$ mesh,\n", "we want to integrate the function above with following type of computations:" ] }, { "cell_type": "code", "execution_count": 102, "metadata": {}, "outputs": [], "source": [ "import fenics as fe\n", "f = fe.Expression('2*x[0]*x[1] - pow(x[0], 2)', degree=2)\n", "mesh = fe.RectangleMesh(fe.Point(1,-1), fe.Point(2,1), 2, 2)\n", "V = fe.FunctionSpace(mesh, 'P', 2)\n", "u = fe.project(f, V)\n", "err = fe.errornorm(f, u, 'L2')\n", "print(err)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, we can now easily compare different type of elements and\n", "mesh resolutions:" ] }, { "cell_type": "code", "execution_count": 103, "metadata": {}, "outputs": [], "source": [ "import fenics as fe\n", "import sympy as sym\n", "x, y = sym.symbols('x[0] x[1]')\n", "\n", "def problem(f, nx=8, ny=8, degrees=[1,2]):\n", " \"\"\"\n", " Plot u along x=const or y=const for Lagrange elements,\n", " of given degrees, on a nx times ny mesh. f is a SymPy expression.\n", " \"\"\"\n", " f = sym.printing.ccode(f)\n", " f = fe.Expression(f, degree=2)\n", " mesh = fe.RectangleMesh(\n", " fe.Point(-1, 0), fe.Point(1, 2), 2, 2)\n", " for degree in degrees:\n", " if degree == 0:\n", " # The P0 element is specified like this in FEniCS\n", " V = fe.FunctionSpace(mesh, 'DG', 0)\n", " else:\n", " # The Lagrange Pd family of elements, d=1,2,3,...\n", " V = fe.FunctionSpace(mesh, 'P', degree)\n", " u = fe.project(f, V)\n", " u_error = fe.errornorm(f, u, 'L2')\n", " print(('||u-f||=%g' % u_error, degree))\n", " comparison_plot2D(\n", " u, f,\n", " n=50,\n", " value=0.4, variation='x',\n", " plottitle='Approximation by P%d elements' % degree,\n", " filename='approx_fenics_by_P%d' % degree,\n", " tol=1E-3)\n", " #fe.plot(u, title='Approx by P%d' % degree)\n", "\n", "if __name__ == '__main__':\n", " # x and y are global SymPy variables\n", " f = 2*x*y - x**16\n", " f = 2*x*y - x**2\n", " problem(f, nx=2, ny=2, degrees=[0, 1, 2])\n", " plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(We note that this code issues a lot of warnings from the `u(point)`\n", "evaluations.)\n", "\n", "We show in [Figure](#fem:approx:fenics:2D:2:fig1)\n", "how $f$ is approximated by P0, P1, and P2 elements\n", "on a very coarse $2\\times 2$ mesh consisting of 8 cells.\n", "\n", "We have also added the result obtained by P2 elements.\n", "\n", "<!-- dom:FIGURE: [fig/approx_fenics_f1.png, width=800 frac=1] Comparison of P0, P1, and P2 approximations (left to right) along a line in a 2D mesh. <div id=\"fem:approx:fenics:2D:2:fig1\"></div> -->\n", "<!-- begin figure -->\n", "<div id=\"fem:approx:fenics:2D:2:fig1\"></div>\n", "\n", "<p>Comparison of P0, P1, and P2 approximations (left to right) along a line in a 2D mesh.</p>\n", "<img src=\"fig/approx_fenics_f1.png\" width=800>\n", "\n", "<!-- end figure -->" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 2 }