Peano
Loading...
Searching...
No Matches
GaussLobatto.py
Go to the documentation of this file.
1# This file is part of the Peano multigrid project. For conditions of
2# distribution and use, please see the copyright notice at www.peano-framework.org
3import numpy as np
4from numpy.polynomial.legendre import leggauss
5from scipy.interpolate import lagrange
6from abc import abstractmethod
7
8import peano4
9import mghype
10
11from .MatrixGenerator import MatrixGenerator
12
13
15 '''!
16
17 Simple matrix generator assuming that we use a Gauss Lobatto unknown layout
18
19 If you use this class, you assuem that a cell hosts @f$ (p+1)^d @f$
20 nodes carrying Legendre polynomials. As you use a Gauss Lobatto layout,
21 the "outer" nodes will end up on the cell faces. Each node can hold a
22 fixed number of unknowns. If you multiply the number of nodes per cell
23 with the corresponding number of unknowns, you get the degrees of freedom
24 within a global assembly matrix which correspond to volumetric data.
25
26 '''
27 def __init__(self,
28 dimensions,
29 poly_degree,
30 unknowns_per_cell_node,
31 unknowns_per_face_node,
32 use_unit_interval=False):
33
34 super( GaussLobatto, self ).__init__(dimensions,
35 poly_degree
36 )
37 self.unknowns_per_cell_node = unknowns_per_cell_node
38 self.unknowns_per_face_node = unknowns_per_face_node
39
41 self.face_nodes = ( self.poly_degreepoly_degree + 1 ) ** (self.dimensionsdimensions - 1)
42
43 self.use_unit_interval = use_unit_interval
44
45 if use_unit_interval:
46 self.points1dpoints1d = self.get_points_()
47
48 r_press = self.poly_degreepoly_degree # polynomial order for p
50
52 """
53 Get the nodal points in the interval [-1,1].
54 These are the nodes used for constructing the Lagrangian basis functions.
55 Note, that they might be different from the quadrature points used for integration.
56 """
57 return self.block_matrix.basis.nodal_points(self.poly_degreepoly_degree)
58
59 def get_points1d(self):
60 """!
61
62 Get spacing of integration points over unit interval
63
64 Overwriting the method in base class. Let's produce Gauss-Lobatto
65 points. Standard behaviour is Legendre points using built-in stuff
66 from scipy.
67
68 We borrow the method from the wikipedia page.
69
70 This routine returns the points in the interval [-1,1]. If you
71 have to rescale them, use get_points_unit_interval() instead.
72 """
73 # let p be the poly degree. Then p+1 is the number of points we aim for
74 # So we gather the n'th polynomial Pn, then return [-1,1], as well as the roots
75 # of the derivative of Pn
76 output = [-1] #we'll append 1 at the end
77 if self.poly_degreepoly_degree > 1: # no need to do anything for degree 1
79 # next few lines are just what needs to be done for numpy
80 coeffs = [0 for _ in range(n+1)]
81 coeffs[n] = 1
82 poly = np.polynomial.legendre.Legendre(coeffs)
83 #get the roots
84 roots = poly.deriv().roots().tolist()
85 # append
86 output += roots
87 #clip the weird rounding errors
88 for i in range(len(output)):
89 if abs(output[i])<1E-10:
90 output[i]=0
91 output.append(1)
92 return output
93
94 def get_weights1d(self):
95 """!
96 Overwrites the method in the base class. Method
97 is borrowed from wikipedia
98 """
99 # number of points
100 n = self.poly_degreepoly_degree + 1
101 firstWeight = 2 / (n*(n-1))
102
103 # need to get P_{n-1}
104 coeffs = [0 for _ in range(n)]
105 coeffs[n-1] = 1
106 poly = np.polynomial.legendre.Legendre(coeffs)
107
108 output = [firstWeight]
109 for p in self.get_points1dget_points1d()[1:-1]:
110 weight = firstWeight / poly(p)**2
111 output.append(weight)
112
113 output.append(firstWeight)
114 return output
115
117 return super( GaussLobatto, self ).get_weights1d()
118
120 return super( GaussLobatto, self ).get_points1d()
121
122
124 scale = lambda x: 0.5*(x+1)
125 return [scale(x) for x in self.points1dpoints1d]
126
127 def eval_integral(self, functions, dim=-1, factor=1):
128 # if we shrank the interval, then we need to pass factor = 0.5 along
129 if self.use_unit_interval:
130 factor = 0.5
131 else:
132 factor = 1
133 # no need to re-implement
134 return super( GaussLobatto, self ).eval_integral(functions, dim, factor)
135
137 dim = self.cell_nodes * self.unknowns_per_cell_node
138 output = np.eye(dim,dim)# * 2**(-self.dimensions)
139 return output
140
142 '''!
143
144 Create a cell-cell mass matrix
145
146 This matrix does not couple the individual unknowns per degree of freedom.
147 The resulting matrix has to be scaled by @f$ h^d @f$.
148
149 '''
150 output = self.block_matrix._RHS_CC[(1,1)]
151 return output
152
154 r"""!
155 Create a cell-cell mass matrix for @f$ \nabla p \cdot \nabla v @f$
156 """
157 return self.block_matrix._A_CC[(0,0)][(0,0)]
158
160
161 """Return the cel-to-face matrix A_p^qf
162 """
163 # SCALING: scaled by h
164 A_CF_q_left = self.block_matrix._A_CF[(0,2)]["left"][(0,1)]
165 A_CF_q_bottom = self.block_matrix._A_CF[(0,2)]["bottom"][(1,0)]
166 A_CF_q_right = self.block_matrix._A_CF[(0,2)]["right"][(0,1)]
167 A_CF_q_top = self.block_matrix._A_CF[(0,2)]["top"][(1,0)]
168
169 # SCALING:
170 # !!! A_{C<-F} has penalty components of different scaling!
171 # For now, we just sum them up into one matrix with scaling (0,0)
172 # Need to work out how to separate them in the solver.
173 A_CF_u_left = self.block_matrix._A_CF[(0,5)]["left"][(0,0)] # + block_matrix._A_CF[(0,5)]["left"][(0,1)]
174 A_CF_u_bottom = self.block_matrix._A_CF[(0,5)]["bottom"][(0,0)] # + block_matrix._A_CF[(0,5)]["bottom"][(1,0)]
175 A_CF_u_right = self.block_matrix._A_CF[(0,5)]["right"][(0,0)] # + block_matrix._A_CF[(0,5)]["right"][(0,1)]
176 A_CF_u_top = self.block_matrix._A_CF[(0,5)]["top"][(0,0)] # + block_matrix._A_CF[(0,5)]["top"][(1,0)]
177
178 A_CF_left = np.concatenate((A_CF_q_left, A_CF_u_left), axis=1)
179 A_CF_bottom = np.concatenate((A_CF_q_bottom, A_CF_u_bottom), axis=1)
180 A_CF_right = np.concatenate((A_CF_q_right, A_CF_u_right), axis=1)
181 A_CF_top = np.concatenate((A_CF_q_top, A_CF_u_top), axis=1)
182
183 A_CF_result = np.concatenate((A_CF_left, A_CF_bottom, A_CF_right, A_CF_top), axis=1)
184
185 return A_CF_result
186
187 # SCALING: Consider our case with 4x16 matrix:
188 #
189 # by h by h by 1 by 1 by h by h by 1 by 1 by h by h by 1 by 1 by h by h by 1 by 1
190 # [[ 0.33333333 0.16666667 0.33333333 0.16666667 -0.33333333 -0.16666667 0.33333333 0.16666667 0. 0. -0.33333333 -0.16666667 0. 0. -0.33333333 -0.16666667]
191 # [ 0. 0. -0.33333333 -0.16666667 -0.16666667 -0.33333333 0.16666667 0.33333333 -0.33333333 -0.16666667 0.33333333 0.16666667 0. 0. -0.16666667 -0.33333333]
192 # [ 0.16666667 0.33333333 0.16666667 0.33333333 0. 0. -0.33333333 -0.16666667 0. 0. -0.16666667 -0.33333333 0.33333333 0.16666667 0.33333333 0.16666667]
193 # [ 0. 0. -0.16666667 -0.33333333 0. 0. -0.16666667 -0.33333333 -0.16666667 -0.33333333 0.16666667 0.33333333 0.16666667 0.33333333 0.16666667 0.33333333]]
194
196 """Return the projection operator
197 """
198
199 A_qp_u_left = self.block_matrix._A_FC[(0,0)]["left"][(0,0)]
200 A_qp_u_bottom = self.block_matrix._A_FC[(0,0)]["bottom"][(0,0)]
201 A_qm_u_right = self.block_matrix._A_FC[(1,0)]["right"][(0,0)]
202 A_qm_u_top = self.block_matrix._A_FC[(1,0)]["top"][(0,0)]
203
204 M_qp_qp_left = self.block_matrix._A_FF[(0,0)]["left"][(0,1)] # These are now the same;
205 M_qp_qp_bottom = self.block_matrix._A_FF[(0,0)]["bottom"][(1,0)] # keep for generality
206 M_qm_qm_right = self.block_matrix._A_FF[(1,1)]["right"][(0,1)] #
207 M_qm_qm_top = self.block_matrix._A_FF[(1,1)]["top"][(1,0)] #
208
209 # SCALING: All P_q matrices are scaled with power -1 (i.e. 1/h)
210 P_qp_u_left = -np.dot(np.linalg.inv(M_qp_qp_left), A_qp_u_left)
211 P_qp_u_bottom = -np.dot(np.linalg.inv(M_qp_qp_bottom), A_qp_u_bottom)
212 P_qm_u_right = -np.dot(np.linalg.inv(M_qm_qm_right), A_qm_u_right)
213 P_qm_u_top = -np.dot(np.linalg.inv(M_qm_qm_top), A_qm_u_top)
214
215
216 A_up_u_left = self.block_matrix._A_FC[(3,0)]["left"][(0,1)]
217 A_up_u_bottom = self.block_matrix._A_FC[(3,0)]["bottom"][(1,0)]
218 A_um_u_right = self.block_matrix._A_FC[(4,0)]["right"][(0,1)]
219 A_um_u_top = self.block_matrix._A_FC[(4,0)]["top"][(1,0)]
220
221 M_up_up_left = self.block_matrix._A_FF[(3,3)]["left"][(0,1)] # These are now the same;
222 M_up_up_bottom = self.block_matrix._A_FF[(3,3)]["bottom"][(1,0)] # keep for generality
223 M_um_um_right = self.block_matrix._A_FF[(4,4)]["right"][(0,1)] #
224 M_um_um_top = self.block_matrix._A_FF[(4,4)]["top"][(1,0)] #
225
226 # SCALING: All P_u matrices are scaled with power 0
227 P_up_u_left = -np.dot(np.linalg.inv(M_up_up_left), A_up_u_left)
228 P_up_u_bottom = -np.dot(np.linalg.inv(M_up_up_bottom), A_up_u_bottom)
229 P_um_u_right = -np.dot(np.linalg.inv(M_um_um_right), A_um_u_right)
230 P_um_u_top = -np.dot(np.linalg.inv(M_um_um_top), A_um_u_top)
231
232 # Components of the resulting matrix
233 # SCALING: Resulting matrix is made by concatenating P_q matrices and P_u matrices in the following order
234 P_left = np.concatenate((P_qp_u_left, P_up_u_left), axis=0)
235 P_bottom = np.concatenate((P_qp_u_bottom, P_up_u_bottom), axis=0)
236 P_right = np.concatenate((P_qm_u_right, P_um_u_right), axis=0)
237 P_top = np.concatenate((P_qm_u_top, P_um_u_top), axis=0)
238
239 # SCALING: As a result, in the output matrix first two rows should be scaled by 1/h, following 2 rows -- by 1, following two rows -- by 1/h, etc.
240 return np.concatenate((P_left, P_bottom, P_right, P_top), axis=0)
241
242 # SCALING: Consider our case with 16x4 matrix:
243 # [[ 1. -1. -0. -0.] -- scale by 1/h
244 # [-0. -0. 1. -1.] -- scale by 1/h
245 # [-1. -0. -0. -0.] -- scale by 1
246 # [-0. -0. -1. -0.] -- scale by 1
247 # [ 1. -0. -1. -0.] -- scale by 1/h
248 # [-0. 1. -0. -1.] -- scale by 1/h
249 # [-1. -0. -0. -0.] -- scale by 1
250 # [-0. -1. -0. -0.] -- scale by 1
251 # [-1. 1. -0. -0.] -- scale by 1/h
252 # [-0. -0. -1. 1.] -- scale by 1/h
253 # [-0. 1. -0. -0.] -- scale by 1
254 # [-0. -0. -0. 1.] -- scale by 1
255 # [-1. -0. 1. -0.] -- scale by 1/h
256 # [-0. -1. -0. 1.] -- scale by 1/h
257 # [-0. -0. 1. -0.] -- scale by 1
258 # [-0. -0. -0. 1.]] -- scale by 1
259
260
262 r"""!
263
264 Simple averaging Riemann solver
265
266 We know that this solver is extremely diffusive, but it makes sense to
267 provide this one as a simple debugging mechanism. In line with our
268 @ref page_multigrid_numerics_Disontinuous_Galerkin "DG description", it
269 is clear that the dimension of the result matrix is @f$ dof \times 2dof @f$
270 where dof is the number of degrees of freedom projected onto a face.
271
272 Please note that unknowns_per_face_node=@f$ 3 \cdot dof @f$ in the equation
273 above.
274
275 """
276 dim = self.face_nodes * self.unknowns_per_face_node * 2 # * 2 at the end since we are glueing together 2 faces
277 return np.eye(dim,dim)
278
280 """
281 Averaging matrix.
282 Implement separately for GaussLobattoMatrixFree class, taking into account solutions_per_face_node and projections_per_face_node.
283 Now assume there are 2 "solutions" (i.e. f-variables: q^f and u^f) and two sets of projections: q^pm an.
284 """
285
286 assert self.dimensionsdimensions == 2, "Only works for 2D"
287
288 n_rows = 2 * (self.poly_degreepoly_degree + 1)
289 n_cols = 4 * (self.poly_degreepoly_degree + 1)
290 matrix = np.zeros((n_rows, n_cols))
291
292 for row in range(n_rows):
293 matrix[row, [row, row + 2*(self.poly_degreepoly_degree + 1)]] = [0.5, 0.5]
294
295 return matrix
296
297
299 """!
300
301
302 """
303 def __init__(self,
304 dimensions,
305 poly_degree,
306 unknowns_per_cell_node,
307 solutions_per_face_node, # number of solutions we hold per node.
308 projections_per_face_node # number of projections we hold per node.
309 ):
310 super( GaussLobattoMatrixFree, self ).__init__(dimensions,
311 poly_degree,
312 unknowns_per_cell_node,
313 solutions_per_face_node
314 )
315 self._solutions_per_face_node = solutions_per_face_node
316 self._projections_per_face_node = projections_per_face_node
317
318 def get_A_tilde(self):
319 '''
320 Compute block-diagonal of the Schur-complement matrix
321 '''
322
323 A_CF = self.get_cell_from_face_matrix()[0]
324 P = self.get_projection_matrices()[0]
326
327 output_matrices = [ +0.5 * np.dot(A_CF[0], P[0]),
328 A_CC + 0.5 * (np.dot(A_CF[0], P[1]) + np.dot(A_CF[1], P[0])),
329 +0.5 * np.dot(A_CF[1], P[1])
330 ]
331
332 output_scalings = [-1, 0, 1]
333
334 return output_matrices, output_scalings
335 #return [self.get_cell_identity_matrix()], [0]
336
338 '''
339 the shape of this should be:
340 unknowns_per_cell_node * ( self.poly_degree + 1 ) ** self.dimensions rows (= 4 for 2D and 1st order)
341 solutions_per_face_node * ( self.poly_degree + 1 ) ** (self.dimensions-1) * 2 *dimensions cols (= 16 for 2D and 1st order)
342
343 solutions_per_face_node counts u and q
344 multiply further by number of nodes per face
345 multiply further by number of faces
346
347 '''
348
349 cell_unknowns = self.unknowns_per_cell_node * ( self.poly_degreepoly_degree + 1 ) ** self.dimensionsdimensionsdimensions # number of cell unknowns
350 f_unknowns_per_face = self._solutions_per_face_node * ( self.poly_degreepoly_degree + 1 ) ** (self.dimensionsdimensionsdimensions-1) # q^f and u^f unknowns per face
351 f_unknowns = f_unknowns_per_face * 2 * self.dimensionsdimensionsdimensions # total number of q^f and u^f unknowns on all faces
352
353 # SCALING: scaled by h
354 A_CF_q_left = self.block_matrix._A_CF[(0,2)]["left"][(0,1)]
355 A_CF_q_bottom = self.block_matrix._A_CF[(0,2)]["bottom"][(1,0)]
356 A_CF_q_right = self.block_matrix._A_CF[(0,2)]["right"][(0,1)]
357 A_CF_q_top = self.block_matrix._A_CF[(0,2)]["top"][(1,0)]
358
359 # SCALING:
360 # !!! A_{C<-F} has penalty components of different scaling!
361 # For now, we just sum them up into one matrix with scaling (0,0)
362 # Need to work out how to separate them in the solver.
363 A_CF_u_left = self.block_matrix._A_CF[(0,5)]["left"][(0,0)] # + block_matrix._A_CF[(0,5)]["left"][(0,1)]
364 A_CF_u_bottom = self.block_matrix._A_CF[(0,5)]["bottom"][(0,0)] # + block_matrix._A_CF[(0,5)]["bottom"][(1,0)]
365 A_CF_u_right = self.block_matrix._A_CF[(0,5)]["right"][(0,0)] # + block_matrix._A_CF[(0,5)]["right"][(0,1)]
366 A_CF_u_top = self.block_matrix._A_CF[(0,5)]["top"][(0,0)] # + block_matrix._A_CF[(0,5)]["top"][(1,0)]
367
368 assert self.dimensionsdimensionsdimensions == 2, "Only works for 2D since DGPoisson2dPenaltyBlockMatrix is currently 2D-specific"
369
370 # select indices corresponding to q^f and u^f unknowns
371 q_indices = [ index for index in range(f_unknowns) if (index % f_unknowns_per_face) < ( self.poly_degreepoly_degree + 1 ) ** (self.dimensionsdimensionsdimensions-1) ]
372 # example for degree = 1: index%4 < 2 selects q_indices = [0,1, 4,5, 8,9, 12,13] out of range(16)
373 u_indices = [ index for index in range(f_unknowns) if index not in q_indices]
374
375 # allocate matrices according to their scaling factors
376 matrix_0 = np.zeros((cell_unknowns, f_unknowns)) # scaled by h**0
377 matrix_1 = np.zeros((cell_unknowns, f_unknowns)) # scaled by h**1
378 matrix_0[ :, u_indices ] = np.concatenate((A_CF_u_left, A_CF_u_bottom, A_CF_u_right, A_CF_u_top), axis=1)
379 matrix_1[ :, q_indices ] = np.concatenate((A_CF_q_left, A_CF_q_bottom, A_CF_q_right, A_CF_q_top), axis=1)
380
381 output_matrices = [ matrix_0, matrix_1 ]
382 output_scalings = [ 0, 1 ]
383
384 assert( len(output_matrices) == len(output_scalings) )
385
386 for matrix in output_matrices:
387 assert matrix.shape == output_matrices[0].shape
388
389 return output_matrices, output_scalings
390
391 # SCALING: Consider our case with 4x16 matrix:
392 #
393 # by h by h by 1 by 1 by h by h by 1 by 1 by h by h by 1 by 1 by h by h by 1 by 1
394 # [[ 0.33333333 0.16666667 0.33333333 0.16666667 -0.33333333 -0.16666667 0.33333333 0.16666667 0. 0. -0.33333333 -0.16666667 0. 0. -0.33333333 -0.16666667]
395 # [ 0. 0. -0.33333333 -0.16666667 -0.16666667 -0.33333333 0.16666667 0.33333333 -0.33333333 -0.16666667 0.33333333 0.16666667 0. 0. -0.16666667 -0.33333333]
396 # [ 0.16666667 0.33333333 0.16666667 0.33333333 0. 0. -0.33333333 -0.16666667 0. 0. -0.16666667 -0.33333333 0.33333333 0.16666667 0.33333333 0.16666667]
397 # [ 0. 0. -0.16666667 -0.33333333 0. 0. -0.16666667 -0.33333333 -0.16666667 -0.33333333 0.16666667 0.33333333 0.16666667 0.33333333 0.16666667 0.33333333]]
398
400 """
401 Compute projection matrix from the FC and FF matrices provided in BlockMatrix interface
402
403 Return face-from-cell projection matrices with and without zero rows corresponding to the redundant projections.
404 Matrix with redundant projections is needed for get_face_from_cell_matrix(self).
405 Matrix without redundant projections is convenient for computing the Schur-complement matrix.
406
407 The output matrix with redundant projections should have: extra factor to account for all the faces
408 2 * self._projections_per_face_node * ( self.poly_degree + 1 ) ** (self.dimensions-1) * 2 *self.dimensions rows (= 32 in 2D 1st order)
409 self.unknowns_per_cell_node * ( self.poly_degree + 1 ) ** self.dimensions cols (= 4 in 2D 1st order)
410
411 The output matrix without redundant projections should have half the number of row as the previous one:
412 rows (= 16 in 2D 1st order)
413 cols (= 4 in 2D 1st order)
414 """
415
416 cell_unknowns = self.unknowns_per_cell_node * ( self.poly_degreepoly_degree + 1 ) ** self.dimensionsdimensionsdimensions # number of cell unknowns
417 nodes_per_face = ( self.poly_degreepoly_degree + 1 ) ** (self.dimensionsdimensionsdimensions-1)
418 proj_unknowns_per_face = 2 * self._projections_per_face_node * nodes_per_face # q^-+, u^-+ projection unknowns per face
419 proj_unknowns = proj_unknowns_per_face * 2 * self.dimensionsdimensionsdimensions # total number of q^-+, u^-+ projection unknowns on all faces
420
421 # number of "not redundant" projections: the same as q^f and u^f unknowns in get_cell_from_face
422 f_unknowns_per_face = self._solutions_per_face_node * ( self.poly_degreepoly_degree + 1 ) ** (self.dimensionsdimensionsdimensions-1) # q^f and u^f unknowns per face
423 f_unknowns = f_unknowns_per_face * 2 * self.dimensionsdimensionsdimensions # total number of q^f and u^f unknowns on all faces = how many projections excluding the redundant ones
424
425
426 A_qp_u_left = self.block_matrix._A_FC[(0,0)]["left"][(0,0)]
427 A_qp_u_bottom = self.block_matrix._A_FC[(0,0)]["bottom"][(0,0)]
428 A_qm_u_right = self.block_matrix._A_FC[(1,0)]["right"][(0,0)]
429 A_qm_u_top = self.block_matrix._A_FC[(1,0)]["top"][(0,0)]
430
431 M_qp_qp_left = self.block_matrix._A_FF[(0,0)]["left"][(0,1)] # These are now the same;
432 M_qp_qp_bottom = self.block_matrix._A_FF[(0,0)]["bottom"][(1,0)] # keep for generality
433 M_qm_qm_right = self.block_matrix._A_FF[(1,1)]["right"][(0,1)] #
434 M_qm_qm_top = self.block_matrix._A_FF[(1,1)]["top"][(1,0)] #
435
436 # SCALING: All P_q matrices are scaled with power -1 (i.e. 1/h)
437 P_qp_u_left = -np.dot(np.linalg.inv(M_qp_qp_left), A_qp_u_left)
438 P_qp_u_bottom = -np.dot(np.linalg.inv(M_qp_qp_bottom), A_qp_u_bottom)
439 P_qm_u_right = -np.dot(np.linalg.inv(M_qm_qm_right), A_qm_u_right)
440 P_qm_u_top = -np.dot(np.linalg.inv(M_qm_qm_top), A_qm_u_top)
441
442
443 A_up_u_left = self.block_matrix._A_FC[(3,0)]["left"][(0,1)]
444 A_up_u_bottom = self.block_matrix._A_FC[(3,0)]["bottom"][(1,0)]
445 A_um_u_right = self.block_matrix._A_FC[(4,0)]["right"][(0,1)]
446 A_um_u_top = self.block_matrix._A_FC[(4,0)]["top"][(1,0)]
447
448 M_up_up_left = self.block_matrix._A_FF[(3,3)]["left"][(0,1)] # These are now the same;
449 M_up_up_bottom = self.block_matrix._A_FF[(3,3)]["bottom"][(1,0)] # keep for generality
450 M_um_um_right = self.block_matrix._A_FF[(4,4)]["right"][(0,1)] #
451 M_um_um_top = self.block_matrix._A_FF[(4,4)]["top"][(1,0)] #
452
453 # SCALING: All P_u matrices are scaled with power 0
454 P_up_u_left = -np.dot(np.linalg.inv(M_up_up_left), A_up_u_left)
455 P_up_u_bottom = -np.dot(np.linalg.inv(M_up_up_bottom), A_up_u_bottom)
456 P_um_u_right = -np.dot(np.linalg.inv(M_um_um_right), A_um_u_right)
457 P_um_u_top = -np.dot(np.linalg.inv(M_um_um_top), A_um_u_top)
458
459
460 assert self.dimensionsdimensionsdimensions == 2, "Only works for 2D since DGPoisson2dPenaltyBlockMatrix is currently 2D-specific"
461 # q^\pm - gradient projections, scaled by h**-1
462 matrix_0_redundant = np.zeros((proj_unknowns, cell_unknowns)) # should be 32x4 for 2D 1st order, includes redundant projections
463 matrix_0 = np.zeros((proj_unknowns // 2, cell_unknowns)) # should be 16x4 for 2D 1st order, no redundant projections
464 # u^\pm - solution projections, scaled by h**0
465 matrix_1_redundant = np.zeros((proj_unknowns, cell_unknowns)) # should be 32x4 for 2D 1st order, includes redundant projections
466 matrix_1 = np.zeros((proj_unknowns // 2, cell_unknowns)) # should be 16x4 for 2D 1st order, no redundant projections
467
468
471
472 # select indices corresponding to q and u projections -- same way as ^f-variables in get_cell_from_face
473 q_indices = [ index for index in range(f_unknowns) if (index % f_unknowns_per_face) < ( self.poly_degreepoly_degree + 1 ) ** (self.dimensionsdimensionsdimensions-1) ]
474 # example for degree = 1: index%4 < 2 selects q_indices = [0,1, 4,5, 8,9, 12,13] out of range(16)
475 u_indices = [ index for index in range(f_unknowns) if index not in q_indices]
476
477 # assign "not redundant" projections rows: q_+, u_+ for the left and bottom; q_-, u_- for the right and top
478 matrix_0[ q_indices, : ] = np.concatenate((P_qp_u_left,
479 P_qp_u_bottom,
480 P_qm_u_right,
481 P_qm_u_top), axis=0)
482
483 matrix_1[ u_indices, : ] = np.concatenate((P_up_u_left,
484 P_up_u_bottom,
485 P_um_u_right,
486 P_um_u_top), axis=0)
487
488
491
492 # select indices corresponding to q^+- and u^+- unknowns
493 q_m_indices = [ index for index in range(proj_unknowns) if (index % proj_unknowns_per_face) < nodes_per_face ]
494 u_m_indices = [ index for index in range(proj_unknowns) if nodes_per_face <= (index % proj_unknowns_per_face) < 2 * nodes_per_face ]
495 q_p_indices = [ index for index in range(proj_unknowns) if 2 * nodes_per_face <= (index % proj_unknowns_per_face) < 3 * nodes_per_face ]
496 u_p_indices = [ index for index in range(proj_unknowns) if 3 * nodes_per_face <= (index % proj_unknowns_per_face) < 4 * nodes_per_face ]
497 # example for degree = 1: index%8 < 2 selects q_m_indices = [0,1, 8,9, 16,17, 24,25] out of range(32)
498
499 Zeros = np.zeros_like(P_qp_u_left)
500 # Zeros corresponds to the redundant projections, as we decided to include them in the projection matrix
501 # In general, Zeros can be replaced by corresponding matrices from BlockMatrix, as they are all-zeros anyway
502 matrix_0_redundant[ q_p_indices, : ] = np.concatenate((P_qp_u_left,
503 P_qp_u_bottom,
504 Zeros,
505 Zeros), axis=0)
506 matrix_0_redundant[ q_m_indices, : ] = np.concatenate((Zeros,
507 Zeros,
508 P_qm_u_right,
509 P_qm_u_top), axis=0)
510
511 matrix_1_redundant[ u_p_indices, : ] = np.concatenate((P_up_u_left,
512 P_up_u_bottom,
513 Zeros,
514 Zeros), axis=0)
515 matrix_1_redundant[ u_m_indices, : ] = np.concatenate((Zeros,
516 Zeros,
517 P_um_u_right,
518 P_um_u_top), axis=0)
519
520
521
522 output_matrices = [ matrix_0, matrix_1 ]
523 output_matrices_redundant = [ matrix_0_redundant, matrix_1_redundant ]
524 output_scalings = [ -1, 0 ]
525
526 assert( len(output_matrices_redundant) == len(output_scalings) )
527
528 for matrix in output_matrices_redundant:
529 assert matrix.shape == output_matrices_redundant[0].shape
530
531 return output_matrices, output_matrices_redundant, output_scalings
532
533 # SCALING: Consider our case with 32x4 matrix:
534 # [[ 0. 0. 0. 0.] -- redundant
535 # [ 0. 0. 0. 0.] -- redundant
536 # [ 0. 0. 0. 0.] -- redundant
537 # [ 0. 0. 0. 0.] -- redundant
538 # [ 1. -1. -0. -0.] -- scale by 1/h
539 # [-0. -0. 1. -1.] -- scale by 1/h
540 # [-1. -0. -0. -0.] -- scale by 1
541 # [-0. -0. -1. -0.] -- scale by 1
542 # [ 0. 0. 0. 0.] -- redundant
543 # [ 0. 0. 0. 0.] -- redundant
544 # [ 0. 0. 0. 0.] -- redundant
545 # [ 0. 0. 0. 0.] -- redundant
546 # [ 1. -0. -1. -0.] -- scale by 1/h
547 # [-0. 1. -0. -1.] -- scale by 1/h
548 # [-1. -0. -0. -0.] -- scale by 1
549 # [-0. -1. -0. -0.] -- scale by 1
550 # [-1. 1. -0. -0.] -- scale by 1/h
551 # [-0. -0. -1. 1.] -- scale by 1/h
552 # [-0. 1. -0. -0.] -- scale by 1
553 # [-0. -0. -0. 1.] -- scale by 1
554 # [ 0. 0. 0. 0.] -- redundant
555 # [ 0. 0. 0. 0.] -- redundant
556 # [ 0. 0. 0. 0.] -- redundant
557 # [ 0. 0. 0. 0.] -- redundant
558 # [-1. -0. 1. -0.] -- scale by 1/h
559 # [-0. -1. -0. 1.] -- scale by 1/h
560 # [-0. -0. 1. -0.] -- scale by 1
561 # [-0. -0. -0. 1.] -- scale by 1
562 # [ 0. 0. 0. 0.] -- redundant
563 # [ 0. 0. 0. 0.] -- redundant
564 # [ 0. 0. 0. 0.] -- redundant
565 # [ 0. 0. 0. 0.] -- redundant
566
568 """
569
570 Calculation of the projection matrix is implemented in get_projection_matrices().
571 Here we use it's output.
572
573 ----------------------------------
574
575 We wanna project the cell solution onto the face projections vector
576 We wanna include redundant rows now.
577
578 How many projections are stored on each face?
579 Well, it's 2 * self._projections_per_face_node on each node.
580
581 So the output of this matrix should have: extra factor to account for all the faces
582 2 * self._projections_per_face_node * ( self.poly_degree + 1 ) ** (self.dimensions-1) * 2 *self.dimensions rows (= 32)
583 self.unknowns_per_cell_node * ( self.poly_degree + 1 ) ** self.dimensions cols (= 4)
584
585 """
586
587 output_matrices = self.get_projection_matrices()[1] # projection matrices with redundant rows
588 output_scalings = self.get_projection_matrices()[2]
589
590 assert( len(output_matrices) == len(output_scalings) )
591
592 for matrix in output_matrices:
593 assert matrix.shape == output_matrices[0].shape
594
595 return output_matrices, output_scalings
596
597 # SCALING: Consider our case with 32x4 matrix:
598 # [[ 0. 0. 0. 0.] -- redundant
599 # [ 0. 0. 0. 0.] -- redundant
600 # [ 0. 0. 0. 0.] -- redundant
601 # [ 0. 0. 0. 0.] -- redundant
602 # [ 1. -1. -0. -0.] -- scale by 1/h
603 # [-0. -0. 1. -1.] -- scale by 1/h
604 # [-1. -0. -0. -0.] -- scale by 1
605 # [-0. -0. -1. -0.] -- scale by 1
606 # [ 0. 0. 0. 0.] -- redundant
607 # [ 0. 0. 0. 0.] -- redundant
608 # [ 0. 0. 0. 0.] -- redundant
609 # [ 0. 0. 0. 0.] -- redundant
610 # [ 1. -0. -1. -0.] -- scale by 1/h
611 # [-0. 1. -0. -1.] -- scale by 1/h
612 # [-1. -0. -0. -0.] -- scale by 1
613 # [-0. -1. -0. -0.] -- scale by 1
614 # [-1. 1. -0. -0.] -- scale by 1/h
615 # [-0. -0. -1. 1.] -- scale by 1/h
616 # [-0. 1. -0. -0.] -- scale by 1
617 # [-0. -0. -0. 1.] -- scale by 1
618 # [ 0. 0. 0. 0.] -- redundant
619 # [ 0. 0. 0. 0.] -- redundant
620 # [ 0. 0. 0. 0.] -- redundant
621 # [ 0. 0. 0. 0.] -- redundant
622 # [-1. -0. 1. -0.] -- scale by 1/h
623 # [-0. -1. -0. 1.] -- scale by 1/h
624 # [-0. -0. 1. -0.] -- scale by 1
625 # [-0. -0. -0. 1.] -- scale by 1
626 # [ 0. 0. 0. 0.] -- redundant
627 # [ 0. 0. 0. 0.] -- redundant
628 # [ 0. 0. 0. 0.] -- redundant
629 # [ 0. 0. 0. 0.] -- redundant
630
632 '''
633 @todo For Alex: shape of this should be
634 solutions_per_face_node * ( self.poly_degree + 1 ) ** (self.dimensions-1) rows (= 4)
635 projections_per_face_node * ( self.poly_degree + 1 ) ** (self.dimensions-1) cols (= 4)
636
637 we are fixing the value of the solution vector on the boundary with the value of
638 the projections here. you can assume everything was set to 0 initially
639
640 @todo For Sean: check the shape
641 '''
642 return np.eye(
643 # (
645 # ,
646 # self._projections_per_face_node * ( self.poly_degree + 1 ) ** (self.dimensions-1))
647 , dtype=int )
648
650 """!
651 Return the superclass's matrix, alongside a array of length
652 1 for scale factor. Hardcoding the number
653 """
654 return [super(GaussLobattoMatrixFree,self).get_cell_system_matrix_for_laplacian()], [0]
655
657 """!
658 Return the superclass's matrix, alongside a array of length
659 1 for scale factor. Hardcoding the number
660 """
661 return [super(GaussLobattoMatrixFree,self).get_cell_mass_matrix()], [2]
662
664 return [super(GaussLobattoMatrixFree,self).get_cell_identity_matrix()], [0]
665
#define assert(...)
Definition LinuxAMD.h:28
__init__(self, dimensions, poly_degree, unknowns_per_cell_node, solutions_per_face_node, projections_per_face_node)
get_cell_system_matrix_for_laplacian(self)
Return the superclass's matrix, alongside a array of length 1 for scale factor.
get_cell_identity_matrix(self)
Construct identity for this particular equation system.
get_cell_from_face_matrix(self)
the shape of this should be: unknowns_per_cell_node * ( self.poly_degree + 1 ) ** self....
get_A_tilde(self)
Compute block-diagonal of the Schur-complement matrix.
get_cell_mass_matrix(self)
Return the superclass's matrix, alongside a array of length 1 for scale factor.
get_projection_matrices(self)
Compute projection matrix from the FC and FF matrices provided in BlockMatrix interface.
get_face_from_cell_matrix(self)
Calculation of the projection matrix is implemented in get_projection_matrices().
Simple matrix generator assuming that we use a Gauss Lobatto unknown layout.
get_points1d(self)
Get spacing of integration points over unit interval.
get_weights1d(self)
Overwrites the method in the base class.
get_cell_system_matrix_for_laplacian(self)
Create a cell-cell mass matrix for .
eval_integral(self, functions, dim=-1, factor=1)
get_cell_identity_matrix(self)
Construct identity for this particular equation system.
__init__(self, dimensions, poly_degree, unknowns_per_cell_node, unknowns_per_face_node, use_unit_interval=False)
get_face_to_cell_matrix(self)
Return the cel-to-face matrix A_p^qf.
get_cell_to_face_matrix(self)
Return the projection operator.
get_cell_mass_matrix(self)
Create a cell-cell mass matrix.
get_averaging_riemann_solver(self)
Simple averaging Riemann solver.
get_nodal_points_1d(self)
Get the nodal points in the interval [-1,1].
Base class for generating matrices that we pipe into the C++ code.
get_points1d(self)
Method to set the quadrature points, used for integration.
Classical DG discretisation of 2d classical Poisson formulation with spurious facet function spaces.