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
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}
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.
Formulation is described by https://doi.org/10.1002/num.20318.
Stable formulation, that is not continuous acrosss the cut interface.
Technically overconstrains the Trefftz condition, but it may still work.
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}
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.