Peano
Loading...
Searching...
No Matches
DGPoisson2dPenaltyBlockMatrix.py
Go to the documentation of this file.
1# from block_matrix import BlockMatrix
2# from nodal_basis import NodalBasisGaussLegendre
3# from finiteelement import DGScalarElement, DGFacetElement
4# from quadrature import GaussianQuadrature, Quadrature2d
5# import numpy as np
6
7from .BlockMatrix import BlockMatrix
8from .nodal_basis import NodalBasisGaussLegendre
9from .finiteelement import DGScalarElement, DGFacetElement
10from .quadrature import GaussianQuadrature, Quadrature2d
11import numpy as np
12
14 """Classical DG discretisation of 2d classical Poisson formulation with spurious facet function spaces
15
16 Order of function spaces (used in the output dictionaries):
17 0 | 0 1 2 3 4 5
18 u | q^+ q^- q^f u^+ u^- u^f
19 """
20
21 def __init__(self, r_press, basis=NodalBasisGaussLegendre(2)):
22 """Initialise instance
23 :arg r_press: polynomial degree of pressure space
24 :arg r_vel: polynomial degree of velocity
25 :arg basis: nodal basis
26 """
27 dim = 2
28 super().__init__(dim, basis)
29 self.basis = basis
30 self._L_C_L_C = [r_press]
31 self._L_F_L_F = 6 * [r_press]
33 self._fs_labels_F_fs_labels_F = ["q+", "q-", "qf", "u+", "u-", "uf"] # projections of the normal derivative onto facets and "flux"
34
35 # Penalty method parameters
36 # Parameters from 10.1016/j.jcp.2019.06.001: Matrix-free multigrid block-preconditioners for higher order discontinuous Galerkin discretisations
37 self._theta = -1.0
38 self._gamma_F = 1.25 * r_press * (r_press + 1) # this should be 1.25 * degree * (degree + 1) / h when scaled
39 # We remove /h here because the contribution of this penalty term cannot be scaled separately from the first penalty term at the moment
40
41
42 # All pairs of function spaces that couple
43 self._coupling_CC = [(0, 0)] # _u_u
44
45 self._coupling_CF = [(0, 2), # _u_qf
46 (0, 5)] # _u_uf
47
48 self._coupling_FC = [(0, 0), # _q+_u
49 (1, 0), # _q-_u
50 (3, 0), # _u+_u
51 (4, 0) # _u-_u
52 ]
53
54 self._coupling_FF = [(0, 0), # _q+_q+
55 (1, 1), # _q-_q-
56 (2, 0), # _qf_q+
57 (2, 1), # _qf_q-
58 (2, 2), # _qf_qf
59 (3, 3), # _u+_u+
60 (4, 4), # _u-_u-
61 (5, 3), # _uf_u+
62 (5, 4), # _uf_u-
63 (5, 5) # _uf_uf
64 ]
65
66 # Lists of entity types
67 self._ent_types_CF_FC = ["left", "right", "bottom", "top"] # ! All the facet integrals will be now assembled for the left facet only !
68 self._ent_types_FF = self._ent_types_CF_FC # ! All the facet integrals will be now assembled for the left facet only !
69 #self._ent_types_FF = ["ih", "iv", "bl", "br", "bb", "bt"] # "interior horizontal/vertical" and "boundary left/right/bottom/top"
70
71 # Cell elements
72 self._fe_C = [None] * len(self._L_C_L_C)
73 self._Xi_C_Xi_C = [None] * len(self._L_C_L_C)
74 for i in range(len(self._L_C_L_C)):
75 self._fe_C[i] = DGScalarElement(dim, self._L_C_L_C[i])
76 self._Xi_C_Xi_C[i] = self._fe_C[i].nodes
77
78 # Facet elements
79 self._fe_F = [None] * len(self._L_F_L_F)
80 self._Xi_F_Xi_F = [None] * len(self._L_F_L_F)
81 for i in range(len(self._L_F_L_F)):
82 self._fe_F[i] = DGFacetElement(dim, self._L_F_L_F[i])
83 self._Xi_F_Xi_F[i] = self._fe_F[i].nodes
84
85
86 self._A_CC_A_CC = {fs_pair: self._assemble_A_CC(fs_pair) for fs_pair in self._coupling_CC}
87
88 self._A_CF_A_CF = {fs_pair: {ent_type: self._assemble_A_CF(fs_pair, ent_type) for ent_type in self._ent_types_CF_FC}
89 for fs_pair in self._coupling_CF
90 }
91
92 self._A_FC_A_FC = {fs_pair: {ent_type: self._assemble_A_FC(fs_pair, ent_type) for ent_type in self._ent_types_CF_FC}
93 for fs_pair in self._coupling_FC
94 }
95
96 self._A_FF_A_FF = {fs_pair: {ent_type: self._assemble_A_FF(fs_pair, ent_type) for ent_type in self._ent_types_FF}
97 for fs_pair in self._coupling_FF
98 }
99
100 # Matrix for rhs
101 # Remake this according to the note; now only works for the mass matrix for the Poisson equation with interior penalty method
103
104
105
108 def _assemble_A_CC(self, fs_pair):
109 """Assemble weak form for A_{CC}^{(k,ell)}
110 :arg fs_pair: pair of cell function space indices that couple
111 k = fs_pair[0]: first function space (to-space): a cell function space
112 ell = fs_pair[1]: second function space (from-space): a cell function space
113 """
114 #assert fs_pair in self._coupling_CC
115
116 k = fs_pair[0]
117 ell = fs_pair[1]
118 ndof_k = self._fe_C[k].ndof
119 ndof_ell = self._fe_C[ell].ndof
120 A_k_ell = np.empty((ndof_k, ndof_ell))
121 gamma = (0, 0) # no sacling, as the contribution h^2 from dxdy is compensated by 1/h^2 from derivatives \nambla u \cdot \nabla v
122
123 # Quadrature rule: based on the max degree of basis in the function spaces pair
124 max_degree = max(self._fe_C[k].degree, self._fe_C[ell].degree)
125 quad2d = Quadrature2d(GaussianQuadrature(max_degree + 1))
126
127 if fs_pair == (0, 0):
128 #func_k = self._fe_C[k].evaluate
129 #func_ell = self._fe_C[ell].evaluate
130 factor = 1.0
131
132 # Assemble local matrix A^(k,ell)
133 for i in range(ndof_k):
134 for j in range(ndof_ell):
135 A_k_ell[i, j] = 0.0
136 for q, w in zip(quad2d.points, quad2d.weights):
137 A_k_ell[i, j] += factor * w * ( self._fe_C[k].evaluate_derivative(i, 0, q) * self._fe_C[ell].evaluate_derivative(j, 0, q)
138 +
139 self._fe_C[k].evaluate_derivative(i, 1, q) * self._fe_C[ell].evaluate_derivative(j, 1, q)
140 )
141 else:
142 A_k_ell = np.zeros((k, ell))
143
144 return {gamma: A_k_ell}
145
146
149 def _assemble_A_CF(self, fs_pair, relative_position):
150 """Assemble weak form for A_{CF}^{(k,ell)}
151 :arg fs_pair: pair of function space indices that couple
152 k = fs_pair[0]: first function space (to-space): a cell function space
153 ell = fs_pair[1]: second function space (from-space): a facet function space
154 :arg relative_position: relative position of the facet within a cell given by
155 the "type of entity"
156 """
157 k = fs_pair[0]
158 ell = fs_pair[1]
159 ndof_k = self._fe_C[k].ndof
160 ndof_ell = self._fe_F[ell].ndof
161 A_k_ell = np.empty((ndof_k, ndof_ell))
162 gamma = (0, 0)
163
164 # Quadrature rule: based on the max degree of basis in the function spaces pair
165 max_degree = max(self._fe_C[k].degree, self._fe_F[ell].degree)
166 quad = GaussianQuadrature(max_degree + 1)
167
168 # Matrix restr applied to [1, q] gives [-1, q], [1, q], [q, -1] or [q, 1] --
169 # 2D coordinates on a facet depending on its relative location
170 # Is's used to later restrict a cell-function to a facet for integrating over facet
171 if relative_position == "left":
172 restr = np.array([[-1.0, 0.0],
173 [ 0.0, 1.0]])
174 n_sign = -1.0 # nx, ny = -1.0, 0.0 # outward unit normal components
175 elif relative_position == "right":
176 restr = np.array([[1.0, 0.0],
177 [0.0, 1.0]])
178 n_sign = +1.0 # nx, ny = +1.0, 0.0
179 elif relative_position == "bottom":
180 restr = np.array([[ 0.0, 1.0],
181 [-1.0, 0.0]])
182 n_sign = -1.0 # nx, ny = 0.0, -1.0
183 elif relative_position == "top":
184 restr = np.array([[0.0, 1.0],
185 [1.0, 0.0]])
186 n_sign = +1.0 # nx, ny = 0.0, +1.0
187 else:
188 raise ValueError("Invalid value of relative_position")
189
190 # The following sets factors and integrands for all the pairs (k,ell);
191 if fs_pair == (0, 2): # -int_F q^f v^u dF
192
193 if relative_position in ["left", "right"]:
194 gamma = (0, 1) # scale by hy
195 else: # ["top", "bottom"]
196 gamma = (1, 0) # scale by hx
197
198 # Define the integrand function for different
199 def func_k(i, q):
200 facet_coord = np.dot(restr, np.array([1.0, q])) # gives [-1, q], [1, q], [q, -1] or [q, 1]
201 return self._fe_C[k].evaluate(i, facet_coord) # restriction of the cell-function to a facet
202 func_ell = self._fe_F[ell].evaluate
203
204 factor = -1.0 * 0.5 * n_sign # -1.0: sign of the flux integral in the weak formulation
205 # 0.5: mesh-scaling is h/2
206
207 # Assemble local matrix A^(k,ell)
208 for i in range(ndof_k):
209 for j in range(ndof_ell):
210 A_k_ell[i, j] = 0.0
211 for q, w in zip(quad.points, quad.weights):
212 A_k_ell[i, j] += factor * w * func_k(i, q) * func_ell(j, q)
213
214 elif fs_pair == (0, 5):
215
217 gamma = (0, 0) # factor 0.5 and saling h compensated by \grad(v)
218 # assuming hx = hy; correct later: should be (\pm 1, \pm 1) in general
219
220 # Define the integrand function for different
221 def func_k(i, q):
222 facet_coord = np.dot(restr, np.array([1.0, q])) # gives [-1, q], [1, q], [q, -1] or [q, 1]
223 if relative_position in ["left", "right"]:
224 return self._fe_C[k].evaluate_derivative(i, 0, facet_coord) # restriction of the cell-function to a facet
225 else:
226 return self._fe_C[k].evaluate_derivative(i, 1, facet_coord) # restriction of the cell-function to a facet
227 func_ell = self._fe_F[ell].evaluate
228
229 factor = 1.0
230
231 # Assemble the penalty term matrix
232 A_penalty_1 = np.zeros_like(A_k_ell)
233 for i in range(ndof_k):
234 for j in range(ndof_ell):
235 for q, w in zip(quad.points, quad.weights):
236 A_penalty_1[i, j] += self._theta * factor * w * func_k(i, q) * func_ell(j, q)
237
238
244
245 # ! SCALING IS REMOVED FOR THE SECOND PENALTY TERMS ! SHOULD BE A SEPARATE KEY->VALUES IN THE OUTPUT DICTIONARY
246 # if relative_position in ["left", "right"]:
247 # gamma = (0, 1) # scale by hy
248 # else:
249 # gamma = (1, 0) # scale by hx
250
251 # Define the integrand function for different
252 def func_k(i, q):
253 facet_coord = np.dot(restr, np.array([1.0, q])) # gives [-1, q], [1, q], [q, -1] or [q, 1]
254 return self._fe_C[k].evaluate(i, facet_coord) # restriction of the cell-function to a facet
255 func_ell = self._fe_F[ell].evaluate
256
257 factor = 0.5 * 2.0 * n_sign # 0.5, because mesh-scaling is h/2
258 # 2.0 comes from the jump in the weak form. It should be 1.0 on the boundary!
259
260 # Assemble the penalty term matrix
261 A_penalty_2 = np.zeros_like(A_k_ell)
262 for i in range(ndof_k):
263 for j in range(ndof_ell):
264 for q, w in zip(quad.points, quad.weights):
265 A_penalty_2[i, j] += self._gamma_F * factor * w * func_k(i, q) * func_ell(j, q)
266
267 # Assemble local matrix A^(k,ell)
268 # !!! Now both components hve the same scaling (0,0)
269 # !!! It should ultimately be two different dictionary entries with different scaling:
270 # !!! {gamma1: A_penalty_1, gamma2: A_penalty_2}
271 A_k_ell = A_penalty_1 + A_penalty_2
272 #print(A_penalty_2)
273 pass
274
275 else:
276 A_k_ell = np.zeros((ndof_k, ndof_ell))
277
278 #print(k, ell, relative_position, "\n", A_k_ell, "\n")
279 return {gamma: A_k_ell}
280
281
285 def _assemble_A_FC(self, fs_pair, relative_position):
286 """Assemble weak form for A_{CF}^{(k,ell)}
287 :arg fs_pair: pair of function space indices that couple
288 k = fs_pair[0]: first function space (to-space): a facet function space
289 ell = fs_pair[1]: second function space (from-space): a cell function space
290 :arg relative_position: relative position of the facet within a cell
291 """
292 k = fs_pair[0]
293 ell = fs_pair[1]
294 ndof_k = self._fe_F[k].ndof
295 ndof_ell = self._fe_C[ell].ndof
296 A_k_ell = np.empty((ndof_k, ndof_ell))
297
298 # Quadrature rule: based on the max degree of basis in the function spaces pair
299 max_degree = max(self._fe_F[k].degree, self._fe_C[ell].degree)
300 quad = GaussianQuadrature(max_degree + 1)
301
302 # Matrix restr applied to [1, q] gives [-1, q], [1, q], [q, -1] or [q, 1] --
303 # 2D coordinates on a facet depending on its relative location
304 # Is's used to later restrict a cell-function to a facet for integrating over facet
305 if relative_position == "left":
306 restr = np.array([[-1.0, 0.0],
307 [ 0.0, 1.0]])
308 # nx, ny = -1.0, 0.0 # outward unit normal components
309 elif relative_position == "right":
310 restr = np.array([[1.0, 0.0],
311 [0.0, 1.0]])
312 # nx, ny = +1.0, 0.0
313 elif relative_position == "bottom":
314 restr = np.array([[ 0.0, 1.0],
315 [-1.0, 0.0]])
316 # nx, ny = 0.0, -1.0
317 elif relative_position == "top":
318 restr = np.array([[0.0, 1.0],
319 [1.0, 0.0]])
320 # nx, ny = 0.0, +1.0
321 else:
322 raise ValueError("Invalid value of relative_position")
323
324 factor = -1.0 # minus from the def. of projection: M*q^\pm + A*u = 0, so A = -integral(...)
325 gamma = (0, 0) # no scaling as it's compensated by the derivative dp/dx or dp/dy
326
327 if fs_pair == (0, 0): # A_q+_u
328 if relative_position in ["left", "bottom"]: # "+"-projection from a give cell is defined on the left and bottom facets
329 # ! Now, q-projections are defined as the restriction of the gradient onto a facet, not the normal component. I.e. the direction of the normal vector is not taken into account.
330 # factor = factor * (-1.0) # * -1 = normal_component: nx for the left or ny for the bottom
331 # Define the integrand functions
332 func_k = self._fe_F[k].evaluate
333 def func_ell(i, q):
334 facet_coord = np.dot(restr, np.array([1.0, q])) # gives [-1, q], [1, q], [q, -1] or [q, 1]
335 if relative_position == "left":
336 # Define the integrand function: projecting x-derivative of p
337 return self._fe_C[ell].evaluate_derivative(i, 0, facet_coord) # restriction of the cell-function to a facet
338 else: # relative_position == "bottom"
339 # projecting y-derivative of p
340 return self._fe_C[ell].evaluate_derivative(i, 1, facet_coord)
341
342 # Assemble local matrix A^(k,ell)
343 for i in range(ndof_k):
344 for j in range(ndof_ell):
345 A_k_ell[i, j] = 0.0
346 for q, w in zip(quad.points, quad.weights):
347 A_k_ell[i, j] += factor * w * func_k(i, q) * func_ell(j, q)
348
349 else: # return 0 for the right and top
350 A_k_ell = np.zeros((ndof_k, ndof_ell))
351
352 elif fs_pair == (1, 0): # A_q-_u
353 if relative_position in ["right", "top"]: # "-"-projection from a give cell is defined on the right and top facets
354 # factor = factor * (1.0) # * 1 = normal_component: nx for the right or ny for the top
355 # Define the integrand functions
356 func_k = self._fe_F[k].evaluate
357 def func_ell(i, q):
358 facet_coord = np.dot(restr, np.array([1.0, q])) # gives [-1, q], [1, q], [q, -1] or [q, 1]
359 if relative_position == "right":
360 # Define the integrand function: projecting x-derivative of p
361 return self._fe_C[ell].evaluate_derivative(i, 0, facet_coord) # restriction of the cell-function to a facet
362 else: # relative_position == "top"
363 # projecting y-derivative of p
364 return self._fe_C[ell].evaluate_derivative(i, 1, facet_coord)
365
366 # Assemble local matrix A^(k,ell)
367 for i in range(ndof_k):
368 for j in range(ndof_ell):
369 A_k_ell[i, j] = 0.0
370 for q, w in zip(quad.points, quad.weights):
371 A_k_ell[i, j] += factor * w * func_k(i, q) * func_ell(j, q)
372
373 else: # return 0 for the left and bottom
374 A_k_ell = np.zeros((ndof_k, ndof_ell))
375
376 elif fs_pair == (3, 0): # A_u+_u
377 factor = -0.5 # minus from the def. of projection: M*u^\pm + A*u = 0, so A = -integral(...)
378 factor = factor * (-1.0) # ! now, the plus-prujections are {(-1)*solution} and the minus-projections are {(+1)*solution}
379 # ! This is to have the jumps (u^- - u^+) in the penalty formulation
380 if relative_position in ["left", "bottom"]:
381 if relative_position == "left":
382 gamma = (0, 1) # scale by hy
383 else:
384 gamma = (1, 0) # scale by hx
385
386 # Define the integrand functions
387 func_k = self._fe_F[k].evaluate
388 def func_ell(i, q):
389 facet_coord = np.dot(restr, np.array([1.0, q])) # gives [-1, q], [1, q], [q, -1] or [q, 1]
390 # Define the integrand function: projecting solution u (or p)
391 return self._fe_C[ell].evaluate(i, facet_coord) # restriction of the cell-function to a facet
392
393 # Assemble local matrix A^(k,ell)
394 for i in range(ndof_k):
395 for j in range(ndof_ell):
396 A_k_ell[i, j] = 0.0
397 for q, w in zip(quad.points, quad.weights):
398 A_k_ell[i, j] += factor * w * func_k(i, q) * func_ell(j, q)
399
400 else: # return 0 for the right and top
401 A_k_ell = np.zeros((ndof_k, ndof_ell))
402
403 # REMAKE THIS, PUT (3, 0) AND (4, 0) TOGETHER AS THE INTEGRANDS ARE THE SAME
404 elif fs_pair == (4, 0): # A_u-_u
405 factor = -0.5 # minus from the def. of projection: M*u^\pm + A*u = 0, so A = -integral(...)
406 if relative_position in ["right", "top"]:
407 if relative_position == "right":
408 gamma = (0, 1) # scale by hy
409 else:
410 gamma = (1, 0) # scale by hx
411
412 # Define the integrand functions
413 func_k = self._fe_F[k].evaluate
414 def func_ell(i, q):
415 facet_coord = np.dot(restr, np.array([1.0, q])) # gives [-1, q], [1, q], [q, -1] or [q, 1]
416 # Define the integrand function: projecting solution u (or p)
417 return self._fe_C[ell].evaluate(i, facet_coord) # restriction of the cell-function to a facet
418
419 # Assemble local matrix A^(k,ell)
420 for i in range(ndof_k):
421 for j in range(ndof_ell):
422 A_k_ell[i, j] = 0.0
423 for q, w in zip(quad.points, quad.weights):
424 A_k_ell[i, j] += factor * w * func_k(i, q) * func_ell(j, q)
425
426 else: # return 0 for the left and bottom
427 A_k_ell = np.zeros((ndof_k, ndof_ell))
428
429 else:
430 A_k_ell = np.zeros((ndof_k, ndof_ell))
431
432 #print(k, ell, relative_position, "\n", A_k_ell, "\n")
433 return {gamma: A_k_ell}
434
435
438 def _assemble_A_FF(self, fs_pair, relative_position):
439 """Assemble weak form for A_{FF}^{(k,ell)}
440 :arg fs_pair: pair of function space indices that couple
441 k = fs_pair[0]: first function space (to-space): a facet function space
442 ell = fs_pair[1]: second function space (from-space): a facet function space
443 :arg relative_position: relative position of the facet within a cell
444 """
445 k = fs_pair[0]
446 ell = fs_pair[1]
447 ndof_k = self._fe_F[k].ndof
448 ndof_ell = self._fe_F[ell].ndof
449 A_k_ell = np.empty((ndof_k, ndof_ell))
450 gamma = (0, 0)
451
452 # Quadrature rule: based on the max degree of basis in the function spaces pair
453 max_degree = max(self._fe_F[k].degree, self._fe_F[ell].degree)
454 quad = GaussianQuadrature(max_degree + 1)
455
456 if fs_pair in [(0,0), (1,1), (3,3), (4,4)]: # Facet space mass matrices A_q+_q+, A_q-_q-, A_u+_u+, A_u-_u-
457 func_k = self._fe_F[k].evaluate
458 func_ell = self._fe_F[ell].evaluate
459 factor = 0.5
460
461 if relative_position in ["left", "right"]:
462 gamma = (0, 1) # scale by hy
463 else:
464 gamma = (1, 0) # scale by hx
465
466 # Assemble local matrix A^(k,ell)
467 for i in range(ndof_k):
468 for j in range(ndof_ell):
469 A_k_ell[i, j] = 0.0
470 for q, w in zip(quad.points, quad.weights):
471 A_k_ell[i, j] += factor * w * func_k(i, q) * func_ell(j, q)
472
473 elif fs_pair == (2, 2): # A_qf_qf
474 gamma = (0, 0)
475 A_k_ell = np.eye(ndof_k)
476
477 elif fs_pair in [(2,0), (2,1)]: # A_q+_qf, A_q-_qf
478 gamma = (0, 0)
479 A_k_ell = -0.5*np.eye(ndof_k)
480
481 else:
482 gamma = (0, 0)
483 A_k_ell = np.zeros((ndof_k, ndof_ell))
484
485 #print(k, ell, relative_position, "\n", A_k_ell, "\n")
486 return {gamma: A_k_ell}
487
488
493 """
494 Assemble the mass matrix for the rhs.
495 It is then left-multiplied by the vector of f-function evaluated at nodal points to generate the rhs vector.
496 """
497
498 k = 0 # cell-cell mass matrix,
499 ell = 0 # so 0-0 pair of cell function spaces
500 ndof_k = self._fe_C[k].ndof
501 ndof_ell = self._fe_C[ell].ndof
502 MassMatrix = np.empty((ndof_k, ndof_ell))
503 gamma = (1, 1) # scaling from dx dy only => hx*hy
504
505 # Quadrature rule: based on the max degree of basis in the function spaces pair
506 max_degree = max(self._fe_C[k].degree, self._fe_C[ell].degree)
507 quad2d = Quadrature2d(GaussianQuadrature(max_degree + 1))
508
509 factor = 0.25
510
511 # Assemble local mass matrix
512 for i in range(ndof_k):
513 for j in range(ndof_ell):
514 MassMatrix[i, j] = 0.0
515 for q, w in zip(quad2d.points, quad2d.weights):
516 MassMatrix[i, j] += factor * w * self._fe_C[k].evaluate(i, q) * self._fe_C[ell].evaluate(j, q)
517
518 return {gamma: MassMatrix}
519
520
521# r_press = 1
522# BM = DGPoisson2dPenaltyBlockMatrix(r_press)
523# pass
Base class for block matrix storage format, defining the interface.
Definition BlockMatrix.py:5
Classical DG discretisation of 2d classical Poisson formulation with spurious facet function spaces.
__init__(self, r_press, basis=NodalBasisGaussLegendre(2))
Initialise instance :arg r_press: polynomial degree of pressure space :arg r_vel: polynomial degree o...
Gaussian quadrature on the reference interval [-1,+1].
Definition quadrature.py:16