12 Associate particles to spacetree vertices
14 Use one of the subclasses which realise various sorting algorithms.
15 All subclasses realise the particle in dual tree storage scheme.
16 Please read through @ref page_toolbox_particles_mesh_storage and
17 @ref page_toolbox_particles_mesh_consistency besides the current
18 class documentation. If you use a particle memory pool, i.e. if you
19 don't hold the particles scattered all over the place, then it is also
20 relevant to read through the @ref toolbox_particles_memorypool "implications for any particle sorting".
23 If you add the action set
24 peano4.toolbox.particles.PlotParticlesInVTKFormat to your algorithm
25 step, the resulting file will contain all associativity information.
26 You can plot to which vertex a particle belongs to.
29 Here's a hand-written cartoon to explain the particle storage:
31 @image html dual-tree.png
34 A tree with three levels.
35 Particles with a large search radius (red) are held on rather coarse levels.
36 Particles with very small search radius are sorted (dropped) into the fine grid
38 Each particle is always associated to its closest vertex (dotted line).
39 It is the mesh which holds (owns) the vertices.
40 The toolset's predefined action set can automatically maintain/update these
41 associations after each grid sweep.
42 It also moves particles up and down the grid hierarchy if the particles' search
43 radius changes or the grid changes.
48 To move a particle, all you have to do is to alter its position:
50 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
51 myParticle->setX( {17.3, 8.2} );
52 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
54 Obviously, any change of a particle position could mean that the particle is no
55 longer associated to its nearest vertex.
59 Peano automatically maintains these relations if you add the action set
60 peano4.toolbox.particles.UpdateParticleGridAssociation to
61 your algorithmic steps.
62 As the convention relies on a smaller-equals sign, adaptive meshes are
63 automatically supported.
64 If particles travel through your domain and the mesh is not very fine, they
65 end up in the tree leaves.
66 If particles have a large search radius, they are stored in rather coarse
70 If you change the search radius or the position of a particle, the particle
71 will stay within its current cell/vertex within the tree.
72 In the next grid sweep, it will however we held by the ``correct'' vertex,
73 i.e. its grid-vertex association will be updated:
74 The action set UpdateParticleGridAssociation updates the
75 particle-tree association, i.e.~sorts the particles, on-the-fly between two mesh
79 While you do not have to worry about how this sorting is realised, it is
80 important to study the action set's documentation (just open the Python code
81 documentation of subclasses) and read when data is consistent.
82 Here is the general invariants that hold for all of them:
84 - The very moment you alter a particle's position, you cannot be sure that
85 it is still associated with the nearest vertex. Consequently, neighbourhood
86 searchers might yield invalid results.
87 - In the grid sweep after the position change, the particle will be
88 associated with the correct vertex (unless you change its position once again
92 The action set UpdateParticleGridAssociation relies on some global
93 data. Therefore, you have to call finishTraversal() on the used
94 particle set in your main routine on each rank after all rank-local traversals have termined.
97 ## Domain decomposition
99 You should combine this action set (or any subclass in fact) with the action
100 set UpdateParallelState. Actually, it is sufficient if only the second grid
101 sweep - remember that association updates always have to be added to two grid
102 sweeps in a row - is added an additional action set that updates the particles'
108 The update association should precede any other action set in your step. The
109 descend_invocation_order value thus should be very small. As priorities are inverted when we
110 walk from fine to coarse resolutions, this means that all dropping into the mesh
111 (in the second grid sweep) happens before anybody else touches the particles,
112 while the extraction of particles or moves from fine to coarse are epilogues to
113 any operation in the primary mesh sweep.
118 All update flavours need 2+ mesh sweeps.
119 In the first sweep, they check if a particle sits with the right vertex. If
120 not, is it either lifted locally or added to a global sort set. In the
121 second sweep, we then insert particles from global sort sets into the
122 domain or we drop the particles down further in the mesh hierarchy.
124 Horizontal tree cuts, i.e. situations where the parent cell of a cell
125 belongs to another tree, are challenging: We cannot know prior to the
126 drop if we would drop into such a cut. If we do however, we have to take
127 the respective particle, send it to the neighbour, pick it up in the next
128 mesh traversal and continue dropping. For a tree with depth L, we have up
129 to L-1 drop iterations.
132 ## Grid resorting followed by particle-particle interactions
134 If you have particle-particle interactions while particles may drop within
135 the mesh (due to dynamic AMR or particle movements), you have to be extra
136 careful. There are two major issues to consider, which both can be
137 illustrated with the sketch below.
139 @image html SievingAlongAMRBoundaries.png
142 ### Particles along AMR boundaries
144 In the sketch, the green particle would logically belong into the finer mesh
145 level. However, we should not move it up and down: The routine
146 touchVertexFirstTime() should grab particles from the coarser level and drop
147 them, but the routine createHangingVertex() should not. If we dropped them,
148 we would have to lift them immediately afterwards again, as hanging vertices
149 are not persistent in Peano.
151 This would yield a lot of memory movements and also potentially destroy
152 any well-aligned and ordered memory arrangement which is poisonous for
153 vectorisation. Therefore, we leave particles on coarser levels. The green
154 particles is not dropped.
156 Let the green particle affect the blue one. As a consequence of the
157 constraint above, the green particle has to be updated with contributions
158 from blue. We need a proper multiscale interaction.
161 ### Multiscale interaction
163 Let green and blue interact. If blue resides on the coarser level and is
164 dropped in the very same mesh traversal, we have to be extra careful:
166 - The blue and the green particle are both cleared on the coarse level.
167 Blue gets contributions from green within the coarse cell, and green gets
168 contributions from blue.
170 - Blue is cleared due to the drop.
171 - Blue is now getting contributions from the coarser level, i.e. green. As
172 this is a multiscale relation, green is also added (fine grid)
173 contributions from blue.
175 Consequently, the particles on the coarser level gets two times the
176 contributions from the particle which is dropped in this very mesh
177 traversal. To avoid this, we either veto any particle-particle interaction
178 in a mesh traversal which also drops, or we find a mechanism to remove the
179 contributions to coarse meshes whenever we drop a particle. I don't do the
180 latter usually, as it is more complicated.
185 It is the sieving where the various subclasses differ. Sieving has to be
186 used whenever particles move through horizontal cuts. Some subclasses use
187 it to manage all particle transitions (bucket sort), while others only
188 use it for these special cuts plus tunneling particles. Consult the
189 discussion at @ref page_toolbox_particles_mesh_consistency for further
192 The sieve set is managed via a static attribute of the particle: Each
193 particle has an instance of toolbox::particles::SieveParticles tied to
194 its class. From hereon, we have to options:
196 - Let each action set clone the particle set and then hand out sieved
197 particles from there.
198 - Work against one global cloned version.
200 Which stategy is the right one depends upon the chosen sieving strategy.
204 DefaultDescendInvocationOrder = -65536
207 super(AbstractUpdateParticleGridAssociation, self).
__init__(
208 descend_invocation_order=-65536, parallel=
False
212 self.
d[
"PARTICLE"] = particle_set.particle_model.name
213 self.
d[
"PARTICLES_CONTAINER"] = particle_set.name
214 self.
d[
"GUARD"] = guard
216 _Template_BeginTraversal = jinja2.Template(
218 // Start template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
220 _numberOfParticleReassignmentsOnCurrentLevel = 0;
223 _numberOfLiftsIntoSieveSet = 0;
224 _numberOfDropsFromSieveSet = 0;
225 _numberOfDropsIntoHorizontalTreeDecomposition = 0;
227 // End template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
231 _Template_EndTraversal = jinja2.Template(
233 // Start template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
235 vertexdata::{{PARTICLES_CONTAINER}}::updateLiftDropStatistics(
238 _numberOfParticleReassignmentsOnCurrentLevel,
239 _numberOfLiftsIntoSieveSet,
240 _numberOfDropsFromSieveSet,
241 _numberOfDropsIntoHorizontalTreeDecomposition
244 // End template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
248 _Template_ValidateParticleLevelAssociation = jinja2.Template(
250 // Start template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
252 for (auto p: fineGridVertex{{PARTICLES_CONTAINER}} ) {
254 marker.isContainedInAdjacentCells( p->getX(), 0.5*(1.0+SortingTolerance) ),
261 // End template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
267 Drop fitting particles from coarser mesh level into fine grid mesh
269 Happens when we touch a vertex for the first time. The only thing this routine
270 does is to drop particles within the tree, i.e. move them to finer levels.
271 No parallelisation is taken into account.
273 We have to check carefully if all coarse grid particles are local. In the
276 @image html UpdateParticleGridAssociation_LiftDrop.png
278 the coarse vertices could be on another rank if the tree is cut vertically.
279 If could also be that the green vertex is actually from an adjacent domain
280 and hence does not hold any reasonable information.
282 This routine is not used by all action sets. The bucket sort for example
283 does not need it. The lift drop sets in return however all use this
284 particular action set.
287 _Template_Drop = jinja2.Template(
290 // Start template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
292 for (int i=0; i<TwoPowerD; i++) {
293 if ( marker.isParentVertexLocal(i) ) {
294 // Define inside for loop, as sieve will define it as well
295 vertexdata::{{PARTICLES_CONTAINER}}::iterator p = coarseGridVertices{{PARTICLES_CONTAINER}}(i).begin();
296 while ( p!=coarseGridVertices{{PARTICLES_CONTAINER}}(i).end() ) {
299 int partID = (*p)->getPartid();
305 ::toolbox::particles::dropParticle(**p,marker)
307 marker.isParentOfSubtree()
309 toolbox::particles::particleWillBeDroppedFurther(**p, marker)
311 _numberOfDropsIntoHorizontalTreeDecomposition++;
312 fineGridVertex{{PARTICLES_CONTAINER}}.particlesCanNotBeDroppedLocallySoRollOverForNextMeshSweep(*p);
314 else if ( ::toolbox::particles::dropParticle(**p,marker) ) {
317 (*p)->setCellH(marker.h());
318 toolbox::particles::updateContainedInAdjacentCellProperty(marker,**p);
320 toolbox::particles::assignmentchecks::detachParticleFromVertex(
324 (*p)->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
325 peano4::datamanagement::reconstructXOfParentVertex(marker, i),
328 "AbstractUpdateParticleGridAssociation::_Template_Drop"
330 toolbox::particles::assignmentchecks::assignParticleToVertex(
334 (*p)->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
338 "AbstractUpdateParticleGridAssociation::_Template_Drop",
339 peano4::datamanagement::reconstructXOfParentVertex(marker, i),
343 fineGridVertex{{PARTICLES_CONTAINER}}.isValid(),
344 fineGridVertex{{PARTICLES_CONTAINER}}.toString(),
345 coarseGridVertices{{PARTICLES_CONTAINER}}(i).toString(),
349 coarseGridVertices{{PARTICLES_CONTAINER}}(i).isValid(),
350 fineGridVertex{{PARTICLES_CONTAINER}}.toString(),
351 coarseGridVertices{{PARTICLES_CONTAINER}}(i).toString(),
354 logDebug( "touchVertexFirstTime(...)", "drop particle " << (*p)->toString() << " from " << coarseGridVertices{{PARTICLES_CONTAINER}}(i).toString() << " into " << marker.toString() );
355 p = fineGridVertex{{PARTICLES_CONTAINER}}.grabParticle( p, coarseGridVertices{{PARTICLES_CONTAINER}}(i) );
358 } // while loop over coarse particles
359 } // if coarse vertex local
362 // End template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
368 Lift all particles to next coarser level
370 Explicit lift of particles to a coarser level. Usually, lifts are
371 realised within touchCellLastTime aka the template
372 __Template_ReassignAndLiftWithinCell. If a vertex is however destroyed or
373 is hanging, i.e. not persistent in-between two mesh traversals,
374 then we have to lift the particles explicitly.
376 If the parent cell is local, we know that all @f$ 2^d @f$ parent
377 vertices are local. However, we have to know if the real parent
380 As we assume that this routine is called for touchVertexLastTime(), we
381 do not have to invoke updateContainedInAdjacentCellProperty(). The
382 relative association to vertices will still be correct at this point.
385 _Template_LiftAllParticles = jinja2.Template(
387 // Start template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
389 std::bitset<Dimensions> target;
390 for (int d=0; d<Dimensions; d++) {
391 target[d] = marker.getRelativePositionWithinFatherCell()(d)>=2;
394 if ( marker.isParentVertexLocal(target.to_ulong()) ) {
395 assertion2( coarseGridVertices{{PARTICLES_CONTAINER}}( target.to_ulong() ).isValid(), coarseGridVertices{{PARTICLES_CONTAINER}}( target.to_ulong() ).toString(), fineGridVertex{{PARTICLES_CONTAINER}}.toString() );
396 assertion2( fineGridVertex{{PARTICLES_CONTAINER}}.isValid(), coarseGridVertices{{PARTICLES_CONTAINER}}( target.to_ulong() ).toString(), fineGridVertex{{PARTICLES_CONTAINER}}.toString() );
397 _numberOfLifts+=fineGridVertex{{PARTICLES_CONTAINER}}.size();
398 for (auto* p: fineGridVertex{{PARTICLES_CONTAINER}}) {
401 int partID = p->getPartid();
406 tarch::la::Vector< Dimensions, double > coarseGridVertexX =
409 tarch::la::multiplyComponents(
410 tarch::la::convertScalar<double>(marker.getRelativePositionWithinFatherCell()),
414 tarch::la::multiplyComponents(
415 tarch::la::Vector<Dimensions,double>(target),
419 p->setCellH(3.0 * marker.h());
420 toolbox::particles::updateContainedInAdjacentCellProperty(coarseGridVertexX,marker.h()*3.0,*p);
422 toolbox::particles::assignmentchecks::detachParticleFromVertex(
426 p->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
430 "AbstractUpdateParticleGridAssociation::_Template_LiftAllParticles (parent vertex is local)"
432 toolbox::particles::assignmentchecks::assignParticleToVertex(
436 p->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
440 "AbstractUpdateParticleGridAssociation::_Template_LiftAllParticles (parent vertex is local)",
445 coarseGridVertices{{PARTICLES_CONTAINER}}( target.to_ulong() ).grabParticles( fineGridVertex{{PARTICLES_CONTAINER}} );
448 _numberOfLiftsIntoSieveSet += fineGridVertex{{PARTICLES_CONTAINER}}.size();
449 for (auto* p: fineGridVertex{{PARTICLES_CONTAINER}}) {
452 int partID = p->getPartid();
457 toolbox::particles::assignmentchecks::detachParticleFromVertex(
461 p->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
465 "AbstractUpdateParticleGridAssociation::_Template_LiftAllParticles"
467 toolbox::particles::assignmentchecks::assignParticleToSieveSet(
471 p->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
475 "AbstractUpdateParticleGridAssociation::_Template_LiftAllParticles"
478 fineGridVertex{{PARTICLES_CONTAINER}}.particlesCanNotBeLiftedLocally();
479 assertion1( fineGridVertex{{PARTICLES_CONTAINER}}.isValid(), fineGridVertex{{PARTICLES_CONTAINER}}.toString() );
482 // End template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
487 return " return std::vector< peano4::grid::GridControlEvent >();\n"
495 Inject all the default includes
497 @see get_body_of_prepareTraversal().
500 result = jinja2.Template(
502#include "tarch/multicore/Lock.h"
504#include "toolbox/particles/MultiscaleTransitions.h"
505#include "toolbox/particles/assignmentchecks/TracingAPI.h"
507#include "vertexdata/{{PARTICLES_CONTAINER}}.h"
508#include "globaldata/{{PARTICLE}}.h"
510#include "Constants.h"
513 return result.render(**self.
d)
520 _spacetreeId = treeNumber;
526 return jinja2.Template(
529 int _numberOfParticleReassignmentsOnCurrentLevel;
532 int _numberOfLiftsIntoSieveSet;
533 int _numberOfDropsFromSieveSet;
534 int _numberOfDropsIntoHorizontalTreeDecomposition;
541 Create prepareTraversal function
543 Every particle resorting has to invoke
545 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
546 vertexdata::{{PARTICLE}}Set::finishedTraversal(DomainOffset, DomainSize, PeriodicBC);
547 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
549 after each and every time step to consolidate the global data exchange
550 containers. This routine has to know the domain size and whether there
551 are periodic boundary conditions, as it also is responsible for the
552 mirroring/translation along periodic BCs. Therefore, we have to include
553 the global constants in get_includes().
556 return jinja2.Template(
558 vertexdata::{{PARTICLES_CONTAINER}}::finishedTraversal(DomainOffset, DomainSize, PeriodicBC);
Action set (reactions to events)
Associate particles to spacetree vertices.
__init__(self, particle_set, guard="true")
get_body_of_getGridControlEvents(self)
user_should_modify_template(self)
Is the user allowed to modify the output.
get_constructor_body(self)
Define a tailored constructor body.
get_body_of_prepareTraversal(self)
Create prepareTraversal function.
get_attributes(self)
Return attributes as copied and pasted into the generated class.
get_includes(self)
Inject all the default includes.