ngstSpaceKit.immersedfe

  1import enum
  2from enum import Enum
  3
  4import ngsolve
  5from ngsolve import (
  6    BBND,
  7    CF,
  8    H1,
  9    L2,
 10    QUAD,
 11    TRIG,
 12    BilinearForm,
 13    Discontinuous,
 14    IfPos,
 15    LinearForm,
 16    Normalize,
 17    dx,
 18    specialcf,
 19)
 20from ngsolve.solve_implementation import GridFunction
 21from ngstrefftz import (
 22    EmbeddedTrefftzFES,
 23    TrefftzEmbedding,
 24)
 25from xfem import (
 26    IF,
 27    NEG,
 28    POS,
 29    CutInfo,
 30    dCut,
 31)
 32
 33from ngstSpaceKit.diffops import grad, hesse
 34from ngstSpaceKit.mesh_properties import (
 35    throw_on_wrong_mesh_dimension,
 36    throw_on_wrong_mesh_eltype,
 37)
 38
 39
 40def ImmersedP1FE(
 41    mesh: ngsolve.comp.Mesh,
 42    lsetp1: ngsolve.CoefficientFunction,
 43    beta_neg: float,
 44    beta_pos: float,
 45    dirichlet: str = "",
 46    dgjumps: bool = False,
 47    check_mesh: bool = True,
 48    stats: dict | None = None,
 49) -> EmbeddedTrefftzFES:
 50    r"""
 51    `check_mesh`: test, if the `mesh` is compatible with this space
 52
 53    `stats`: use the `stats` flag of the `TrefftzEmbeddin` method
 54
 55    This Immersed P1 space is tailored towards solving the following interface problem.
 56
 57    Let $\Omega$ be a domain, which is decomposed by a cut $\Gamma$ into $\Omega = \Omega^- \cup \Gamma \cup \Omega^+$.
 58    Let $\beta$ be a piecewise constant coefficient
 59    \begin{align}
 60    \beta(x) &:=
 61        \begin{cases}
 62            \beta^-, &\text{if } x \in \Omega^- \\\\
 63            \beta^+, &\text{if } x \in \Omega^+ \\\\
 64        \end{cases}, \\\\
 65    \beta^-, \beta^+ &> 0.
 66    \end{align}
 67
 68    Then, find $u$, s.t.
 69
 70    \begin{align}
 71        -\operatorname{div} (\beta \nabla u) &= f \text{ in } \Omega^- \cup \Omega^+, \\\\
 72                                        〚u〛 &= 0 \text{ on } \Gamma, \\\\
 73	        〚\beta \nabla u \cdot n_\Gamma〛 &= 0 \text{ on } \Gamma, \\\\
 74	                                       u &= 0 \text{ on } \partial \Gamma. \\\\
 75    \end{align}
 76
 77    In particular, the functions in this space fulfil the property
 78    \begin{align}
 79                                〚u〛 &= 0 \text{ on } \Gamma, \\\\
 80	〚\beta \nabla u \cdot n_\Gamma〛 &= 0 \text{ on } \Gamma, \\\\
 81    \end{align}
 82    as well as being continuous at mesh vertices.
 83    The space consists of piecewise linear functions.
 84
 85    Actually, the returned space consists of vectorial functions of order $1$,
 86    that have to be interpreted in the following way: the first component represents the
 87    piecewise linear function in $\Omega^-$, the second component in $\Omega^+$.
 88    Formally, for $v = \begin{pmatrix} v^- \\\\ v^+ \end{pmatrix}$ we define the piecewise linear function
 89    \begin{align}
 90        \hat{v}(x) &:=
 91            \begin{cases}
 92                v^-(x), &\text{if } x \in \Omega^- \\\\
 93                v^+(x), &\text{if } x \in \Omega^+ \\\\
 94            \end{cases}.
 95    \end{align}
 96
 97    `lsetp1`: The levelset function $p$ used to describe the cut as $\Gamma := \\{x \in \Omega \mid p(x) = 0 \\}$,
 98        as well as $\Omega^- := \\{x \in \Omega \mid p(x) < 0 \\}$ and $\Omega^- := \\{x \in \Omega \mid p(x) > 0 \\}$.
 99        $p$ needs to be affine linear on each element. E.g. set `lsetp1` as a `Gridfunction(H1(mesh, order=1))`.
100
101    `beta_neg`: diffusion coefficient for $\Omega^-$. Should be a positive number.
102
103    `beta_pos`: diffusion coefficient for $\Omega^+$. Should be a positive number.
104
105    # Conforming Trefftz Formulation
106    - $\mathbb{V}_h := [\mathbb{P}^{1, \text{disc}}(\mathcal{T}_h)]^2$
107    - $\mathbb{Q}_h := \mathbb{V}_h$
108    - $\mathbb{Z}_h := \mathbb{P}^{1}(\mathcal{T}_h)$
109    - \begin{align}
110      \mathcal{C}_K(v_h, z_h) &:=
111          \sum_{p \text{ is vertex}} \hat{v}_h(p) z_h(p) \\\\
112      \mathcal{D}_K(y_h, z_h) &:=
113          \sum_{p \text{ is vertex}} y_h(p) z_h(p) \\\\
114      \end{align}
115    - \begin{align}
116      (\mathcal{L}_K v_h, q_h) :=
117        \int_\Gamma 〚\hat{v}_h〛 〚\hat{q}_h〛 \;dS + \int_\Gamma〚\beta \nabla \hat{v}_h \cdot n_\Gamma〛 〚\beta \nabla \hat{q}_h \cdot n_\Gamma〛\;dS
118      \end{align}
119    """
120
121    if check_mesh:
122        throw_on_wrong_mesh_dimension(mesh, 2)
123        throw_on_wrong_mesh_eltype(mesh, TRIG)
124
125    ci = CutInfo(mesh, lsetp1)
126    dS = dCut(lsetp1, IF, definedonelements=ci.GetElementsOfType(IF), order=2)
127    n = Normalize(grad(lsetp1, mesh.dim))
128    h = specialcf.mesh_size
129
130    fes = L2(mesh, order=1, dgjumps=dgjumps) * L2(
131        mesh, order=1, dgjumps=dgjumps
132    )
133    conformity_space = H1(mesh, order=1, dirichlet=dirichlet)
134
135    (u_neg, u_pos), (v_neg, v_pos) = fes.TnT()
136
137    top = u_neg * v_neg * dx(definedonelements=ci.GetElementsOfType(POS))
138    top += u_pos * v_pos * dx(definedonelements=ci.GetElementsOfType(NEG))
139    top += 1 / h * (u_pos - u_neg) * (v_pos - v_neg) * dS
140    top += (
141        h
142        * ((beta_pos * grad(u_pos) - beta_neg * grad(u_neg)) * n)
143        * ((beta_pos * grad(v_pos) - beta_neg * grad(v_neg)) * n)
144        * dS
145    )
146
147    uc, vc = conformity_space.TnT()
148
149    cop = IfPos(lsetp1, u_pos, u_neg) * vc * dx(element_vb=BBND)
150
151    crhs = uc * vc * dx(element_vb=BBND)
152
153    emb = TrefftzEmbedding(
154        top=top,
155        trhs=None,
156        cop=cop,
157        crhs=crhs,
158        ndof_trefftz=0,
159        stats=stats,
160    )
161    imp1fe = EmbeddedTrefftzFES(emb)
162    return imp1fe
163
164
165class ImmersedQ1Impl(Enum):
166    """
167    Represents a Conforming Trefftz implementation
168    for the `ImmersedQ1FE` space.
169    """
170
171    Canonical = enum.auto()
172    """
173    Formulation is described by <https://doi.org/10.1002/num.20318>.
174    """
175
176    NonConforming = enum.auto()
177    """
178    Stable formulation, that is not continuous acrosss the cut interface.
179    """
180
181    Overloaded = enum.auto()
182    """
183    Technically overconstrains the Trefftz condition,
184    but it may still work.
185    """
186
187
188def ImmersedQ1FE(
189    mesh: ngsolve.comp.Mesh,
190    lsetq1: ngsolve.GridFunction,
191    beta_neg: float,
192    beta_pos: float,
193    dirichlet: str = "",
194    dgjumps: bool = False,
195    stats: dict | None = None,
196    impl: ImmersedQ1Impl = ImmersedQ1Impl.NonConforming,
197) -> EmbeddedTrefftzFES:
198    r"""
199    This is the version of `ImmersedP1FE` for quadrilateral meshes.
200    Refer to the documentation of `ImmersedP1FE` for most details.
201
202    `lsetq1`: The levelset function, as a `GridFunction` over the `H1` space with `order=1`.
203        In geleral, the cut may not be piecewise linear, as the `H1` space of `order=1` contains bilinear functions on quads.
204        You can use `straighten_levelset` inorder to produce a levelset function
205        with a piecewise linear cut.
206
207    `impl`: declare what Trefftz implementation you want to use
208
209    # Canonical Conforming Trefftz Formulation
210    This implements the formulation of <https://doi.org/10.1002/num.20318>.
211    - $\mathbb{V}_h := [\mathbb{Q}^{1, \text{disc}}(\mathcal{T}_h)]^2$
212    - $\mathbb{Q}_h := \mathbb{Q}^{1, \text{disc}}(\mathcal{T}_h) \times \mathbb{Q}^{0, \text{disc}}(\mathcal{T}_h)$
213    - $\mathbb{Z}_h := \mathbb{Q}^{1}(\mathcal{T}_h)$
214    - \begin{align}
215      \mathcal{C}_K(v_h, z_h) &:=
216          \sum_{p \text{ is vertex}} \hat{v}_h(p) z_h(p) \\\\
217      \mathcal{D}_K(y_h, z_h) &:=
218          \sum_{p \text{ is vertex}} y_h(p) z_h(p) \\\\
219      \end{align}
220    - \begin{align}
221      (\mathcal{L}_K (v_h, (q_{h, 1}, q_{h, 0})) :=
222        \int_\Gamma 〚\hat{v}_h〛q_{h,1} \;dS + \int_\Gamma〚\beta \nabla \hat{v}_h \cdot n_\Gamma〛 q_{h,0} \;dS
223      \end{align}
224
225    # Non-Conforming Conforming Trefftz Formulation
226    - $\mathbb{V}_h := [\mathbb{Q}^{1, \text{disc}}(\mathcal{T}_h)]^2$
227    - $\mathbb{Q}_h := [\mathbb{Q}^{0, \text{disc}}(\mathcal{T}_h)]^4$
228    - $\mathbb{Z}_h := \mathbb{Q}^{1}(\mathcal{T}_h)$
229    - \begin{align}
230    \mathcal{C}_K(v_h, z_h) &:=
231        \sum_{p \text{ is vertex}} \hat{v}_h(p) z_h(p) \\\\
232    \mathcal{D}_K(y_h, z_h) &:=
233        \sum_{p \text{ is vertex}} y_h(p) z_h(p) \\\\
234    \end{align}
235    - \begin{align}
236    (\mathcal{L}_K v_h, (q_h, q_\tau, q_n, q_{\tau n})) &:=
237        \int_\Gamma \frac{1}{h}〚\hat{v}_h〛 q_h \;dS + \int_\Gamma〚\nabla \hat{v}_h \cdot \tau_\Gamma〛 q_\tau \;dS \\\\
238        &+ \int_\Gamma〚\beta \nabla \hat{v}_h \cdot n_\Gamma〛 q_n \;dS + \int_\Gamma h〚\beta n_\Gamma^T \mathbf{H}_{\hat{v}_h} \tau_\Gamma〛 q_{\tau n} \;dS
239    \end{align}
240
241    # Overloaded Conforming Trefftz Formulation
242    - $\mathbb{V}_h := [\mathbb{Q}^{1, \text{disc}}(\mathcal{T}_h)]^2$
243    - $\mathbb{Q}_h := \mathbb{V}_h$
244    - $\mathbb{Z}_h := \mathbb{Q}^{1}(\mathcal{T}_h)$
245    - \begin{align}
246    \mathcal{C}_K(v_h, z_h) &:=
247        \sum_{p \text{ is vertex}} \hat{v}_h(p) z_h(p) \\\\
248    \mathcal{D}_K(y_h, z_h) &:=
249        \sum_{p \text{ is vertex}} y_h(p) z_h(p) \\\\
250    \end{align}
251    - \begin{align}
252    (\mathcal{L}_K v_h, q_h) :=
253        \int_\Gamma 〚\hat{v}_h〛 〚\hat{q}_h〛 \;dS + \int_\Gamma〚\beta \nabla \hat{v}_h \cdot n_\Gamma〛 〚\beta \nabla \hat{q}_h \cdot n_\Gamma〛\;dS
254    \end{align}
255    """
256
257    throw_on_wrong_mesh_dimension(mesh, 2)
258    throw_on_wrong_mesh_eltype(mesh, QUAD)
259
260    if not (
261        (
262            isinstance(lsetq1.space, H1)
263            or isinstance(lsetq1.space, Discontinuous)
264        )
265        and lsetq1.space.globalorder == 1
266    ):
267        raise ValueError(
268            f"lsetq1 must be a GridFunction on an H1(order=1) or Discontinuous(H1(order=1)) space. You got: {lsetq1.space}"
269        )
270
271    ci = CutInfo(mesh, lsetq1)
272    dS = dCut(lsetq1, IF, definedonelements=ci.GetElementsOfType(IF), order=4)
273    n = Normalize(grad(lsetq1, mesh.dim))
274    h = specialcf.mesh_size
275
276    fes = L2(mesh, order=1, dgjumps=dgjumps) * L2(
277        mesh, order=1, dgjumps=dgjumps
278    )
279
280    conformity_space = H1(mesh, order=1, dirichlet=dirichlet)
281
282    match impl:
283        case ImmersedQ1Impl.Overloaded:
284            (u_neg, u_pos), (v_neg, v_pos) = fes.TnT()
285
286            top = (
287                u_neg * v_neg * dx(definedonelements=ci.GetElementsOfType(POS))
288            )
289            top += (
290                u_pos * v_pos * dx(definedonelements=ci.GetElementsOfType(NEG))
291            )
292            top += 1 * (u_pos - u_neg) * (v_pos - v_neg) * dS
293            top += (
294                h
295                * ((beta_pos * grad(u_pos) - beta_neg * grad(u_neg)) * n)
296                * ((beta_pos * grad(v_pos) - beta_neg * grad(v_neg)) * n)
297                * dS
298            )
299        case ImmersedQ1Impl.Canonical:  # old
300            fes_test = L2(mesh, order=1) * L2(mesh, order=0)
301
302            (u_neg, u_pos) = fes.TrialFunction()
303            v_bilin, v_const = fes_test.TestFunction()
304
305            top = (
306                u_neg
307                * v_bilin
308                * dx(definedonelements=ci.GetElementsOfType(POS))
309            )
310            top += (
311                u_pos
312                * v_bilin
313                * dx(definedonelements=ci.GetElementsOfType(NEG))
314            )
315            top += 1 / h**2 * (u_pos - u_neg) * v_bilin * dS
316            top += (
317                h
318                * ((beta_pos * grad(u_pos) - beta_neg * grad(u_neg)) * n)
319                * v_const
320                * dS
321            )
322        case ImmersedQ1Impl.NonConforming:  # new version
323            fes_test = (
324                L2(mesh, order=1)
325                * L2(mesh, order=0)
326                * L2(mesh, order=0)
327                * L2(mesh, order=0)
328                * L2(mesh, order=0)
329            )
330
331            (u_neg, u_pos) = fes.TrialFunction()
332            v_bilin, v1, v2, v3, v4 = fes_test.TestFunction()
333
334            top = (
335                u_neg
336                * v_bilin
337                * dx(definedonelements=ci.GetElementsOfType(POS))
338            )
339            top += (
340                u_pos
341                * v_bilin
342                * dx(definedonelements=ci.GetElementsOfType(NEG))
343            )
344            t = CF((n[1], -n[0]))
345            top += 1 / h * (u_pos - u_neg) * v1 * dS
346            top += (grad(u_pos) - grad(u_neg) | t) * v2 * dS
347            top += (
348                (beta_pos * grad(u_pos) - beta_neg * grad(u_neg) | n) * v3 * dS
349            )
350            top += (
351                h
352                * (
353                    beta_pos * (hesse(u_pos) * t | n)
354                    - beta_neg * (hesse(u_neg) * t | n)
355                )
356                * v4
357                * dS
358            )
359
360    uc, vc = conformity_space.TnT()
361
362    cop = IfPos(lsetq1, u_pos, u_neg) * vc * dx(element_vb=BBND)
363    crhs = uc * vc * dx(element_vb=BBND)
364
365    emb = TrefftzEmbedding(
366        top=top,
367        trhs=None,
368        cop=cop,
369        crhs=crhs,
370        ndof_trefftz=0,
371        stats=stats,
372    )
373    imq1fe = EmbeddedTrefftzFES(emb)
374    return imq1fe
375
376
377def straighten_levelset(lsetq1: GridFunction) -> GridFunction:
378    """
379    Produces a new levelset function with an element-wise
380    straight cut.
381
382    This is interesting for straightening of levelset functions
383    on quad meshes.
384    """
385    eps = 1e-9
386    fes = L2(lsetq1.space.mesh, order=1)
387    u, v = fes.TnT()
388    op = (hesse(u) | hesse(v)) * dx + u * v * dCut(
389        lsetq1, IF, element_boundary=True
390    )
391    emb = TrefftzEmbedding(op, eps=eps)
392
393    etfes = EmbeddedTrefftzFES(emb)
394    u, v = etfes.TnT()
395    a = BilinearForm(u * v * dx).Assemble()
396    f = LinearForm(lsetq1 * v * dx).Assemble()
397    inv = a.mat.Inverse(inverse="sparsecholesky")
398    lsetp1_straight_trefftz = GridFunction(etfes)
399    lsetp1_straight_trefftz.vec.data = inv * f.vec
400
401    lsetp1_straight = GridFunction(fes)
402    lsetp1_straight.vec.data = emb.Embed(lsetp1_straight_trefftz.vec)
403
404    lsetp1_straight_final = GridFunction(
405        Discontinuous(H1(lsetq1.space.mesh, order=1))
406    )
407    lsetp1_straight_final.Set(lsetp1_straight)
408    return lsetp1_straight_final
def ImmersedP1FE( mesh: ngsolve.comp.Mesh, lsetp1: ngsolve.fem.CoefficientFunction, beta_neg: float, beta_pos: float, dirichlet: str = '', dgjumps: bool = False, check_mesh: bool = True, stats: dict | None = None) -> ngstrefftz.EmbeddedTrefftzFES:
 41def ImmersedP1FE(
 42    mesh: ngsolve.comp.Mesh,
 43    lsetp1: ngsolve.CoefficientFunction,
 44    beta_neg: float,
 45    beta_pos: float,
 46    dirichlet: str = "",
 47    dgjumps: bool = False,
 48    check_mesh: bool = True,
 49    stats: dict | None = None,
 50) -> EmbeddedTrefftzFES:
 51    r"""
 52    `check_mesh`: test, if the `mesh` is compatible with this space
 53
 54    `stats`: use the `stats` flag of the `TrefftzEmbeddin` method
 55
 56    This Immersed P1 space is tailored towards solving the following interface problem.
 57
 58    Let $\Omega$ be a domain, which is decomposed by a cut $\Gamma$ into $\Omega = \Omega^- \cup \Gamma \cup \Omega^+$.
 59    Let $\beta$ be a piecewise constant coefficient
 60    \begin{align}
 61    \beta(x) &:=
 62        \begin{cases}
 63            \beta^-, &\text{if } x \in \Omega^- \\\\
 64            \beta^+, &\text{if } x \in \Omega^+ \\\\
 65        \end{cases}, \\\\
 66    \beta^-, \beta^+ &> 0.
 67    \end{align}
 68
 69    Then, find $u$, s.t.
 70
 71    \begin{align}
 72        -\operatorname{div} (\beta \nabla u) &= f \text{ in } \Omega^- \cup \Omega^+, \\\\
 73                                        〚u〛 &= 0 \text{ on } \Gamma, \\\\
 74	        〚\beta \nabla u \cdot n_\Gamma〛 &= 0 \text{ on } \Gamma, \\\\
 75	                                       u &= 0 \text{ on } \partial \Gamma. \\\\
 76    \end{align}
 77
 78    In particular, the functions in this space fulfil the property
 79    \begin{align}
 80                                〚u〛 &= 0 \text{ on } \Gamma, \\\\
 81	〚\beta \nabla u \cdot n_\Gamma〛 &= 0 \text{ on } \Gamma, \\\\
 82    \end{align}
 83    as well as being continuous at mesh vertices.
 84    The space consists of piecewise linear functions.
 85
 86    Actually, the returned space consists of vectorial functions of order $1$,
 87    that have to be interpreted in the following way: the first component represents the
 88    piecewise linear function in $\Omega^-$, the second component in $\Omega^+$.
 89    Formally, for $v = \begin{pmatrix} v^- \\\\ v^+ \end{pmatrix}$ we define the piecewise linear function
 90    \begin{align}
 91        \hat{v}(x) &:=
 92            \begin{cases}
 93                v^-(x), &\text{if } x \in \Omega^- \\\\
 94                v^+(x), &\text{if } x \in \Omega^+ \\\\
 95            \end{cases}.
 96    \end{align}
 97
 98    `lsetp1`: The levelset function $p$ used to describe the cut as $\Gamma := \\{x \in \Omega \mid p(x) = 0 \\}$,
 99        as well as $\Omega^- := \\{x \in \Omega \mid p(x) < 0 \\}$ and $\Omega^- := \\{x \in \Omega \mid p(x) > 0 \\}$.
100        $p$ needs to be affine linear on each element. E.g. set `lsetp1` as a `Gridfunction(H1(mesh, order=1))`.
101
102    `beta_neg`: diffusion coefficient for $\Omega^-$. Should be a positive number.
103
104    `beta_pos`: diffusion coefficient for $\Omega^+$. Should be a positive number.
105
106    # Conforming Trefftz Formulation
107    - $\mathbb{V}_h := [\mathbb{P}^{1, \text{disc}}(\mathcal{T}_h)]^2$
108    - $\mathbb{Q}_h := \mathbb{V}_h$
109    - $\mathbb{Z}_h := \mathbb{P}^{1}(\mathcal{T}_h)$
110    - \begin{align}
111      \mathcal{C}_K(v_h, z_h) &:=
112          \sum_{p \text{ is vertex}} \hat{v}_h(p) z_h(p) \\\\
113      \mathcal{D}_K(y_h, z_h) &:=
114          \sum_{p \text{ is vertex}} y_h(p) z_h(p) \\\\
115      \end{align}
116    - \begin{align}
117      (\mathcal{L}_K v_h, q_h) :=
118        \int_\Gamma 〚\hat{v}_h〛 〚\hat{q}_h〛 \;dS + \int_\Gamma〚\beta \nabla \hat{v}_h \cdot n_\Gamma〛 〚\beta \nabla \hat{q}_h \cdot n_\Gamma〛\;dS
119      \end{align}
120    """
121
122    if check_mesh:
123        throw_on_wrong_mesh_dimension(mesh, 2)
124        throw_on_wrong_mesh_eltype(mesh, TRIG)
125
126    ci = CutInfo(mesh, lsetp1)
127    dS = dCut(lsetp1, IF, definedonelements=ci.GetElementsOfType(IF), order=2)
128    n = Normalize(grad(lsetp1, mesh.dim))
129    h = specialcf.mesh_size
130
131    fes = L2(mesh, order=1, dgjumps=dgjumps) * L2(
132        mesh, order=1, dgjumps=dgjumps
133    )
134    conformity_space = H1(mesh, order=1, dirichlet=dirichlet)
135
136    (u_neg, u_pos), (v_neg, v_pos) = fes.TnT()
137
138    top = u_neg * v_neg * dx(definedonelements=ci.GetElementsOfType(POS))
139    top += u_pos * v_pos * dx(definedonelements=ci.GetElementsOfType(NEG))
140    top += 1 / h * (u_pos - u_neg) * (v_pos - v_neg) * dS
141    top += (
142        h
143        * ((beta_pos * grad(u_pos) - beta_neg * grad(u_neg)) * n)
144        * ((beta_pos * grad(v_pos) - beta_neg * grad(v_neg)) * n)
145        * dS
146    )
147
148    uc, vc = conformity_space.TnT()
149
150    cop = IfPos(lsetp1, u_pos, u_neg) * vc * dx(element_vb=BBND)
151
152    crhs = uc * vc * dx(element_vb=BBND)
153
154    emb = TrefftzEmbedding(
155        top=top,
156        trhs=None,
157        cop=cop,
158        crhs=crhs,
159        ndof_trefftz=0,
160        stats=stats,
161    )
162    imp1fe = EmbeddedTrefftzFES(emb)
163    return imp1fe

