Peano
Loading...
Searching...
No Matches
CollocatedMG.py
Go to the documentation of this file.
2import peano4.api
3import jinja2
5import dastgen2
6import os
7
8from .Solver import Solver
9from .actionsets.CollocatedMGSolver.InitDofs import InitDofsCollocatedMG
10from .actionsets.CollocatedMGSolver.ResetAndUpdateResidual import ResetAndUpdateResidual
11from .actionsets.CollocatedMGSolver.UpdateSolution import UpdateSolution
12from .actionsets.CollocatedMGSolver.Restriction import Restriction
13from .actionsets.CollocatedMGSolver.Prolongation import Prolongation
14from .actionsets.PlotVertexDataInPeanoBlockFormat import (
15 PlotVertexDataInPeanoBlockFormat,
16)
17
18from abc import abstractmethod
19from peano4.api.solversteps.ActionSet import ActionSet
20
21
23 r"""!
24
25 A full mathematical description will follow later, but for now we outline some algorithmic steps here.
26
27 We borrow heavily from the original Collocated Solver. See \ref matrixfree_collocated_solver.
28
29 We intialise by setting the values for u and the RHS in implementation files. So far this has been tested
30 with the standard poisson set up ( \f$ 8\pi^2 sin(x)sin(y) \f$ on the rhs, with zeroes in the initial guess).
31 We do this only on the finest level, and every other level receives 0.
32
33 We enumerate each level according to its coarseness. Ie the top level is marked as 0, and we increase this number
34 as we refine.
35
36 ## Algorithmic Blueprint
37
38 1. On the finest level:
39 - Solve with Jacobi for a pre-determined number of pre-smoothing steps. Use standard mass matrix
40 - On the final sweep, we take the final residual and write this into the rhs of the vertex and one level above. We set its initial guess to 0.
41 - We then move up one level coarser
42
43 2. On the next level up:
44 - Solve with Jacobi for a pre-determined number of pre-smoothing steps. Use identity instead of mass matrix
45 - On the final sweep, we take the final residual and write this into the rhs of the vertex and one level above. We set its initial guess to 0.
46 - We then move up one level coarser
47
48 3. We continue until we are on the coarsest level. We then solve with Jacobi for the predetermined number of pre-smoothing steps.
49
50 4. We then solve on the coarsest level with the predetermined number of post-smoothing steps.
51 - After this, we move one level finer
52
53 5. On the next level down:
54 - When we touch a vertex for the first time, we add prolongation of the solution from the level above to our solution.
55 - We then solve with the predetermined number of post-smoothing steps
56 - After this, we move one level finer
57
58 6. We continue until we reach the finest level, at which point we add prolongation from the level above, and run for predetermined number of post-smoothing steps. We then go back to step 1.
59
60 Every time we run a sweep on the finest level, we update the global residuals. We terminate when the solution on the
61 finest level conforms to the given stopping criterion.
62
63 ## Implementation
64
65 We have 4 fundamental action sets, which we discuss here in order of invocation. Each of their operations is guarded by a bool function that lives in the abstract solver class.
66
67 ### UpdateSolution
68
69 \see mghype.matrixfree.solvers.api.actionsets.CollocatedMGSolver.UpdateSolution
70
71 This is where we take the residual and diag, and set the solution on vertices to \f$ u + du = u + \omega / (diag * residual) \f$. We want to avoid
72 zero-divsion, as reset the diagonal every sweep, meaning we need to wait until the second pre/post-smoothing sweep to update the solution. We also
73 need to make sure we finalise the solution updates before we restrict/prolongate as well. updateSolution() in the abstract solver class handles all
74 of this behaviour.
75
76 To make sure the solver works in multiple trees, we tell the vertices that lie on parallel domain boundaries to share residual and diagonal with
77 each other, to make sure contributions from neighbouring cells are registered properly. See get_vertex_merge_method in this class.
78
79 This action set also makes sure that the RHS is correctly consolidated - we write our restrictions into the vertex field NewRhs. When we touch
80 a vertex for the first time after restriction, we make sure that the rhs is set to the value of what we restricted (ie the newrhs).
81
82 Ditto for delta after prolongation.
83
84 ### Prolongation
85 \see mghype.matrixfree.solvers.api.actionsets.CollocatedMGSolver.Prolongation
86
87 When we touch a vertex on the coarse level, we set the delta field to be \f$ U - OldU \f$. When we touch a vertex on the fine level, we prolongate
88 the coarse delta into the fine level delta. On the next sweep, we update the fine solution to be \f$ u + delta \f$.
89
90 The abstract solver just needs to check if its smoothing state is set to prolongation, so the logic is much simpler.
91
92 ### ResetAndUpdateResidual
93
94 \see mghype.matrixfree.solvers.api.actionsets.CollocatedMGSolver.ResetAndUpdateResidual
95
96 When we touch the vertex for the first time, we reset residual and diag to 0, or apply boundary conditions.
97 When we touch the cell for the first time, we perform the jacobi iteration and write the residual into the \f$ 2^D \f$ adjacent vertices.
98
99 The abstract solver class does this if updateResidual() returns true, which it does when:
100 - The refinement level matches the active level
101 - The smoothing state is presmoothing or postsmoothing
102
103 ### Restriction
104
105 \see mghype.matrixfree.solvers.api.actionsets.CollocatedMGSolver.Restriction
106
107 When we touch a coarse vertex for the first time, we reset everything to 0, i.e. rhs and solution.
108 When we touch a fine vertex for the first time, we hand over to the mghype::matrixfree::solvers::cgmultigrid::restrictToNextLevel
109 which writes the fine level residual into the newRhs of the level above.
110
111 ## Infrastructure
112
113 You can now specify the number of levels that you want to use in the solver itself. The finest level is set by the refinement of
114 the mesh and is currently determined at runtime when the mesh is descended for the first time. If we find that the finest level
115 isn't at least one larger than the coarest level, we throw a std::runtime_error.
116
117 """
118
120 self,
121 name,
122 unknowns_per_vertex,
123 dimensions,
124 min_h,
125 max_h,
126 local_assembly_matrix,
127 local_assembly_matrix_scaling,
128 mass_matrix,
129 mass_matrix_scaling,
130 correction_matrix,
131 correction_matrix_scaling,
132 solver_tolerance,
133 pre_smoothing_steps,
134 post_smoothing_steps,
135 descend_order,
136 restriction_matrix,
137 prolongation_matrix,
138 coarsest_level=0,
139 smoother_relaxation=0.6,
140 plot_predicate="true",
141 ):
142 super(CollocatedMG, self).__init__(name, min_h=min_h, max_h=max_h)
143
144 assert pre_smoothing_steps >= 1
145 assert post_smoothing_steps >= 1
146 assert coarsest_level >= 0
147
148 self._pre_smoothing_steps = pre_smoothing_steps
149 self._post_smoothing_steps = post_smoothing_steps
150
151 self._restriction_matrix = restriction_matrix
152 self._prolongation_matrix = prolongation_matrix
153
154 self._coarsest_level = coarsest_level
155
156 self._vertex_data.data.add_attribute(
157 dastgen2.attributes.Integer(name="Level", initval=-1)
158 )
159 self._cell_data.data.add_attribute(
160 dastgen2.attributes.Integer(name="Level", initval=-1)
161 )
162
163 self._unknowns_per_vertex_node = unknowns_per_vertex
164
165 self._vertex_data.data.add_attribute(
166 peano4.api.dastgen2.Peano4DoubleArray("u", str(unknowns_per_vertex))
167 )
168 self._vertex_data.data.add_attribute(
169 peano4.api.dastgen2.Peano4DoubleArray("oldU", str(unknowns_per_vertex))
170 )
171 self._vertex_data.data.add_attribute(
172 peano4.api.dastgen2.Peano4DoubleArray("delta", str(unknowns_per_vertex))
173 )
174 self._vertex_data.data.add_attribute(
175 peano4.api.dastgen2.Peano4DoubleArray("rhs", str(unknowns_per_vertex))
176 )
177 self._vertex_data.data.add_attribute(
178 peano4.api.dastgen2.Peano4DoubleArray("diag", str(unknowns_per_vertex))
179 )
180 self._vertex_data.data.add_attribute(
181 peano4.api.dastgen2.Peano4DoubleArray("residual", str(unknowns_per_vertex))
182 )
183
184 self._vertex_data.peano4_mpi_and_storage_aspect.merge_implementation = (
186 )
187 self._vertex_data.peano4_mpi_and_storage_aspect.implementation_file_includes += (
188 """\n#include "repositories/SolverRepository.h"\n"""
189 )
190
191 self._local_assembly_matrix = local_assembly_matrix
192 self._local_assembly_matrix_scaling = local_assembly_matrix_scaling
193
194 self._mass_matrix = mass_matrix
195 self._mass_matrix_scaling = mass_matrix_scaling
196 self._correction_matrix = correction_matrix
197 self._correction_matrix_scaling = correction_matrix_scaling
198 self._solver_tolerance = solver_tolerance
199
200 self._basic_descend_order = descend_order
201
202 self._smoother_relaxation = smoother_relaxation
203 self._plot_predicate = plot_predicate
204
205 def add_to_Peano4_datamodel(self, datamodel, verbose):
206 super(CollocatedMG, self).add_to_Peano4_datamodel(datamodel, verbose)
207
208 def add_use_statements(self, observer):
209 super(CollocatedMG, self).add_use_statements(observer)
210
211 def add_to_create_grid(self, observer):
213
214 def add_to_init_solution(self, observer):
215 """!
216
217 Solution initialisation
218
219 """
220 observer.add_action_set(InitDofsCollocatedMG(self))
221 pass
222
223 def add_to_solve(self, observer):
224 invocation_order = (
225 observer.highest_descend_invocation_order() + self._basic_descend_order + 1
226 )
227 observer.add_action_set(
228 UpdateSolution(self, descend_invocation_order=invocation_order + 0)
229 )
230 observer.add_action_set(
231 Prolongation(self, descend_invocation_order=invocation_order + 1)
232 )
233 observer.add_action_set(
234 ResetAndUpdateResidual(self, descend_invocation_order=invocation_order + 2)
235 )
236 observer.add_action_set(
237 Restriction(self, descend_invocation_order=invocation_order + 3)
238 )
239 pass
240
241 def add_to_plot(self, observer):
242 cell_predicate = f"""fineGridCell{self.typename()}.getLevel() == repositories::{self.instance_name()}.getFinestLevel() """
243 observer.add_action_set(
245 self,
246 "u",
247 "getU",
248 plot_predicate=self._plot_predicate,
249 cell_predicate=cell_predicate,
250 )
251 )
252 observer.add_action_set(
254 self,
255 "rhs",
256 "getRhs",
257 plot_predicate=self._plot_predicate,
258 cell_predicate=cell_predicate,
259 )
260 )
261 pass
262
263 def add_implementation_files_to_project(self, namespace, output, subdirectory=""):
264 """
265
266 The solver creates two classes: An abstract base class and its
267 implementations. The abstract base class will be overwritten if
268 there is one in the directory. I pipe all the Python constants
269 into this base class, so they are available in the C++ code.
270
271 """
272 templatefile_prefix = (
273 os.path.dirname(os.path.realpath(__file__)) + "/templates/"
274 )
275 templatefile_prefix += self.__class__.__name__
276
277 if subdirectory:
278 subdirectory += "/"
279
280 dictionary = {
281 "SOLVER_INCLUDES": "",
282 "MIN_H": self.min_hmin_h,
283 "MAX_H": self.max_hmax_h,
284 "LOCAL_ASSEMBLY_MATRIX": self._local_assembly_matrix,
285 "LOCAL_ASSEMBLY_MATRIX_SCALING": self._local_assembly_matrix_scaling,
286 "MASS_MATRIX": self._mass_matrix,
287 "MASS_MATRIX_SCALING": self._mass_matrix_scaling,
288 "VERTEX_CARDINALITY": self._unknowns_per_vertex_node,
289 "SOLVER_NAME": self.typename(),
290 "SOLVER_TOLERANCE": self._solver_tolerance,
291 "SMOOTHER_RELAXATION": self._smoother_relaxation,
292 "PRE_SMOOTHING_STEPS": self._pre_smoothing_steps,
293 "POST_SMOOTHING_STEPS": self._post_smoothing_steps,
294 "RESTRICTION_MATRIX": self._restriction_matrix,
295 "PROLONGATION_MATRIX": self._prolongation_matrix,
296 "CORRECTION_MATRIX": self._correction_matrix,
297 "CORRECTION_MATRIX_SCALING": self._correction_matrix_scaling,
298 "COARSEST_LEVEL": self._coarsest_level,
299 }
300
301 generated_abstract_header_file = (
303 templatefile_prefix + ".Abstract.template.h",
304 templatefile_prefix + ".Abstract.template.cpp",
305 "Abstract" + self.typename(),
306 namespace,
307 subdirectory + ".",
308 dictionary,
309 True,
310 True,
311 )
312 )
313 generated_solver_files = (
315 templatefile_prefix + ".template.h",
316 templatefile_prefix + ".template.cpp",
317 self.typename(),
318 namespace,
319 subdirectory + ".",
320 dictionary,
321 False,
322 True,
323 )
324 )
325
326 output.add(generated_abstract_header_file)
327 output.add(generated_solver_files)
328 output.makefile.add_h_file(
329 subdirectory + "Abstract" + self._name + ".h", generated=True
330 )
331 output.makefile.add_h_file(subdirectory + self._name + ".h", generated=True)
332 output.makefile.add_cpp_file(
333 subdirectory + "Abstract" + self._name + ".cpp", generated=True
334 )
335 output.makefile.add_cpp_file(subdirectory + self._name + ".cpp", generated=True)
336
338 d = {}
339 d["SOLVER_INSTANCE"] = self.instance_name()
340
341 output = """
342
343 // we can get away with only doing these on the active level
344 if (
345 repositories::{{SOLVER_INSTANCE}}.consolidateResidualAndDiag( getLevel() )
346 ) {
347 // naive += with the diag
348 setDiag( getDiag() + neighbour.getDiag() );
349
350 // ditto for residual
351 setResidual( getResidual() + neighbour.getResidual() );
352 }
353
354 """
355
356 return jinja2.Template(output).render(**d)
Specialisation of array using Peano's tarch.
Action set (reactions to events)
Definition ActionSet.py:6
A full mathematical description will follow later, but for now we outline some algorithmic steps here...
add_to_init_solution(self, observer)
Solution initialisation.
__init__(self, name, unknowns_per_vertex, dimensions, min_h, max_h, local_assembly_matrix, local_assembly_matrix_scaling, mass_matrix, mass_matrix_scaling, correction_matrix, correction_matrix_scaling, solver_tolerance, pre_smoothing_steps, post_smoothing_steps, descend_order, restriction_matrix, prolongation_matrix, coarsest_level=0, smoother_relaxation=0.6, plot_predicate="true")
add_to_Peano4_datamodel(self, datamodel, verbose)
add_implementation_files_to_project(self, namespace, output, subdirectory="")
The solver creates two classes: An abstract base class and its implementations.
add_use_statements(self, observer)
This routine should still be called even if overwritten in child class.
Abstract base class for matrix free solvers.
Definition Solver.py:9
instance_name(self)
Return the name of the object that will be created for this solver.
Definition Solver.py:63
This also should be called before the solver stuff.
This class comes last in the pecking order so that we restrict the residual after we've finished comp...
Definition Restriction.py:6
We use this action set to apply the residual and solution updates to the vertices.