This paper was converted on www.awesomepapers.org from LaTeX by an anonymous user.
Want to know more? Visit the Converter page.

Implementation of Polygonal Mesh Refinement in MATLAB

Yue Yu
terenceyuyue@sjtu.edu.cn
Abstract

We present a simple and efficient MATLAB implementation of the local refinement for polygonal meshes. The purpose of this implementation is primarily educational, especially on the teaching of adaptive virtual element methods.

Keywords Mesh refinement  \cdot Polygonal meshes  \cdot MATLAB implementation  \cdot Virtual element method  \cdot A posteriori error estimate

1 Introduction

The virtual element method (VEM), first introduced in [1], is a generalization of the standard finite element method on general polytopal meshes. In the past few years, people have witnessed rapid progress of virtual element methods (VEMs) for numerically solving various partial differential equations, see for examples [2, 3, 4, 5]. One can also refer to [6] for a transparent MATLAB implementation of the conforming virtual element method for Poisson equation. Due to the large flexibility of the meshes, researchers gradually turn their attention to the posterior error analysis of the VEMs and have made some progress in recent years (cf. [7, 8, 9, 10, 11]).

As we all know, standard adaptive algorithms based on the local mesh refinement can be written as the following loops

𝐒𝐎𝐋𝐕𝐄𝐄𝐒𝐓𝐈𝐌𝐀𝐓𝐄𝐌𝐀𝐑𝐊𝐑𝐄𝐅𝐈𝐍𝐄.{\bf SOLVE}\to{\bf ESTIMATE}\to{\bf MARK}\to{\bf REFINE}.

Given an initial polygonal subdivision 𝒯0\mathcal{T}_{0}, to get 𝒯k+1\mathcal{T}_{k+1} from 𝒯k\mathcal{T}_{k} we first solve the VEM problem under consideration to get the numerical solution uku_{k} on 𝒯k\mathcal{T}_{k}. The error is then estimated by using uku_{k}, 𝒯k\mathcal{T}_{k} and the a posteriori error bound. And the local error bound is used to mark a subset of elements in 𝒯k\mathcal{T}_{k} for refinement. The marked polygons and possible more neighboring elements are refined in such a way that the subdivision meets certain conditions, such as shape regularity. In the implementation, it is usually time-consuming to write a mesh refinement routine since we need to carefully design the rule for dividing the marked elements to get a refined mesh of high quality.

In this paper, we are intended to present an efficient MATLAB implementation of the mesh refinement for polygonal meshes. To the best of our knowledge, this is the first publicly available implementation of the polygonal mesh refinement algorithms. We divide elements by connecting the midpoint of each edge to its barycenter, which may be the most natural partition frequently used in VEM papers, referred to as 4-node subdivision in this context. To remove small edges, some additional neighboring polygons of the marked elements are included in the refinement set by requiring the one-hanging-node rule: limit the mesh to have just one hanging node per edge. We discuss the implementation step by step and give an application in the posteriori error analysis for Poisson equation in the last section. The current implementation or the 4-node subdivision requires that the barycenter is an internal point of each element.

2 Strategy for removing small edges

For polygonal meshes, a natural mesh refinement strategy may be the 4-node subdivision as illustrated in Fig. 1 (a) given in [8], where a target element is divided by connecting the midpoint of each edge to the barycenter. For the element with hanging node QQ, it is better to add a new edge from QQ to the barycenter instead of bisecting two edges PQPQ and QRQR, otherwise degenerate triangles may appear. We refer to this treatment as the admissible bisection. As shown in Fig. 1 (b), the triangle Δ123\Delta 123 is generated by bisecting PQPQ and QRQR and the new triangle Δ567\Delta 567 is obtained from Δ123\Delta 123 in a similar manner. According to the properties of triangles, the barycenter z7z_{7} lies on the median line e34e_{34} and |e47|:|e73|=1:2|e_{47}|:|e_{73}|=1:2, which implies that 7>3\angle 7>\angle 3 and hence a degenerate triangle will appear if we continue with the “two-edge bisection”. On the other hand, the admissible bisection leads to a simpler polygonal mesh.

