Peano
Loading...
Searching...
No Matches
AbstractUpdateParticleGridAssociation.py
Go to the documentation of this file.
1# This file is part of the Peano project. For conditions of distribution and
2# use, please see the copyright notice at www.peano-framework.org
3from peano4.solversteps.ActionSet import ActionSet
4
5
6import jinja2
7
8
10 """!
11
12 Associate particles to spacetree vertices
13
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".
21
22
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.
27
28
29 Here's a hand-written cartoon to explain the particle storage:
30
31 @image html dual-tree.png
32
33
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
37 levels.
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.
44
45
46 ## Move particles
47
48 To move a particle, all you have to do is to alter its position:
49
50 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
51 myParticle->setX( {17.3, 8.2} );
52 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
53
54 Obviously, any change of a particle position could mean that the particle is no
55 longer associated to its nearest vertex.
56 We have to sort them.
57
58
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
67 levels of the tree.
68
69
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
76 traversals.
77
78
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:
83
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
89 obviously).
90
91
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.
95
96
97 ## Domain decomposition
98
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'
103 parallel state.
104
105
106 ## Priorities
107
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.
114
115
116 ## Number of sweeps
117
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.
123
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.
130
131
132 ## Grid resorting followed by particle-particle interactions
133
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.
138
139 @image html SievingAlongAMRBoundaries.png
140
141
142 ### Particles along AMR boundaries
143
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.
150
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.
155
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.
159
160
161 ### Multiscale interaction
162
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:
165
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.
169 - Now we drop 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.
174
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.
181
182
183 ## Sieving
184
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
190 contextualisation.
191
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:
195
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.
199
200 Which stategy is the right one depends upon the chosen sieving strategy.
201
202 """
203
204 DefaultDescendInvocationOrder = -65536
205
206 def __init__(self, particle_set, guard="true"):
207 super(AbstractUpdateParticleGridAssociation, self).__init__(
208 descend_invocation_order=-65536, parallel=False
209 )
210 self._particle_set = particle_set
211 self.d = {}
212 self.d["PARTICLE"] = particle_set.particle_model.name
213 self.d["PARTICLES_CONTAINER"] = particle_set.name
214 self.d["GUARD"] = guard
215
216 _Template_BeginTraversal = jinja2.Template(
217 """
218 _numberOfParticleReassignmentsOnCurrentLevel = 0;
219 _numberOfLifts = 0;
220 _numberOfDrops = 0;
221 _numberOfLiftsIntoSieveSet = 0;
222 _numberOfDropsFromSieveSet = 0;
223 _numberOfDropsIntoHorizontalTreeDecomposition = 0;
224"""
225 )
226
227 _Template_EndTraversal = jinja2.Template(
228 """
229 vertexdata::{{PARTICLES_CONTAINER}}::updateLiftDropStatistics(
230 _numberOfLifts,
231 _numberOfDrops,
232 _numberOfParticleReassignmentsOnCurrentLevel,
233 _numberOfLiftsIntoSieveSet,
234 _numberOfDropsFromSieveSet,
235 _numberOfDropsIntoHorizontalTreeDecomposition
236 );
237"""
238 )
239
240 _Template_ValidateParticleLevelAssociation = jinja2.Template(
241 """
242 for (auto p: fineGridVertex{{PARTICLES_CONTAINER}} ) {
243 assertion3(
244 marker.isContainedInAdjacentCells( p->getX(), 0.5*(1.0+SortingTolerance) ),
245 marker.x(),
246 marker.toString(),
247 p->toString()
248 );
249 }
250"""
251 )
252
253 """!
254
255 Drop fitting particles from coarser mesh level into fine grid mesh
256
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.
260
261 We have to check carefully if all coarse grid particles are local. In the
262 example below
263
264 @image html UpdateParticleGridAssociation_LiftDrop.png
265
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.
269
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.
273
274 """
275 _Template_Drop = jinja2.Template(
276 """
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() ) {
282
283 #if PeanoDebug > 0
284 int partID = (*p)->getPartid();
285 #else
286 int partID = -1;
287 #endif
288
289 if (
290 ::toolbox::particles::dropParticle(**p,marker)
291 and
292 marker.isParentOfSubtree()
293 and
294 toolbox::particles::particleWillBeDroppedFurther(**p, marker)
295 ) {
296 _numberOfDropsIntoHorizontalTreeDecomposition++;
297 fineGridVertex{{PARTICLES_CONTAINER}}.particlesCanNotBeDroppedLocallySoRollOverForNextMeshSweep(*p);
298 }
299 else if ( ::toolbox::particles::dropParticle(**p,marker) ) {
300 _numberOfDrops++;
301
302 (*p)->setCellH(marker.h());
303 toolbox::particles::assignmentchecks::detachParticleFromVertex(
304 "{{PARTICLE}}",
305 (*p)->getX(),
306 partID,
307 (*p)->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
308 peano4::datamanagement::reconstructXOfParentVertex(marker, i),
309 marker.h()*3.0,
310 _spacetreeId,
311 "AbstractUpdateParticleGridAssociation::_Template_Drop"
312 );
313 toolbox::particles::assignmentchecks::assignParticleToVertex(
314 "{{PARTICLE}}",
315 (*p)->getX(),
316 partID,
317 (*p)->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
318 marker.x(),
319 marker.h(),
320 _spacetreeId,
321 "AbstractUpdateParticleGridAssociation::_Template_Drop"
322 );
323 assertion3(
324 fineGridVertex{{PARTICLES_CONTAINER}}.isValid(),
325 fineGridVertex{{PARTICLES_CONTAINER}}.toString(),
326 coarseGridVertices{{PARTICLES_CONTAINER}}(i).toString(),
327 _spacetreeId
328 );
329 assertion3(
330 coarseGridVertices{{PARTICLES_CONTAINER}}(i).isValid(),
331 fineGridVertex{{PARTICLES_CONTAINER}}.toString(),
332 coarseGridVertices{{PARTICLES_CONTAINER}}(i).toString(),
333 _spacetreeId
334 );
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) );
337 }
338 else p++;
339 } // while loop over coarse particles
340 } // if coarse vertex local
341 }
342"""
343 )
344
345 """!
346
347 Lift all particles to next coarser level
348
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.
354
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
357 is local, too.
358
359 """
360 _Template_LiftAllParticles = jinja2.Template(
361 """
362 std::bitset<Dimensions> target;
363 for (int d=0; d<Dimensions; d++) {
364 target[d] = marker.getRelativePositionWithinFatherCell()(d)>=2;
365 }
366
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());
373 }
374 for (auto* p: fineGridVertex{{PARTICLES_CONTAINER}}) {
375
376 #if PeanoDebug > 0
377 int partID = p->getPartid();
378 #else
379 int partID = -1;
380 #endif
381
382 toolbox::particles::assignmentchecks::detachParticleFromVertex(
383 "{{PARTICLE}}",
384 p->getX(),
385 partID,
386 p->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
387 marker.x(),
388 marker.h(),
389 _spacetreeId,
390 "AbstractUpdateParticleGridAssociation::_Template_LiftAllParticles (parent vertex is local)"
391 );
392 toolbox::particles::assignmentchecks::assignParticleToVertex(
393 "{{PARTICLE}}",
394 p->getX(),
395 partID,
396 p->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
397 marker.x()
398 -
399 tarch::la::multiplyComponents(
400 tarch::la::convertScalar<double>(marker.getRelativePositionWithinFatherCell()),
401 marker.h()
402 )
403 +
404 tarch::la::multiplyComponents(
405 tarch::la::Vector<Dimensions,double>(target),
406 3.0*marker.h()
407 ),
408 3.0*marker.h(),
409 _spacetreeId,
410 "AbstractUpdateParticleGridAssociation::_Template_LiftAllParticles (parent vertex is local)"
411 );
412 }
413 coarseGridVertices{{PARTICLES_CONTAINER}}( target.to_ulong() ).grabParticles( fineGridVertex{{PARTICLES_CONTAINER}} );
414 }
415 else {
416 _numberOfLiftsIntoSieveSet += fineGridVertex{{PARTICLES_CONTAINER}}.size();
417 for (auto* p: fineGridVertex{{PARTICLES_CONTAINER}}) {
418
419 #if PeanoDebug > 0
420 int partID = p->getPartid();
421 #else
422 int partID = -1;
423 #endif
424
425 toolbox::particles::assignmentchecks::detachParticleFromVertex(
426 "{{PARTICLE}}",
427 p->getX(),
428 partID,
429 p->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
430 marker.x(),
431 marker.h(),
432 _spacetreeId,
433 "AbstractUpdateParticleGridAssociation::_Template_LiftAllParticles"
434 );
435 toolbox::particles::assignmentchecks::assignParticleToSieveSet(
436 "{{PARTICLE}}",
437 p->getX(),
438 partID,
439 p->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
440 marker.h(),
441 _spacetreeId,
442 "AbstractUpdateParticleGridAssociation::_Template_LiftAllParticles"
443 );
444 }
445 fineGridVertex{{PARTICLES_CONTAINER}}.particlesCanNotBeLiftedLocally();
446 assertion1( fineGridVertex{{PARTICLES_CONTAINER}}.isValid(), fineGridVertex{{PARTICLES_CONTAINER}}.toString() );
447 }
448"""
449 )
450
452 return " return std::vector< peano4::grid::GridControlEvent >();\n"
453
455 return False
456
457 def get_includes(self):
458 """!
459
460 Inject all the default includes
461
462 @see get_body_of_prepareTraversal().
463
464 """
465 result = jinja2.Template(
466 """
467#include "tarch/multicore/Lock.h"
468
469#include "toolbox/particles/MultiscaleTransitions.h"
470#include "toolbox/particles/assignmentchecks/TracingAPI.h"
471
472#include "vertexdata/{{PARTICLES_CONTAINER}}.h"
473#include "globaldata/{{PARTICLE}}.h"
474
475#include "Constants.h"
476"""
477 )
478 return result.render(**self.d)
479
481 return (
482 super(AbstractUpdateParticleGridAssociation, self).get_constructor_body()
483 + jinja2.Template(
484 """
485 _spacetreeId = treeNumber;
486 """
487 ).render(**self.d)
488 )
489
490 def get_attributes(self):
491 return jinja2.Template(
492 """
493 int _spacetreeId;
494 int _numberOfParticleReassignmentsOnCurrentLevel;
495 int _numberOfLifts;
496 int _numberOfDrops;
497 int _numberOfLiftsIntoSieveSet;
498 int _numberOfDropsFromSieveSet;
499 int _numberOfDropsIntoHorizontalTreeDecomposition;
500"""
501 ).render(**self.d)
502
504 """!
505
506 Create prepareTraversal function
507
508 Every particle resorting has to invoke
509
510 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
511 vertexdata::{{PARTICLE}}Set::finishedTraversal(DomainOffset, DomainSize, PeriodicBC);
512 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
513
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().
519
520 """
521 return jinja2.Template(
522 """
523 vertexdata::{{PARTICLES_CONTAINER}}::finishedTraversal(DomainOffset, DomainSize, PeriodicBC);
524 """
525 ).render(**self.d)
Action set (reactions to events)
Definition ActionSet.py:6