1 Introduction

This paper presents a new framework to solve partial differential equations (PDEs) using adaptive isogeometric analysis (IGA) based on T-splines, called deal.t. This package includes two and three-dimensional T-splines as explained in [1], and can be used to solve different PDEs of order two numerically with various (possibly mixed) boundary conditions on single patch domains.

IGA is the idea of directly using the shape functions from CAx modelling of physical domains as ansatz functions for the Finite Element Method (FEM). There are numerous software libraries available involving FEM-based solvers, e.g. FEniCS, see [2, 3], and deal.II, see [4], and libraries specifically designed for IGA-based FEM like e.g. G+Smo, see [5, 6], PetIGA, see [7]. However, the only publicly available implementations of T-splines for PDE discretizations currently known to the authors can be found in the Matlab package igafem, see [8], and the Python package tIGAr, see [9]. tIGAr uses a T-spline plugin for the commercial CAD software Rhinoceros 3D in order to run its calculations via T-splines. The underlying plugin has been discontinued from support and does not guarantee analysis-suitable T-splines.

IGA has been outlined in [10] where predominantly non-uniform rational B-splines (NURBS) were used as a tool to solve PDEs. Different concepts of refinement for NURBS have been introduced, i.e. h-refinement through knot insertion, p-refinement through order elevation, which are described in detail in [11], and k-refinement which simultaneously increases order and continuity.

However, local refinement techniques had already been introduced for B-splines, see e.g. hierarchical B-splines (HB-splines) in [12, 13], from which truncation methods of basis functions were developed in [14] (THB-splines) to further improve local refinement and properties of basis functions. T-splines have been introduced as an alternative to HB-splines in [15] for CAD as a strategy to directly refine the control mesh. They have been introduced as a mathematical tool in [16] and applied with promising results in [17], but it was eventually noticed that linear independence of basis functions is not always guaranteed, see [18]. Properties to guarantee linear independence have been introduced in [19] which introduced analysis suitability and analysis suitable (AS) T-splines. As an alternative to T-splines, locally refined (LR) B-splines were introduced in [20]. A first refinement algorithm for LR B-splines was introduced in [21] which showed promising results. However, linear independence is an issue for LR B-splines as well.

As T-splines were introduced, definitions were restricted to the two-dimensional case with degree three in both directions, and later AS T-splines of arbitrary degrees have been introduced in [22]. T-splines in arbitrary dimensions but odd polynomial degrees have been introduced in [23, 24] with proper refinement algorithms to retain analysis-suitability after refinement. Lastly, T-splines in arbitrary dimensions and arbitrary polynomial degrees were recently introduced in our previous work, see [1], allowing us to finally use T-splines in a framework for FEM, with arbitrary degree and local mesh refinement in three dimensions.

Recent advances in IGA can be found e.g. in [25], where a thorough comprehension of adaptive IGA is given for hierarchical splines with some applications. These results also include boundary element methods for an adaptive framework.

This paper is structured as follows. In Sect. 2, we discuss the prerequisites needed for the main parts. This includes notation and the definition of T-junctions in higher dimensions. We also briefly explain the concept of (geometric) analysis-suitability and AS T-splines with some examples. For a detailed guide through AS T-splines, we refer the reader to our previous work [1]. Section 2.2 is dedicated to the local refinement procedure explained in [23] and used in deal.t.

Further, we discuss in Sect. 3 possible meshes to serve as a base for implementing T-splines, with focus on the index mesh and the parametric mesh. We explain how both mesh classes are connected and how refinement on the parametric mesh affects the index mesh. Pseudocodes are given for relevant algorithms.

Section 4 explains main differences when using deal.II with deal.t compared to usual deal.II applications. It explains how to set up a proper triangulation, i.e. mesh, and the differences in assembly loops. deal.t is suited with a residual error estimator for Poisson-like problems, which is also explained in detail in Sect. 4.1. A full tutorial for readers unfamiliar with deal.II is given in Appendix C.

Numerical benchmark tests are discussed in Sect. 5. We consider two model problems based on a standard Poisson problem with Neumann and Dirichlet boundary conditions. The first problem is the result of the example given in Appendix C for various polynomial degrees, which is considered a pure benchmark test without singularities. The second problem is defined on the L-shape domain, given a solution with a corner singularity. A demonstration of mesh refinement in 3D is given in Fig. 2.

deal.t is based on the open source software deal.II and made available at [26], where the results of this work can be reproduced.

2 Prerequisites

In this section, we introduce the required mathematical notation. For details, see [1].

For any \(x\in \mathbb {R}\) we will denote by \(\lceil x \rceil \) the rounding of x to its next integer \(n\in \mathbb {N}\) with \(n\ge x\). Analogously, we denote by \(\lfloor x \rfloor \) the rounding of x to its next integer \(n\in \mathbb {N}\) with \(n\le x\).

We consider a box-shaped index domain , with \(N_k\in \mathbb {N}\), for \(k=1,\dots ,d\), and an associated parametric domain , with \(p_k\)-open knot vectors \(\Xi ^{(k)}=\{\xi _0^{(k)},\dots , \xi _{N_k}^{(k)}\}\), for polynomial degrees \(p_k\in \mathbb {N}\). Let \(\widehat{\mathscr {T}}= \mathscr {T}({\widehat{\Omega }})\) be a mesh of \({\widehat{\Omega }}\), consisting of open axis-parallel boxes. We suppose that \({\widehat{\Omega }}\) is constructed via symmetric bisection from an initial tensor-product mesh with integer vertices, which is described in detail in [1, Algorithm 2.1]. Consequently, we suppose that for each vertex \(\mathscr {V}= \{\texttt {V}_1\} \times \dots \times \{\texttt {V}_d\}\) in the index mesh, the components are of the form \(\texttt {V}_k = a_k\cdot 2^{-b_k}\), with \(a_k,b_k\in \mathbb {N}\), \(k=1,\dots ,d\). Therefore, all indices used below are from the set

(1)

In Sect. 3 we will see that the implementation is given on the parametric mesh \(\mathscr {T}= \mathscr {T}(\Omega )\), however, relevant definitions are done on the index mesh \(\widehat{\mathscr {T}}\). Throughout this paper, we will distinguish between the parametric mesh/elements \(\texttt {Q}\in \mathscr {T}\) and index mesh/elements \(\widehat{\texttt {Q}} \in \widehat{\mathscr {T}}\) with a hat in index domain notations which is dropped on the parametric domain. Where necessary, we introduce arguments to indicate which mesh we consider in this case.

For \(k=1,\dots ,d\), we denote the k-dimensional mesh entities of \(\widehat{\mathscr {T}}\) by \(\mathscr {H}^{(k)}(\widehat{\mathscr {T}})\), e.g. by \(\mathscr {H}^{(0)}(\widehat{\mathscr {T}})\) the set of vertices, resp. nodes, by \(\mathscr {H}^{(1)}(\widehat{\mathscr {T}})\) the set of one-dimensional edges without start and end point, and so on. The union of all d-dimensional element boundaries

$$\begin{aligned} \textrm{Sk}= \bigcup _{\texttt {Q}\in \mathscr {H}^{(d)}(\widehat{\mathscr {T}})}\partial \texttt {Q}\end{aligned}$$
(2)

is called the skeleton of \(\widehat{\mathscr {T}}\).

For an index set \(\kappa = \{ \kappa _1,\dots , \kappa _\ell \}\subset \{1,\dots ,d\}\) and a d-dimensional element \(\texttt {Q}= \texttt {Q}_1 \times \dots \times \texttt {Q}_d\in \mathscr {H}^{(d)}(\widehat{\mathscr {T}})\) we denote the \((d-\ell )\)-dimensional, \(\kappa \)-orthogonal interfaces by \(\texttt{H}^{(\kappa )}(\texttt {Q})\), e.g.

(3)

For polynomial degrees \({\textbf {p}}= (p_1,\dots ,p_d)\in \mathbb {N}^d\), we split the index domain \({\widehat{\Omega }}\) into an active region and a frame region , with

(4)
(5)

We continue to explain what a T-junction in higher dimensions is.

Definition 1

(T-junctions) We call an interface \(\texttt {T}\in \mathscr {H}^{(d-2)}(\widehat{\mathscr {T}})\) with \(\texttt {T}\nsubseteq \partial {\widehat{\Omega }}\) a T-junction if it is in the boundary of a cell \(\texttt {Q}=\texttt {Q}_1\times \dots \times \texttt {Q}_d\in \mathscr {T}\) without being connected to any of its vertices, \(\texttt {T}\subset \partial \texttt {Q}\), \(\overline{\texttt {T}}\cap \partial \texttt {Q}_1\times \dots \times \partial \texttt {Q}_d=\varnothing \). We then call \(\texttt {Q}\) the associated cell of \(\texttt {T}\) and write \(\texttt {Q}={{\,\textrm{ascell}\,}}(\texttt {T})\). Since \(\texttt {T}= \texttt {T}_1 \times \dots \times \texttt {T}_d\in \mathscr {H}^{(d-2)}(\widehat{\mathscr {T}})\), there are two unique and distinct directions \(i,j\in \{1,\dots ,d\}\) such that \(\texttt {T}_i,\texttt {T}_j\) are singletons, \(\texttt {T}\in \texttt{H}^{(\{i,j\})}(\widehat{\mathscr {T}})\), \(\texttt {T}_i\subsetneq \texttt {Q}_i\) and \(\texttt {T}_j \subsetneq \partial \texttt {Q}_j\). We call i the orthogonal direction and j the pointing direction of \(\texttt {T}\), and write \({{\,\textrm{odir}\,}}(\texttt {T})=i\), \({{\,\textrm{pdir}\,}}(\texttt {T})=j\).