Refer to caption
Refer to caption
Fig. 1: Edge bisection. (a) Admissible bisection: add a new edge from hanging node to the barycenter; (b) Two-edge bisection: cut two edges with hanging node as endpoint

A mesh refinement function has the following form

[node,elem] = PolyMeshRefine(node,elem,idElemMarked).

Here, node and elem are two basic data structure, storing the coordinates of nodes and the connectivity list, respectively, and the array idElemMarked gives the index of marked elements. Generally, the elements to be refined are more than marked elements. By imposing no restriction on the number of hanging nodes on a single edge, we are at risk of violating the “no short edge” assumption (cf. [8]). Although this requirement does not seem to be necessary for the VEMs to remain accurate and stable in practice, it is still expected to produce high quality meshes without small edges. To this end, some additional cells should be included in the refinement set.

Refer to caption
Refer to caption
Fig. 2: (a) Initial mesh; (b) Refinement of only the marked elements.

Given an initial mesh shown in Fig. 2 (a), we find that the short edge problem reduces to the refinement of the two adjacent polygons ① and ⑤ as a typical case.

  • Suppose that ① is a marked element: idElemMarked = 1. If we only refine the marked element as shown in Fig. 2 (b) and proceed to divide the small cells in the lower right corner, then small edges appear in ⑤ due to the frequently added hanging nodes. This phenomenon also happens in the two-edge bisection illustrated in Fig. 1 (b), which is resolved by using the admissible bisection.

  • To avoid the occurrence of short edges, we further refine the adjacent polygon ⑤, yielding a new mesh given in Fig. 3. That is, for two elements K1K_{1} and K2K_{2} sharing an edge ee, we refine both K1K_{1} and K2K_{2} if K1K_{1} is in the refinement set and one endpoint of ee is the hanging node of K2K_{2}.

  • For elements in Fig. 2 (a), ⑤ and ⑦ can be viewed as ① and ⑤, respectively. For the same reason, we also need to partition the polygon ⑦.

  • Repeating the above procedure, one can collect all the new elements for refinement and hence avoid producing small edges since the above treatment ensures that each edge in the resulting mesh contains at most one hanging node (a midpoint of the collinear edge).

Refer to caption
Fig. 3: Refine both ① and ⑤

In the following, an edge is called nontrivial if one of its endpoints is hanging node.

3 Implementation

3.1 Data structure

We first discuss the data structure to represent polygonal meshes so as to facilitate the refinement procedure. The idea stems from the treatment of triangulation in iFEM (cf. [12]), which is generalized to polygonal meshes with certain modifications.

We will only maintain and update two basic data structure node and elem. In the node matrix node, the first and second rows contain xx- and yy-coordinates of the nodes in the mesh. The cell array elem records the vertices of each element with a counterclockwise order.

We always use the symbols N, NT, and NE to represent the number of nodes, triangles, and edges, respectively. The following code will build auxiliary data structure.

