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

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