From [1] we have uniqueness of the associated cell for each T-junction.

Before we jump into definitions of anchors and index vectors, we first define a criterion for meshes that may be taken into consideration for further computations.

Definition 2

(Admissible meshes) We define the slice

(6)

for \(k=1,\dots ,d\) and \(n=0,\dots ,N_k\), and the k-th frame region

(7)

A T-mesh \(\widehat{\mathscr {T}}\) is called admissible, if for \(k=1,\dots ,d\), there is no T-junction \(\texttt {T}\) with \({{\,\textrm{odir}\,}}(\texttt {T})=k\) or \({{\,\textrm{pdir}\,}}(\texttt {T})=k\) in the k-th frame region, and

$$\begin{aligned} \textrm{S}_k(n)\subseteq \textrm{Sk}\quad \text {for}\,\, \begin{aligned} n&\in \Bigl [0, \bigl \lfloor \tfrac{p_k + 1}{2}\bigr \rfloor \Bigr ]\cap \mathbb {N}\text { and }\\ n&\in \Bigl [N_k - \bigl \lfloor \tfrac{p_k + 1}{2}\bigr \rfloor , N_k\Bigr ]\cap \mathbb {N}. \end{aligned} \end{aligned}$$
(8)

For the rest of this paper, the index mesh \(\widehat{\mathscr {T}}\) is assumed to be admissible whenever used.

Fig. 1
figure 1

Demonstration for the construction of local index vectors of marked anchors for polynomial degrees \({\textbf {p}}= (3, 2, 2)\) (top) and \({\textbf {p}}= (4, 1, 2)\) (bottom)

2.1 Multivariate T-splines and analysis-suitability

Anchors are a subset of mesh entities that have a one-to-one correspondence to the set of T-splines. For higher dimension and arbitrary degree \({\textbf {p}}= (p_1,\dots ,p_d)\), they have been introduced in [1] as

(9)

with \(\kappa =\{ \ell \in \{1,\dots ,d\}\mid p_\ell \text { odd } \}\). We associate to each anchor and each axis direction \(k=1,\dots ,d\) a non-decreasing knot vector of \(p_k+2\) indices from \(\mathbb {N}^{(2)}\). In [22, 27, 28], these index vectors (in 2D) are constructed via ray-tracing the anchor through the mesh along the k-th direction and choosing the \(p_k+2\) consecutive indices centered around the k-th component \({\textbf {A}}_k\) of \({\textbf {A}}\). This is generalized in [1] to arbitrary dimensions, i.e. for any mesh entity \({\texttt {E}}={\texttt {E}}_1\times \dots \times {\texttt {E}}_d\) and \(k\in \{1,\dots ,d\}\), we define the projection \(P_{k,n}({\texttt {E}})={\texttt {E}}|_{{\texttt {E}}_k=\{n\}}\) of \({\texttt {E}}\) on the slice \(\textrm{S}_k(n)\), and the global knot vector

$$\begin{aligned} \mathscr {I}_{k}({\texttt {E}})&:=\Bigl (n \in \mathbb {N}^{(2)} \mid P_{k,n}({\texttt {E}})\subset \textrm{Sk}_k\Bigr ), \end{aligned}$$
(10)

with entries in ascending order. The local knot vector for an anchor \({\textbf {A}}= {\textbf {A}}_1\times \dots \times {\textbf {A}}_d\) is given by the \(p_k+2\) consecutive indices \(\ell _0,\dots ,\ell _{p_k+1}\in \mathscr {I}_{k}({\textbf {A}})\), such that \(\ell _k = \inf {\textbf {A}}_k\) for \(k = \lfloor \tfrac{p_k+1}{2}\rfloor \). If \(p_k\) is odd, the singleton \({\textbf {A}}_k\) contains the middle entry of , and if \(p_k\) is even, the two middle entries of are the boundary values of \({\textbf {A}}_k\).

An example is given in Fig. 1. Depicted is the construction principle using ray-tracing to generate the corresponding local index vectors. For on the top, we obtain . In contrast, for , the projection onto the slice at \(({\bar{m}} +3)\) is not a part of the skeleton of the mesh. The same applies to the index \({\bar{m}} + 1\), which yields . Note that \(p_1\) is odd and that is the middle element of the respective local index vectors.

On the lower part of Fig. 1, we get as before . However, the projection of onto the slice at \(m_2\) is not part of the skeleton, which yields . Note that \(p_1\) is even and that is the middle section of the local index vector .

Using anchors and their local index vectors, we can define a multivariate T-spline associated to an anchor.

Definition 3

(T-spline) For \(p_k\in \mathbb {N}\), we denote by the univariate B-spline function of degree \(p_k\) that is returned by the Cox-deBoor recursion with knot vector , see e.g. [11]. We assume that \(\xi _{\ell _0}^{(k)}<\xi _{\ell _{p_k+1}}^{(k)}\) is always fulfilled. The T-spline function associated with the anchor \({\textbf {A}}\) is defined as

(11)

and the corresponding T-spline space is given by . The index support of \(T_{\textbf {A}}\) will be denoted by , where is the closed interval from the first to the last entry of .

For the concept of analysis-suitability, we use a geometric concept based on the concepts from [22, 27, 28]. Note that there are different versions of analysis-suitability, see [1] for details.

Definition 4

(Geometric analysis-suitability) Let \(\texttt {T}\) be a T-junction with \(\texttt {Q}={{\,\textrm{ascell}\,}}(\texttt {T})\), \(i={{\,\textrm{odir}\,}}(\texttt {T})\) and \(j={{\,\textrm{pdir}\,}}(\texttt {T})\). We then define local knot vectors as follows.

  1. 1.

    For \(k = j\), we define as the vector of \((p_j+1)\) consecutive indices from \(\mathscr {I}_{j}(\texttt {T})\), such that

    $$\begin{aligned} \begin{aligned}&\{\ell _{p_j/2}\} = \texttt {T}_j,&\text { if }p_j\text { is even}, \\&\left. \begin{aligned} \ell _{\lfloor p_j/2\rfloor }&= \inf \texttt {Q}_j,\quad \\ \ell _{\lceil p_j/2\rceil }&= \sup \texttt {Q}_j, \end{aligned} \right\}&\text { if }p_j\text { is odd}. \end{aligned} \end{aligned}$$
    (12)
  2. 2.

    For \(k=i\), the local knot vector is the singleton .

  3. 3.

    For \(k \not \in \{i, j\}\) we define , where \(c_k = p_k \text { mod }2\), as the vector of \((p_k + 2 + c_k)\) consecutive indices from \(\mathscr {I}_{k}(\texttt {T})\), such that

    $$\begin{aligned} \texttt {T}_k = (\ell _{\lceil p_k/2\rceil }, \ell _{\lceil p_k/2\rceil +1}). \end{aligned}$$
    (13)

    This means that the local knot vector has \(p_k+3\) elements if \(p_k\) is odd and \(p_k + 2\) if \(p_k\) is even, and \(\texttt {T}_k\) is centred within these elements, cf.@ the definition of local knot vectors for anchors.

We then call

(14)

the geometric T-junction extension (GTJ) of \(\texttt {T}\), and we say that it is an i-orthogonal extension in j-direction. Note, that .

A mesh \(\mathscr {T}\) is strongly geometrically analysis-suitable (), if for any two T-junctions \(\texttt {T}_1,\texttt {T}_2\) with orthogonal directions \(i_1= {{\,\textrm{odir}\,}}(\texttt {T}_1)\ne {{\,\textrm{odir}\,}}(\texttt {T}_2)=i_2\) there is

(15)

We call \(\mathscr {T}\) weakly geometrically analysis-suitable (), if (15) holds for any two T-junctions \(\texttt {T}_1,\texttt {T}_2\) with orthogonal directions \({{\,\textrm{odir}\,}}(\texttt {T}_1)\ne {{\,\textrm{odir}\,}}(\texttt {T}_2)\) and pointing directions \({{\,\textrm{pdir}\,}}(\texttt {T}_1)\ne {{\,\textrm{pdir}\,}}(\texttt {T}_2)\).

We will omit the dependency of the orthogonal direction when it is clear from the context, e.g. write , for .

