.. !split .. _fem:deq:1D:fem1: Computing with finite elements ============================== The purpose of this section is to demonstrate in detail how the finite element method can the be applied to the model problem .. math:: -u''(x) = 2,\quad x\in (0,L),\ u(0)=u(L)=0, with variational formulation .. math:: (u',v') = (2,v)\quad\forall v\in V{\thinspace .} Any :math:`v\in V` must obey :math:`v(0)=v(L)=0` because of the Dirichlet conditions on :math:`u`. The variational formulation is derived in the section :ref:`fem:deq:1D:varform`. Finite element mesh and basis functions --------------------------------------- We introduce a finite element mesh with :math:`N_e` cells, all with length :math:`h`, and number the cells from left to right. Choosing P1 elements, there are two nodes per cell, and the coordinates of the nodes become .. math:: x_{i} = i h,\quad h=L/N_e,\quad i=0,\ldots,N_n-1=N_e{\thinspace .} Any node :math:`i` is associated with a finite element basis function :math:`{\varphi}_i(x)`. When approximating a given function :math:`f` by a finite element function :math:`u`, we expand :math:`u` using finite element basis functions associated with *all* nodes in the mesh. The parameter :math:`N`, which counts the unknowns from 0 to :math:`N`, is then equal to :math:`N_n-1` such that the total number of unknowns, :math:`N+1`, is the total number of nodes. However, when solving differential equations we will often have :math:`N` can be written as .. _Eq:fem:deq:1D:fem:ex1:c: .. math:: \tag{57} -\frac{1}{h}c_{i-1} + \frac{2}{h}c_{i} - \frac{1}{h}c_{i+1} = 2h{\thinspace .} Let us introduce the notation :math:`u_j` for the value of :math:`u` at node :math:`j`: :math:`u_j=u(x_{j})`, since we have the interpretation :math:`u(x_{j})=\sum_jc_j{\varphi}(x_{j})=\sum_j c_j\delta_{ij}=c_j`. The unknowns :math:`c_0,\ldots,c_N` are :math:`u_1,\ldots,u_{N_n-2}`. Shifting :math:`i` with :math:`i+1` in :ref:`(57) ` and inserting :math:`u_i = c_{i-1}`, we get .. _Eq:fem:deq:1D:fem:ex1: .. math:: \tag{58} -\frac{1}{h}u_{i-1} + \frac{2}{h}u_{i} - \frac{1}{h}u_{i+1} = 2h, A finite difference discretization of :math:`-u''(x)=2` by a centered, second-order finite difference approximation :math:`u''(x_i)\approx [D_x D_x u]_i` with :math:`\Delta x = h` yields .. _Eq:_auto29: .. math:: \tag{59} -\frac{u_{i-1} - 2u_{i} + u_{i+1}}{h^2} = 2, which is, in fact, equivalent to :ref:`(58) ` if :ref:`(58) ` is divided by :math:`h`. Therefore, the finite difference and the finite element method are equivalent in this simple test problem. Sometimes a finite element method generates the finite difference equations on a uniform mesh, and sometimes the finite element method generates equations that are different. The differences are modest, but may influence the numerical quality of the solution significantly, especially in time-dependent problems. It depends on the problem at hand whether a finite element discretization is more or less accurate than a corresponding finite difference discretization. .. There will be many examples illustrating this point. .. _fem:deq:1D:comp:elmwise: Cellwise computations (1) ---------------------------------- Software for finite element computations normally employs the cell by cell computational procedure where an element matrix and vector are calculated for each cell and assembled in the global linear system. Let us go through the details of this type of algorithm. All integrals are mapped to the local reference coordinate system :math:`X\in [-1,1]`. In the present case, the matrix entries contain derivatives with respect to :math:`x`, .. math:: A_{i-1,j-1}^{(e)}=\int_{\Omega^{(e)}} {\varphi}_i'(x){\varphi}_j'(x) {\, \mathrm{d}x} = \int_{-1}^1 \frac{d}{dx}{\tilde{\varphi}}_r(X)\frac{d}{dx}{\tilde{\varphi}}_s(X) \frac{h}{2} {\, \mathrm{d}X}, where the global degree of freedom :math:`i` is related to the local degree of freedom :math:`r` through :math:`i=q(e,r)`. Similarly, :math:`j=q(e,s)`. The local degrees of freedom run as :math:`r,s=0,1` for a P1 element. The integral for the element matrix ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There are simple formulas for the basis functions :math:`{\tilde{\varphi}}_r(X)` as functions of :math:`X`. However, we now need to find the derivative of :math:`{\tilde{\varphi}}_r(X)` with respect to :math:`x`. Given .. math:: {\tilde{\varphi}}_0(X)=\frac{1}{2}(1-X),\quad{\tilde{\varphi}}_1(X)=\frac{1}{2}(1+X), we can easily compute :math:`d{\tilde{\varphi}}_r/ dX`: .. math:: \frac{d{\tilde{\varphi}}_0}{dX} = -\frac{1}{2},\quad \frac{d{\tilde{\varphi}}_1}{dX} = \frac{1}{2}{\thinspace .} From the chain rule, .. _Eq:_auto30: .. math:: \tag{60} \frac{d{\tilde{\varphi}}_r}{dx} = \frac{d{\tilde{\varphi}}_r}{dX}\frac{dX}{dx} = \frac{2}{h}\frac{d{\tilde{\varphi}}_r}{dX}{\thinspace .} The transformed integral is then .. math:: A_{i-1,j-1}^{(e)}=\int_{\Omega^{(e)}} {\varphi}_i'(x){\varphi}_j'(x) {\, \mathrm{d}x} = \int_{-1}^1 \frac{2}{h}\frac{d{\tilde{\varphi}}_r}{dX}\frac{2}{h}\frac{d{\tilde{\varphi}}_s}{dX} \frac{h}{2} {\, \mathrm{d}X} {\thinspace .} The integral for the element vector ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The right-hand side is transformed according to .. math:: b_{i-1}^{(e)} = \int_{\Omega^{(e)}} 2{\varphi}_i(x) {\, \mathrm{d}x} = \int_{-1}^12{\tilde{\varphi}}_r(X)\frac{h}{2} {\, \mathrm{d}X},\quad i=q(e,r),\ r=0,1 {\thinspace .} Detailed calculations of the element matrix and vector ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Specifically for P1 elements we arrive at the following calculations for the element matrix entries: .. math:: \tilde A_{0,0}^{(e)} &= \int_{-1}^1\frac{2}{h}\left(-\frac{1}{2}\right) \frac{2}{h}\left(-\frac{1}{2}\right)\frac{h}{2} {\, \mathrm{d}X} = \frac{1}{h}\\ \tilde A_{0,1}^{(e)} &= \int_{-1}^1\frac{2}{h}\left(-\frac{1}{2}\right) \frac{2}{h}\left(\frac{1}{2}\right)\frac{h}{2} {\, \mathrm{d}X} = -\frac{1}{h}\\ \tilde A_{1,0}^{(e)} &= \int_{-1}^1\frac{2}{h}\left(\frac{1}{2}\right) \frac{2}{h}\left(-\frac{1}{2}\right)\frac{h}{2} {\, \mathrm{d}X} = -\frac{1}{h}\\ \tilde A_{1,1}^{(e)} &= \int_{-1}^1\frac{2}{h}\left(\frac{1}{2}\right) \frac{2}{h}\left(\frac{1}{2}\right)\frac{h}{2} {\, \mathrm{d}X} = \frac{1}{h} The element vector entries become .. math:: \tilde b_0^{(e)} &= \int_{-1}^12\frac{1}{2}(1-X)\frac{h}{2} {\, \mathrm{d}X} = h\\ \tilde b_1^{(e)} &= \int_{-1}^12\frac{1}{2}(1+X)\frac{h}{2} {\, \mathrm{d}X} = h{\thinspace .} Expressing these entries in matrix and vector notation, we have .. _Eq:fem:deq:1D:ex1:Ab:elm: .. math:: \tag{61} \tilde A^{(e)} =\frac{1}{h}\left(\begin{array}{rr} 1 & -1\\ -1 & 1 \end{array}\right),\quad \tilde b^{(e)} = h\left(\begin{array}{c} 1\\ 1 \end{array}\right){\thinspace .} Contributions from the first and last cell ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The first and last cell involve only one unknown and one basis function because of the Dirichlet boundary conditions at the first and last node. The element matrix therefore becomes a :math:`1\times 1` matrix and there is only one entry in the element vector. On cell 0, only :math:`{\psi}_0={\varphi}_1` is involved, corresponding to integration with :math:`{\tilde{\varphi}}_1`. On cell :math:`N_e`, only :math:`{\psi}_N={\varphi}_{N_n-2}` is involved, corresponding to integration with :math:`{\tilde{\varphi}}_0`. We then get the special end-cell contributions .. _Eq:fem:deq:1D:ex1:Ab:elm:ends: .. math:: \tag{62} \tilde A^{(e)} =\frac{1}{h}\left(\begin{array}{r} 1 \end{array}\right),\quad \tilde b^{(e)} = h\left(\begin{array}{c} 1 \end{array}\right), for :math:`e=0` and :math:`e=N_e`. In these cells, we have only one degree of freedom, not two as in the interior cells. Assembly ~~~~~~~~ The next step is to assemble the contributions from the various cells. The assembly of an element matrix and vector into the global matrix and right-hand side can be expressed as .. math:: A_{q(e,r),q(e,s)} = A_{q(e,r),q(e,s)} + \tilde A^{(e)}_{r,s},\quad b_{q(e,r)} = b_{q(e,r)} + \tilde b^{(e)}_{r},\quad for :math:`r` and :math:`s` running over all local degrees of freedom in cell :math:`e`. To make the assembly algorithm more precise, it is convenient to set up Python data structures and a code snippet for carrying out all details of the algorithm. For a mesh of four equal-sized P1 elements and :math:`L=2` we have .. code-block:: python vertices = [0, 0.5, 1, 1.5, 2] cells = [[0, 1], [1, 2], [2, 3], [3, 4]] dof_map = [[0], [0, 1], [1, 2], [2]] The total number of degrees of freedom is 3, being the function values at the internal 3 nodes where :math:`u` is unknown. In cell 0 we have global degree of freedom 0, the next cell has :math:`u` unknown at its two nodes, which become global degrees of freedom 0 and 1, and so forth according to the ``dof_map`` list. The mathematical :math:`q(e,r)` quantity is nothing but the ``dof_map`` list. Assume all element matrices are stored in a list ``Ae`` such that ``Ae[e][i,j]`` is :math:`\tilde A_{i,j}^{(e)}`. A corresponding list for the element vectors is named ``be``, where ``be[e][r]`` is :math:`\tilde b_r^{(e)}`. A Python code snippet illustrates all details of the assembly algorithm: .. code-block:: python # A[i,j]: coefficient matrix, b[i]: right-hand side for e in range(len(Ae)): for r in range(Ae[e].shape[0]): for s in range(Ae[e].shape[1]): A[dof_map[e,r],dof_map[e,s]] += Ae[e][i,j] b[dof_map[e,r]] += be[e][i,j] The general case with ``N_e`` P1 elements of length ``h`` has .. code-block:: python N_n = N_e + 1 vertices = [i*h for i in range(N_n)] cells = [[e, e+1] for e in range(N_e)] dof_map = [[0]] + [[e-1, e] for i in range(1, N_e)] + [[N_n-2]] Carrying out the assembly results in a linear system that is identical to :ref:`(56) `, which is not surprising, since the procedures is mathematically equivalent to the calculations in the physical domain. So far, our technique for computing the matrix system have assumed that :math:`u(0)=u(L)=0`. The next section deals with the extension to nonzero Dirichlet conditions.