Unified Implementation of Finite Element Methods involving Jumps and Averages in Matlab

Extracted source text · arXiv 2209.10800

\documentclass[11pt,a4paper]{article}


\usepackage{mathtools}
\usepackage{authblk} % author
\usepackage{algpseudocode,algorithmicx,algorithm}


% --------------- Equation -------------------------------------
\usepackage{mathrsfs}
\usepackage{latexsym,bm}
\usepackage{amsmath,amsfonts,amsmath,amssymb,amsthm}
\usepackage{extarrows}

% -------------- figure, table ----------------------------------------------
\usepackage{graphicx,subfigure,epstopdf,float}
\usepackage{enumerate,cases,multirow}
\usepackage{makecell}
%\usepackage[justification=centering]{caption}
%\usepackage[margin=10pt,font=small,labelfont=bf,labelsep=period]{caption}
\usepackage{caption}

\usepackage{longtable,colortbl,arydshln,threeparttable}
\definecolor{mygray}{gray}{.9}

% -------------- paragraph ----------------------------------------
\usepackage{indentfirst}
\setlength{\parindent}{2em}
%\textwidth =15cm \topmargin =0mm \textheight =22.5cm
%\oddsidemargin=30pt \evensidemargin=30pt
\usepackage[top=25mm,bottom=20mm,left=25mm,right=20mm]{geometry}
%\usepackage{geometry}
%\geometry{left=3.5cm,right=3.5cm,top=3cm,bottom=3cm}
\baselineskip=16mm
\renewcommand{\baselinestretch}{1.3}

% ------------------ hyperref -------------------------------------