check_mesh: test, if the mesh is compatible with this space

stats: use the stats flag of the TrefftzEmbeddin method

This Immersed P1 space is tailored towards solving the following interface problem.

Let $\Omega$ be a domain, which is decomposed by a cut $\Gamma$ into $\Omega = \Omega^- \cup \Gamma \cup \Omega^+$. Let $\beta$ be a piecewise constant coefficient \begin{align} \beta(x) &:= \begin{cases} \beta^-, &\text{if } x \in \Omega^- \\ \beta^+, &\text{if } x \in \Omega^+ \\ \end{cases}, \\ \beta^-, \beta^+ &> 0. \end{align}

Then, find $u$, s.t.

\begin{align} -\operatorname{div} (\beta \nabla u) &= f \text{ in } \Omega^- \cup \Omega^+, \\ 〚u〛 &= 0 \text{ on } \Gamma, \\ 〚\beta \nabla u \cdot n_\Gamma〛 &= 0 \text{ on } \Gamma, \\ u &= 0 \text{ on } \partial \Gamma. \\ \end{align}

In particular, the functions in this space fulfil the property \begin{align} 〚u〛 &= 0 \text{ on } \Gamma, \\ 〚\beta \nabla u \cdot n_\Gamma〛 &= 0 \text{ on } \Gamma, \\ \end{align} as well as being continuous at mesh vertices. The space consists of piecewise linear functions.