1%% Get auxiliary data
2NT = size(elem,1);
3if ¬\negiscell(elem), elem = mat2cell(elem,ones(NT,1),length(elem(1,:))); end
4% centroid
5centroid = zeros(NT,2); diameter = zeros(NT,1);
6s = 1;
7for iel = 1:NT
8 index = elem{iel};
9 verts = node(index,:); verts1 = verts([2:end,1],:);
10 area_components = verts(:,1).*verts1(:,2)-verts1(:,1).*verts(:,2);
11 ar = 0.5*abs(sum(area_components));
12 centroid(s,:) = sum((verts+verts1).*repmat(area_components,1,2))/(6*ar);
13 diameter(s) = max(pdist(verts));
14 s = s+1;
15end
16if max(diameter)<4*eps, error('The mesh is too dense'); end
17% totalEdge
18shiftfun = @(verts) [verts(2:end),verts(1)];
19T1 = cellfun(shiftfun, elem, 'UniformOutput', false);
20v0 = horzcat(elem{:})'; v1 = horzcat(T1{:})';
21totalEdge = sort([v0,v1],2);
22% edge, elem2edge
23[edge, i1, totalJ] = unique(totalEdge,'rows');
24elemLen = cellfun('length',elem);
25elem2edge = mat2cell(totalJ',1,elemLen)';
26% edge2elem
27Num = num2cell((1:NT)'); Len = num2cell(elemLen);
28totalJelem = cellfun(@(n1,n2) n1*ones(n2,1), Num, Len, 'UniformOutput', false);
29totalJelem = vertcat(totalJelem{:});
30[¬\neg, i2] = unique(totalJ(end:-1:1),'rows');
31i2 = length(totalEdge)+1-i2;
32edge2elem = totalJelem([i1,i2]);
33% neighbor
34neighbor = cell(NT,1);
35for iel = 1:NT
36 index = elem2edge{iel};
37 ia = edge2elem(index,1); ib = edge2elem(index,2);
38 ia(ia==iel) = ib(ia==iel);
39 neighbor{iel} = ia';
40end
41% number
42N = size(node,1); NE = size(edge,1);

In the edge matrix edge, the first and second rows contain indices of the starting and ending points. The row of edge is sorted in such a way that edge(k,1)<edge(k,2) for the kk-th edge. The cell array elem2edge establishes the map of local index of edges in each polygon to its global index in matrix edge. We also use the cell array neighbor to record the neighboring polygons for each element. The coordinates of barycenters of all elements are stored in matrix centroid.

Algorithm 1 Find additional elements for refinement
  • -

    Initialize idElemMarkedNew and idElemNew as the set of marked elements idElemMarked.

  • -

    Find the adjacent elements, stored in idElemNewAdj, of new added elements in idElemNew and obtain the numbers of edges of all elements in idEdgeMarkedNew.

  • -

    Update idElemNew by looping for elements in idElemNewAdj: Determine the index numbers of nontrivial edges in current element, denoted by idEdgeDg. If idEdgeDg intersects with idEdgeMarkedNew, then the current element needs to be refined.

  • -

    Update idElemMarkedNew by adding the elements in idElemNew.

  • -

    If idElemNew is empty, stop; otherwise, go back to the first step. All the additional elements are then given by idElemAdjRefine = setdiff(idElemMarkedNew,idElemMarked).

3.2 The additional elements for refinement

We now collect the additional elements to be refined. Let idElemMarked record the marked elements and idElemNew record the newly added elements in each step. The vector idElemMarkedNew collects the marked elements and all the additional elements up to the current step. The discussion in Sect. 2 is summarized in Algorithm 1.

We list the code below and explain it briefly.

1%% Find the additional elements to be refined
2% initialized as marked elements
3idElemMarkedNew = idElemMarked; % marked and all new elements
4idElemNew = idElemMarked; % new elements
5while ¬\negisempty(idElemNew)
6 % adjacent polygons of new elements
7 idElemNewAdj = unique(horzcat(neighbor{idElemNew}));
8 idElemNewAdj = setdiff(idElemNewAdj,idElemMarkedNew);
9 % edge set of new marked elements
10 idEdgeMarkedNew = unique(horzcat(elem2edge{idElemMarkedNew}));
11 % find the adjacent elements to be refined
12 nElemNewAdj = length(idElemNewAdj);
13 isRefine = false(nElemNewAdj,1);
14 for s = 1:nElemNewAdj
15 % current element
16 iel = idElemNewAdj(s);
17 index = elem{iel}; indexEdge = elem2edge{iel}; Nv = length(index);
18 % local logical index of elements with hanging nodes
19 v1 = [Nv,1:Nv-1]; v0 = 1:Nv; v2 = [2:Nv,1]; % left,current,right
20 p1 = node(index(v1),:); p0 = node(index(v0),:); p2 = node(index(v2),:);
21 err = sqrt(sum((p0-0.5*(p1+p2)).^2,2));
22 ism = (err<eps); % is midpoint
23 % start the next loop if no hanging nodes exist
24 if sum(ism)<1, continue; end
25 % index numbers of edges connecting hanging nodes in the
26 % adjacent elements to be refined
27 idEdgeDg = unique(indexEdge([v1(ism),v0(ism)]));
28 % whether or not the above edges are in the edge set of new marked elements
29 if intersect(idEdgeDg, idEdgeMarkedNew), isRefine(s) = true; end
30 end
31 idElemNew = idElemNewAdj(isRefine);
32 idElemMarkedNew = unique([idElemMarkedNew(:); idElemNew(:)]);
33end
34idElemAdjRefine = setdiff(idElemMarkedNew,idElemMarked);

The strategy in Sect. 2 ensures that each edge has at most one hanging node for an initial mesh of high quality and the hanging node coincides with the midpoint of the collinear edge. For this reason, we can find the hanging node in a given element by computing the following errors

err(i)=|zi12(zi1+zi+1)|,i=1,,Nv,{\rm err}(i)=\Big{|}z_{i}-\frac{1}{2}(z_{i-1}+z_{i+1})\Big{|},\quad i=1,\cdots,N_{v},

where ziz_{i} are the vertices and NvN_{v} is the vertex number, as coded in Line 19-22. We remark that this process will also be used in the element refinement and element extension introduced in the following.

3.3 Refinement of the additional elements

We observe from Fig. 3 that the hanging node will appear in the subcells of the partitioned additional elements. For this reason, we first divide the additional elements and then extend all possible elements together by adding hanging nodes. The other reason is that we need the data structure elem2edge in the element extension since the midpoints will be labeled by using the edge index.

Note that some midpoints of edges and barycenters need to be added to the matrix node. We relabel the vertices, edges and elements in the following order

z1,,zN;e1,,eNE;K1,,KNTz_{1},\cdots,z_{\rm N};~{}~{}e_{1},\cdots,e_{\rm NE};~{}~{}K_{1},\cdots,K_{\rm NT}

with a single index i=1,2,,N+NE+NTi=1,2,\cdots,{\rm N}+{\rm NE}+{\rm NT}, referred to as the connection number. However, in most cases it is more convenient to use the index number in matrix edge.

To construct the 4-node subcells, first consider an example with the connection numbers of vertices and edges listed as

z1,e1,z2,e2,z3,e3,z4,e4,z5,e5,Nv=5,z_{1},e_{1},{\color[rgb]{1,0,0}z_{2}},e_{2},z_{3},e_{3},z_{4},e_{4},{\color[rgb]{1,0,0}z_{5}},e_{5},\quad N_{v}=5,

where z2z_{2} and z5z_{5} are hanging nodes and the subscript stands for local index. Next, we replace the connection numbers of nontrivial edges by the ones of hanging nodes as

z1,e1,z2,e2,z3,e3,z4,e4,z5,e5\displaystyle z_{1},e_{1},{\color[rgb]{1,0,0}z_{2}},e_{2},z_{3},e_{3},z_{4},e_{4},{\color[rgb]{1,0,0}z_{5}},e_{5}
\displaystyle\hookrightarrow~{} z1,z2,z2,z2,z3,e3,z4,z5,z5,z5,Nv=5.\displaystyle z_{1},{\color[rgb]{1,0,0}z_{2},z_{2},z_{2}},z_{3},e_{3},z_{4},{\color[rgb]{1,0,0}z_{5},z_{5},z_{5}},\quad N_{v}=5.

Then we can construct the corresponding data structure elem in a unified way, which applies to the marked elements with or without hanging nodes.

For the data structure elem2edge, we simply record or modify the index numbers of trivial edges by zero.

The code is listed as follows.

1%% Partition the adjacent elements to be refined
2nAdjRefine = length(idElemAdjRefine);
3elemAdjRefine = cell(nAdjRefine,1);
4elem2edgeAdjRefine = cell(nAdjRefine,1);
5for s = 1:nAdjRefine
6 % current element
7 iel = idElemAdjRefine(s);
8 index = elem{iel}; indexEdge = elem2edge{iel}; Nv = length(index);
9 % find midpoint
10 v1 = [Nv,1:Nv-1]; v0 = 1:Nv; v2 = [2:Nv,1];
11 p1 = node(index(v1),:); p0 = node(index(v0),:); p2 = node(index(v2),:);
12 err = sqrt(sum((p0-0.5*(p1+p2)).^2,2));
13 ism = (err<eps);
14 % modify the edge number
15 ide = indexEdge+N; % the connection number
16 ide(v1(ism)) = index(ism); ide(ism) = index(ism);
17 % elem
18 nsub = Nv-sum(ism);
19 z1 = ide(v1(¬\negism)); z0 = index(¬\negism);
20 z2 = ide(¬\negism); zc = iel*ones(nsub,1)+N+NE;
21 elemAdjRefine{s} = [z1(:), z0(:), z2(:), zc(:)];
22 % elem2edge
23 ise = false(Nv,1); ise([v1(ism),v0(ism)]) = true;
24 idg = zeros(Nv,1); idg(ise) = indexEdge(ise);
25 e1 = idg(v1(¬\negism)); e2 = idg(¬\negism);
26 e3 = zeros(nsub,1); e4 = zeros(nsub,1); % numbered as 0
27 elem2edgeAdjRefine{s} = [e1(:), e2(:), e3(:), e4(:)];
28end
29addElem = vertcat(elemAdjRefine{:});
30addElem2edge = vertcat(elem2edgeAdjRefine{:}); % transform to cell arrays
31if ¬\negisempty(addElem) % may be empty
32 addElem = mat2cell(addElem,ones(size(addElem,1),1),4);
33 addElem2edge = mat2cell(addElem2edge,ones(size(addElem2edge,1),1),4);
34end

3.4 Element extension by adding hanging nodes

The elements for extension are composed of some neighboring elements of the ones in refinement set and some subcells of additional elements to be refined. Denote by idEdgeCut the index numbers of all trivial edges in elements for refinement. Then at least one edge of the extension element corresponds to the index number in idEdgeCut. We first derive the vector idEdgeCut as follows.

1%% Extend elements by adding hanging nodes
2% elements to be refined
3idElemRefine = [idElemAdjRefine(:); idElemMarked(:)]; % the order cannot be changed
4nRefine = length(idElemRefine);
5% natural numbers of edges without hanging nodes
6isEdgeCut = false(NE,1);
7for s = 1:nRefine
8 iel = idElemRefine(s);
9 index = elem{iel}; indexEdge = elem2edge{iel}; Nv = length(index);
10 v1 = [Nv,1:Nv-1]; v0 = 1:Nv; v2 = [2:Nv,1];
11 p1 = node(index(v1),:); p0 = node(index(v0),:); p2 = node(index(v2),:);
12 err = sqrt(sum((p0-0.5*(p1+p2)).^2,2));
13 ism = (err<eps);
14 idx = true(Nv,1);
15 idx(v1(ism)) = false; idx(ism) = false;
16 isEdgeCut(indexEdge(idx)) = true;
17end
18idEdgeCut = find(isEdgeCut);

To extend an element, we first generate a zero vector of length 2Nv2N_{v}. In the odd place, we insert the vertex numbers, while only insert index numbers of edges in idEdgeCut in the even place. We then obtain the connectivity vector by deleting the zero entries.

1% adjacent polygons of elements to be refined
2idElemRefineAdj = unique(horzcat(neighbor{idElemRefine}));
3idElemRefineAdj = setdiff(idElemRefineAdj,idElemRefine);
4% basic data structure of elements to be extended
5elemExtend = [elem(idElemRefineAdj); addElem];
6elem2edgeExtend = [elem2edge(idElemRefineAdj); addElem2edge];
7% extend the elements
8for s = 1:length(elemExtend)
9 index = elemExtend{s}; indexEdge = elem2edgeExtend{s};
10 Nv = length(index);
11 [idm,id] = intersect(indexEdge,idEdgeCut);
12 idvec = zeros(1,2*Nv);
13 idvec(1:2:end) = index; idvec(2*id) = idm+N;
14 elemExtend{s} = idvec(idvec>0);
15end
16% replace the old elements
17nRefineAdj = length(idElemRefineAdj);
18elem(idElemRefineAdj) = elemExtend(1:nRefineAdj);
19addElem = elemExtend(nRefineAdj+1:end);
20elem(idElemAdjRefine) = addElem(1:nAdjRefine);
21addElem = addElem(nAdjRefine+1:end);

It should be pointed out that in Line 5 all the neighboring elements and all the subcells are grouped into the vector elemExtend. The redundant elements do not affect the result since we simply record or modify the index numbers of trivial edges of additional elements by zero.

3.5 Partition of the marked elements

We can refine the marked elements in the same way as the additional elements for refinement. Note that in obtaining the vector idEdgeCut, the original data structure elem of marked elements are needed. Therefore, it is necessary to partition the marked elements after element extension.

1%% Partition the marked elements
2nMarked = length(idElemMarked);
3addElemMarked = cell(nMarked,1);
4for s = 1:nMarked
5 % current element
6 iel = idElemMarked(s);
7 index = elem{iel}; indexEdge = elem2edge{iel}; Nv = length(index);
8 % find midpoint
9 v1 = [Nv,1:Nv-1]; v0 = 1:Nv; v2 = [2:Nv,1];
10 p1 = node(index(v1),:); p0 = node(index(v0),:); p2 = node(index(v2),:);
11 err = sqrt(sum((p0-0.5*(p1+p2)).^2,2));
12 ism = (err<eps);
13 % replace the edge numbers with the numbers of hanging nodes
14 ide = indexEdge+N; % connection number
15 ide(v1(ism)) = index(ism); ide(ism) = index(ism);
16 % partition the elements with or without hanging nodes
17 nsub = Nv-sum(ism);
18 z1 = ide(v1(¬\negism)); z0 = index(¬\negism);
19 z2 = ide(¬\negism); zc = iel*ones(nsub,1)+N+NE;
20 addElemMarked{s} = [z1(:), z0(:), z2(:), zc(:)];
21end
22% replace the old elements
23addElemMarked = vertcat(addElemMarked{:});
24addElemMarked = mat2cell(addElemMarked, ones(size(addElemMarked,1),1), 4);
25elem(idElemMarked) = addElemMarked(1:nMarked);
26addElemMarked = addElemMarked(nMarked+1:end);

3.6 Update of the basic data structure

We finally update the basic data structure node and elem by adding all new elements and reordering the vertices.

1%% Update node and elem
2idElemRefine = unique(idElemRefine); % in ascending order
3z1 = node(edge(idEdgeCut,1),:); z2 = node(edge(idEdgeCut,2),:);
4nodeEdgeCut = (z1+z2)/2;
5nodeCenter = centroid(idElemRefine,:);
6node = [node; nodeEdgeCut; nodeCenter];
7elem = [elem; addElem; addElemMarked];
8%% Reorder the vertices
9[¬\neg,¬\neg,totalid] = unique(horzcat(elem{:})');
10elemLen = cellfun('length',elem);
11elem = mat2cell(totalid', 1, elemLen)';

4 Application in the posteriori error estimates for virtual element methods

The mesh refinement routine PolyMeshRefine.m is formed by collecting all the code fragments in the last section, which is also available from GitHub (https://github.com/Terenceyuyue/mVEM) as part of the mVEM package containing efficient and easy-following codes for various VEMs published in the literature. The refinement function has been tested for many initial meshes generated by PolyMesher (cf. [13]), a polygonal mesh generator based on the Centroidal Voronoi Tessellations (CVTs), which confirms the effectiveness and correctness of our code. We omit the details and in turn consider the application in the posteriori error estimates of the VEM for Poisson equation.

Consider the Poisson equation with Dirichlet boundary condition on the unit square. The exact solution is given by

u(x,y)=xy(1x)(1y)exp(1000((x0.5)2+(y0.117)2)).u(x,y)=xy(1-x)(1-y){\text{exp}}\left(-1000((x-0.5)^{2}+(y-0.117)^{2})\right).

The error estimator is taken from [9]. We employ the VEM in the lowest order case and use the Dörfler marking strategy with parameter θ=0.4\theta=0.4 to select the subset of elements for refinement.

The initial mesh and the final adapted meshes after 20 and 30 refinement steps are presented in Fig. 4 (a-c), respectively. The detail of the last mesh is shown in Fig. 4 (d). Clearly, no small edges are observed. We also plot the adaptive approximation in Fig. 5, which almost coincides with the exact solution. The full code is still available from mVEM package. The subroutine PoissonVEM_indicator.m is used to compute the local indicators and the test script is main_Poisson_avem.m.

Refer to caption
Refer to caption
Refer to caption
Refer to caption
Fig. 4: The initial and the final adapted meshes. (a) The initial mesh; (b) After 20 refinement steps; (c) After 30 refinement steps; (d) The zoomed mesh in (c)
Refer to caption
(a) Exact
Refer to caption
(b) Numerical
Fig. 5: The exact and numerical solutions

References

  • [1] L. Beirão Da Veiga, F. Brezzi, A. Cangiani, G. Manzini, L. D. Marini, and A. Russo. Basic principles of virtual element methods. Math. Models Meth. Appl. Sci., 23(1):199–214, 2013.
  • [2] B. Ahmad, A. Alsaedi, F. Brezzi, L.D. Marini, and A. Russo. Equivalent projectors for virtual element methods. Comput. Math. Appl., 66(3):376–391, 2013.
  • [3] B. A. De Dios, K. Lipnikov, and G. Manzini. The nonconforming virtual element method. ESAIM Math. Model. Numer. Anal., 50(3):879–904, 2016.
  • [4] J. Zhao, B. Zhang, S. Chen, and S. Mao. The Morley-type virtual element for plate bending problems. J. Sci. Comput., 76(1):610–629, 2018.
  • [5] L. Beirão Da Veiga, F. Brezzi, A. Cangiani, G. Manzini, L. D. Marini, and A. Russo. Basic principles of mixed virtual element methods. ESAIM Math. Model. Numer. Anal., 48(4):1227–1240, 2014.
  • [6] O. J. Sutton. The virtual element method in 50 lines of MATLAB. Numer. Algorithms, 75(4):1141–1159, 2017.
  • [7] L. Beirão Da Veiga and G. Manzini. Residual a posteriori error estimation for the virtual element method for elliptic problems. ESAIM Math. Model. Numer. Anal., 49(2):577–599, 2015.
  • [8] A. Cangiani, E. H. Georgoulis, T. Pryer, and O. J. Sutton. A posteriori error estimates for the virtual element method. Numer. Math., 137(4):857–893, 2017.
  • [9] S. Berrone and A. Borio. A residual a posteriori error estimate for the virtual element method. Math. Models Methods Appl. Sci., 27(8):1423–1458, 2017.
  • [10] H. Chi, L. Beirão Da Veiga, and G. H. Paulino. A simple and effective gradient recovery scheme and a posteriori error estimator for the virtual element method (VEM). Comput. Methods Appl. Mech. Engrg., 347:21–58, 2019.
  • [11] L. Beirão Da Veiga, G. Manzini, and L. Mascotto. A posteriori error estimation and adaptivity in hphp virtual elements. Numer. Math., 143(1):139–175, 2019.
  • [12] L. Chen. iFEM: an integrated finite element method package in MATLAB. Technical report, University of California at Irvine, 2009.
  • [13] C. Talischi, G. H. Paulino, and A. Pereira. Polymesher: a general-purpose mesh generator for polygonal elements written in matlab. Struct. Multidiscip. Optim., 45(3):309–328, 2012.