It was proven in [1] that is sufficient for linear independence of the generated spline space, which makes them suitable for application in a finite element setting.

2.2 Local refinement procedure

figure a
figure b

We now continue to explain the refinement strategy for a given T-mesh, intended to be applied in an adaptive Galerkin scheme with the steps solve, estimate, mark and refine as follows.

figure c

In Sect. 3 we will explain in detail how the parametric mesh \(\mathscr {T}= \mathscr {T}(\Omega )\) and the index mesh \(\widehat{\mathscr {T}}\) are connected and what we have to consider during the implementation. To this extent, we will thus only explain the refinement process on the parametric mesh \(\mathscr {T}\). The definitions below strictly follow [23, 24].

As for the index mesh, we consider a box-shaped parametric mesh \(\Omega \) consisting of axis-aligned open boxes that are generated via symmetric box bisections from an initial tensor-product structure. Note that a tensor-product structure is strongly geometric analysis-suitable due to the absence of T-junctions. We denote the initial tensor-product mesh by \(\mathscr {T}^{(0)}\) and every consecutive mesh by \(\mathscr {T}^{(n)}\), \(n = 1,\dots \)

Let \(\texttt {Q}\in \mathscr {T}^{(n)}\) be some cell. We define its level by the index \(m\le n\), s.t. \(\texttt {Q}\) is an element of the m-th uniform refinement of \(\mathscr {T}^{(0)}\), and write \(\ell (\texttt {Q}) = m\). Its vector-valued size is defined to be the component-wise length of its edges, i.e.

(16)

The level-dependent subdivision of a cell \(\texttt {Q}\in \mathscr {T}^{(n)}\) is defined by the \(k_{\ell (\texttt {Q})}\)-orthogonal bisection of \(\texttt {Q}\) where \(k_{\ell (\texttt {Q})} = 1 + (\ell (\texttt {Q}) \text { mod } d)\), denoted by \(\textsc {subdiv}(\texttt {Q}, k_{\ell (\texttt {Q})})\).

Fig. 2
figure 2

Cross-sections of a 3D L-Shape domain at different refinement levels. Dashed, blue lines indicate the next level after marking cells adjacent to the thick, dashed, red line \(\texttt {L} = \{0\} \times \{0\} \times [-1, 1]\). The levels given are 3 and 4 (top left), 5 and 6 (top right), 7 and 8 (bottom left), and 9 and 10 (bottom right)

For a point \(z\in \Omega \), we define the vector-valued distance between z and \(\texttt {Q}\) as

(17)
(18)

and \({{\,\mathrm{\textsc {{mid}}}\,}}(\texttt {Q})\) is defined as the midpoint of \(\texttt {Q}\). For two cells \(\texttt {Q}^{(1)}\) and \(\texttt {Q}^{(2)}\), we define its distance as the vector-valued distance of its cells.

(19)

For two vectors \(x, y\in \Omega \), we denote by \(x \circ y\) the component-wise product, i.e.

$$\begin{aligned} x\circ y :=(x_1 y_1,\dots , x_d y_d). \end{aligned}$$
(20)

With these definitions, we define the open environment \(U(\texttt {Q})\) of a cell \(\texttt {Q}\in \mathscr {T}^{(n)}\) by

(21)

and its coarse neighbourhood \(\mathscr {N}(\mathscr {T}^{(n)}, \texttt {Q})\) of a cell \(\texttt {Q}\in \mathscr {T}^{(n)}\) in the mesh \(\mathscr {T}^{(n)}\) by

