Peano
Loading...
Searching...
No Matches
CollocatedDLinearDiscretisationWithPointJacobi.py
Go to the documentation of this file.
2import jinja2
4import dastgen2
5import os
6
7from .Solver import Solver
8from .actionsets.CollocatedSolver.InitDofs import InitDofsCollocated
9from .actionsets.CollocatedSolver.UpdateSolution import UpdateSolution
10from .actionsets.CollocatedSolver.ResetAndUpdateResidual import ResetAndUpdateResidual
11from .actionsets.PlotVertexDataInPeanoBlockFormat import (
12 PlotVertexDataInPeanoBlockFormat,
13)
14
15from abc import abstractmethod
16from peano4.api.solversteps.ActionSet import ActionSet
17
18
20 r"""!
21
22 Matrix-free Collocated Solver for d-linear shape functions.
23
24 Mathematical and algorithmic descriptions are to follow later. For now,
25 we describe how to use this solver and a little about how it works under
26 the hood.
27
28 Each vertex stores a certain number of values, given by the parameter
29 unknowns_per_vertex. In essence, if this parameter is more than one,
30 we are solving multiple different equations simultaneously, or we
31 can equivalently think of our PDE being vector-valued.
32
33 The user needs to provide an assembly matrix, and a mass matrix. If
34 our vector-valued PDE has \f$ K \f$ components, then each of these
35 matrices should be square, with \f$ K * 2^D \f$ rows.
36
37 In particular, they should be structured as block diagonals. Let
38 \f$ A_i \f$ be square with \f$ 2^D \f$ rows. Then the mass/assembly
39 matrices should be structured as follows:
40
41 \f$ diag( A_1, A_2, \dots A_K ) \f$.
42
43 During the actual solver step, we run through each equation one
44 after another, fetching the appropriate mass and assembly matrices
45 for the equation in hand.
46
47 At present, it hasn't been decided how to plot the output of multiple
48 equations, since only 1 is supported.
49
50 # Data held on the vertices
51
52 ## Solution (formerly named Value)
53 This array holds the actual solution which we later plot. This is updated when we touch a vertex
54 for the last time.
55
56 ## Rhs
57
58 This array holds the right-hand side of the equation. We assign this in
59 experiment-specific implementation files.
60
61 ## Diagonal
62 This array holds the sum of the diagonals of the local assembly matrix. This is
63 set to 0 at the start of every solver step
64
65 ## Residual
66 This is used to store \f$ Mb - Ax \f$, i.e. the RHS multiplied by the matrix, and subtract
67 the solution multiplied by the assembly matrix.
68
69 # Solver Steps
70
71 ## Initialisation
72
73 After the grid is created, we run around the mesh and assign types to the vertices and cells.
74
75 Vertices:
76 - "Coarse" if the vertex is due to be refined
77 - "Interior" if it's not on the boundary
78 - "Boundary" if it is on the boundary
79
80 Cells:
81 - "Coarse" if it's due to be refined
82 - Otherwise marked as "Interior"
83
84 ## Solve
85
86 There is only one action set in this solver. We do something non-trivial during touchCellFirstTime,
87 touchVertexFirstTime and touchVertexLastTime.
88
89 ### touchVertexFirstTime
90
91 We check that the Vertex is not a coarse grid vertex, then we set the residuals and the diagonals to 0.
92
93 ### touchCellFirstTime
94
95 All four vertices are in scope here. We create three vectors, each of length \f$ 2^D \f$ to hold
96 one value per vertex. For each vertex in the cell (regardless of whether it is on the boundary), we :
97 - Update the residual to be \f$ Mb - Ax \f$
98 - Increment the diagonal
99
100 ### touchVertexLastTime
101 We update the solution value (on all vertices) to be equal to the solution at the previous sweep, and we add on
102 \f$ \omega \times r / d \f$
103 where \f$ r \f$ and \f$ d \f$ stand for the residual and diagonal respectively.
104
105 # Enforcement of boundary conditions
106
107
108 # Todos
109
110 We currently overcount the residual on vertices that are shared across boundaries. This isn't an issue at present,
111 since we only check for the max, which is unaffected by this bug. But something to fix at later date.
112
113 """
114
116 self,
117 name,
118 unknowns_per_vertex,
119 dimensions,
120 min_h,
121 max_h,
122 local_assembly_matrix,
123 local_assembly_matrix_scaling,
124 mass_matrix,
125 mass_matrix_scaling,
126 solver_tolerance,
127 smoother_relaxation=0.6,
128 custom_init_actionset=None,
129 custom_plot_actionset=None, # just pass in a list of tuples, one for the variable name and the other for the getter
130 custom_init_vertex="",
131 custom_boundary_conditions="",
132 enable_plotting=True, # sometimes we want to disable standard plotting...
133 plot_predicate="true", # enable custom plotting behaviour
134 ):
135 """!
136
137 Collocated low-order (d-linear) Finite Elements
138
139 """
140 super(CollocatedDLinearDiscretisationWithPointJacobi, self).__init__(
141 name,
142 min_h,
143 max_h,
144 )
145
146 matrix_dims = (2**dimensions) * unknowns_per_vertex
147
148 # already initialised this in superclass!
149 self._vertex_data.data.add_attribute(
150 peano4.api.dastgen2.Peano4DoubleArray("u", str(unknowns_per_vertex))
151 )
152 self._vertex_data.data.add_attribute(
153 peano4.api.dastgen2.Peano4DoubleArray("rhs", str(unknowns_per_vertex))
154 )
155 self._vertex_data.data.add_attribute(
156 peano4.api.dastgen2.Peano4DoubleArray("diag", str(unknowns_per_vertex))
157 )
158 self._vertex_data.data.add_attribute(
159 peano4.api.dastgen2.Peano4DoubleArray("residual", str(unknowns_per_vertex))
160 )
161 self._vertex_data.peano4_mpi_and_storage_aspect.merge_implementation = (
163 )
164
165 self._unknowns_per_vertex_node = unknowns_per_vertex
166
167 self._local_assembly_matrix = local_assembly_matrix
168 self._local_assembly_matrix_scaling = local_assembly_matrix_scaling
169
170 self._mass_matrix = mass_matrix
171 self._mass_matrix_scaling = mass_matrix_scaling
172 self._solver_tolerance = solver_tolerance
173
175 self._smoother_relaxation = smoother_relaxation
176
177 self._custom_init_actionset = custom_init_actionset
178 self._custom_plot_actionset = custom_plot_actionset
179
180 self._custom_init_vertex = custom_init_vertex
181 self._custom_boundary_conditions = custom_boundary_conditions
182
183 self._enable_plotting = enable_plotting
184 self._plot_predicate = plot_predicate
185
186 def add_to_Peano4_datamodel(self, datamodel, verbose):
187 super(
188 CollocatedDLinearDiscretisationWithPointJacobi, self
189 ).add_to_Peano4_datamodel(datamodel, verbose)
190
191 def add_use_statements(self, observer):
192 super(CollocatedDLinearDiscretisationWithPointJacobi, self).add_use_statements(
193 observer
194 )
195
196 def add_to_plot(self, observer):
197 """!
198
199 Tell the project's observer how to plot the data
200
201 Nothing fancy here. We add plotters from Peano's toolbox to visualise
202 solution and right-hand side.
203
204 """
205 if self._enable_plotting: # true by default
206 observer.add_action_set(
208 self, "u", "getU", plot_predicate=self._plot_predicate
209 )
210 )
211 observer.add_action_set(
213 self, "rhs", "getRhs", plot_predicate=self._plot_predicate
214 )
215 )
216
218 for name, getter in self._custom_plot_actionset:
219 observer.add_action_set(
221 self, name, getter, plot_predicate=self._plot_predicate
222 )
223 )
224
225 pass
226
227 def add_to_create_grid(self, observer):
229 pass
230
231 def add_to_init_solution(self, observer):
232 """!
233
234 Solution initialisation. We can either use the
235 standard initialisation procedure or pass in
236 another one when we instantiate this solver
237 class.
238
239 """
240 if self._custom_init_actionset is None:
241 observer.add_action_set(InitDofsCollocated(self))
242 else:
243 observer.add_action_set(self._custom_init_actionset(self))
244 pass
245
246 def add_to_solve(self, observer):
247 descend_invocation_order = (
248 observer.highest_descend_invocation_order() + self._basic_descend_order + 1
249 )
250 observer.add_action_set(
251 UpdateSolution(self, descend_invocation_order + 0, False)
252 )
253 observer.add_action_set(
254 ResetAndUpdateResidual(self, descend_invocation_order + 1, False)
255 )
256 pass
257
259 self, namespace, output, subdirectory="", abstract_overwrite=True
260 ):
261 """
262
263 The solver creates two classes: An abstract base class and its
264 implementations. The abstract base class will be overwritten if
265 there is one in the directory. I pipe all the Python constants
266 into this base class, so they are available in the C++ code.
267
268 """
269 templatefile_prefix = (
270 os.path.dirname(os.path.realpath(__file__)) + "/templates/"
271 )
272 templatefile_prefix += self.__class__.__name__
273
274 if subdirectory:
275 subdirectory += "/"
276
277 dictionary = {
278 "SOLVER_INCLUDES": "",
279 "MIN_H": self.min_hmin_h,
280 "MAX_H": self.max_hmax_h,
281 "LOCAL_ASSEMBLY_MATRIX": self._local_assembly_matrix,
282 "LOCAL_ASSEMBLY_MATRIX_SCALING": self._local_assembly_matrix_scaling,
283 "MASS_MATRIX": self._mass_matrix,
284 "MASS_MATRIX_SCALING": self._mass_matrix_scaling,
285 "VERTEX_CARDINALITY": self._unknowns_per_vertex_node,
286 "SOLVER_NAME": self.typename(),
287 "SOLVER_TOLERANCE": self._solver_tolerance,
288 "SMOOTHER_RELAXATION": self._smoother_relaxation,
291 "CUSTOM_INIT_VERTEX": self._custom_init_vertex,
292 "CUSTOM_BOUNDARY_CONDITIONS": self._custom_boundary_conditions,
293 }
294
295 generated_abstract_header_file = (
297 templatefile_prefix + ".Abstract.template.h",
298 templatefile_prefix + ".Abstract.template.cpp",
299 "Abstract" + self.typename(),
300 namespace,
301 subdirectory + ".",
302 dictionary,
303 abstract_overwrite,
304 True,
305 )
306 )
307 generated_solver_files = (
309 templatefile_prefix + ".template.h",
310 templatefile_prefix + ".template.cpp",
311 self.typename(),
312 namespace,
313 subdirectory + ".",
314 dictionary,
315 False,
316 True,
317 )
318 )
319
320 output.add(generated_abstract_header_file)
321 output.add(generated_solver_files)
322 output.makefile.add_h_file(
323 subdirectory + "Abstract" + self._name + ".h", generated=True
324 )
325 output.makefile.add_h_file(subdirectory + self._name + ".h", generated=True)
326 output.makefile.add_cpp_file(
327 subdirectory + "Abstract" + self._name + ".cpp", generated=True
328 )
329 output.makefile.add_cpp_file(subdirectory + self._name + ".cpp", generated=True)
330
332 d = {}
333 d["SOLVER_NAME"] = self.typename()
334
335 output = """
336 if( !marker.willBeRefined() ) {
337
338 // naive += with the diag
339 setDiag( getDiag() + neighbour.getDiag() );
340
341 // ditto for residual
342 setResidual( getResidual() + neighbour.getResidual() );
343 }
344 """
345
346 return output
Specialisation of array using Peano's tarch.
Action set (reactions to events)
Definition ActionSet.py:6
__init__(self, name, unknowns_per_vertex, dimensions, min_h, max_h, local_assembly_matrix, local_assembly_matrix_scaling, mass_matrix, mass_matrix_scaling, solver_tolerance, smoother_relaxation=0.6, custom_init_actionset=None, custom_plot_actionset=None, custom_init_vertex="", custom_boundary_conditions="", enable_plotting=True, plot_predicate="true")
Collocated low-order (d-linear) Finite Elements.
add_implementation_files_to_project(self, namespace, output, subdirectory="", abstract_overwrite=True)
The solver creates two classes: An abstract base class and its implementations.
Abstract base class for matrix free solvers.
Definition Solver.py:9
In touchVertexFirstTime, we set the type of vertex we see (either Interior, Boundary or Coarse),...
Definition InitDofs.py:6
This action set goes first, so that if we've done at least one sweep, we can update the solution base...