Actually, the returned space consists of vectorial functions of order $1$, that have to be interpreted in the following way: the first component represents the piecewise linear function in $\Omega^-$, the second component in $\Omega^+$. Formally, for $v = \begin{pmatrix} v^- \\ v^+ \end{pmatrix}$ we define the piecewise linear function \begin{align} \hat{v}(x) &:= \begin{cases} v^-(x), &\text{if } x \in \Omega^- \\ v^+(x), &\text{if } x \in \Omega^+ \\ \end{cases}. \end{align}

lsetp1: The levelset function $p$ used to describe the cut as $\Gamma := \{x \in \Omega \mid p(x) = 0 \}$, as well as $\Omega^- := \{x \in \Omega \mid p(x) < 0 \}$ and $\Omega^- := \{x \in \Omega \mid p(x) > 0 \}$. $p$ needs to be affine linear on each element. E.g. set lsetp1 as a Gridfunction(H1(mesh, order=1)).

beta_neg: diffusion coefficient for $\Omega^-$. Should be a positive number.

beta_pos: diffusion coefficient for $\Omega^+$. Should be a positive number.

Conforming Trefftz Formulation

  • $\mathbb{V}_h := [\mathbb{P}^{1, \text{disc}}(\mathcal{T}_h)]^2$
  • $\mathbb{Q}_h := \mathbb{V}_h$
  • $\mathbb{Z}_h := \mathbb{P}^{1}(\mathcal{T}_h)$
  • \begin{align} \mathcal{C}_K(v_h, z_h) &:= \sum_{p \text{ is vertex}} \hat{v}_h(p) z_h(p) \\ \mathcal{D}_K(y_h, z_h) &:= \sum_{p \text{ is vertex}} y_h(p) z_h(p) \\ \end{align}
  • \begin{align} (\mathcal{L}_K v_h, q_h) := \int_\Gamma 〚\hat{v}_h〛 〚\hat{q}_h〛 \;dS + \int_\Gamma〚\beta \nabla \hat{v}_h \cdot n_\Gamma〛 〚\beta \nabla \hat{q}_h \cdot n_\Gamma〛\;dS \end{align}
