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
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}
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.
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.
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}
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.