Peano
Loading...
Searching...
No Matches
MatrixGenerator.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
3
4from abc import abstractmethod
5
6import numpy as np
7from numpy.polynomial.legendre import leggauss
8from scipy.interpolate import lagrange
9
10from itertools import product
11
13 """!
14
15 Base class for generating matrices that we pipe into the C++ code
16
17 Every method marked abstract is intended to be overwritten in child classes, but some will come
18 with default behaviour.
19
20 """
21 def __init__(self,
22 dimensions,
23 poly_degree,
24 unknowns_per_node=1
25 ):
26 self.dimensions = dimensions
27 self.poly_degree = poly_degree
28 self.unknowns_per_node = unknowns_per_node
29
31 self.derivatives1d = [ p.deriv() for p in self.polynomials1d ]
32 self.points1d = self.get_points1d()
34
35 @abstractmethod
37 """!
38 Method to create a list of 1D polynomials.
39
40 We promote these to higher dimensions by
41 taking tensor products.
42
43 By default, we will return Lagrange polynomials,
44 but we can overwrite this in child class
45 """
46 polys = []
47
48 '''
49 take a linspace from -1 -> 1 as this is how
50 gaussian quadrature points are defined. Note
51 this may later require us to convert from
52 coordinates in [-1,1] to the global mesh
53 coordinates.
54 '''
55 x = np.linspace(-1,1, self.poly_degree + 1)
56 for j in range(self.poly_degree + 1):
57 y = np.zeros_like(x)
58 y[j] = 1.0
59 polys.append( lagrange(x,y) )
60 return polys
61
62 @abstractmethod
63 def get_points1d(self):
64 """!
65 Method to set the quadrature points, used for
66 integration.
67
68 Provide a method here to produce Gauss-Legendre
69 points, but this can be overwritten in child
70 class.
71
72 Slightly redundant use of the leggauss function
73 here, but I thought it better to separate the
74 calculation of points and weights for ease of
75 implementing in child class.
76 """
77 points, weights = leggauss( self.poly_degree + 1 )
78 return points
79
81 """!
82
83 Most schemes define the integration point over the interval [-1,1]
84 whereas MGHyPE often needs it on the unit interval. Therefore, I
85 offer this genereric recalibration routine.
86
87 """
88 shift = -min(self.get_points1d())
89 scaling = 1.0/(max(self.get_points1d())-min(self.get_points1d()))
90 return [ (x+shift)*scaling for x in self.get_points1d() ]
91
92 @abstractmethod
93 def get_weights1d(self):
94 """!
95 Method to set the quadrature weights, used for
96 integration.
97
98 Provide a method here to produce Gauss-Legendre
99 weights, but this can be overwritten in child
100 class.
101
102 Slightly redundant use of the leggauss function
103 here, but I thought it better to separate the
104 calculation of points and weights for ease of
105 implementing in child class.
106 """
107 points, weights = leggauss( self.poly_degree + 1 )
108 return weights
109
110 def get_points_for_dimension(self, dim=-1):
111 """!
112 We promote the 1D quadrature points
113 in into the dimensions of our problem
114 by taking cartesian product. This works
115 for any dimension and does not need to
116 be implemented in the child class.
117
118 We may need to produce arrays of points
119 for number of dimensions which is less
120 """
121 if dim==-1:
122 dim=self.dimensions
123 #make array of length dim, where each
124 #entry is a copy of self.points1d
125 points = [self.points1d for _ in range(dim)]
126
127 '''!
128 This is a bit of python magic. We unpack points, so we
129 are in effect passing Dimensions different arguments to
130 iterools.product. itertools.product then produces every
131 possible combination, in tuple form. We then capture this
132 in a list, and return it. Ie if we start with a list of
133 1d points: [p0,p1,p2,p3,...], we return, for Dimensions==3:
134 [
135 (p0,p0,p0),
136 (p0,p0,p1),
137 (p0,p0,p2),
138 ...
139 ]
140 '''
141 return [ *product( *points ) ]
142
143 def get_weights_for_dimension(self, dim=-1):
144 """!
145 We promote the 1D quadrature weights
146 in into the dimensions of our problem
147 by taking cartesian product. This works
148 for any dimension and does not need to
149 be implemented in the child class.
150 """
151 if dim==-1:
152 dim = self.dimensions
153 #dummy function to return product of entries in array
154 def arrayProduct(array):
155 output = 1
156 for el in array: output *= el
157 return output
158
159 #create an array of length dim, where each
160 #each entry of the array is a copy of the 1d weights
161 weights = [self.weights1d for _ in range(dim)]
162
163 '''
164 This time, given 1d weights [w0,w1,w2,...], we want to
165 return:
166 [
167 w0*w0*w0,
168 w0*w0*w1,
169 w0*w0*w2,
170 ...
171 ]
172 '''
173 return [ arrayProduct(array) for array in product( *weights ) ]
174
175 def convert_index_to_dim(self, index, dim=-1):
176 """!
177 This method helps with indexing. Typically,
178 for polynomial degree p, we have (p+1) nodal
179 points per coordinate axis, for a total of
180 @f$ p^d @f$. This method converts an index
181 in the range @f$ (0, ..., p^d) @f$ into a
182 tuple of (x,y,z) coordinates.
183 """
184 #support converting index to any dimension. default to max
185 if dim==-1:
186 dim=self.dimensions
187 assert index in range((self.poly_degree+1) ** dim), "index outside of range!"
188 output = [0 for _ in range(dim)]
189 for d in range(dim-1,-1,-1):
190 pToD = (self.poly_degree+1)**(d)
191 output[d] = (index - index%pToD) // pToD
192 index = index % pToD
193 return output
194
195 def get_polynomial(self, index, dim=-1):
196 """!
197 Promote 1d polynomials to many dimensions
198 by taking products.
199
200 We pass in an index and a dimension, which will, by default
201 be set to the number of dimensions of the problem.
202
203 The index should be betweeen 0 and (self.poly_degree)^self.dimensions
204
205 We convert the index into a tuple (x,y,z) of indices, pull
206 out appropriate polynomials and return a function object.
207 This function object will expect dim arguments.
208 """
209 if dim == -1:
210 dim = self.dimensions
211
212 # the indices of the polynomials we wanna fetch
213 # warning: will misfire if "index" not in correct range for dim
214 indices = self.convert_index_to_dim(index, dim)
215 polys = [ self.polynomials1d[i] for i in indices ]
216
217 #define function object to return:
218 def returnFunc(*args):
219 '''
220 The intention of this part is as follows:
221 We have (for example) 3 arguments for a 3d function: [arg_x, arg_y, arg_z]
222 We have correspondingly 3 polynomials, since we promote a 1d basis to a 3d
223 basis by taking products: [poly_x, poly_y, poly_z].
224 Then, we return poly_x(arg_x) * poly_y(arg_y) * poly_z(arg_z)
225
226 The polys don't go out of scope.
227 '''
228 assert(len(args)==dim), f"Supplied {len(args)} args to a {dim}d function!"
229 out = 1
230 for i, arg in enumerate(args):
231 # dummy evaluation of i'th polynomial
232 out *= polys[i](arg)
233 return out
234
235 #return this function object
236 return returnFunc
237
238 def get_deriv(self, index, dimForDeriv, dim=-1):
239 """!
240 dimForDeriv is the singular dimension that we wanna take a polyderiv in
241
242 Behaves the same as get_polynomial, except in that in the specified
243 dimension, we take a derivative rather than the poly itself
244 """
245 if dim == -1:
246 dim = self.dimensions
247 assert dimForDeriv in range(dim), f"dimForDeriv={dimForDeriv}, dim={dim}"
248
249
250 # the indices of the polynomials we wanna fetch
251 # warning: will misfire if "index" not in correct range for dim
252 indices = self.convert_index_to_dim(index, dim)
253 polys = []
254 for i, index in enumerate(indices):
255 if i == dimForDeriv:
256 # get poly deriv instead
257 polys.append( self.derivatives1d[index] )
258 else:
259 polys.append( self.polynomials1d[index] )
260
261 #define function object to return:
262 def returnFunc(*args):
263 assert(len(args)==dim), f"Supplied {len(args)} args to a {dim}d function!"
264 out = 1
265 for i, arg in enumerate(args):
266 # dummy evaluation of i'th polynomial
267 out *= polys[i](arg)
268 return out
269
270 #return this function object
271 return returnFunc
272
273 def eval_integral(self, functions, dim=-1, factor=1):
274 if dim == -1:
275 dim = self.dimensions
276 points = self.get_points_for_dimension(dim)
277 weights = self.get_weights_for_dimension(dim)
278 output = 0
279
280 for p, w in zip(points, weights):
281 localEval = 1
282 for func in functions:
283 # do the following for each function we pass in
284 # need to unpack p since this function will expect
285 # 3 arguments for 3d, etc...
286 localEval *= func( *p )
287 # aggregate
288 output += w * localEval
289 return output * factor
290
291 @abstractmethod
293 """!
294
295 Construct the Laplacian
296
297 Create the Laplacian. This routine represents the fact
298 that the Laplacian is kind of the evergreen of many
299 PDEs, and we therefore provide a factory method for
300 this one always.
301
302 These factory methods all return two lists. The first
303 one is a list of matrices @f$ A_1, A_2, ... @f$ defined relative to the unit
304 cube or square, respectively. The second list has the
305 same length as the first list, and stores integer indices
306 @f$ k_1, k_2, k_3, ... @f$. Throughout the assemly, you
307 should then apply the local matrix
308 @f$ A = h^{k_1} A_1 + h^{k_2} A_2 + ... @f$. The mass
309 matrix typically is only one matrix scaled with d, i.e.
310 we return a list hosting only one matrix as first argument
311 and the second list only hosts the integer self.dimensions.
312
313 Implement this in child class.
314
315 @return [Matrix] x [Int]
316 The two lists have to have the same number of entries.
317
318 """
319 raise NotImplementedError
320
321 @abstractmethod
323 """!
324 Factory for cell mass matrix.
325
326 Consult get_cell_system_matrix_for_laplacian() for an overview
327 of the result types. As the mass matrix is typically fairly
328 simple (it does not host any derivative), and as we work within
329 a finite element mindset, this routine returns a list with one
330 matrix as first argument, and then a list with the integer
331 self.dimensions.
332
333 """
334 raise NotImplementedError
335
336 @abstractmethod
338 """!
339
340 Construct identity for this particular equation system
341
342 Not that this is identity construction has to take the number of
343 unknowns into account as well as the number of degrees of freedom
344 hosted within a cell.
345
346 """
347 raise NotImplementedError
#define assert(...)
Definition LinuxAMD.h:28
Base class for generating matrices that we pipe into the C++ code.
__init__(self, dimensions, poly_degree, unknowns_per_node=1)
get_integration_points_over_unit_interval(self)
Most schemes define the integration point over the interval [-1,1] whereas MGHyPE often needs it on t...
get_weights_for_dimension(self, dim=-1)
We promote the 1D quadrature weights in into the dimensions of our problem by taking cartesian produc...
get_polynomial1d(self)
Method to create a list of 1D polynomials.
get_cell_identity_matrix(self)
Construct identity for this particular equation system.
get_points1d(self)
Method to set the quadrature points, used for integration.
get_weights1d(self)
Method to set the quadrature weights, used for integration.
get_points_for_dimension(self, dim=-1)
We promote the 1D quadrature points in into the dimensions of our problem by taking cartesian product...
convert_index_to_dim(self, index, dim=-1)
This method helps with indexing.
get_deriv(self, index, dimForDeriv, dim=-1)
dimForDeriv is the singular dimension that we wanna take a polyderiv in
get_polynomial(self, index, dim=-1)
Promote 1d polynomials to many dimensions by taking products.