class ImmersedQ1Impl(enum.Enum):
166class ImmersedQ1Impl(Enum):
167    """
168    Represents a Conforming Trefftz implementation
169    for the `ImmersedQ1FE` space.
170    """
171
172    Canonical = enum.auto()
173    """
174    Formulation is described by <https://doi.org/10.1002/num.20318>.
175    """
176
177    NonConforming = enum.auto()
178    """
179    Stable formulation, that is not continuous acrosss the cut interface.
180    """
181
182    Overloaded = enum.auto()
183    """
184    Technically overconstrains the Trefftz condition,
185    but it may still work.
186    """

Represents a Conforming Trefftz implementation for the ImmersedQ1FE space.

Canonical = <ImmersedQ1Impl.Canonical: 1>

Formulation is described by https://doi.org/10.1002/num.20318.

NonConforming = <ImmersedQ1Impl.NonConforming: 2>

Stable formulation, that is not continuous acrosss the cut interface.

Overloaded = <ImmersedQ1Impl.Overloaded: 3>

Technically overconstrains the Trefftz condition, but it may still work.

def ImmersedQ1FE( mesh: ngsolve.comp.Mesh, lsetq1: ngsolve.comp.GridFunction, beta_neg: float, beta_pos: float, dirichlet: str = '', dgjumps: bool = False, stats: dict | None = None, impl: ImmersedQ1Impl = <ImmersedQ1Impl.NonConforming: 2>) -> ngstrefftz.EmbeddedTrefftzFES:
189def ImmersedQ1FE(
190    mesh: ngsolve.comp.Mesh,
191    lsetq1: ngsolve.GridFunction,
192    beta_neg: float,
193    beta_pos: float,
194    dirichlet: str = "",
195    dgjumps: bool = False,
196    stats: dict | None = None,
197    impl: ImmersedQ1Impl = ImmersedQ1Impl.NonConforming,
198) -> EmbeddedTrefftzFES:
199    r"""
200    This is the version of `ImmersedP1FE` for quadrilateral meshes.
201    Refer to the documentation of `ImmersedP1FE` for most details.
202
203    `lsetq1`: The levelset function, as a `GridFunction` over the `H1` space with `order=1`.
204        In geleral, the cut may not be piecewise linear, as the `H1` space of `order=1` contains bilinear functions on quads.
205        You can use `straighten_levelset` inorder to produce a levelset function
206        with a piecewise linear cut.
207
208    `impl`: declare what Trefftz implementation you want to use
209
210    # Canonical Conforming Trefftz Formulation
211    This implements the formulation of <https://doi.org/10.1002/num.20318>.
212    - $\mathbb{V}_h := [\mathbb{Q}^{1, \text{disc}}(\mathcal{T}_h)]^2$
213    - $\mathbb{Q}_h := \mathbb{Q}^{1, \text{disc}}(\mathcal{T}_h) \times \mathbb{Q}^{0, \text{disc}}(\mathcal{T}_h)$
214    - $\mathbb{Z}_h := \mathbb{Q}^{1}(\mathcal{T}_h)$
215    - \begin{align}
216      \mathcal{C}_K(v_h, z_h) &:=
217          \sum_{p \text{ is vertex}} \hat{v}_h(p) z_h(p) \\\\
218      \mathcal{D}_K(y_h, z_h) &:=
219          \sum_{p \text{ is vertex}} y_h(p) z_h(p) \\\\
220      \end{align}
221    - \begin{align}
222      (\mathcal{L}_K (v_h, (q_{h, 1}, q_{h, 0})) :=
223        \int_\Gamma 〚\hat{v}_h〛q_{h,1} \;dS + \int_\Gamma〚\beta \nabla \hat{v}_h \cdot n_\Gamma〛 q_{h,0} \;dS
224      \end{align}
225
226    # Non-Conforming Conforming Trefftz Formulation
227    - $\mathbb{V}_h := [\mathbb{Q}^{1, \text{disc}}(\mathcal{T}_h)]^2$
228    - $\mathbb{Q}_h := [\mathbb{Q}^{0, \text{disc}}(\mathcal{T}_h)]^4$
229    - $\mathbb{Z}_h := \mathbb{Q}^{1}(\mathcal{T}_h)$
230    - \begin{align}
231    \mathcal{C}_K(v_h, z_h) &:=
232        \sum_{p \text{ is vertex}} \hat{v}_h(p) z_h(p) \\\\
233    \mathcal{D}_K(y_h, z_h) &:=
234        \sum_{p \text{ is vertex}} y_h(p) z_h(p) \\\\
235    \end{align}
236    - \begin{align}
237    (\mathcal{L}_K v_h, (q_h, q_\tau, q_n, q_{\tau n})) &:=
238        \int_\Gamma \frac{1}{h}〚\hat{v}_h〛 q_h \;dS + \int_\Gamma〚\nabla \hat{v}_h \cdot \tau_\Gamma〛 q_\tau \;dS \\\\
239        &+ \int_\Gamma〚\beta \nabla \hat{v}_h \cdot n_\Gamma〛 q_n \;dS + \int_\Gamma h〚\beta n_\Gamma^T \mathbf{H}_{\hat{v}_h} \tau_\Gamma〛 q_{\tau n} \;dS
240    \end{align}
241
242    # Overloaded Conforming Trefftz Formulation
243    - $\mathbb{V}_h := [\mathbb{Q}^{1, \text{disc}}(\mathcal{T}_h)]^2$
244    - $\mathbb{Q}_h := \mathbb{V}_h$
245    - $\mathbb{Z}_h := \mathbb{Q}^{1}(\mathcal{T}_h)$
246    - \begin{align}
247    \mathcal{C}_K(v_h, z_h) &:=
248        \sum_{p \text{ is vertex}} \hat{v}_h(p) z_h(p) \\\\
249    \mathcal{D}_K(y_h, z_h) &:=
250        \sum_{p \text{ is vertex}} y_h(p) z_h(p) \\\\
251    \end{align}
252    - \begin{align}
253    (\mathcal{L}_K v_h, q_h) :=
254        \int_\Gamma 〚\hat{v}_h〛 〚\hat{q}_h〛 \;dS + \int_\Gamma〚\beta \nabla \hat{v}_h \cdot n_\Gamma〛 〚\beta \nabla \hat{q}_h \cdot n_\Gamma〛\;dS
255    \end{align}
256    """
257
258    throw_on_wrong_mesh_dimension(mesh, 2)
259    throw_on_wrong_mesh_eltype(mesh, QUAD)
260
261    if not (
262        (
263            isinstance(lsetq1.space, H1)
264            or isinstance(lsetq1.space, Discontinuous)
265        )
266        and lsetq1.space.globalorder == 1
267    ):
268        raise ValueError(
269            f"lsetq1 must be a GridFunction on an H1(order=1) or Discontinuous(H1(order=1)) space. You got: {lsetq1.space}"
270        )
271
272    ci = CutInfo(mesh, lsetq1)
273    dS = dCut(lsetq1, IF, definedonelements=ci.GetElementsOfType(IF), order=4)
274    n = Normalize(grad(lsetq1, mesh.dim))
275    h = specialcf.mesh_size
276
277    fes = L2(mesh, order=1, dgjumps=dgjumps) * L2(
278        mesh, order=1, dgjumps=dgjumps
279    )
280
281    conformity_space = H1(mesh, order=1, dirichlet=dirichlet)
282
283    match impl:
284        case ImmersedQ1Impl.Overloaded:
285            (u_neg, u_pos), (v_neg, v_pos) = fes.TnT()
286
287            top = (
288                u_neg * v_neg * dx(definedonelements=ci.GetElementsOfType(POS))
289            )
290            top += (
291                u_pos * v_pos * dx(definedonelements=ci.GetElementsOfType(NEG))
292            )
293            top += 1 * (u_pos - u_neg) * (v_pos - v_neg) * dS
294            top += (
295                h
296                * ((beta_pos * grad(u_pos) - beta_neg * grad(u_neg)) * n)
297                * ((beta_pos * grad(v_pos) - beta_neg * grad(v_neg)) * n)
298                * dS
299            )
300        case ImmersedQ1Impl.Canonical:  # old
301            fes_test = L2(mesh, order=1) * L2(mesh, order=0)
302
303            (u_neg, u_pos) = fes.TrialFunction()
304            v_bilin, v_const = fes_test.TestFunction()
305
306            top = (
307                u_neg
308                * v_bilin
309                * dx(definedonelements=ci.GetElementsOfType(POS))
310            )
311            top += (
312                u_pos
313                * v_bilin
314                * dx(definedonelements=ci.GetElementsOfType(NEG))
315            )
316            top += 1 / h**2 * (u_pos - u_neg) * v_bilin * dS
317            top += (
318                h
319                * ((beta_pos * grad(u_pos) - beta_neg * grad(u_neg)) * n)
320                * v_const
321                * dS
322            )
323        case ImmersedQ1Impl.NonConforming:  # new version
324            fes_test = (
325                L2(mesh, order=1)
326                * L2(mesh, order=0)
327                * L2(mesh, order=0)
328                * L2(mesh, order=0)
329                * L2(mesh, order=0)
330            )
331
332            (u_neg, u_pos) = fes.TrialFunction()
333            v_bilin, v1, v2, v3, v4 = fes_test.TestFunction()
334
335            top = (
336                u_neg
337                * v_bilin
338                * dx(definedonelements=ci.GetElementsOfType(POS))
339            )
340            top += (
341                u_pos
342                * v_bilin
343                * dx(definedonelements=ci.GetElementsOfType(NEG))
344            )
345            t = CF((n[1], -n[0]))
346            top += 1 / h * (u_pos - u_neg) * v1 * dS
347            top += (grad(u_pos) - grad(u_neg) | t) * v2 * dS
348            top += (
349                (beta_pos * grad(u_pos) - beta_neg * grad(u_neg) | n) * v3 * dS
350            )
351            top += (
352                h
353                * (
354                    beta_pos * (hesse(u_pos) * t | n)
355                    - beta_neg * (hesse(u_neg) * t | n)
356                )
357                * v4
358                * dS
359            )
360
361    uc, vc = conformity_space.TnT()
362
363    cop = IfPos(lsetq1, u_pos, u_neg) * vc * dx(element_vb=BBND)
364    crhs = uc * vc * dx(element_vb=BBND)
365
366    emb = TrefftzEmbedding(
367        top=top,
368        trhs=None,
369        cop=cop,
370        crhs=crhs,
371        ndof_trefftz=0,
372        stats=stats,
373    )
374    imq1fe = EmbeddedTrefftzFES(emb)
375    return imq1fe

