Peano
Loading...
Searching...
No Matches
Particle.py
Go to the documentation of this file.
1# use, please see the copyright notice at www.peano-framework.org
3
4import dastgen2
5from exaparty.api.particle.AlgorithmStep import AlgorithmStep
6from abc import abstractmethod
7import copy
8
9
11 """!
12
13 Base class for any particle in the project
14
15 You usually do not initialise this base class, but you pick one of the
16 subclasses which define which time stepping scheme is realised by a
17 particle. The time stepping scheme defines through which phases or steps
18 each particle runs through per time step, while this generic base class
19 has no steps at all.
20
21 The type is a specialisation of Peano's particle as it is provided via the
22 toolbox. In line with the docu there, you can use this particle and add
23 further attributes via
24
25 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
26 my_particle.data.add_attribute( peano4.api.dastgen2.Peano4DoubleArray("v","Dimensions") )
27 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
28
29
30
31 """
32
33 class SpeciesAspect(object):
34 """
35
36 Every generated particle belongs to a species. I originally thought I'd
37 use some kind of inheritance to model this, but actually it is easier to
38 introduce a static field for the time being that stores the species
39 information.
40
41 """
42
43 def __init__(self):
44 pass
45
46 def get_include(self):
47 return """
48#include "exaparty/ParticleSpecies.h"
49"""
50
51 def set_model(self, data_model):
52 self._data_model = data_model
53
54 def get_attributes(self):
55 return ""
56
57 def get_method_declarations(self, full_qualified_name):
58 return """
59 static ::exaparty::ParticleSpecies& getSpecies();
60"""
61
62 def get_implementation(self, full_qualified_name):
63 return (
64 """
65::exaparty::ParticleSpecies& """
66 + full_qualified_name
67 + """::getSpecies() {
68 static ::exaparty::ParticleSpecies species;
69 return species;
70}
71"""
72 )
73
75 self,
76 name,
77 particles_per_cell,
78 min_h,
79 max_h,
80 ):
81 """!
82
83 Initialise the particle
84
85 This is the baseclass for a particle, i.e. we only add the absolute minimum
86 of information to a particle. As we inherit from the toolbox particle, we
87 already have some attributes defined. These are the guys which are not
88 explicitly visible from the code snippet below, i.e. they are introduced by
89 the superclass constructor.
90
91 Here's an overview of pre-defined attributes that each and every particle
92 hosts:
93
94 - A position which is a double vector with Dimension entries.
95 - A search radius which describes the range with which other particles a
96 particle might theoretically interact. The effective interaction might
97 be smaller if a code decides to ignore potential interaction partners,
98 i.e. this is an absolute maximum. The search radius will decide on which
99 resolution level within the tree a particle is held.
100 - A MoveState flag. This flag is essential. We use it later to label those
101 particles that have been moved to avoid that we move particles multiple
102 times.
103 - A flag that indicates if a particle has been updated within a cell.
104 Consult our code release papers: In short, a particle does not uniquely
105 belong to one cell but can belong to many cells. We nevertheless want to
106 update them only once.
107
108 @see exaparty.api.actionsets.UpdateParticleMarker
109
110 """
111 super(Particle, self).__init__(name)
112
113 self.data.add_attribute(
115 "MoveState", ["New", "NotMovedYet", "Moved"]
116 )
117 )
118 self.data.add_attribute(dastgen2.attributes.Boolean("CellHasUpdatedParticle"))
119 self.data.add_aspect(Particle.SpeciesAspect())
120
121 if particles_per_cell <= 0:
122 print(
123 "WARNING: Particles per cell set to zero, so code might refine up to maximum depth even if there is only one particle"
124 )
125 self.particles_per_cell = particles_per_cell
126 self.min_h = min_h
127 self.max_h = max_h
128
129 return
130
131 @abstractmethod
133 """!
134
135 Return sequence of algorithm steps that have to be called per time step
136
137 """
138 assert False, "Should be overwritten by subclass"
139 return []
140
141 @abstractmethod
143 """!
144
145 Return sequence of algorithm steps that have to be performed throughout initialisation
146
147 """
148 assert False, "Should be overwritten by subclass"
149 return []
150
151 @property
153 """!
154
155 Create default readme descriptor
156
157 You might want to overwrite this for your particular particle
158 species to get more detailed info the the README.md file generated
159 by Peano.
160
161 """
162 return (
163 """### Particle """
164 + self.namenamename
165 + """
166
167Particle properties:
168
169- particles per cell: """
170 + str(self.particles_per_cell)
171 + """
172- h_min: """
173 + str(self.min_h)
174 + """
175- h_max: """
176 + str(self.max_h)
177 + """
178- attributes: """
179 + str(len(self.data._attributes))
180 + """
181
182 """
183 )
184
185 DependencyChecks_Attribute_Prefix = "dependencyChecks"
186 DependencyChecks_Ifdefs = ["PeanoDebug > 0"]
187
189 self,
190 steplist,
191 step_type_name,
192 peano4_event_enum,
193 ):
194 """!
195
196 Add dependency checks as well as mesh consistency checks to the algorithm steps
197
198 This function actually modifies the AlgorithmSteps to contain the
199 debug dependency checks. Is called by self._add_dependency_checks()
200 and uses the validation routines from exaparty::dependencychecks.
201
202 The routine is used to either add checks for the init phase or the
203 actual time stepping. Which one is meant is identified through
204 step_type_name. You can obviously call the routine twice: once for
205 init and once for the time stepping. Later on, you might want to
206 add further steps.
207
208 Besides the name of interest, we also require a list of algorithmic
209 steps through which the code runs through.
210
211
212 ## Default settings
213
214 If the user does not define bespoke default settings, this routine adds
215 default ones. These are set through instances of exaparty::dependencychecks::Invariant.
216
217 - For vertices, this is touch-at-most-once-mask-out-otherwise-all-previous-steps-update-at-least-once.
218 - For cells, this is also touch-at-most-once-mask-out-otherwise-all-previous-steps-update-at-least-once.
219
220 For any step whose effect may trigger a rerun, we switch to the
221 _SweepMayRerun variant of the invariant instead, as such a step may
222 legitimately touch a particle more than once within a sweep.
223 @see AlgorithmStep.may_trigger_rerun()
224
225 ## Additional attributes
226
227 First, the particle is added two counters which count up how often a
228 particle is touched or masked out.
229
230 @param Steplist: list
231 list of AlgorithmStep objects to work on
232
233 @param step_type_name: str
234 name of type of steps we're dealing with. Either AlgorithmStep
235 or InitStep.
236
237 @param peano4_event_enum: list
238 list of enums of peano4 events within a single mesh traversal
239 (e.g. touch vertex first time, cell kernel, ...)
240
241 @return steps: list
242 the modified list of AlgorithmSteps, now including dependency checks
243
244 """
245 steps = copy.deepcopy(steplist)
246 nsteps = len(steps)
247 nevents = AlgorithmStep.PeanoEventUsedBySwift.EVENT_COUNT.value
248
249 # add enum to particle for each AlgorithmStep. This enum tracks in
250 # which algorithmic step a particle currently is. See
251 # DependencyChecks.h.
252 step_name_list = [step.name for step in steps]
253 step_name_list.append("count") # add a count so we can loop over enums
254 step_name_enum = dastgen2.attributes.Enumeration(
255 self.DependencyChecks_Attribute_Prefix + step_type_name + "LastUpdated",
256 step_name_list,
257 ifdefs=self.DependencyChecks_Ifdefs,
258 )
259 self.data.add_attribute(step_name_enum)
260
261 # add (flattened) array for flags
262 number_of_updates = dastgen2.attributes.IntegerArray(
263 self.DependencyChecks_Attribute_Prefix + step_type_name + "Updates",
264 nsteps * nevents,
265 ifdefs=self.DependencyChecks_Ifdefs,
266 )
267 self.data.add_attribute(number_of_updates)
268
269 number_of_mask_outs = dastgen2.attributes.IntegerArray(
270 self.DependencyChecks_Attribute_Prefix + step_type_name + "MaskOuts",
271 nsteps * nevents,
272 ifdefs=self.DependencyChecks_Ifdefs,
273 )
274 self.data.add_attribute(number_of_mask_outs)
275
276 # String template for dependency check call
277 check_template_cell_operation = """
278 ::exaparty::dependencychecks::check{STEP_TYPE}(
279 ::exaparty::dependencychecks::Invariant::{INVARIANT},
280 localParticles,
281 activeParticles,
282 marker,
283 globaldata::{NAME}::DependencyChecks{STEP_TYPE}LastUpdated::{STEP_NAME},
284 globaldata::{NAME}::{PEANO4_EVENT_TYPE}::{EVENT},
285 {CHECK_FUNCTION},
286 _spacetreeId
287 );
288"""
289
290 check_template_vertex_operation = """
291 ::exaparty::dependencychecks::check{STEP_TYPE}(
292 ::exaparty::dependencychecks::Invariant::{INVARIANT},
293 assignedParticles,
294 marker,
295 globaldata::{NAME}::DependencyChecks{STEP_TYPE}LastUpdated::{STEP_NAME},
296 globaldata::{NAME}::{PEANO4_EVENT_TYPE}::{EVENT},
297 {CHECK_FUNCTION},
298 _spacetreeId
299 );
300"""
301
302 mark_template_cell_operation = """
303 ::exaparty::dependencychecks::mark{STEP_TYPE}(
304 localParticles,
305 activeParticles,
306 marker,
307 globaldata::{NAME}::DependencyChecks{STEP_TYPE}LastUpdated::{STEP_NAME},
308 globaldata::{NAME}::{PEANO4_EVENT_TYPE}::{EVENT},
309 ::exaparty::dependencychecks::Invariant::{INVARIANT},
310 {CHECK_FUNCTION},
311 _spacetreeId
312 );
313"""
314
315 mark_template_vertex_operation = """
316 ::exaparty::dependencychecks::mark{STEP_TYPE}(
317 assignedParticles,
318 marker,
319 globaldata::{NAME}::DependencyChecks{STEP_TYPE}LastUpdated::{STEP_NAME},
320 globaldata::{NAME}::{PEANO4_EVENT_TYPE}::{EVENT},
321 ::exaparty::dependencychecks::Invariant::{INVARIANT},
322 {CHECK_FUNCTION},
323 _spacetreeId
324 );
325"""
326
327 check_template_vertex_operation_local_particles_after_sweep = """
328 ::exaparty::dependencychecks::checkParticlesAssignedToVertexInTouchLastTime{STEP_TYPE}(
329 ::exaparty::dependencychecks::Invariant::{INVARIANT},
330 assignedParticles,
331 globaldata::{NAME}::DependencyChecks{STEP_TYPE}LastUpdated::{STEP_NAME},
332 _spacetreeId
333 );
334"""
335
336 for i, step in enumerate(steps):
337 d = {
338 "NAME": self.namenamename,
339 "STEP_TYPE": step_type_name,
340 "STEP_NAME": step.name,
341 "PEANO4_EVENT_TYPE": peano4_event_enum.get_accessor_name(),
342 "CHECK_FUNCTION": None,
343 "EVENT": None,
344 "INVARIANT": None,
345 }
346
347 step.prepare_traversal_kernel = (
348 """
349 toolbox::particles::assignmentchecks::startMeshSweep( "{}" );
350 """.format(
351 step.name
352 )
353 + step.prepare_traversal_kernel
354 )
355
356 if (
357 step.touch_vertex_first_time_dependency_policy == None
358 and AlgorithmStep.may_trigger_rerun(step.effect)
359 ):
360 d[
361 "INVARIANT"
362 ] = "TouchFirstUsage_MaskOutAfterwards_AllPreviousStepsUpdateAtLeastOnce_SweepMayRerun"
363 elif step.touch_vertex_first_time_dependency_policy == None:
364 d[
365 "INVARIANT"
366 ] = "TouchFirstUsage_MaskOutAfterwards_AllPreviousStepsUpdateAtLeastOnce"
367 else:
368 d["INVARIANT"] = step.touch_vertex_first_time_dependency_policy
369
370 # TOUCH VERTEX FIRST TIME
371 # -----------------------
372 d["EVENT"] = AlgorithmStep.get_event_name(
373 AlgorithmStep.PeanoEventUsedBySwift.TOUCH_VERTEX_FIRST_TIME
374 )
375 d[
376 "CHECK_FUNCTION"
377 ] = "::exaparty::kernels::localParticleCanBeUpdatedInVertexKernel<globaldata::{}>".format(
378 self.namenamename
379 )
380
381 if step.touch_vertex_first_time_kernel is None:
382 step.touch_vertex_first_time_kernel = ""
383
384 if i == 0:
385 # Only exception is first AlgorithmStep: There, first re-set
386 # all counters
387 step.touch_vertex_first_time_kernel = (
388 "\n ::exaparty::dependencychecks::clearDependencyChecks"
389 + step_type_name
390 + "(assignedParticles);\n"
391 + mark_template_vertex_operation.format(**d)
392 + step.touch_vertex_first_time_kernel
393 )
394 else:
395 step.touch_vertex_first_time_kernel = (
396 mark_template_vertex_operation.format(**d)
397 + step.touch_vertex_first_time_kernel
398 )
399
400 # Finally, add the dependency check *after* the actual function call
401 step.touch_vertex_first_time_kernel += (
402 check_template_vertex_operation.format(**d)
403 )
404
405 # CELL_KERNEL
406 # -----------
407 d["EVENT"] = AlgorithmStep.get_event_name(
408 AlgorithmStep.PeanoEventUsedBySwift.CELL_KERNEL
409 )
410 d[
411 "CHECK_FUNCTION"
412 ] = "::exaparty::kernels::localParticleCanBeUpdatedInCellKernelFromAnyOtherParticle<globaldata::{}>".format(
413 self.namenamename
414 )
415
416 if (
417 step.cell_kernel_dependency_policy == None
418 and AlgorithmStep.may_trigger_rerun(step.effect)
419 ):
420 d[
421 "INVARIANT"
422 ] = "TouchAtMostOnce_MaskOutOtherwise_AllPreviousStepsUpdateAtLeastOnce_SweepMayRerun"
423 elif step.cell_kernel_dependency_policy == None:
424 d[
425 "INVARIANT"
426 ] = "TouchAtMostOnce_MaskOutOtherwise_AllPreviousStepsUpdateAtLeastOnce"
427 else:
428 d["INVARIANT"] = step.cell_kernel_dependency_policy
429
430 if step.cell_kernel is None:
431 step.cell_kernel = ""
432
433 step.cell_kernel = (
434 mark_template_cell_operation.format(**d) + step.cell_kernel
435 )
436
437 # Now add the dependency check *after* the actual function call
438 step.cell_kernel += check_template_cell_operation.format(**d)
439
440 # TOUCH VERTEX LAST TIME
441 # ----------------------
442 d["EVENT"] = AlgorithmStep.get_event_name(
443 AlgorithmStep.PeanoEventUsedBySwift.TOUCH_VERTEX_LAST_TIME
444 )
445 d[
446 "CHECK_FUNCTION"
447 ] = "::exaparty::kernels::localParticleCanBeUpdatedInVertexKernel<globaldata::{}>".format(
448 self.namenamename
449 )
450
451 if (
452 step.touch_vertex_last_time_dependency_policy == None
453 and AlgorithmStep.may_trigger_rerun(step.effect)
454 ):
455 d[
456 "INVARIANT"
457 ] = "TouchFirstUsage_MaskOutAfterwards_AllPreviousStepsUpdateAtLeastOnce_SweepMayRerun"
458 elif step.touch_vertex_last_time_dependency_policy == None:
459 d[
460 "INVARIANT"
461 ] = "TouchFirstUsage_MaskOutAfterwards_AllPreviousStepsUpdateAtLeastOnce"
462 else:
463 d["INVARIANT"] = step.touch_vertex_last_time_dependency_policy
464
465 if step.touch_vertex_last_time_kernel is None:
466 step.touch_vertex_last_time_kernel = ""
467
468 step.touch_vertex_last_time_kernel = (
469 mark_template_vertex_operation.format(**d)
470 + step.touch_vertex_last_time_kernel
471 )
472
473 # Now add the dependency check *after* the actual function call
474 step.touch_vertex_last_time_kernel += (
475 check_template_vertex_operation.format(**d)
476 )
477
478 # touch vertex last time is special: Add additional dependency check
479 # which makes sure all activeParticles have been local at some point
480 # during this algorithm step
481 step.touch_vertex_last_time_kernel += (
482 check_template_vertex_operation_local_particles_after_sweep.format(**d)
483 )
484
485 return steps
486
488 """!
489
490 Add dependency (particle consistency) checks
491
492 This routine should be called in the subclass directly after you have
493 befilled self._algorithm_steps and self._initialisation_steps. It is
494 safe to add this stuff to your particle model all the time - the
495 actual checks kick in if and only if you run in non-release modes.
496 However, you might want to call the function only in these cases to
497 ensure that no (empty) C++ code is generated and the compiler can
498 optimise more aggressively.
499
500 ## Realisation
501
502 We first create Enumerations representing each
503 algorithm step defined for the Particle, as well as each sweep stage of
504 any grid traversal.
505
506 Then for each defined algorithm step, we add a call to the dependency
507 check functions for each sweep stage (touch vertex first time, cell
508 kernel, touch vertex last time...)
509
510 The check consists of
511 1) marking down which step in the full algorithm a particle has
512 completed in the current simulation step
513 2) verifying that each step and sweep stage before the one the particle
514 currently finds itself in has been completed.
515
516 This is done both for the main algorithm steps as well as for the
517 initialisation steps individually.
518
519
520 """
521
522 ifdefs = ["PeanoDebug > 0"]
523
524 # First add enum to particle class for each sweep stage. These are identical
525 # for both algorithm steps and initialisation steps.
526 peano4_event_enum = dastgen2.attributes.Enumeration(
527 self.DependencyChecks_Attribute_Prefix + "PeanoEventUsedBySwift",
528 [
529 AlgorithmStep.get_event_name(s)
531 ],
532 ifdefs=ifdefs,
533 )
534 self.data.add_attribute(peano4_event_enum)
535
536 # Add debug dependency checks to the actual algorithm steps:
537 steplist = self.algorithm_steps()
538 step_type_name = "AlgorithmStep"
539 modified_steps = self._dependency_checks_modify_steps(
540 steplist, step_type_name, peano4_event_enum
541 )
542 self._algorithm_steps = modified_steps
543
544 # Same but for initialisation steps
545 steplist = self.initialisation_steps()
546 step_type_name = "InitStep"
547 modified_steps = self._dependency_checks_modify_steps(
548 steplist, step_type_name, peano4_event_enum
549 )
550 self._initialisation_steps = modified_steps
Wrapper around C++ enumerations which is not a datatype supported natively by MPI.
Definition Enumeration.py:6
Enumeration of different Peano events during a grid traversal into which ExaParty plugs in.
Every generated particle belongs to a species.
Definition Particle.py:33
get_method_declarations(self, full_qualified_name)
Definition Particle.py:57
Base class for any particle in the project.
Definition Particle.py:10
__init__(self, name, particles_per_cell, min_h, max_h)
Initialise the particle.
Definition Particle.py:80
algorithm_steps(self)
Return sequence of algorithm steps that have to be called per time step.
Definition Particle.py:132
initialisation_steps(self)
Return sequence of algorithm steps that have to be performed throughout initialisation.
Definition Particle.py:142
_dependency_checks_modify_steps(self, steplist, step_type_name, peano4_event_enum)
Add dependency checks as well as mesh consistency checks to the algorithm steps.
Definition Particle.py:193
_add_dependency_checks(self)
Add dependency (particle consistency) checks.
Definition Particle.py:487