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 _numberOfParticleReassignmentsOnCurrentLevel = 0;
221 _numberOfLiftsIntoSieveSet = 0;
222 _numberOfDropsFromSieveSet = 0;
223 _numberOfDropsIntoHorizontalTreeDecomposition = 0;
227 _Template_EndTraversal = jinja2.Template(
229 vertexdata::{{PARTICLES_CONTAINER}}::updateLiftDropStatistics(
232 _numberOfParticleReassignmentsOnCurrentLevel,
233 _numberOfLiftsIntoSieveSet,
234 _numberOfDropsFromSieveSet,
235 _numberOfDropsIntoHorizontalTreeDecomposition
240 _Template_ValidateParticleLevelAssociation = jinja2.Template(
242 for (auto p: fineGridVertex{{PARTICLES_CONTAINER}} ) {
244 marker.isContainedInAdjacentCells( p->getX(), 0.5*(1.0+SortingTolerance) ),
255 Drop fitting particles from coarser mesh level into fine grid mesh
257 Happens when we touch a vertex for the first time. The only thing this routine
258 does is to drop particles within the tree, i.e. move them to finer levels.
259 No parallelisation is taken into account.
261 We have to check carefully if all coarse grid particles are local. In the
264 @image html UpdateParticleGridAssociation_LiftDrop.png
266 the coarse vertices could be on another rank if the tree is cut vertically.
267 If could also be that the green vertex is actually from an adjacent domain
268 and hence does not hold any reasonable information.
270 This routine is not used by all action sets. The bucket sort for example
271 does not need it. The lift drop sets in return however all use this
272 particular action set.
275 _Template_Drop = jinja2.Template(
277 for (int i=0; i<TwoPowerD; i++) {
278 if ( marker.isParentVertexLocal(i) ) {
279 // Define inside for loop, as sieve will define it as well
280 vertexdata::{{PARTICLES_CONTAINER}}::iterator p = coarseGridVertices{{PARTICLES_CONTAINER}}(i).begin();
281 while ( p!=coarseGridVertices{{PARTICLES_CONTAINER}}(i).end() ) {
284 int partID = (*p)->getPartid();
290 ::toolbox::particles::dropParticle(**p,marker)
292 marker.isParentOfSubtree()
294 toolbox::particles::particleWillBeDroppedFurther(**p, marker)
296 _numberOfDropsIntoHorizontalTreeDecomposition++;
297 fineGridVertex{{PARTICLES_CONTAINER}}.particlesCanNotBeDroppedLocallySoRollOverForNextMeshSweep(*p);
299 else if ( ::toolbox::particles::dropParticle(**p,marker) ) {
302 (*p)->setCellH(marker.h());
303 toolbox::particles::assignmentchecks::detachParticleFromVertex(
307 (*p)->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
308 peano4::datamanagement::reconstructXOfParentVertex(marker, i),
311 "AbstractUpdateParticleGridAssociation::_Template_Drop"
313 toolbox::particles::assignmentchecks::assignParticleToVertex(
317 (*p)->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
321 "AbstractUpdateParticleGridAssociation::_Template_Drop"
324 fineGridVertex{{PARTICLES_CONTAINER}}.isValid(),
325 fineGridVertex{{PARTICLES_CONTAINER}}.toString(),
326 coarseGridVertices{{PARTICLES_CONTAINER}}(i).toString(),
330 coarseGridVertices{{PARTICLES_CONTAINER}}(i).isValid(),
331 fineGridVertex{{PARTICLES_CONTAINER}}.toString(),
332 coarseGridVertices{{PARTICLES_CONTAINER}}(i).toString(),
335 logDebug( "touchVertexFirstTime(...)", "drop particle " << (*p)->toString() << " from " << coarseGridVertices{{PARTICLES_CONTAINER}}(i).toString() << " into " << marker.toString() );
336 p = fineGridVertex{{PARTICLES_CONTAINER}}.grabParticle( p, coarseGridVertices{{PARTICLES_CONTAINER}}(i) );
339 } // while loop over coarse particles
340 } // if coarse vertex local
347 Lift all particles to next coarser level
349 Explicit lift of particles to a coarser level. Usually, lifts are
350 realised within touchCellLastTime aka the template
351 __Template_ReassignAndLiftWithinCell. If a vertex is however destroyed or
352 is hanging, i.e. not persistent in-between two mesh traversals,
353 then we have to lift the particles explicitly.
355 If the parent cell is local, we know that all @f$ 2^d @f$ parent
356 vertices are local. However, we have to know if the real parent
360 _Template_LiftAllParticles = jinja2.Template(
362 std::bitset<Dimensions> target;
363 for (int d=0; d<Dimensions; d++) {
364 target[d] = marker.getRelativePositionWithinFatherCell()(d)>=2;
367 if ( marker.isParentVertexLocal(target.to_ulong()) ) {
368 assertion2( coarseGridVertices{{PARTICLES_CONTAINER}}( target.to_ulong() ).isValid(), coarseGridVertices{{PARTICLES_CONTAINER}}( target.to_ulong() ).toString(), fineGridVertex{{PARTICLES_CONTAINER}}.toString() );
369 assertion2( fineGridVertex{{PARTICLES_CONTAINER}}.isValid(), coarseGridVertices{{PARTICLES_CONTAINER}}( target.to_ulong() ).toString(), fineGridVertex{{PARTICLES_CONTAINER}}.toString() );
370 _numberOfLifts+=fineGridVertex{{PARTICLES_CONTAINER}}.size();
371 for (auto& p: fineGridVertex{{PARTICLES_CONTAINER}}) {
372 p->setCellH(3.0 * marker.h());
374 for (auto* p: fineGridVertex{{PARTICLES_CONTAINER}}) {
377 int partID = p->getPartid();
382 toolbox::particles::assignmentchecks::detachParticleFromVertex(
386 p->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
390 "AbstractUpdateParticleGridAssociation::_Template_LiftAllParticles (parent vertex is local)"
392 toolbox::particles::assignmentchecks::assignParticleToVertex(
396 p->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
399 tarch::la::multiplyComponents(
400 tarch::la::convertScalar<double>(marker.getRelativePositionWithinFatherCell()),
404 tarch::la::multiplyComponents(
405 tarch::la::Vector<Dimensions,double>(target),
410 "AbstractUpdateParticleGridAssociation::_Template_LiftAllParticles (parent vertex is local)"
413 coarseGridVertices{{PARTICLES_CONTAINER}}( target.to_ulong() ).grabParticles( fineGridVertex{{PARTICLES_CONTAINER}} );
416 _numberOfLiftsIntoSieveSet += fineGridVertex{{PARTICLES_CONTAINER}}.size();
417 for (auto* p: fineGridVertex{{PARTICLES_CONTAINER}}) {
420 int partID = p->getPartid();
425 toolbox::particles::assignmentchecks::detachParticleFromVertex(
429 p->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
433 "AbstractUpdateParticleGridAssociation::_Template_LiftAllParticles"
435 toolbox::particles::assignmentchecks::assignParticleToSieveSet(
439 p->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
442 "AbstractUpdateParticleGridAssociation::_Template_LiftAllParticles"
445 fineGridVertex{{PARTICLES_CONTAINER}}.particlesCanNotBeLiftedLocally();
446 assertion1( fineGridVertex{{PARTICLES_CONTAINER}}.isValid(), fineGridVertex{{PARTICLES_CONTAINER}}.toString() );
452 return " return std::vector< peano4::grid::GridControlEvent >();\n"
460 Inject all the default includes
462 @see get_body_of_prepareTraversal().
465 result = jinja2.Template(
467#include "tarch/multicore/Lock.h"
469#include "toolbox/particles/MultiscaleTransitions.h"
470#include "toolbox/particles/assignmentchecks/TracingAPI.h"
472#include "vertexdata/{{PARTICLES_CONTAINER}}.h"
473#include "globaldata/{{PARTICLE}}.h"
475#include "Constants.h"
478 return result.render(**self.
d)
485 _spacetreeId = treeNumber;
491 return jinja2.Template(
494 int _numberOfParticleReassignmentsOnCurrentLevel;
497 int _numberOfLiftsIntoSieveSet;
498 int _numberOfDropsFromSieveSet;
499 int _numberOfDropsIntoHorizontalTreeDecomposition;
506 Create prepareTraversal function
508 Every particle resorting has to invoke
510 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
511 vertexdata::{{PARTICLE}}Set::finishedTraversal(DomainOffset, DomainSize, PeriodicBC);
512 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
514 after each and every time step to consolidate the global data exchange
515 containers. This routine has to know the domain size and whether there
516 are periodic boundary conditions, as it also is responsible for the
517 mirroring/translation along periodic BCs. Therefore, we have to include
518 the global constants in get_includes().
521 return jinja2.Template(
523 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.