This is the version of ImmersedP1FE for quadrilateral meshes. Refer to the documentation of ImmersedP1FE for most details.

lsetq1: The levelset function, as a GridFunction over the H1 space with order=1. In geleral, the cut may not be piecewise linear, as the H1 space of order=1 contains bilinear functions on quads. You can use straighten_levelset inorder to produce a levelset function with a piecewise linear cut.

impl: declare what Trefftz implementation you want to use

Canonical Conforming Trefftz Formulation

This implements the formulation of https://doi.org/10.1002/num.20318.

  • $\mathbb{V}_h := [\mathbb{Q}^{1, \text{disc}}(\mathcal{T}_h)]^2$
  • $\mathbb{Q}_h := \mathbb{Q}^{1, \text{disc}}(\mathcal{T}_h) \times \mathbb{Q}^{0, \text{disc}}(\mathcal{T}_h)$
  • $\mathbb{Z}_h := \mathbb{Q}^{1}(\mathcal{T}_h)$
  • \begin{align} \mathcal{C}_K(v_h, z_h) &:= \sum_{p \text{ is vertex}} \hat{v}_h(p) z_h(p) \\ \mathcal{D}_K(y_h, z_h) &:= \sum_{p \text{ is vertex}} y_h(p) z_h(p) \\ \end{align}
  • \begin{align} (\mathcal{L}_K (v_h, (q_{h, 1}, q_{h, 0})) := \int_\Gamma 〚\hat{v}_h〛q_{h,1} \;dS + \int_\Gamma〚\beta \nabla \hat{v}_h \cdot n_\Gamma〛 q_{h,0} \;dS \end{align}

Non-Conforming Conforming Trefftz Formulation

  • $\mathbb{V}_h := [\mathbb{Q}^{1, \text{disc}}(\mathcal{T}_h)]^2$
  • $\mathbb{Q}_h := [\mathbb{Q}^{0, \text{disc}}(\mathcal{T}_h)]^4$
  • $\mathbb{Z}_h := \mathbb{Q}^{1}(\mathcal{T}_h)$
  • \begin{align} \mathcal{C}_K(v_h, z_h) &:= \sum_{p \text{ is vertex}} \hat{v}_h(p) z_h(p) \\ \mathcal{D}_K(y_h, z_h) &:= \sum_{p \text{ is vertex}} y_h(p) z_h(p) \\ \end{align}
  • \begin{align} (\mathcal{L}_K v_h, (q_h, q_\tau, q_n, q_{\tau n})) &:= \int_\Gamma \frac{1}{h}〚\hat{v}_h〛 q_h \;dS + \int_\Gamma〚\nabla \hat{v}_h \cdot \tau_\Gamma〛 q_\tau \;dS \\ &+ \int_\Gamma〚\beta \nabla \hat{v}_h \cdot n_\Gamma〛 q_n \;dS + \int_\Gamma h〚\beta n_\Gamma^T \mathbf{H}_{\hat{v}_h} \tau_\Gamma〛 q_{\tau n} \;dS \end{align}

Overloaded Conforming Trefftz Formulation

  • $\mathbb{V}_h := [\mathbb{Q}^{1, \text{disc}}(\mathcal{T}_h)]^2$
  • $\mathbb{Q}_h := \mathbb{V}_h$
  • $\mathbb{Z}_h := \mathbb{Q}^{1}(\mathcal{T}_h)$
  • \begin{align} \mathcal{C}_K(v_h, z_h) &:= \sum_{p \text{ is vertex}} \hat{v}_h(p) z_h(p) \\ \mathcal{D}_K(y_h, z_h) &:= \sum_{p \text{ is vertex}} y_h(p) z_h(p) \\ \end{align}
  • \begin{align} (\mathcal{L}_K v_h, q_h) := \int_\Gamma 〚\hat{v}_h〛 〚\hat{q}_h〛 \;dS + \int_\Gamma〚\beta \nabla \hat{v}_h \cdot n_\Gamma〛 〚\beta \nabla \hat{q}_h \cdot n_\Gamma〛\;dS \end{align}
def straighten_levelset(lsetq1: ngsolve.comp.GridFunction) -> ngsolve.comp.GridFunction:
378def straighten_levelset(lsetq1: GridFunction) -> GridFunction:
379    """
380    Produces a new levelset function with an element-wise
381    straight cut.
382
383    This is interesting for straightening of levelset functions
384    on quad meshes.
385    """
386    eps = 1e-9
387    fes = L2(lsetq1.space.mesh, order=1)
388    u, v = fes.TnT()
389    op = (hesse(u) | hesse(v)) * dx + u * v * dCut(
390        lsetq1, IF, element_boundary=True
391    )
392    emb = TrefftzEmbedding(op, eps=eps)
393
394    etfes = EmbeddedTrefftzFES(emb)
395    u, v = etfes.TnT()
396    a = BilinearForm(u * v * dx).Assemble()
397    f = LinearForm(lsetq1 * v * dx).Assemble()
398    inv = a.mat.Inverse(inverse="sparsecholesky")
399    lsetp1_straight_trefftz = GridFunction(etfes)
400    lsetp1_straight_trefftz.vec.data = inv * f.vec
401
402    lsetp1_straight = GridFunction(fes)
403    lsetp1_straight.vec.data = emb.Embed(lsetp1_straight_trefftz.vec)
404
405    lsetp1_straight_final = GridFunction(
406        Discontinuous(H1(lsetq1.space.mesh, order=1))
407    )
408    lsetp1_straight_final.Set(lsetp1_straight)
409    return lsetp1_straight_final

Produces a new levelset function with an element-wise straight cut.

This is interesting for straightening of levelset functions on quad meshes.