Peano
Loading...
Searching...
No Matches
UpdateParallelState.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
6 AbstractUpdateParticleGridAssociation,
7)
8
9import jinja2
10
11
13 """!
14
15 Update the parallel state of particles and keep stats of them
16
17 Particles within a parallel code hold a boolean flag ParallelState.
18 It is this action set's responsibility to maintain
19 this property. Users' codes may use the ParallelState, but as read-only
20 only.
21
22 We have two options per particle (held within ParallelState): local and virtual.
23 In our data model, particles are either local or virtual throughout
24 the mesh traversal, but they might change their position and
25 consequently alter their state. Virtual particles can fly in through
26 the boundary, while particles can leave the local domain (and hence become
27 virtual) by moving through the boundary. Virtual particles (another
28 word would be halo particles) are mere copies of particles somewhere
29 else. You should not alter their value, and we can safely throw them
30 away after a traversal, as the subsequent merger at the parallel
31 boundary will re-introduce them. We can also keep them as long as
32 we know that their position doesn't change. In this case, the merger
33 will update the copies in-situ.
34
35 To accommodate the fact that a particle moves and switches from local
36 to virtual, we update its state in touchVertexFirstTime().
37
38
39 ## Validity/correctness
40
41 Particles may not change their position throughout the mesh traversal. They
42 can change their position in touchVertexLastTime(). This effectively means
43 they change x in-between two grid sweeps. Consequently, their state might
44 be wrong in-between two sweeps.
45
46 This routine has to be injected ***after*** additional particles are
47 added to the mesh. That can happen throughout boundary merges or due to
48 resorts. This action set has to be used after each mesh sweep that
49 alters particle positions.
50
51 The action set throws away halo particles in touchVertexFirstTime().
52 Therefore, if you don't use it after each and every mesh sweep, you
53 might get redundant data at the boundary (due to repeated particle
54 inflow) or outdated data. Both is automatically avoided in the boundary
55 merges, which eliminate replica.
56
57
58 ## Merges along domain boundary
59
60 We would like all state updates to be localised within this action set.
61 However, we cannot do everything here: We also have to update the
62 parallel state when we merge along the domain boundary. This happens
63 in the particle set's merge(). The documentation around merge() is
64 summarise here, as we have multiple particle set implementations, but
65 all of them are realised through Jinja templates. So it is better to
66 have stuff in one place only.
67
68 The merge in principle is trivial: If a neighbour sends you in a
69 virtual particle, don't do anything with it. Just ignore it. If it
70 is local at the neighbour, we add it to our local vertex.
71
72 @image html UpdateParallelState_merge.png
73
74 The sketch above illustrates why it is important to neglect virtual
75 particles sent in: In the sketch, the left subdomain sends its
76 particle (green) over to the right, where it is marked as virtual
77 (red). The right one now sends this particle back (dark green) and
78 we would hold redundant information.
79
80 The sketch also illustrates why we throw away virtual particles after
81 the mesh sweep: If the particle moves on the owning rank - and it can
82 only move there - the neighbour would have two virtual copies (as it
83 cannot match old one to new one). So we better throw away all old stuff
84 by default.
85
86 With that data flow in mind, particles crossing domain boundaries are
87 automatically given the correct state. We wouldn't have to implement
88 and local/non-local logic in the merge(). This could all be done in
89 this action set. However, we still need some logic here.
90
91 The reason is simple: A merge happens in each and every mesh sweep,
92 even if particles do not move. When a particle flies into a domain
93 (it doesn't have to fly, most of the time it has been there anyway)
94 and is a local one at the neighbour, we cannot imply toggle it to
95 virtual here, as it might be owned redundantly if it sits directly
96 on the domain boundaries. We have to rerun the "is local
97 here" analysis after each merge.
98
99
100 ## Statistics
101
102 The mapping keeps some statistics on any state update. These statistics
103 however are not shared via MPI. Instead, we hand them over to the
104 particle set which then can consolidate the data among ranks and cores.
105
106
107 """
108
109 DefaultDescendInvocationOrder = (
110 AbstractUpdateParticleGridAssociation.DefaultDescendInvocationOrder + 1
111 )
112
114 self,
115 particle_set,
116 ):
117 super(UpdateParallelState, self).__init__(
118 descend_invocation_order=self.DefaultDescendInvocationOrder, parallel=False
119 )
120 self._particle_set = particle_set
121 self.d = {}
122 self.d["PARTICLE"] = particle_set.particle_model.name
123 self.d["PARTICLES_CONTAINER"] = particle_set.name
124
125 __Template_BeginTraversal = jinja2.Template(
126 """
127 // template in python/peano4/toolbox/particles/api/UpdateParallelState.py
128
129 _numberOfRemainingLocalParticles = 0;
130 _numberOfExpiredHaloParticles = 0;
131 _numberOfParticlesThatHaveLeftTheirDomain = 0;
132
133 // end template
134"""
135 )
136
137 __Template_EndTraversal = jinja2.Template(
138 """
139 // template in python/peano4/toolbox/particles/api/UpdateParallelState.py
140
141 vertexdata::{{PARTICLES_CONTAINER}}::updateNumberOfLocalAndExpiredParticles(
142 _numberOfRemainingLocalParticles,
143 _numberOfExpiredHaloParticles,
144 _numberOfParticlesThatHaveLeftTheirDomain
145 );
146
147 // end template
148"""
149 )
150
151 __Template_TouchVertexFirstTime = jinja2.Template(
152 """
153// template in python/peano4/toolbox/particles/api/UpdateParallelState.py
154
155for (auto& p: fineGridVertex{{PARTICLES_CONTAINER}} ) {
156 const bool particleWillBeLocal = toolbox::particles::particleAssignedToVertexWillBeLocal( p->getX(), marker );
157
158 // Have left the domain in the previous mesh sweep. So we set it to virtual
159 // now. After this iteration, the particle will be thrown away. This can only
160 // happen if the previous mesh sweep has altered the position, i.e. while we
161 // set the particle to virtual here, some other tree will just receive this
162 // one and set it local there. In most cases, this will be a replace of an
163 // existing (virtual) particle copy.
164 if (not particleWillBeLocal and p->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local ) {
165 p->setParallelState( globaldata::{{PARTICLE}}::ParallelState::Virtual );
166 logDebug( "touchVertexLastTime(...)", "particle has left domain=" << p->toString() << " (attached to vertex " << marker.toString() << ", will be deleted after this sweep but should be inflying on other tree)" );
167 _numberOfParticlesThatHaveLeftTheirDomain++;
168
169 #if PeanoDebug > 0
170 int partID = p->getPartid();
171 #else
172 int partID = -1;
173 #endif
174
175 toolbox::particles::assignmentchecks::detachParticleFromVertex(
176 "{{PARTICLE}}",
177 p->getX(),
178 partID,
179 true, // particle is (currently) local
180 marker.x(),
181 marker.h(),
182 _treeNumber,
183 "UpdateParallelState::__Template_TouchVertexFirstTime"
184 );
185 toolbox::particles::assignmentchecks::assignParticleToVertex(
186 "{{PARTICLE}}",
187 p->getX(),
188 partID,
189 false, // particle is (now) virtual
190 marker.x(),
191 marker.h(),
192 _treeNumber,
193 "UpdateParallelState::__Template_TouchVertexFirstTime",
194 marker.x(),
195 marker.h()
196 );
197 }
198}
199
200// end template
201"""
202 )
203
204 __Template_TouchVertexLastTime = jinja2.Template(
205 """
206// template in python/peano4/toolbox/particles/api/UpdateParallelState.py
207
208// run over all adjacent vertices
209auto p = fineGridVertex{{PARTICLES_CONTAINER}}.begin();
210while ( p!=fineGridVertex{{PARTICLES_CONTAINER}}.end() ) {
211 if (
212 (*p)->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local
213 ) {
214 logDebug( "touchVertexLastTime(...)", "particle " << (*p)->toString() << " is local and remains local" );
215 _numberOfRemainingLocalParticles++;
216 p++;
217 }
218 else {
219 #if PeanoDebug > 0
220 int partID = (*p)->getPartid();
221 #else
222 int partID = -1;
223 #endif
224
225 logDebug( "touchVertexLastTime(...)", "particle " << (*p)->toString() << " is virtual. Remove local copy from vertex " << marker.toString() << " on tree " << _treeNumber );
226 toolbox::particles::assignmentchecks::detachParticleFromVertex(
227 "{{PARTICLE}}",
228 (*p)->getX(),
229 partID,
230 false, // particle is virtual
231 marker.x(),
232 marker.h(),
233 _treeNumber,
234 "UpdateParallelState::__Template_TouchVertexLastTime"
235 );
236 toolbox::particles::assignmentchecks::eraseParticle(
237 "{{PARTICLE}}",
238 (*p)->getX(),
239 partID,
240 false, // particle is virtual
241 marker.x(),
242 marker.h(),
243 _treeNumber,
244 "UpdateParallelState::__Template_TouchVertexLastTime"
245 );
246 // #if !defined(Parallel)
247 // This assertion breaks when running without MPI on 1 tree with periodic
248 // boundary conditions on. It doesn't account for periodic BCs being mapped
249 // onto a boundary exchange too.
250 // assertion2(
251 // ::peano4::parallel::SpacetreeSet::getInstance().getLocalSpacetrees().size()>1,
252 // (*p)->toString(),
253 // marker.toString()
254 // );
255 // #endif
256 _numberOfExpiredHaloParticles++;
257 p = fineGridVertex{{PARTICLES_CONTAINER}}.deleteParticle(p);
258 }
259}
260
261// end template
262"""
263 )
264
266 return """
267 _treeNumber = treeNumber;
268"""
269
270 def get_body_of_operation(self, operation_name):
271 result = "\n"
272 if operation_name == ActionSet.OPERATION_BEGIN_TRAVERSAL:
273 result = self.__Template_BeginTraversal.render(**self.d)
274 if operation_name == ActionSet.OPERATION_TOUCH_VERTEX_FIRST_TIME:
275 result = self.__Template_TouchVertexFirstTime.render(**self.d)
276 if operation_name == ActionSet.OPERATION_TOUCH_VERTEX_LAST_TIME:
277 result = self.__Template_TouchVertexLastTime.render(**self.d)
278 if operation_name == ActionSet.OPERATION_END_TRAVERSAL:
279 result = self.__Template_EndTraversal.render(**self.d)
280 return result
281
283 return " return std::vector< peano4::grid::GridControlEvent >();\n"
284
286 return __name__.replace(".py", "").replace(".", "_")
287
289 return False
290
291 def get_includes(self):
292 result = jinja2.Template(
293 """
294#include "tarch/multicore/multicore.h"
295#include "tarch/multicore/Lock.h"
296
297#include "peano4/parallel/SpacetreeSet.h"
298
299#include "toolbox/particles/MultiscaleTransitions.h"
300#include "toolbox/particles/assignmentchecks/TracingAPI.h"
301
302#include "vertexdata/{{PARTICLES_CONTAINER}}.h"
303#include "globaldata/{{PARTICLE}}.h"
304"""
305 )
306 return result.render(**self.d)
307
308 def get_attributes(self):
309 return """
310 int _treeNumber;
311
312 int _numberOfRemainingLocalParticles;
313 int _numberOfExpiredHaloParticles;
314 int _numberOfParticlesThatHaveLeftTheirDomain;
315"""
316
318 template = jinja2.Template(
319 """
320 vertexdata::{{PARTICLES_CONTAINER}}::clearParticleStateStatistics();
321"""
322 )
323 return template.render(**self.d)
Action set (reactions to events)
Definition ActionSet.py:6
Update the parallel state of particles and keep stats of them.
get_body_of_operation(self, operation_name)
Return actual C++ code snippets to be inserted into C++ code.
get_attributes(self)
Return attributes as copied and pasted into the generated class.
user_should_modify_template(self)
Is the user allowed to modify the output.