$$\begin{aligned} \mathscr {N}(\mathscr {T}^{(n)}, \texttt {Q}):=\Bigl \lbrace \texttt {Q}'\in \mathscr {T}^{(n)}\,\big \vert \, \begin{aligned}&\texttt {Q}' \cap U(\texttt {Q})\ne \varnothing \\&\ell (\texttt {Q}') = \ell (\texttt {Q}) - 1 \end{aligned} \Bigr \rbrace . \end{aligned}$$
(22)

Let \(\mathscr {M}\subset \mathscr {T}^{(n)}\) be a set of elements marked for refinement. Its closure is then defined recursively via

  1. 1.

    Set \(\mathscr {N}^{(0)}(\mathscr {T}^{(n)}, \mathscr {M}) :=\bigcup _{\texttt {Q}\in \mathscr {M}} \mathscr {N}(\mathscr {T}^{(n)}, \texttt {Q})\) and \(\mathscr {M}^{(0)} = \mathscr {M}\),

  2. 2.

    for \(j=1,2,\dots \), define

    $$\begin{aligned} \mathscr {M}^{(j)} :=\mathscr {N}^{(j-1)}(\mathscr {T}^{(n)}, \mathscr {M}), \end{aligned}$$
  3. 3.

    break recursion if \(\mathscr {M}^{(j)} = \mathscr {M}^{(j-1)}\),

  4. 4.

    define \(\texttt{closure}(\mathscr {T}^{(n)}, \mathscr {M}):=\mathscr {M}^{(j)}\).

Note that this recursion will terminate in the worst case if \(\mathscr {N}^{(j)}(\mathscr {T}^{(n)}, \mathscr {M}) = \mathscr {T}^{(n)}\). We then define the refined mesh \(\mathscr {T}^{(n+1)}\) by

$$\begin{aligned} \begin{aligned} \mathscr {T}^{(n+1)}:=&\mathscr {T}^{(n)}\setminus \texttt{closure}(\mathscr {T}^{(n)}, \mathscr {M})\\&\cup \bigcup _{\texttt {Q}\in \texttt{closure}(\mathscr {T}^{(n)}, \mathscr {M})} \textsc {subdiv}(\texttt {Q}). \end{aligned} \end{aligned}$$
(23)

From [23] we know that if \(\mathscr {T}^{(n)}\) is analysis-suitable, \(\mathscr {T}^{(n+1)}\) is also analysis-suitable. Note that the analysis-suitability used in [23] is abstract and thus different from the introduced geometric analysis-suitability above. However, in [1] it was shown that the geometric one yields also the abstract one. Thus, we only use geometric analysis-suitability to find Bézier elements. A C++-style pseudocode for the construction of the coarse neighbourhood is given in Algorithm 1. Note that this algorithm has to be called recursively in order to get the complete coarse neighbourhood, see Algorithm 2.

An example is given in Fig. 2, where the 3D L-shape domain is considered. Cells that are adjacent to the red line \(\texttt {L} = \{0\}\times \{0\} \times [-1, 1]\) are marked for refinement in each iteration. Given a problem on the whole 3D L-Shape domain that is symmetric along the diagonal

(24)

this half domain is sufficient for computations. For simplicity, we have depicted only a 3D view of a cross-section along the diagonal \(\texttt {D}\). The blue dashed lines denote the next level of refinement. This example thus also demonstrates the definition of the coarse neighbourhood from Algorithm 1.

After refinement, the T-splines, resp. local knot vectors have to be updated. It was shown in [1] that under certain assumptions, local knot vectors may be inherited from parent anchors. The result reads as follows.

Lemma 1

Let be the associated index mesh to the parametric mesh \(\mathscr {T}\). Assume that each cell \(\widehat{\texttt {Q}}\in \widehat{\mathscr {T}}\) has active neighbours in at least three distinct directions for \(d\ge 3\) and two distinct directions if \(d=2\), i.e. the neighbouring cells are in the active region. Define for an arbitrary cell \(\texttt {Q}\in \mathscr {T}\) the subdivided parametric mesh

$$\begin{aligned} \mathscr {T}' = \mathscr {T}\setminus \{\texttt {Q}\} \cup \textsc {subdiv}(\texttt {Q}, k_{\ell (\texttt {Q})}) \end{aligned}$$
(25)

together with its subdivided index mesh . Then for every new index anchor there exists an old anchor with , .

The assumption from Lemma 1 is always fulfilled if the polynomial degree is greater than one. If the polynomial degree is one in some direction k, uniform refinement steps may be performed until direction k is refined to ensure this assumption.

3 Data structures

Fig. 3
figure 3

An overview of possible mesh types that can be used for an implementation. This implementation is focused on the highlighted mesh types, i.e. the parametric mesh together with the parametric bezier mesh

Fig. 4
figure 4

The data structure on the index mesh (left) is compressed for the data structure on the parametric domain (right) to disregard empty parametric cells

There are several possible meshes to take into consideration for an implementation, see Fig. 3. Firstly, there is the index mesh \(\mathscr {T}({\widehat{\Omega }})\) where theoretical definitions are done, see Sect. 2. However, if we base computations on the index mesh alone, this will lead to integrals over empty domains, as some distinct indices can lead to knots that coincide. This is no problem per se, but it will practically cause loop iterations within the program, that essentially do nothing. This should be avoided.

Another possible solution is the mesh in the physical domain \(\mathscr {T}(\overline{\Omega })\). This is indeed already done in another software project, called G+Smo, see [5], available at [29] with an overview of existing modules in [6], where other isogeometric concepts are already fully implemented, e.g. (hierarchical) NURBS, (hierarchical) LR-Splines, etc. However, an implementation of T-Splines in neither 2D nor 3D within the G+Smo library is unknown to the authors extent.

A standard finite element code is already given by the deal.II library, see [4], or FEniCS, see [30] and libraries derived thereof, e.g. [31]. We have opted to base our implementations on the deal.II library (version 9.3 [32]), as it features a larger group of researchers together with some important base implementations, the most important being an implementation of Bernstein polynomials which are used for Bézier extraction. We aim to keep integration as simple as possible during the implementation, which is the case if elements of a considered mesh are axis-aligned. Doing so ensures integral variables to be independent of each other, i.e. we integrate over d dimensional boxes. This is either achieved on the index domain or the parametric domain and as the index domain is already disregarded the implementation works fully on the parametric domain. However, as explained in [28, Proposition  7.6], we have to use the Bézier mesh \(\mathscr {B}(\Omega )\) of the parametric domain to be able to integrate a fixed number of splines on a specific cell.

For the implementation we thus consider the associated parametric domain \(\Omega \) from the underlying index domain \({\widehat{\Omega }}\). For the mesh \(\widehat{\mathscr {T}}\), we also consider the associated parametric mesh \(\mathscr {T}\), where knot repetitions are ignored; i.e. if \(\widehat{\texttt {E}}\in \mathscr {H}^{(k)}(\widehat{\mathscr {T}})\) is an arbitrary k-dimensional element of \(\widehat{\mathscr {T}}\), then its associated parametric element \({\texttt {E}}\) is an element of the parametric mesh \(\mathscr {T}\), if and only if for all directions j there is

$$\begin{aligned} \xi ^{(j)}_{\inf \widehat{\texttt {E}}_j} = \xi ^{(j)}_{{\sup \widehat{\texttt {E}}_j}} \iff {\inf \widehat{\texttt {E}}_j} = {{\sup \widehat{\texttt {E}}_j}}. \end{aligned}$$
(26)

In other words, no direction j may collapse to a single point, if the direction j was an interval in the index mesh.

Anchors are mapped together with its knot repetitions, i.e. if is an anchor of the index domain, then the anchor on the parametric domain is given by

(27)
Fig. 5
figure 5

Mapping behaviour of index anchors to parametric anchors. We again consider the example given in Fig. 4

Consider the following example. Let \(d = 2\) and \(\Xi _1 = \{0,0,1,1\}\), \(p_1 = 1\), and \(\Xi _2 = \{0, 0, 0, 1, 1, 1\}\), \(p_2=2\). The indices are given by \(I_1 = \{0,1,2,3\}\) and \(I_2 = \{0, 1, 2, 3, 4, 5\}\) and the set of index anchors are the horizontal lines of the index mesh constructed by \(I_1\times I_2\) within the active region. Choose \(\widehat{{\textbf {A}}} = \{1\} \times (2, 3)\), then its parametric anchor is given by \({\textbf {A}}= \{ 0 \} \times [0, 1]\). However, if we consider the parametric anchor from \(\widehat{{\textbf {A}}} = \{1\}\times (1, 2)\), we obtain \({\textbf {A}}=\{0\}\times \{0\}\). This demonstrates that anchors on the parametric mesh do not follow a certain rule, as is the case for the index mesh.

Further, local index vectors for an index anchor \(\widehat{{\textbf {A}}}\) are linked one-to-one to its parametric anchor \({\textbf {A}}\), i.e.

(28)

Local index vectors for T-junctions \(\widehat{\texttt {T}}\) are mapped in a similar way. However, if in some direction \(j\ne {{\,\textrm{pdir}\,}}(\widehat{\texttt {T}}) \), \(j\ne {{\,\textrm{odir}\,}}(\widehat{\texttt {T}})\) the interval \(\widehat{\texttt {T}}_j\) collapses to a point, the T-junction is not considered for the parametric mesh. This case will be addressed in Sect. 3.3.

3.1 Parametric mesh

The implementation is solely on the parametric mesh \(\mathscr {T}\), where knot repetitions are ignored for the construction of cells. However, they are essential for the construction of T-junction extensions and have to be available. For this, we store an array mof that stores the multiplicity of each face, i.e. mof[id] returns the multiplicity of face \(\texttt {F}_{\texttt {id}}\in \mathscr {H}^{(d-1)}(\widehat{\mathscr {T}})\). This can later be used to define terminating conditions for the construction of T-junction extensions. Note that per deal.II standards, faces of cells are indexed consecutively for the global mesh.

From [1] we have a dual-compatible set of T-Splines, hence [28, Proposition 7.6] gives us an upper bound for the amount of T-Splines on each element. The upper bound is met exactly, if cells along T-junction extensions are subdivided accordingly. On each such element we can then compute the Bézier representation of each T-Spline, i.e. let \(\mathscr {T}_{\texttt {T}}(\Omega )\) be the mesh described above, then there exists for each cell \(\texttt {Q}\in \mathscr {T}_{\texttt {T}}(\Omega )\) operators \({\textbf {C}}_\texttt {Q}\), s.t. the set of T-Splines \({\textbf {T}}\) with support on \(\texttt {Q}\) are given by a linear combination of Bernstein polynomials \({\textbf {B}}\) on the reference element \([0, 1]^d\),

$$\begin{aligned} {{\textbf {T}}}= {{\textbf {C}}}^T_\texttt {Q}{{\textbf {B}}}, \end{aligned}$$
(29)

see also [33, 34] for a detailed description of Bézier extraction of NURBS, resp. T-Splines.

The values of the Bézier splines on the reference elements can be computed preemptively. To use this in our programming, we need the arrays

  1. 1.

    bezier_elements that stores every (active) cell that is intersecting some T-junction extension.

  2. 2.

    extraction_operators that stores the extraction operators used in (29). Each extraction operator \({\textbf {C}}_\texttt {Q}\) is further represented as a matrix.

  3. 3.

    IEN_array that returns a set of indices for a specific cell \(\texttt {Q}\). The returned indices correspond to indices of the globally indexed T-splines.

The extraction operators are computed based on the algorithm described in [34], however, some minor changes had to be done. The altered code can be found as MATLAB adaption in Appendix A, Algorithm 5.

In detail, for every T-Spline \({\textbf {T}}\), we use its 1D knot vectors to compute 1D extraction operator rows \({\textbf {c}}^j_\texttt {Q}\) from Algorithm 5 for a given cell \(\texttt {Q}\) with \({{\,\textrm{supp}\,}}{\textbf {T}}\cap \texttt {Q}\ne \varnothing \), and use the tensor structure to generate the full extraction operator row \({\textbf {c}}_\texttt {Q}\) for \({\textbf {T}}\), s.t.

$$\begin{aligned} \begin{aligned} {\textbf {T}}= {\textbf {c}}_\texttt {Q}{\textbf {B}}, \quad \text { with }\quad {\textbf {c}}_\texttt {Q}= \bigotimes _{j = 1}^d {\textbf {c}}_\texttt {Q}^j. \end{aligned} \end{aligned}$$
(30)

To work with boundary indicators, an array boundary_dofs is stored that returns for a given boundary index a set of splines that have support on that boundary.

An example for the data structure used is given in Fig. 4. The example on the right displays the quantities that are stored, except for extraction operators and the IEN_array. In this example we consider polynomial degrees \(p_1 = 3\) and \(p_2 = 2\), the frame region of the index mesh on the left is highlighted by diagonal lines and consists of the outermost cells. Cells in the active region with knot repetitions are highlighted in green. Only the parametric mesh on the right, together with the multiplicities of each face, is stored. The numbers in the circles on the lines indicate the multiplicity of that line (face). These values are stored in the aforementioned mof-array. Further, note that the T-junction extension from \(\texttt {T}= \{ 6 \} \times \{ 5 \}\) yields only a single cell in the parametric mesh due to knot repetitions. Since the implementation is on the parametric mesh, this example also demonstrates that not every cell in the active region of the index mesh will be exclusively marked for refinement, albeit no theoretical restrictions forbid it. For example, it is theoretically allowed to refine \(\texttt {Q}= (2, 3) \times (3, 4)\) in any direction, but since it is practically non-existent in the parametric mesh, it will never be marked for refinement on its own.

Fig. 6
figure 6

Relative placement of the barycentre of each anchor given by the example from Fig. 5. Each red dot corresponds to an anchor on the index domain, resp. a T-Spline on the parametric domain

3.2 T-Splines

A T-Spline \({\textbf {T}}_{\textbf {A}}\) is a function defined by its parametric anchor \({\textbf {A}}\), resp. the corresponding knot vectors. Hence, they are internally described by a d-dimensional array of arrays kv, where kv[k] corresponds to the k-th index vector. Note that we do not have to calculate the knot vectors for each T-Spline; from Lemma 1 we know that knot vectors are inherited by bisection. We stress again the fact that the initial mesh is a tensor-product mesh of the given knots. In addition to its knot vectors we also store its associated anchor \({\textbf {A}}\) as a pair of two points in the parametric mesh. They describe the bounding box for the anchor and can be used to obtain information about the global position of this T-Spline, e.g. we can easily access information whether or not the T-Spline is located at a specific face. The example from Fig. 4 is continued in Fig. 5 to demonstrate how index anchors and parametric anchors correspond to each other. In this example, index anchors are given as vertical lines of the mesh and are marked here by dots on these lines. When they are brought to the parametric domain on the right, some anchors collapse to a single point, and some anchors may coincide in their parametric representation. This is denoted by concentric circles in the parametric mesh on the right, where the number of circles denote the amount of parametric anchors on that entity. Note that we can not give a well-defined order of anchors, as some parametric anchors coincide.

Fig. 7
figure 7

Effect of subdividing a parametric cell on its corresponding index cells. On the left we have a cell \(\texttt {Q}\) marked for refinement, where each face has the multiplicity stated in the nodes on the faces. The type of refinement is indicated by the red line

Hence, we define the barycenter of the T-Spline associated to the parametric anchor \({\textbf {A}}\) by

(31)

The barycenters of each T-spline can be used to define a global order. Hence, a T-Spline also stores its barycenter. The example from Figs. 4 and 5 is continued in Fig. 6 to demonstrate the placement of barycenters throughout a given 2D mesh. This information can be used to order the T-splines in the mesh, e.g. in a lexicographic way, see (57).

We have chosen to store the needed control points used for the isogeometric mapping directly within the T-Splines data structure. This way, we do not need to worry about indexing errors between T-Splines and control points. It should be mentioned that in fact the weighted control points are stored, i.e. if P is a control point with weight \(\omega \in (0, 1)\), then the control point saved is \(P^\omega = (\omega P, \omega )\).

3.3 T-junctions

Since the implementation works on the parametric mesh, subdividing a (parametric) cell \(\texttt {Q}\) in direction k with a face of multiplicity greater than one, will also result in a subdivision of all related index cells corresponding to that face. In detail, consider \(\texttt {Q}\in \mathscr {T}\) from its associated index cell \(\widehat{\texttt {Q}} \in \widehat{\mathscr {T}}\) with a subdivision along direction \(k'\) with a face \(\texttt {F}\subset \partial \texttt {Q}\), \(\texttt {F}\in \mathscr {H}^{(d-1)}(\mathscr {T})\cap \texttt{H}^{(k)}(\mathscr {T})\), that is k-orthogonal, \(k\ne k'\), and has multiplicity \(m = \,\) mof[id] > 1, where id is the global index of the given face. Since the multiplicity is greater than one, there exists multiple cells \(\widehat{\texttt {Q}}^{(i)}\), \(i = 1,\dots ,m-1\) on the index mesh \(\widehat{\mathscr {T}}\) that are associated to \(\texttt {F}\), i.e. (26) reads

$$\begin{aligned} \inf \widehat{\texttt {Q}}^{(i)}_k \ne \sup \widehat{\texttt {Q}}^{(i)}_k \implies \xi ^{(k)}_{\inf \widehat{\texttt {Q}}^{(i)}_k } = \xi ^{(k)}_{\sup \widehat{\texttt {Q}}^{(i)}_k } = \texttt {F}_k, \end{aligned}$$
(32)

for all \(i=1,\dots ,m-1\). The subdivision of cell \(\texttt {Q}\) along direction k then also subdivides the above face \(\texttt {F}\) into two children \({\texttt {F}^{(1)}}\) and \({\texttt {F}^{(2)}}\) with multiplicity m. Each child then has new multiple cells from the index mesh \(\widehat{\texttt {Q}}^{(c, i)}\), \(c = 1,2\), \(i = 1,\dots ,m-1\). Now, since \(\texttt {F}^{(1)}\) and \(\texttt {F}^{(2)}\) originate from the same face \(\texttt {F}\) there is a common interface \(\texttt {T}= \partial \texttt {F}^{(1)}\cap \partial \texttt {F}^{(2)}\) and in particular \(\inf \texttt {F}^{(1)}_{k'} = \sup \texttt {F}^{(2)}_{k'}\) or vice versa. This yields common faces for the index cells \(\widehat{\texttt {Q}}^{(c, i)}\), i.e. \(\inf \widehat{\texttt {Q}}^{(1, i)}_{k'} = \sup \widehat{\texttt {Q}}^{(2,i)}_{k'}\) or vice versa for all \(i=1,\dots ,m\). However, this is equivalent to subdividing cells \(\texttt {Q}^{(i)}\) along direction \(k'\) and obtaining the children \(\texttt {Q}^{(c, i)}\), \(c=1, 2\), \(i=1,\dots ,m-1\). See also Fig. 7.

The example depicted shows the effect of subdividing a 2D parametric cell \(\texttt {Q}\) (left) on its corresponding index mesh counterparts (right). As in the previous examples, for the parametric mesh the numbers in circles denote the multiplicity of the corresponding line (face). From this setup, we can infer the that there is a cell \(\widehat{\texttt {Q}}\) in the index mesh that will be mapped to the parametric mesh. Consider now its third face, i.e. the face on the top of the cell with multiplicity two. From the multiplicity we infer a knot repetition of two in the parametric domain, hence there are two indices in the index mesh. This yields a cell \(\widehat{\texttt {Q}'}\) above the corresponding index cell \(\widehat{\texttt {Q}}\) whose corresponding parametric “cell” has empty volume. From the multiplicity of face 1, i.e. the right line, we infer two consecutive index cells that collapse to a single line by the same arguments. Repeating these arguments for the remaining two faces, we obtain the corresponding index region on the right of Fig. 7. The cells found above are highlighted in green. The subdivision of \(\texttt {Q}\) yields a subdivision of its faces, and from the corresponding index cells we also get a subdivision of these cells. To stress the fact that only corresponding cells are subdivided in the index mesh, we have displayed a slightly larger region in the index mesh highlighted by diagonal lines.

Thus, the refinement of the parametric mesh ensures that every generated T-junction on the index level is mapped according to condition (26).

T-junctions are not stored internally, instead we only store the cells that intersect the T-junction extensions as mentioned in Sect. 3.1. However, to find these cells, we first have to detect T-junctions. Since the mesh is generated from a tensor product structure on the coarsest level using symmetric box subdivisions, we can find associated cells of T-junctions \(\texttt {T}\) by iterating the faces \(\texttt {F}\subset \partial \texttt {Q}\) of a cell and check whether or not it has children. From the associated cell \(\texttt {Q}\) and a face \(\texttt {F}\) of the cell, we can then use the definition of local knot vectors for T-junctions to find all cells that intersect the T-junction extension.

figure d

How to exactly find the Bézier cells is given in Algorithm 3 and , for the dimensions 2 and 3 respectively. Each algorithm finds the Bézier cells \(\mathcal {Q}\), i.e. the cells cut by the T-junction extension, of a T-junction \(\texttt {T}\) with its associated cell \(\texttt {Q}= {{\,\textrm{ascell}\,}}(\texttt {T})\). The input face_no refers to the corresponding index, s.t. \(\texttt {T}\subset \texttt {F}_{\texttt {face\_no}}\subset \partial \texttt {Q}\). Further, both algorithms use neighbouring relations between cells. This is a functionality made available by the deal.II library. In general, neighbour(\(\texttt {Q}\), f) returns the cell on the opposite side of face f. Note that the neighbour may be an in-active cell, i.e. it may already be refined. For the readers unfamiliar with deal.II internal data structures and conventions, we have briefly explained enumeration of entities and neighbours in Appendix B.

The algorithm for \(d=2\) is relatively simple, as we just have to find cells along a single direction, that is the pointing direction. We distinguish between positive and negative directions \(\pm {{\,\textrm{pdir}\,}}(\texttt {T}) = \pm k\), where we traverse the neighbours in positive direction, if \(\texttt {T}_{k} \subset \inf {{\,\textrm{ascell}\,}}(\texttt {T})_{k}\), and in negative direction, if \(\texttt {T}_{k} \subset \sup {{\,\textrm{ascell}\,}}(\texttt {T})\). And since face_no yields the face of \(\texttt {Q}\) on which the T-junction lies, we simply have to search the neighbours in the opposite direction.

If the new neighbour \(\texttt {Q}' = \texttt {neighbor}(\texttt {Q}, \texttt {ofn})\) in that direction is already refined, the refinement algorithm provided by [23] guarantees that this cells refinement direction is not orthogonal to the pointing direction of \(\texttt {T}\), i.e. in 2D it is refined by a \(k'\)-orthogonal subdivision, \(k' = {{\,\textrm{odir}\,}}(\texttt {T})\). If the next neighbour \(\texttt {neighbor}(\texttt {Q}, \texttt {ofn})\) is again not refined, then the next neighbour is an associated cell of a new T-junction \(\texttt {T}'\) from which we run the algorithm again. Thus, the algorithm for \(\texttt {T}\) may stop at \(\texttt {Q}'\).

figure e

The 3D case is very similar, however, we have to extend the T-junction in two directions now. We denote the second direction to traverse neighbours with rdir in Algorithm 4. For every step in ofn, defined as in 2D, we traverse the neighbours in positive and negative direction. Since the T-junction extension is defined by the index vectors at the position of the T-junction, we have to ensure that these limits are given while traversing all neighbours. The quantities are denoted by rmin, resp. rmax, and are defined as

(33)

for the parametric knot vectors

(34)

where \(\widehat{\texttt {T}}\in \mathscr {T}({\widehat{\Omega }})\) is the associated T-junction in the index domain.

4 Differences between deal.II and deal.t

In this section we will discuss main differences when using a deal.t based finite element solver versus a standard deal.ii technique. A full Tutorial for readers unfamiliar with deal.II is given in Appendix C.

As the TS_Triangulation class inherits from deal.IIs Triangulation, we can use every function of the base class for the derived class. However, some functions have been overwritten to fit either the data structures explained in Sect. 3, or enforce special behaviour. For example the create_triangulation usually generates a grid from a list of vertices and a list of cell data. It has been overwritten to take the data used for the isoparametric mapping instead, i.e. a list of knot vectors, a list of polynomial degrees, and a list of weighted control points. The function header is then declared as

figure f

We also provide an object IPF_Data that essentially stores these data types and allows us to overload the definition of create_triangulation with

figure g

These two functions are then called respectively within the constructor of TS_Triangulation. We highly recommend using the constructor based on IPF_Data as it allows creating the correct grid within a single line, see also the example in Appendix C.

Further, remember that the implementation differs between the parametric mesh \(\mathscr {T}\) and the parametric Bézier mesh \(\mathscr {B}\). The former is used for refinement and the latter for assembly routines. To switch between these types of meshes we have provided the functions

figure h

that find all relevant Bézier elements using Algorithm 3 in 2D, resp. Algorithm 4 in 3D, and refines them via a symmetric, level-dependent box bisection, resp. coarsens the refined Bézier elements.

Since finite elements are defined globally and not on the reference cell, we have to provide information to applications about which T-spline, resp. DoF, is at the boundary. This information can be obtained with

figure i

where the former function computes the object returned by the latter. We advise to directly call set_boundary_dofs after each refinement.

The rest of this chapter explains main differences when assembling vectors, matrices, (estimated) errors, etc., manipulating the mesh, and other miscellaneous functions such as output routines to print e.g. the grid.

4.1 Assembly

Similar to deal.IIs FEValues object to define finite element values on a cell, deal.T provides an object of the type TSValues that mimics the behaviour of the deal.II original function. Note that it does not inherit from the original. T-spline values on a cell are computed using Bézier extraction, as explained in Sect. 3.2. During construction of a TSValues object, we store tables of Bernstein-values on quadrature points on the reference element \([0,1]^d\), \(d = 2, 3\). The constructor takes as input a reference to the used triangulation, to retrieve the necessary data, i.e. control points and extraction operators, a list of integers to determine the amount of points used for Gaussian quadrature and a deal.II object UpdateFlags, see Appendix C for a short description of this object, or [4] for a detailed explanation. In summary, we get

figure j

Note that we allow different polynomial degrees in every direction, i.e. an-isotropic polynomials can be used for applications.

Similarly, deal.t provides an object TSFaceValues, that represents finite element values on faces. Its syntax and construction is as above.

Similar to the deal.II equivalents, these functions are suited with appropriate reinit() methods, in order to reinitialize finite element values on given cells, resp. faces.

Thus, applications with deal.t are very similar to deal.II, i.e. we get approximately the following lines

figure k

Note, that deal.t does not provide a deal.II equivalent to the DoFHandler object. In standard deal.II applications this object does all the work of handling the information about DoFs at each vertex, line, quad, etc. This information is processed already within the TS_Triangulation implementation. An object TS_DoFHandler is already planned out for future implementations. This will enable further deal.II functionality that require an object DoFHandler.

For error estimations we have suited the TS_Triangulation with an internal error estimator for scalar Poisson-like problems, i.e. find \(u:\mathbb {R}^d\rightarrow \mathbb {R}\), s.t.

$$\begin{aligned} \left\{ \begin{aligned} -\nabla \cdot (\sigma \nabla u)&= f, \quad{} & {} \text { in }\overline{\Omega }\\ \tfrac{\partial u}{\partial n}&= g_i^N, \quad{} & {} \text { on } \Gamma _i^N\subset \partial \overline{\Omega },\\ u&= g_j^D, \quad{} & {} \text { on } \Gamma _j^D\subset \partial \overline{\Omega }, \end{aligned} \right. \end{aligned}$$
(35)

where \(\sigma :\mathbb {R}^d\rightarrow \mathbb {R}\), \(f:\mathbb {R}^d\rightarrow \mathbb {R}\), \(g_i^N:\mathbb {R}^d\rightarrow \mathbb {R}\), \(i=1,\dots ,n\), \(g_j^D:\mathbb {R}^d\rightarrow \mathbb {R}\), \(j=1,\dots ,m\), \(n,m\in \mathbb {N}\) are given. The error estimator used is given by a standard residual error estimator, i.e. \(\eta _\mathscr {T}:\mathscr {T}\rightarrow \mathbb {R}\) given the Galerkin solution \(u_h\) is defined by

$$\begin{aligned} \begin{aligned} \eta _\mathscr {T}(\texttt {Q}) :=\biggl (&h_\texttt {Q}^2\Vert \nabla \cdot (\sigma \nabla u_h) + f \Vert _\texttt {Q}^2 \\ {}&+ \sum _{\begin{array}{c} {\texttt {E}}\subset \partial \texttt {Q}\\ {\texttt {E}}\in \mathscr {H}^{(d-1)}(\mathscr {T}) \end{array}}h_{{\texttt {E}}}\Vert R_{{\texttt {E}}}(u_h) \Vert _E^2 \biggr )^{\tfrac{1}{2}}, \end{aligned} \end{aligned}$$
(36)

where \(h_\texttt {Q}\) is the diameter of \(\texttt {Q}\) given by

$$\begin{aligned} h_\texttt {Q}= \max _{i=1,\dots ,d} (\sup \texttt {Q}_i - \inf \texttt {Q}_i) \end{aligned}$$
(37)

\({\texttt {E}}\) is a face of \(\texttt {Q}\), \(h_{\texttt {E}}\) is the diameter of \({\texttt {E}}\), i.e.

$$\begin{aligned} h_{\texttt {E}}= \max _{i\ne k} (\sup {\texttt {E}}_i - \inf {\texttt {E}}_i), \end{aligned}$$
(38)

where k is the index s.t. \({\texttt {E}}\) is a singleton. The edge residual \(R_E\) is given by

$$\begin{aligned} R_{\texttt {E}}(u_h) :={\left\{ \begin{array}{ll} \tfrac{1}{2} \llbracket \tfrac{\partial u_h}{\partial n} \rrbracket _{\texttt {E}}, \quad &{}\text {if } {\texttt {E}}\in \overline{\Omega }\setminus \partial \overline{\Omega },\\ g_i - \tfrac{\partial u_h}{\partial n} &{}\text {if } {\texttt {E}}\in \Gamma _i^N, \\ 0 &{}\text {if } {\texttt {E}}\in \Gamma _j^D, \end{array}\right. } \end{aligned}$$
(39)

where n is a normal to the face \({\texttt {E}}= \texttt {Q}\cap \texttt {Q}'\) and \(\llbracket \bullet \rrbracket _{\texttt {E}}= \bullet |_\texttt {Q}- \bullet |_{\texttt {Q}'}\) denotes the jump along the face \({\texttt {E}}\).

The general syntax of the implemented error estimator is given by

figure l

Corresponding overloads are given for \(\sigma = 1\) and/or \(n=0\), i.e. no Neumann boundary conditions, by dropping the respective arguments.

Note that if there is at least \(C^1\)-continuity along some face \({\texttt {E}}\), the jump along this face is zero. The implemented error estimator uses the mof-array, described in Sect. 3, to detect \(C^0\)-faces and only computes jumps at those faces. A face \({\texttt {E}}\in \texttt{H}^{(k)}(\mathscr {T})\subset \mathscr {H}^{(d-1)}(\mathscr {T})\) has \(C^0\)-continuity if mof[F] = \(p_{f}\). This follows from the definition of B-splines.

4.2 Manipulation of the mesh

We have provided different functionality to modify the mesh. We have overwritten the base class function execute_coarsening_and_refinement() to perform the refinements of the mesh and execute knot insertion from the subdivided cells. For more information on knot insertion see e.g. [11, 35]. It also computes the coarse neighbourhood of marked cells, as explained in Sect. 2.2. Note that despite the name, we have not yet implemented a proper coarsening algorithm for T-splines, since there is no coarsening algorithm given for T-splines to the authors extent.

We have also overwritten the base class function refine_global(int t) that refines the mesh globally t-times using a simple for loop of declaring the appropriate refinement flags on a cell and then executing the refinement.

For local refinement, the user has to provide a list of cells to be marked for refinement. Internally, upon initialization of a TS_Triangulation, we store an offset off for refinement that ensures that the first refinement is along the longest edge. E.g. if the mesh is given by the cell \(\texttt {Q}= [0, 1] \times [0, 2] \times [0, \tfrac{1}{2}]\) then the cell is longest in the second dimension and the first refinement will be carried out by a 2-orthogonal subdivision to obtain \(\texttt {Q}_1 = [0, 1]\times [0, 1]\times [0, \tfrac{1}{2}]\) and \(\texttt {Q}_2 = [0, 1]\times [1, 2]\times [0, \tfrac{1}{2}]\). Using this offset, the level-dependent subdivision of a cell \(\texttt {Q}\in \mathscr {T}\) becomes the \(k_{\ell (\texttt {Q})}\)-orthogonal bisection of \(\texttt {Q}\) where \(k_{\ell (\texttt {Q})} = 1 + \bigl ((\ell (\texttt {Q}) + \texttt {off}) \text { mod }d\bigr )\).

The user may handle this offset on his own and set the refinement flags manually, however, we also provide a function that considers this. To let the class handle the refinement flags, we use

figure m

where we provide a list of cells to be flagged for refinement.

Another important part of isogeometric grid manipulation is degree elevation, see again [11, 35] for details. Degree elevation in this implementation is only allowed at the coarsest level and can be obtained with

figure n

where the former elevates the degree of all T-splines by t in direction d and the latter elevates the degree in every direction by t.

4.3 Miscellaneous functions

It may be of interest to print different results of the grid to some files. One possibility from deal.II is to use a GridOut object to print the grid, e.g. to a .svg file. However, this will only print the underlying parametric mesh and not the mapped parametric mesh, which is of more significance for results.

We have provided functions to print quadrature points in the physical domain, grid lines, vertices, and the T-spline functions to ".dat" files.

To print the isoparametric mapping and or the respective T-splines used, we simply use

figure o

where we provide a std::string as output file, and a precision that determines how many decimals are being saved. A suffix "_IPF_2d.dat" or "_IPF_3d.dat" is added to the name for the isoparametric mapping, depending on the space dimension. The suffix "_splines.dat" is added to the name of the T-spline file. Note that corresponding folders and sub-folders have to exist. Note further that this function assumes that we are on the parametric Bézier mesh and have already calculated the Bernstein tables with setup_tables.

To obtain the grid lines in the physical domain, we use

figure p

where a suffix "_parametric_grid.dat" is added to the declared output name. The new input n_intervals determines the amount of sub-intervals an existing edge is partitioned for the output. Choose a low number if the physical mesh consists of straight lines only, see e.g. Figure 2 and choose a higher number if the mesh has curved parts, see Fig. 8.

To obtain the grid lines in the parametric domain, we use print_grid(...) with the same arguments as before. A suffix "_grid.dat" is added to the declared output name.

To obtain the corresponding grid lines of the Bézier mesh, rerun the above functions on the Bézier mesh, preferably with a different name to ensure the previous outputs are not overwritten. Alternatively, for the parametric Bézier mesh, there is a function print_bezier_grid().

4.4 Limitations

As mentioned before, one big limitation is the restriction of scalar-valued problems.

Note that the algorithm provided by [23] allows q-graded refinement, i.e. the k-orthogonal subdivision of a cell \(\texttt {Q}\) into q equal parts. However, by deal.II limitations this is not possible since multi-level hanging nodes are not implemented.

Fig. 8
figure 8

Depiction of the physical domain used for demonstration along its parametric counterpart which is actually implemented

Fig. 9
figure 9

Physical mesh from Sect. 5.1 after different refinement levels. On the left is the physical mesh with red dashed lines indicating Bézier extensions. On the right is the physical Bézier mesh after 15 refinement levels

Further, note that the refinement we carry out within our implementation is anisotropic. There is currently no support for a parallel triangulation when using anisotropic refinements. This, however, does not restrict us from using parallel computations as assembly loops may be divided along processors or threads. It just means that the subdivision of a TS_Triangulation may not be carried out on multiple processors or threads.

The way we designed the TSValues and TSFaceValues objects uses a lot of space to store the necessary T-spline value tables. This can be reduced by further adapting deal.II behaviour, i.e. by reinitializing the tables of a TSValues object on each cell instead of assigning each cell a new object of that type.

5 Experiments

In this section we consider a few examples to use deal.t. Each subsection gives the corresponding problem to be solved, explains the data used for the geometric mapping, and gives an example after some refinement steps. Where necessary for Neumann boundary conditions, the geometric mapping is explicitly stated. Lastly, results are given for various polynomial degrees.

Note that every considered problem uses isotropic polynomial degrees, i.e. \(p_x = p_y (= p_z)\). However, it is technically possible to use anisotropic polynomials. Further, the geometric mapping is given at a base level, and higher polynomial degrees are obtained by order elevation, see [11], and where necessary global refinements are employed to obtain a finer initial mesh.

Further, the resulting linear systems \(K_h u_h = F_h\) are solved using CG-iterations and a diagonal preconditioner. On level \(l>0\) a relative accuracy of \(e_{l-1}\cdot 10^{-4}\) for the iterative solver was chosen, and for level \(l=0\), the relative accuracy was defined as \(10^{-4}\).

5.1 Poisson’s equation on a squished domain

We consider

$$\begin{aligned} \left\{ \begin{aligned} -\Delta u&= f(x, y), \quad{} & {} \text { in }\overline{\Omega }\\ \tfrac{\partial u}{\partial n}(x, y)&= g(x, y), \quad{} & {} \text { on } \Gamma _N,\\ u(x, y)&= 0, \quad{} & {} \text { on } \Gamma _D, \end{aligned} \right. \end{aligned}$$
(40)

where the right-hand-side function f and the boundary function g are defined using the exact solution

$$\begin{aligned} u(x, y) = \frac{1}{5\pi ^2} \sin (2\pi x)\cos (\pi y). \end{aligned}$$
(41)

The physical domain is defined using the data \(\Xi _x = \{0, 0, 0, 1, 1, 1\} = \Xi _y\), \(p = 2\), and controls

$$\begin{aligned} {\textbf {P}}^w = \begin{bmatrix} 0.0 &{} 0.2 &{} 1.0 &{} 0.0 &{} 0.2 &{} 1.0 &{} 0.0 &{} 0.2 &{} 1.0\\ 0.0 &{} 0.5 &{} 0.0 &{} 0.5 &{} 0.5 &{} 0.5 &{} 1.0 &{} 0.5 &{} 1.0 \\ 1.0 &{} 1.0 &{} 1.0 &{} 1.0 &{} 1.0 &{} 1.0 &{} 1.0 &{} 1.0 &{} 1.0 \end{bmatrix}. \end{aligned}$$
(42)

The resulting domain is depicted in Fig. 8 together with its boundaries \(\Gamma _N\) and \(\Gamma _D\). This data yields the isoparametric mapping

$$\begin{aligned} \Phi (x, y) = \begin{bmatrix} 0.4x + 0.6x^2 \\ (x-x^2)(1-2y) + y \end{bmatrix} \end{aligned}$$
(43)

from which we can define the normal vectors to the boundary \(\overline{\Gamma }_N\) as orthogonal vectors to the tangents \(\partial _x \Phi (x, 0)\) and \(\partial _x \Phi (x, 1)\), since

$$\begin{aligned} \Gamma _{N} = \bigcup _{i=0, 1}\{\Phi (x, i) \mid x\in [0, 1]\}. \end{aligned}$$
(44)

For error estimation, we use the standard residual error estimator \(\eta (\texttt {Q})\) on a cell \(\texttt {Q}\) given in Sect. 4.1. We use a quantile marking strategy by refining the

$$\begin{aligned} N_r = \lceil \alpha \#\mathscr {T}\rceil \end{aligned}$$
(45)

cells with the highest errors, where \(\alpha = 0.0075\). The resulting meshes are depicted in Fig. 9. On the left we see the physical mesh for polynomial degree \(p = 3\) after 8 refinement levels. Red dashed lines indicate Bézier extensions and hence the Bézier mesh. On the right, we see the same domain after 15 refinement levels.

Fig. 10
figure 10

\(H^1\)-Errors of the problem from Sect. 5.1 with various polynomial degrees. The reference lines are given in matching colours and markers as dashed lines

We have computed the numerical solution for T-spline degrees \(p = 3,\dots , 7\) and given the \(H^1\)-errors to the exact solution over the degrees of freedom in Fig. 10. Additionally, the \(H^1\)-error using a standard finite element method with \(p=1\) is given. We have performed refinements, until we reached an error smaller than \(10^{-8}\). Note, that in FEM, each new cell yields \((p+1)^d\) new basis functions. Using IGA-FEM with T-splines on the other hand, a bisection of a cell yields \((p+1)^{d-1}\) additional basis functions, assuming the same polynomial degree in every direction. Thus, for a full refinement of a cell, i.e. the cell is bisected once in each direction, we obtain (\(d>1\))

$$\begin{aligned} (p+1)^{d-1}\sum _{i=0}^{d-1}2^i = (p+1)^{d-1}(2^d-1) \end{aligned}$$
(46)

additional basis functions. Since for fixed \(d>1\), the term \(2^d-1\) is constant in p, the number of new basis functions added to the system grows by one order slower.

Note that we consider this example as a benchmark for our implementation. It does not involve any sort of singularities. It is just a short demonstration of deal.t to show its usage as explained in Appendix C and demonstrates how to set Neumann boundary conditions for a manufactured problem on curved domains.

Asymptotically, we obtain optimal convergence rates, as shown in Fig. 10. Note that the resulting linear systems are relatively small compared to systems derived from standard FEM. In fact, the considered example goes as high as around \(10^5\) DoFs for the \(p=3\) case. The other polynomial degrees are below \(10^4\) DoFs. As mentioned in the introduction, for a fair comparison we opted to use a CG-solver with a diagonal preconditioner to solve the linear systems. However, considering the size of the problems, it is more reasonable to use a direct method for T-splines.

This also becomes clear, when considering iteration numbers for the iterative method in Fig. 11. The reason lies in the nature of IGA-FEM, since the basis functions span multiple cells of a triangulation, especially for higher degrees. This results in mass- and stiffness-matrices with low sparsity compared to standard FEM.

Fig. 11
figure 11

CG-iteration numbers over degrees of freedom for the Problem considered in Sect. 5.1

As a last note on this subsection, let us explain the procedure for solving this problem using standard FEM. In this case, we have to define a mapping to get the new vertices of the triangulation after refinement, in order to reduce the discretization error at the smooth boundary \(\Gamma _N\). This mapping is know as \(\Phi \). A new vertex after (isotropic) subdivision is then given as

$$\begin{aligned} P^{new} = \Phi ^{-1}\bigl ( \tfrac{1}{2} \Phi (P_0) + \tfrac{1}{2} \Phi (P_1) \bigr ), \end{aligned}$$
(47)

where \(P_0\), and \(P_1\) are two neighbored vertices of a common cell. This includes knowledge of the inverse, \(\Phi ^{-1}\), which in this case is given by

$$\begin{aligned} \Phi ^{-1}_1({\hat{x}},{\hat{y}})&= \frac{(\sqrt{15 {\hat{x}} + 1} - 1)}{3} \end{aligned}$$
(48)
$$\begin{aligned} \Phi ^{-1}_2({\hat{x}},{\hat{y}})&= \frac{9{\hat{y}} - 5(\sqrt{15 {\hat{x}} + 1} - 3{\hat{x}} - 1)}{30{\hat{x}} - 10\sqrt{15{\hat{x}} + 1} +19}. \end{aligned}$$
(49)

There are also other methods to get an approximation of the boundary vertices, e.g. by assigning certain types of manifolds to it. However, the above method guarantees exact boundary vertices.

5.2 L-Shape domain

Fig. 12
figure 12

Depiction of the physical domain used for the L-Shape domain along with its parametric counterpart

Fig. 13
figure 13

Employing symmetry on the domain from Fig. 12, we obtain the complete domain

We consider again problem (40) on the L-Shape domain \(\Omega _L = [-1, 1]^2 \setminus (-1, 0)^2\). For this setting, we have \(f(x, y) = 0\) and the boundary function g is defined from the exact solution

$$\begin{aligned} u(r, \varphi ) = r^{\tfrac{2}{3}}\sin \bigl (\tfrac{2\varphi + \pi }{3}\bigr ), \end{aligned}$$
(50)

given in polar coordinates. The solution is symmetric along the diagonal \(y = x\), hence we only give a parametrization of half the L-Shape domain by knot vectors \(\Xi _x = \{0, 0, 1, 1\} = \Xi _y\), degrees \(p = 1\), and control points

$$\begin{aligned} {\textbf {P}}^w = \begin{bmatrix} -1.0 &{} +0.0 &{} -1.0 &{} +1.0 \\ +0.0 &{} +0.0 &{} +1.0 &{} +1.0 \end{bmatrix}. \end{aligned}$$
(51)

The resulting mapping is depicted in Fig. 12. Note that we did not set boundary conditions along the line \(y=x\). An intermediate mesh at level 21 and polynomial degree \(p = 2\) is given in Fig. 13, where symmetry is already employed. Computations began after three initial global refinement steps, yielding eight elements as initial mesh. In general, the initial mesh for computations for any considered polynomial degree p was globally refined \(N_p = \lfloor \tfrac{3p}{2}\rfloor \) times to obtain \(2^{N_p}\) elements before local refinements and error estimations took part.

The marking strategy used for refinement is again the same as in Sect. 5.1. The results for polynomial degrees \(p = 3, \dots , 7\) are given in Fig. 14. Note that in contrast to Sect. 5.1, we refined until we reached 41 (half-)levels, yielding 20 full refinement steps.

Fig. 14
figure 14

\(H^1\) errors of the problem from Sect. 5.2 with various polynomial degrees. The reference lines are given in matching colours and markers as dashed lines

Fig. 15
figure 15

\(H^1\) errors of the problem from Sect. 5.2 with various polynomial degrees using standard \(Q_p\) elements. Reference lines are omitted for simplicity

Fig. 16
figure 16

CG iterations for the problem described in Sect. 5.2 using T-splines

Note that the reference lines correspond to \(\mathcal {O}(h^{p+1})\), thus the results exceed optimal convergence. Note also that we beat the optimal convergence rates by up to three magnitudes, e.g. the result for \(p=6\) behaves asymptotically as \(\mathcal {O}(h^9)\). This is most likely due to some symmetry effects of the considered PDE.

For comparison we have solved this problem with standard \(Q_p\) elements for the same polynomial degrees on the whole domain. The results are depicted in Fig. 15. There, we have not run a proper number of global refinements, which can be seen in the plots as well, by a sudden drop of the error. The super-convergent effects mentioned before can be seen here as well. This may be subject to Gaussian quadrature rules used in this example. For simplicity’s sake, we have omitted the reference lines in this plot.

To get an idea about the condition number of the matrix, we take a look at the quotient

$$\begin{aligned} \frac{k^p_i / k^p_{i+1}}{N^p_{i} / N^p_{i+1}}, \end{aligned}$$
(52)

where \(k^p_i\) and \(N^p_i\) are the iteration numbers, resp. number of DoFs of the CG-iteration for the basis functions of degree p at level \(i>0\). Take e.g. \(p = 4\), and \(i = 4\). From Fig. 16 we infer

$$\begin{aligned} \frac{k^p_i / k^p_{i+1}}{N^p_{i} / N^p_{i+1}} = \frac{87 / 95}{325 / 389} \approx 1.0961. \end{aligned}$$
(53)

In fact, we can see that

$$\begin{aligned} \frac{k^p_i / k^p_{i+1}}{N^p_{i} / N^p_{i+1}} \sim 1, \end{aligned}$$
(54)

for all \(p= 3,\dots , 7\), and i. This yields

$$\begin{aligned} \kappa (K_{p, h}) \sim \mathcal {O}(N_p^2), \end{aligned}$$
(55)

for the condition number \(\kappa (K_{p, h})\) of the stiffness matrix from polynomial degree p.

We finish this subsection with a comparison of DoFs between the two approaches. Note that the data from Fig. 15 was computed on a full parametrization of the L-shape, with three initial quads. In Table 1 we see a direct comparison of the DoFs needed to obtain an error smaller than \(5\cdot 10^{-4}\). Note that T-splines need less DoFs for each polynomial degree.

Table 1 Number of DoFs needed to obtain an \(H^1\) error smaller then \(5\cdot 10^{-4}\)

6 Outlook

We have successfully implemented and demonstrated a framework to use isogeometric analysis within deal.II. Standard error estimators are given in the implementation for different Poisson-like problems, see (35). During applications, the main difficulty for the user is the correct application of Neumann boundary data for the error estimator. In the presented examples, it was necessary to use the explicit definition of the geometric mapping to define the normals on the boundary. This will be automated in future extensions.

We only considered scalar elliptic PDEs in this work, however, in the next step the focus will also shift to vector valued problems, e.g. for linear elasticity. Once this is done, deal.II allows us to extend the results to almost arbitrary PDEs of order two.

Further, note that the considered problems are defined on single patch domains. We will also focus on an implementation of multiple patched domains to apply T-splines with deal.t in a real-world setting with vector valued problems. The source-code is available at [26] with instructions to reconstruct the given examples.