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 // Start template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
219
220 _numberOfParticleReassignmentsOnCurrentLevel = 0;
221 _numberOfLifts = 0;
222 _numberOfDrops = 0;
223 _numberOfLiftsIntoSieveSet = 0;
224 _numberOfDropsFromSieveSet = 0;
225 _numberOfDropsIntoHorizontalTreeDecomposition = 0;
226
227 // End template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
228"""
229 )
230
231 _Template_EndTraversal = jinja2.Template(
232 """
233 // Start template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
234
235 vertexdata::{{PARTICLES_CONTAINER}}::updateLiftDropStatistics(
236 _numberOfLifts,
237 _numberOfDrops,
238 _numberOfParticleReassignmentsOnCurrentLevel,
239 _numberOfLiftsIntoSieveSet,
240 _numberOfDropsFromSieveSet,
241 _numberOfDropsIntoHorizontalTreeDecomposition
242 );
243
244 // End template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
245"""
246 )
247
248 _Template_ValidateParticleLevelAssociation = jinja2.Template(
249 """
250 // Start template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
251
252 for (auto p: fineGridVertex{{PARTICLES_CONTAINER}} ) {
253 assertion3(
254 marker.isContainedInAdjacentCells( p->getX(), 0.5*(1.0+SortingTolerance) ),
255 marker.x(),
256 marker.toString(),
257 p->toString()
258 );
259 }
260
261 // End template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
262"""
263 )
264
265 """!
266
267 Drop fitting particles from coarser mesh level into fine grid mesh
268
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.
272
273 We have to check carefully if all coarse grid particles are local. In the
274 example below
275
276 @image html UpdateParticleGridAssociation_LiftDrop.png
277
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.
281
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.
285
286 """
287 _Template_Drop = jinja2.Template(
288 """
289
290 // Start template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
291
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() ) {
297
298 #if PeanoDebug > 0
299 int partID = (*p)->getPartid();
300 #else
301 int partID = -1;
302 #endif
303
304 if (
305 ::toolbox::particles::dropParticle(**p,marker)
306 and
307 marker.isParentOfSubtree()
308 and
309 toolbox::particles::particleWillBeDroppedFurther(**p, marker)
310 ) {
311 _numberOfDropsIntoHorizontalTreeDecomposition++;
312 fineGridVertex{{PARTICLES_CONTAINER}}.particlesCanNotBeDroppedLocallySoRollOverForNextMeshSweep(*p);
313 }
314 else if ( ::toolbox::particles::dropParticle(**p,marker) ) {
315 _numberOfDrops++;
316
317 (*p)->setCellH(marker.h());
318 toolbox::particles::updateContainedInAdjacentCellProperty(marker,**p);
319
320 toolbox::particles::assignmentchecks::detachParticleFromVertex(
321 "{{PARTICLE}}",
322 (*p)->getX(),
323 partID,
324 (*p)->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
325 peano4::datamanagement::reconstructXOfParentVertex(marker, i),
326 marker.h()*3.0,
327 _spacetreeId,
328 "AbstractUpdateParticleGridAssociation::_Template_Drop"
329 );
330 toolbox::particles::assignmentchecks::assignParticleToVertex(
331 "{{PARTICLE}}",
332 (*p)->getX(),
333 partID,
334 (*p)->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
335 marker.x(),
336 marker.h(),
337 _spacetreeId,
338 "AbstractUpdateParticleGridAssociation::_Template_Drop",
339 peano4::datamanagement::reconstructXOfParentVertex(marker, i),
340 marker.h()*3.0
341 );
342 assertion3(
343 fineGridVertex{{PARTICLES_CONTAINER}}.isValid(),
344 fineGridVertex{{PARTICLES_CONTAINER}}.toString(),
345 coarseGridVertices{{PARTICLES_CONTAINER}}(i).toString(),
346 _spacetreeId
347 );
348 assertion3(
349 coarseGridVertices{{PARTICLES_CONTAINER}}(i).isValid(),
350 fineGridVertex{{PARTICLES_CONTAINER}}.toString(),
351 coarseGridVertices{{PARTICLES_CONTAINER}}(i).toString(),
352 _spacetreeId
353 );
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) );
356 }
357 else p++;
358 } // while loop over coarse particles
359 } // if coarse vertex local
360 }
361
362 // End template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
363"""
364 )
365
366 """!
367
368 Lift all particles to next coarser level
369
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.
375
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
378 is local, too.
379
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.
383
384 """
385 _Template_LiftAllParticles = jinja2.Template(
386 """
387 // Start template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
388
389 std::bitset<Dimensions> target;
390 for (int d=0; d<Dimensions; d++) {
391 target[d] = marker.getRelativePositionWithinFatherCell()(d)>=2;
392 }
393
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}}) {
399
400 #if PeanoDebug > 0
401 int partID = p->getPartid();
402 #else
403 int partID = -1;
404 #endif
405
406 tarch::la::Vector< Dimensions, double > coarseGridVertexX =
407 marker.x()
408 -
409 tarch::la::multiplyComponents(
410 tarch::la::convertScalar<double>(marker.getRelativePositionWithinFatherCell()),
411 marker.h()
412 )
413 +
414 tarch::la::multiplyComponents(
415 tarch::la::Vector<Dimensions,double>(target),
416 3.0*marker.h()
417 );
418
419 p->setCellH(3.0 * marker.h());
420 toolbox::particles::updateContainedInAdjacentCellProperty(coarseGridVertexX,marker.h()*3.0,*p);
421
422 toolbox::particles::assignmentchecks::detachParticleFromVertex(
423 "{{PARTICLE}}",
424 p->getX(),
425 partID,
426 p->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
427 marker.x(),
428 marker.h(),
429 _spacetreeId,
430 "AbstractUpdateParticleGridAssociation::_Template_LiftAllParticles (parent vertex is local)"
431 );
432 toolbox::particles::assignmentchecks::assignParticleToVertex(
433 "{{PARTICLE}}",
434 p->getX(),
435 partID,
436 p->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
437 coarseGridVertexX,
438 3.0*marker.h(),
439 _spacetreeId,
440 "AbstractUpdateParticleGridAssociation::_Template_LiftAllParticles (parent vertex is local)",
441 marker.x(),
442 marker.h()
443 );
444 }
445 coarseGridVertices{{PARTICLES_CONTAINER}}( target.to_ulong() ).grabParticles( fineGridVertex{{PARTICLES_CONTAINER}} );
446 }
447 else {
448 _numberOfLiftsIntoSieveSet += fineGridVertex{{PARTICLES_CONTAINER}}.size();
449 for (auto* p: fineGridVertex{{PARTICLES_CONTAINER}}) {
450
451 #if PeanoDebug > 0
452 int partID = p->getPartid();
453 #else
454 int partID = -1;
455 #endif
456
457 toolbox::particles::assignmentchecks::detachParticleFromVertex(
458 "{{PARTICLE}}",
459 p->getX(),
460 partID,
461 p->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
462 marker.x(),
463 marker.h(),
464 _spacetreeId,
465 "AbstractUpdateParticleGridAssociation::_Template_LiftAllParticles"
466 );
467 toolbox::particles::assignmentchecks::assignParticleToSieveSet(
468 "{{PARTICLE}}",
469 p->getX(),
470 partID,
471 p->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local,
472 marker.x(),
473 marker.h(),
474 _spacetreeId,
475 "AbstractUpdateParticleGridAssociation::_Template_LiftAllParticles"
476 );
477 }
478 fineGridVertex{{PARTICLES_CONTAINER}}.particlesCanNotBeLiftedLocally();
479 assertion1( fineGridVertex{{PARTICLES_CONTAINER}}.isValid(), fineGridVertex{{PARTICLES_CONTAINER}}.toString() );
480 }
481
482 // End template in python/peano4/toolbox/particles/api/AbstractUpdateParticleGridAssociation.py
483"""
484 )
485
487 return " return std::vector< peano4::grid::GridControlEvent >();\n"
488
490 return False
491
492 def get_includes(self):
493 """!
494
495 Inject all the default includes
496
497 @see get_body_of_prepareTraversal().
498
499 """
500 result = jinja2.Template(
501 """
502#include "tarch/multicore/Lock.h"
503
504#include "toolbox/particles/MultiscaleTransitions.h"
505#include "toolbox/particles/assignmentchecks/TracingAPI.h"
506
507#include "vertexdata/{{PARTICLES_CONTAINER}}.h"
508#include "globaldata/{{PARTICLE}}.h"
509
510#include "Constants.h"
511"""
512 )
513 return result.render(**self.d)
514
516 return (
517 super(AbstractUpdateParticleGridAssociation, self).get_constructor_body()
518 + jinja2.Template(
519 """
520 _spacetreeId = treeNumber;
521 """
522 ).render(**self.d)
523 )
524
525 def get_attributes(self):
526 return jinja2.Template(
527 """
528 int _spacetreeId;
529 int _numberOfParticleReassignmentsOnCurrentLevel;
530 int _numberOfLifts;
531 int _numberOfDrops;
532 int _numberOfLiftsIntoSieveSet;
533 int _numberOfDropsFromSieveSet;
534 int _numberOfDropsIntoHorizontalTreeDecomposition;
535"""
536 ).render(**self.d)
537
539 """!
540
541 Create prepareTraversal function
542
543 Every particle resorting has to invoke
544
545 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
546 vertexdata::{{PARTICLE}}Set::finishedTraversal(DomainOffset, DomainSize, PeriodicBC);
547 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
548
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().
554
555 """
556 return jinja2.Template(
557 """
558 vertexdata::{{PARTICLES_CONTAINER}}::finishedTraversal(DomainOffset, DomainSize, PeriodicBC);
559 """
560 ).render(**self.d)
Action set (reactions to events)
Definition ActionSet.py:6