\usepackage{cite}
%\usepackage[superscript]{cite}
\newcommand{\upcite}[1]{\textsuperscript{\cite{#1}}} %

\usepackage{listings}

\usepackage{makeidx}        % generate an index, automatically
\usepackage{booktabs}
%\usepackage{color}
\usepackage[bookmarksnumbered,colorlinks,citecolor=red,linkcolor=red,hyperindex,linktocpage=true]{hyperref}



% --------- self defined -----------
\newcommand{\ket}[1]{| #1 \rangle} % |u>
\newcommand{\bra}[1]{\langle #1 |} % <u|
\newcommand{\braket}[2]{ \langle  #1 | #2  \rangle }
\newcommand{\ketbra}[2]{ | #1 \rangle \langle #2 |}
\newcommand{\bb}{\boldsymbol}
\newcommand{\vect}{\boldsymbol}
\newcommand{\littleo}{{\scriptstyle \mathcal{O}}}
\def \d {\mathrm{d}}
\def \e {\mathrm{e}}
\def \i {\mathrm{i}}
\def \D {\Delta}


\newcounter{parentalgorithm}
\newenvironment{subalgorithms}{%
  \refstepcounter{algorithm}%
  %\protected@edef\theparentalgorithm{\thealgorithm}%
  \setcounter{parentalgorithm}{\value{algorithm}}%
  \setcounter{algorithm}{0}%
  \def\thealgorithm{\theparentalgorithm\alph{algorithm}}%
  \ignorespaces
}{%
  \setcounter{algorithm}{\value{parentalgorithm}}%
  \ignorespacesafterend
}
\makeatother



\newtheorem{theorem}{Theorem}[section]
\newtheorem{lemma}{Lemma}[section]
\newtheorem{proposition}{Proposition}[section]
\newtheorem{corollary}{Corollary}[section]
\newtheorem{definition}{Definition}[section]
\newtheorem{example}{\bf Example}[section]
\theoremstyle{remark}
\newtheorem{remark}{\bf Remark}[section]


\renewcommand\figurename{Fig.}
\renewcommand\tablename{Tab.}

\numberwithin{equation}{section}

%% Algorithm, code
\usepackage{algorithm}
\usepackage{listings,xcolor} % ³ÌÐò´úÂë
\usepackage{textcomp}
\lstset{breaklines}
\lstset{extendedchars=false}
\usepackage[framed,numbered,autolinebreaks,useliterate]{mcode} % mcode.sty is needed

\lstset{
language=Matlab,
basicstyle=\scriptsize \ttfamily, % Standardschrift
numbers=left,
numberstyle=\tiny,
numbersep=5pt,
rulesepcolor=\color{red!20!green!20!blue!20},
xleftmargin=20pt,
frame=tb,
framexleftmargin=20pt
}


\begin{document}


\title{\bf Unified Implementation of Adaptive Finite Element Methods in Matlab}
\author{Yue Yu\thanks{terenceyuyue@sjtu.edu.cn}}
\affil{School of Mathematical Sciences, Institute of Natural Sciences, MOE-LSC, Shanghai Jiao Tong University, Shanghai, 200240, P. R. China.}
\date{}
\maketitle

\begin{abstract}
  We provide a unified implementation of the adaptive finite element methods for the Poisson equation in Matlab by combing the use of the software package varFEM. The design idea can be extended to other Galerkin-based methods, for example, the discontinuous Galerkin methods and the virtual element methods.
\end{abstract}



\section{Finite element methods for the Poisson equation} \label{sect:varFEM}

In this section we briefly review the finite element method and introduce the numerical implementation in varFEM, a Matlab software package for the finite element method.
For simplicity, we consider the Poisson equation with the homogeneous Dirichlet boundary conditions.

\subsection{The finite element method for the Poisson equation}

Let $\Omega$ be a bounded Lipschitz domain in $\mathbb{R}^2$ with polygonal boundary $\partial \Omega$. Consider the Poisson equation
\begin{equation}\label{modelP}
\begin{cases}
   - \Delta u = f \quad & \text{in}~~\Omega ,  \\
  u = 0\quad & \text{on}~~\partial \Omega,
\end{cases}
\end{equation}
where $f\in L^2(\Omega)$ is a given function. The continuous variational problem is to find $u \in V: = H_0^1(\Omega )$ such that
\[a(u,v) = \ell(v),\quad v \in V,\]
where
\[a(u,v) = \int_\Omega  \nabla u \cdot \nabla v {\rm d}\sigma, \qquad \ell (v) = \int_\Omega  fv {\rm d}\sigma.\]

For the finite element discretization, we discuss the conforming Lagrange elements. Let $\mathcal{T}_h$ be a shape regular triangulation. The generic element will be denoted as $K$ in the sequel. Define
\[V_h = \{ v \in V: v |_K \in \mathbb{P}_k(K), \quad K \in \mathcal{T}_h \},\]
where $k \le 3$. The discrete problem is to seek $u_h \in V_h$ satisfying
\begin{equation}\label{FEM}
a(u_h,v) = \ell (v), \qquad v \in V_h.
\end{equation}


\subsection{Data structures for triangular meshes} \label{subsec:datastructure}

We adopt the data structures given in $i$FEM \cite{ChenL-iFEM-2009}. All related data are stored in the Matlab structure \mcode{Th}, which is computed by using the subroutine \mbox{FeMesh2d.m} as
\vspace{-0.8cm}
\begin{lstlisting}
Th = FeMesh2d(node,elem,bdStr);
\end{lstlisting}
where the basic data structures \mcode{node} and \mcode{elem} are generated by
\vspace{-0.8cm}
\begin{lstlisting}
[node,elem] = squaremesh([x1 x2 y1 y2], h1, h2);
\end{lstlisting}
For clarity, we take a simple mesh shown in Fig.~\ref{fig:datastructure} as an example.

\begin{figure}[H]
  \centering
  \includegraphics[scale = 0.45]{datastructure}\\
  \caption{Illustration of the data structures}\label{fig:datastructure}
\end{figure}





The triangular meshes are represented by two basic data structures \mcode{node} and \mcode{elem}, where \mcode{node} is an $\mcode{N} \times 2$ matrix with the first and second columns contain $x$- and $y$-coordinates of the nodes in the mesh, and \mcode{elem} is an $\mcode{NT} \times 3$ matrix recording the vertex indices of each element in a counterclockwise order, where \mcode{N} and \mcode{NT} are the numbers of the vertices and triangular elements. For the mesh given in Fig.~\ref{fig:datastructure},
\[\mcode{elem} = \begin{bmatrix} 2 & 4 & 1\\ 3 & 1 & 4 \end{bmatrix}\]

In the current version, we only consider the $\mathbb{P}_k$-Lagrange finite element spaces with $k$ up to 3. In this case, there are two important data structures \mcode{edge} and \mcode{elem2edge}. In the matrix \mcode{edge(1:NE,1:2)}, the first and second rows contain indices of the starting and ending points. The column is sorted in the way that for the $k$-th edge, \mcode{edge(k,1)<edge(k,2)} for $k=1,2,\cdots,\mcode{NE}$. For the given triangulation,
\[\mcode{edge} = \begin{bmatrix}
1	&   2 \\
1   &	3 \\
1   &	4 \\
2   &	4 \\
3   &	4
\end{bmatrix}.\]
The matrix \mcode{elem2edge} establishes the map of local index of edges in each triangle to its global index in matrix \mcode{edge}.
By convention, we label three edges of a triangle such that the $i$-th edge is opposite to the $i$-th vertex. For the given mesh,
\[\mcode{elem2edge} = \begin{bmatrix}
3   &	1 &	4 \\
3   &	5 &	2 \\
\end{bmatrix}.\]
We refer the reader to \url{https://www.math.uci.edu/~chenlong/ifemdoc/mesh/auxstructuredoc.html} for some detailed information.

To deal with boundary integrals, we first extract the boundary edges from \mcode{edge} and store them in matrix \mcode{bdEdge}.
In the input of \mcode{FeMesh2d}, the string \mcode{bdStr} is used to indicate the interested boundary part in \mcode{bdEdge}. For example, for the unit square $\Omega = (0,1)^2$,
\begin{itemize}
  \item \mcode{bdStr = 'x==1'} divides \mcode{bdEdge} into two parts: \mcode{bdEdgeType\{1\}}
gives the boundary edges on $x=1$, and \mcode{bdEdgeType\{2\}} stores the remaining part.
  \item \mcode{bdStr = \{'x==1','y==0'\} } separates the boundary data structure \mcode{bdEdge} into three parts: \mcode{bdEdgeType\{1\}} and \mcode{bdEdgeType\{2\}} give the boundary edges on $x=1$ and $y=0$, respectively, and \mcode{bdEdgeType\{3\}} stores the remaining part.
  \item \mcode{bdStr = []} implies that \mcode{bdEdgeType\{1\} = bdEdge}.
\end{itemize}

We also use \mcode{bdEdgeIdxType} to record the index in matrix \mcode{edge}, and \mcode{bdNodeIdxType} to store the nodes for respective boundary parts. Note that we determine the boundary of interest by the coordinates of the midpoint of the edge, so \mcode{'x==1'} can also be replaced by a statement like \mcode{'x>0.99'}.


\subsection{Implementation of the FEM in varFEM} \label{subsect:implementationFEM}

FreeFEM is a popular 2D and 3D partial differential equations (PDE) solver based on finite element methods \cite{FreeFEM}, which has been used by thousands of researchers across the world. The highlight is that the programming language is consistent with the variational formulation of the underlying PDEs, referred to as the variational formulation based programming in \cite{varFEM}, where we have developed an FEM package in a similar way of FreeFEM using the language of Matlab, named varFEM. The similarity here only refers to the programming style of the main or test script, not to the internal architecture of the software.

Consider the Poisson equation with Dirichlet boundary condition on the unit square. The exact solution is given by
\[u(x,y) = xy(1 - x)(1 - y){\text{exp}}\left(  - 1000((x - 0.5)^2 + (y - 0.117)^2) \right).\]
The PDE data is generated by \mcode{pde = Poissondata\_afem()}.  The function file is simply given as follows.
\vspace{-0.8cm}
\begin{lstlisting}
function uh = varPoisson(Th,pde,Vh,quadOrder)

%% Assemble stiffness matrix
Coef  = 1;
Test  = 'v.grad';
Trial = 'u.grad';
kk = assem2d(Th,Coef,Test,Trial,Vh,quadOrder);

%% Assemble right hand side
% Omega
Coef = pde.f;  Test = 'v.val';
ff = assem2d(Th,Coef,Test,[],Vh,quadOrder);

%% Apply Dirichlet boundary conditions
g_D = pde.g_D;
on = 1;
uh = apply2d(on,Th,kk,ff,Vh,g_D);
\end{lstlisting}

In the above code, the structure \mcode{pde} stores the information of the PDE, including the exact solution \mcode{pde.uexact}, the gradient \mcode{pde.Du}, etc.  We set up the triple \mcode{(Coef,Test,Trial)} for the coefficients, test functions and trial functions in variational form, respectively. It is obvious that \mcode{v.grad} is for $\nabla v$ and \mcode{v.val} is for $v$ itself. The routine \mcode{assem2d.m}  computes the stiffness matrix corresponding to the bilinear form on the two-dimensional region, i.e.
\[A = (a_{ij}),\quad a_{ij} = a(\Phi _i,\Phi _j),\]
where $\Phi _i$ are the global shape functions of the finite element space \mcode{Vh}. The integral of the bilinear form, as
$(\nabla \Phi _i, \nabla \Phi _j)_\Omega$, is approximated by using the Gaussian quadrature formula with \mcode{quadOrder} being the order of accuracy.

We remark that the \mcode{Coef} has three forms:
\begin{enumerate}
  \item A function handle or a constant.
  \item The numerical degrees of freedom of a finite element function.
  \item A coefficient matrix \mcode{CoefMat} resulting from the numerical integration.
\end{enumerate}
In the computation, the first two forms in fact will be transformed to the third one. Given a function $c(p)$, where $p = (x,y)$, the coefficient matrix is in the following form
\begin{equation}\label{coefmat}
\mcode{CoefMat} = \begin{bmatrix}
c(p_1^1) & c(p_2^1) & \cdots  & c(p_{n_g}^1) \\
c(p_1^2) & c(p_2^2) & \cdots  & c(p_{n_g}^2) \\
\vdots   & \vdots   & \vdots  & \vdots \\
c(p_1^{\text{NT}}) & c(p_2^{\text{NT}}) & \cdots  & c(p_{n_g}^{\text{NT}}) \\
\end{bmatrix}.
\end{equation}
Here, $p_1^i, p_2^i, \cdots, p_{n_g}^i$ are the quadrature points on the element $K_i$.

We display the numerical result in Fig.~\ref{fig:AFEM0} for the uniform triangular mesh with $h_1 = h_2 = 1/50$ generated by
\vspace{-0.8cm}
\begin{lstlisting}
[node,elem] = squaremesh([0 1 0 1], 1/50, 1/50);
\end{lstlisting}

\begin{figure}[!htb]
  \centering
  \includegraphics[scale=0.45]{AFEM0}\\
  \caption{Numerical and exact solutions with $h_1 = h_2 = 1/50$}\label{fig:AFEM0}
\end{figure}



\section{Adaptive finite element methods for the Poisson equation} \label{sect:varFEM}

In this section we briefly introduce the ingredients of the adaptive finite element method, and provide the overall structure of the implementation.

\subsection{The ingredients of the adaptive FEM}

For the conforming FEM, one can establish the following residual based a-posteriori error estimate
\[\| u - u_h \|_1 \lesssim \eta (u_h),\]
where $\eta  = \Big( \sum\limits_{K \in \mathcal{T}_h} \eta _K^2  \Big)^{1/2}$ and
\begin{equation}\label{localErrorIndicator}
\eta _K^2 = h_K^2\| f + \Delta u_h\|_{0,K}^2 + \sum\limits_{e \subset \partial K} h_e\| [\partial _{n_e}u_h] \|_{0,e}^2
\end{equation}
are referred to as the global and local error indicators, respectively. Here, $f + \Delta u_h$ is the interior residual, and
\[[\partial _{n_e}u_h] = \partial _{n_e}u_h |_{K_1} - \partial _{n_e}u_h |_{K_2}\]
is the jump term, where $n_e$ is the unit outer normal vector of the target element $K = K_1$. Note that for an edge $e$ on the domain boundary $\partial \Omega$, we assume $\partial _{n_e}u_h |_{K_2} = 0$.

Standard adaptive algorithms based on the local mesh refinement can be written as loops of the form
\[{\bf SOLVE} \to {\bf ESTIMATE} \to {\bf MARK} \to {\bf REFINE}.\]
Given an initial subdivision $\mathcal{T}_0$, to get $\mathcal{T}_{k+1}$ from $\mathcal{T}_k$ we
first solve the FEM problem under consideration to get the numerical solution $u_k$ on $\mathcal{T}_k$. The error is then estimated
by using $u_k$, $\mathcal{T}_k$ and the a posteriori error bound $\eta(u_h)$. The local error bound $\eta_K$ is used to mark a subset $\mathcal{M}$
of elements in $\mathcal{T}_k$ for refinement. The marked triangles and possible more neighboring elements are refined
in such a way that the subdivision meets certain conditions, for example, the resulting triangular mesh is still shape
regular. The above procedures are included in the test script with an overview given as follows
\vspace{-0.8cm}
\begin{lstlisting}
for k = 1:maxIt
    % Step 1: SOLVE
    Th = FeMesh2d(node,elem,bdStr);
    uh = varPoisson(Th,pde,Vh,quadOrder);
    % Step 2: ESTIMATE
    eta = Poisson_indicator(Th,uh,pde,Vh,quadOrder);
    % Step 3: MARK
    elemMarked = mark(elem,eta,theta);
    % Step 4: REFINE
    [node,elem] = bisect(node,elem,elemMarked);
end
\end{lstlisting}

Three important modules are involved: the local error indicator in Step 2, the marking algorithm in Step 3 and the local refinement algorithm in Step 4. We employ the D\"{o}rfler marking strategy to select the subset of elements and then use the newest vertex method to refine the mesh. Note that the subroutines \mcode{mark.m} and \mcode{bisect.m} are extracted from $i$FEM \cite{ChenL-iFEM-2009} with some minor modifications. In this article, we are only concerned with the computation of the error indicator. For the later two steps, we refer the reader to \cite{ChenL-iFEM-2009} for details on the implementation.

\subsection{The unified implementation of the error indicator}

\subsubsection{The computation of the elementwise residuals}

With the help of varFEM, the first term $h_K^2\| f + \Delta u_h\|_{0,K}^2$ in \eqref{localErrorIndicator} can be simply computed as
\vspace{-0.8cm}
\begin{lstlisting}
%% elementwise residuals
fc = interp2dMat(pde.f,'.val',Th,Vh,quadOrder);
uxxc = interp2dMat(uh,'.dxx',Th,Vh,quadOrder);
uyyc = interp2dMat(uh,'.dyy',Th,Vh,quadOrder);
Coef = (fc + uxxc + uyyc).^2;
[~,elemIh] = integral2d(Th,Coef,Vh,quadOrder);
elemRes = diameter.^2.*elemIh;
\end{lstlisting}
In the above code, \mcode{elemIh} stores all the local error indicators $[\eta_{K_1}, \cdots, \eta_{K_{\text{NT}}}]^T$; The function \mcode{interp2dMat} is used to generate the coefficient matrix (see \eqref{coefmat}). It is obvious that the coefficient matrix of $(f + \Delta u_h)^2$ is \mcode{(fc+uxxc+uyyc).^2}, where \mcode{fc}, \mcode{uxxc} and \mcode{uyyc} are the coefficient matrices of $f$, $\partial_{xx}u_h$ and $\partial_{yy}u_h$, respectively.

In what follows, we focus on the unified implementation of the jump term or jump integral $\sum\limits_{e \subset \partial K} h_e\| [\partial _{n_e}u_h] \|_{0,e}^2$. We remark that any other type of jump terms can be easily adapted or designed accordingly as will be seen.


\subsubsection{The elementwise interior and exterior indices of the quadrature points}


The integral over $e$ is calculated by using the one-dimensional Gaussian numerical integration formula
\[\int_e f \d s = |e|( w_1 f(p_1) + w_2 f(p_2) + \cdots + w_{n_g} f(p_{n_g})),\]
where $w_i$ and $p_i$ are the quadrature weights and points on $e$, and $n_g$ is the number of the quadrature points. Note that the endpoints of $e$ are not included.


\begin{figure}[H]
  \centering
  \includegraphics[scale=0.5]{integrationconnec1.png}\\
  \caption{Illustration of the quadrature points}\label{fig:quadpoints}
\end{figure}

\begin{enumerate}
  \item Indexing rule for the quadrature points. In Fig.~\ref{fig:quadpoints} the direction of the edge $e$ is specified by the arrow. In the implementation, the direction is determined by the data structure \mcode{edge} which satisfies \mcode{edge(k,1)<edge(k,2)}. For the first edge $e = e_1$ in \mcode{edge}, the quadrature points will be numbered by $1,2,\cdots, n_g$ or $1:n_g$ for short when restricted to the left triangle. Similarly, the quadrature points on the $i$-th edge $e_i$ will be numbered by
  \[p_i^L = (1:n_g) + (i-1)n_g , \qquad i = 1,2,\cdots, \mcode{NE}.\]
  The right-hand restrictions are accordingly numbered as
  \[p_i^R = p_i^L + n_g\cdot \mcode{NE}, \qquad i = 1,2,\cdots, \mcode{NE}.\]
  \item Elementwise sign matrix. To characterize the direction of an edge on an element $K$, one can introduce the elementwise sign matrix
  \vspace{-0.8cm}
  \begin{lstlisting}
% sign of elementwise edges
sgnelem = sign([elem(:,3)-elem(:,2), elem(:,1)-elem(:,3), elem(:,2)-elem(:,1)]);
  \end{lstlisting}
  In some cases, it is better to restore the positive sign for edges on the boundary of the domain, or one can set the signs to be zero for later use:
\vspace{-0.8cm}
\begin{lstlisting}
E = false(NE,1); E(bdEdgeIdx) = 1; sgnbd = E(elem2edge);
sgnelem(sgnbd) = 0;
  \end{lstlisting}
  Here, the data structures \mcode{elem2edge} and \mcode{bdEdgeIdx} have been introduced in Subsect.~\ref{subsec:datastructure}.

  \item Elementwise interior indices. According to the indexing rule, one easily obtains the interior indices of the first sides of all triangles:
    \vspace{-0.8cm}
  \begin{lstlisting}
% first side
e1 = elem2edge(:,1); sgn1 = sgnelem(:,1);
id = repmat(1:ng, NT,1);
id(sgn1<0,:) = repmat((ng:-1:1)+NE*ng, sum(sgn1<0), 1);
elemQuade1 = id + (e1-1)*ng;
  \end{lstlisting}
  Note that for the edges with positive and zero signs the natural indices are $1:ng$, while for those with negative signs the natural indices are reversed. One can similarly introduce the interior indices of other sides, and hence give the elementwise interior indices
   \vspace{-0.8cm}
  \begin{lstlisting}
elemQuadM = [elemQuade1,elemQuade2,elemQuade3];
  \end{lstlisting}
  where the letter \mcode{M} stands for ``Minus''.

  \item Elementwise exterior indices. To compute the jump, we also need to introduce the elementwise exterior indices \mcode{elemQuadP}, where \mcode{P} is for ``Plus''.  Given an edge, assume that the interior indices of the quadrature points are $i_1,\cdots,i_{n_g}$, and the exterior indices are $i'_1,\cdots,i'_{n_g}$. Since $|i_k-i'_k| = n_g\cdot \mcode{NE}$, one just needs to subtract $n_g\cdot \mcode{NE}$ for those indices in \mcode{elemQuadM} greater than  $n_g\cdot \mcode{NE}$, and add $n_g\cdot \mcode{NE}$ to those indices less than $n_g\cdot \mcode{NE}$.
     \vspace{-0.8cm}
  \begin{lstlisting}
index = ( elemQuadM > ng*NE ) ;
elemQuadP = elemQuadM + (-ng*NE)*index + ng*NE*(~index);
  \end{lstlisting}
  Obviously, for edges on the domain boundary, the exterior indices are greater than $n_g \cdot \mcode{NE}$.
\end{enumerate}



\subsubsection{The elementwise interior and exterior evaluations}

Given $u_h$, we now determine the interior evaluations \mcode{elemuhM} and the exterior evaluations \mcode{elemuhP} of $u_h$ corresponding to \mcode{elemQuadM} and \mcode{elemQuadP}. To this end, one can compute the evaluations of the basis functions at the quadrature points along the boundary $\partial K$.
Given a triangle $K$, let $\lambda_1$, $\lambda_2$ and $\lambda_3$ be the barycentric coordinate functions. Since $\lambda_i$ are usually used to construct the basis functions for the FEMs, one can first specify the evaluations of $\lambda_i$ and the derivatives $\partial_x \lambda_i$ and $\partial_y \lambda_i$ at the quadrature points.

Given some points $p_1,p_2,\cdots,p_{n_G}$ on the triangle $K$, for example, the quadrature points for the integration of the bilinear forms. In varFEM, the evaluations of $\lambda_i$ are stored in the following form
\[ \begin{bmatrix}
\lambda_1(p_1) & \lambda_2(p_1) & \lambda_3(p_1) \\
\lambda_1(p_2) & \lambda_2(p_2) & \lambda_3(p_2) \\
\vdots         & \vdots         & \vdots         \\
\lambda_1(p_{n_G}) & \lambda_2(p_{n_G}) & \lambda_3(p_{n_G}) \\
\end{bmatrix}.\]
At this time, one can choose $p_j$ as the quadrature points along the boundary $\partial K$. Let $\partial K = e_1 \cup e_2 \cup e_3$. The quadrature points on $e_i$ are denoted by $p_{1,e_i},\cdots, p_{n_g,e_i}$.
In this case, $n_G = 3n_g$, and one can specify the values $\lambda_i(p_j)$ by using the 1-D quadrature points $r_1, r_2, \cdots, r_{n_g}$. The Gaussian quadrature points and weights $r=[r_1,r_2,\cdots,r_{n_g}]$ and $w = [w_1,w_2,\cdots,w_{n_g}]$ on $[0,1]$ are given by \mcode{quadpts1.m}:
\vspace{-0.8cm}
\begin{lstlisting}
[lambda1d,weight1d] = quadpts1(4);  ng = length(weight1d);
[~,id] = sort(lambda1d(:,1));
lambda1d = lambda1d(id,:); weight1d = weight1d(id);
\end{lstlisting}
Here we use \mcode{sort} to guarantee $r_1<r_2<\cdots<r_{n_g}$. Note that
\[\mcode{lambda1d} = \begin{bmatrix} r_1 & r_{n_g} \\
r_2 & r_{n_g-1} \\
\vdots & \vdots \\
r_{n_g} & r_1 \end{bmatrix},\]
and all the row sums are 1, i.e. $r_1 + r_{n_g} = r_2 + r_{n_g-1} = \cdots = 1$. By the definition of the barycentric coordinate functions,  the coordinates $(\lambda_1,\lambda_2, \lambda_3 = 1-\lambda_1-\lambda_2)$ on the three sides of $K$ are:
\begin{itemize}
  \item 1-th side: $(0,r_{n_g}, r_1), (0, r_{n_g-1}, r_2), \cdots, (0,r_1, r_{n_g})$;
  \item 2nd side: $(r_1,0,r_{n_g}), (r_2,0, r_{n_g-1}),\cdots,(r_{n_g},0, r_1)$;
  \item 3rd side: $(r_{n_g}, r_1,0), (r_{n_g-1},r_2,0),\cdots,(r_1, r_{n_g}, 0)$.
\end{itemize}
Therefore the $n_G = 3n_g$ points can be given as
\vspace{-0.8cm}
  \begin{lstlisting}
function [lambdaBd,weightBd] = quadptsBd(order)
%%Gauss quadrature points along the boundary of triangles

% quadrature on [0,1]
[lambda1d,weight1d] = quadpts1(order);  ng = length(weight1d);
[~,id] = sort(lambda1d(:,1));
lambda1d = lambda1d(id,:); weight1d = weight1d(id);
% quadrature on each side
lambdae1 = [zeros(ng,1), lambda1d(:,2), lambda1d(:,1)];
lambdae2 = [lambda1d(:,1), zeros(ng,1), lambda1d(:,2)];
lambdae3 = 1 - lambdae1 - lambdae2;
% quadrature along the boundary of each element
lambdaBd = [lambdae1; lambdae2; lambdae3];
weightBd = repmat(weight1d,1,3);
  \end{lstlisting}


With these quadrature points one can construct the evaluations of the basis functions. The details are omitted. Please refer to \mcode{Base2dBd.m} for implementation details. For the $\mathbb{P}_1$ element, the basis functions are $\phi_i = \lambda_i$, and the elementwise interior evaluations of $u_h$ are given as follows
\vspace{-0.8cm}
  \begin{lstlisting}
elemuhM = zeros(NT,3*ng);
for p = 1:3*ng
    % interpolation at the p-th quadrture point
    for i = 1:length(phi)
        base = phi{i};
        elemuhM(:,p) = elemuhM(:,p) + uh(elem2dof(:,i)).*base(:,p);
    end
end
  \end{lstlisting}
For other types of finite elements, one just needs to modify the basis functions \mcode{base}. For evaluations of the derivatives, for example, $\partial_xu_h$, one needs to change \mcode{base} to the partial derivative $\partial_x$ of the basis functions. The elementwise exterior evaluations of $u_h$ can be computed as
\vspace{-0.8cm}
  \begin{lstlisting}
uhI = zeros(2*NE*ng,1);
uhI(elemQuadM) = elemuhM;
elemuhP = uhI(elemQuadP);
  \end{lstlisting}
We remark that the exterior evaluations on the domain boundary are zero since the corresponding indices in \mcode{elemQuadM} are less than or equal to $n_g \cdot \mcode{NE}$.

The above discussions are summarized in a subroutine:
\vspace{-0.8cm}
  \begin{lstlisting}
function [elemuhM,elemuhP] = elem2edgeInterp(wStr,Th,uh,Vh,quadOrder)
  \end{lstlisting}
One can obtain the evaluations of $u_h$, $\partial_x u_h$ and $\partial_y u_h$ by setting \mcode{wStr} as \mcode{.val}, \mcode{.dx} and \mcode{.dy}, respectively. For convenience, we also return the elementwise unit normal vectors on the quadrature points:
\vspace{-0.8cm}
  \begin{lstlisting}
%% elementwise unit normal vectors of edges on quadrature points
if nargout==4
    rep = ones(1,ng);
    z1 = node(elem(:,1),:);
    z2 = node(elem(:,2),:);
    z3 = node(elem(:,3),:);
    e1 = z2-z3;   e1 = e1./vecnorm(e1,2,2); % -e1
    e2 = z3-z1;   e2 = e2./vecnorm(e2,2,2);
    e3 = z1-z2;   e3 = e3./vecnorm(e3,2,2);
    elemQuadnx = -[e1(:,2*rep),e2(:,2*rep),e3(:,2*rep)];
    elemQuadny = [e1(:,rep),e2(:,rep),e3(:,rep)];
end
  \end{lstlisting}


\subsubsection{The computation of the jump integral}

With the above preparations, we are able to compute the jump integral
\[
h_e\| [\partial _{n_e}u_h] \|_{0,e}^2
 = h_e\int_e ( [\partial _xu_h]n_x + [\partial _yu_h]n_y )^2 \text{d}s
 = h_e \cdot |e|\sum\limits_{i = 1}^{n_g} w_i ( [\partial _xu_h]n_x + [\partial _yu_h]n_y )^2 (p_{i,e}).
\]

\begin{itemize}
  \item The first step is to compute the elementwise interior and exterior evaluations of $\partial _xu_h$ and $\partial _yu_h$:
  \vspace{-0.8cm}
  \begin{lstlisting}
%% elementwise interior and exterior evaluations at quadrature points
[elemuhxM,elemuhxP,elemnx,elemny] = elem2edgeInterp('.dx',Th,uh,Vh,quadOrder);
[elemuhyM,elemuhyP] = elem2edgeInterp('.dy',Th,uh,Vh,quadOrder);
  \end{lstlisting}
  \item The elementwise jumps are
  \vspace{-0.8cm}
  \begin{lstlisting}
elem2Jumpx = elemuhxM - elemuhxP;
elem2Jumpy = elemuhyM - elemuhyP;
  \end{lstlisting}
  \item The jump integral can be computed by looping of triangle sides:
    \vspace{-0.8cm}
  \begin{lstlisting}
elemJump = zeros(NT,1);
[~,weight1d] = quadpts1(quadOrder);
ng = length(weight1d);
for i = 1:3 % loop of triangle sides
    hei = he(elem2edge(:,i));
    id = (1:ng)+(i-1)*ng;
    cei = hei;
    neix = elemnx(:,id);
    neiy = elemny(:,id);
    Jumpnx = elem2Jumpx(:,id).*neix;
    Jumpny = elem2Jumpy(:,id).*neiy;
    Jumpn = (Jumpnx+Jumpny).^2;
    elemJump = elemJump + cei.*hei.*(Jumpn*weight1d(:));
end
  \end{lstlisting}
  \item We finally get the local error indicators
  \vspace{-0.8cm}
  \begin{lstlisting}
%% Local error indicator
eta = (abs(elemRes) + elemJump).^(1/2);
  \end{lstlisting}
Here we add \mcode{abs} since the third-order quadrature rule has negative weights (???).
\end{itemize}


\section{Numerical example}

Consider the example given in Subsect.~\ref{subsect:implementationFEM}. We employ the D\"{o}rfler marking strategy with parameter $\theta = 0.4$ to select the subset of elements for refinement.

\begin{figure}[!htb]
  \centering
  \subfigure[]{\includegraphics[scale=0.35]{Amesh0}}
  \subfigure[]{\includegraphics[scale=0.35]{Amesh1}}
  \subfigure[]{\includegraphics[scale=0.35]{Amesh2}}\\
  \caption{The initial and the final adapted meshes. (a) The initial mesh;
  (b) After 20 refinement steps; (c) After 30 refinement steps}\label{Amesh}
\end{figure}

The initial mesh and the final adapted meshes after 20 and 30 refinement steps are presented in Fig.~\ref{Amesh}~(a-c),
respectively, where the $\mathbb{P}_1$ element is used. We also plot the adaptive approximation in Fig.~\ref{fig:solutionP1}, which almost coincides with the exact solution. The convergence rates of the error estimators and the errors in $H^1$ norm are shown in Fig.~\ref{AFEMrate}, from which we observe the optimal rates of convergence as predicted by the theory. The full code is available from varFEM package (\url{https://github.com/Terenceyuyue/varFEM}). The subroutine \mcode{PoissonVEM\_indicator.m} is used to compute the local indicator and the test script is \mcode{main\_Poisson\_afem.m}.

\begin{figure}[!htb]
  \centering
  \includegraphics[scale=0.45,trim = 40 20 40 20,clip]{AFEM1}\\
  \caption{The exact and numerical solutions}\label{fig:solutionP1}
\end{figure}


\begin{figure}[!htb]
  \centering
  \subfigure[$\mathbb{P}_1$]{\includegraphics[scale=0.35]{AFEM_rateP1}}
  \subfigure[$\mathbb{P}_2$]{\includegraphics[scale=0.35]{AFEM_rateP2}}
  \subfigure[$\mathbb{P}_3$]{\includegraphics[scale=0.35]{AFEM_rateP3}}\\
  \caption{The convergence rates of the error estimators and the errors in $H^1$ norm}\label{AFEMrate}
\end{figure}


\section{Concluding remarks} \label{sec:conclude}

In this paper, a unified implementation of the adaptive finite element methods was presented for the model problem, which combines the use of the Matlab software package varFEM. The design idea can be extended to other types of Galerkin methods.




\begin{thebibliography}{1}

\bibitem{ChenL-iFEM-2009}
L.~Chen.
\newblock {iFEM}: an integrated finite element method package in {MATLAB}.
\newblock Technical report, University of California at Irvine, 2009.

\bibitem{FreeFEM}
F.~Hecht.
\newblock New development in freefem++.
\newblock {\em J. Numer. Math.}, 20(3-4):251--265, 2012.

\bibitem{varFEM}
Y.~Yu.
\newblock {varFEM}: variational formulation based programming for finite
  element methods in {M}atlab.
\newblock {\em \rm arXiv:2206.06918}, 2022.

\end{thebibliography}




\end{document}