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 );
195 }
196}
197
198// end template
199"""
200 )
201
202 __Template_TouchVertexLastTime = jinja2.Template(
203 """
204// template in python/peano4/toolbox/particles/api/UpdateParallelState.py
205
206// run over all adjacent vertices
207auto p = fineGridVertex{{PARTICLES_CONTAINER}}.begin();
208while ( p!=fineGridVertex{{PARTICLES_CONTAINER}}.end() ) {
209 if (
210 (*p)->getParallelState()==globaldata::{{PARTICLE}}::ParallelState::Local
211 ) {
212 logDebug( "touchVertexLastTime(...)", "particle " << (*p)->toString() << " is local and remains local" );
213 _numberOfRemainingLocalParticles++;
214 p++;
215 }
216 else {
217 #if PeanoDebug > 0
218 int partID = (*p)->getPartid();
219 #else
220 int partID = -1;
221 #endif
222
223 logDebug( "touchVertexLastTime(...)", "particle " << (*p)->toString() << " is virtual. Remove local copy from vertex " << marker.toString() << " on tree " << _treeNumber );
224 toolbox::particles::assignmentchecks::detachParticleFromVertex(
225 "{{PARTICLE}}",
226 (*p)->getX(),
227 partID,
228 false, // particle is virtual
229 marker.x(),
230 marker.h(),
231 _treeNumber,
232 "UpdateParallelState::__Template_TouchVertexLastTime"
233 );
234 toolbox::particles::assignmentchecks::eraseParticle(
235 "{{PARTICLE}}",
236 (*p)->getX(),
237 partID,
238 false, // particle is virtual
239 marker.h(),
240 _treeNumber,
241 "UpdateParallelState::__Template_TouchVertexLastTime"
242 );
243 // #if !defined(Parallel)
244 // This assertion breaks when running without MPI on 1 tree with periodic
245 // boundary conditions on. It doesn't account for periodic BCs being mapped
246 // onto a boundary exchange too.
247 // assertion2(
248 // ::peano4::parallel::SpacetreeSet::getInstance().getLocalSpacetrees().size()>1,
249 // (*p)->toString(),
250 // marker.toString()
251 // );
252 // #endif
253 _numberOfExpiredHaloParticles++;
254 p = fineGridVertex{{PARTICLES_CONTAINER}}.deleteParticle(p);
255 }
256}
257
258// end template
259"""
260 )
261
263 return """
264 _treeNumber = treeNumber;
265"""
266
267 def get_body_of_operation(self, operation_name):
268 result = "\n"
269 if operation_name == ActionSet.OPERATION_BEGIN_TRAVERSAL:
270 result = self.__Template_BeginTraversal.render(**self.d)
271 if operation_name == ActionSet.OPERATION_TOUCH_VERTEX_FIRST_TIME:
272 result = self.__Template_TouchVertexFirstTime.render(**self.d)
273 if operation_name == ActionSet.OPERATION_TOUCH_VERTEX_LAST_TIME:
274 result = self.__Template_TouchVertexLastTime.render(**self.d)
275 if operation_name == ActionSet.OPERATION_END_TRAVERSAL:
276 result = self.__Template_EndTraversal.render(**self.d)
277 return result
278
280 return " return std::vector< peano4::grid::GridControlEvent >();\n"
281
283 return __name__.replace(".py", "").replace(".", "_")
284
286 return False
287
288 def get_includes(self):
289 result = jinja2.Template(
290 """
291#include "tarch/multicore/multicore.h"
292#include "tarch/multicore/Lock.h"
293
294#include "peano4/parallel/SpacetreeSet.h"
295
296#include "toolbox/particles/MultiscaleTransitions.h"
297#include "toolbox/particles/assignmentchecks/TracingAPI.h"
298
299#include "vertexdata/{{PARTICLES_CONTAINER}}.h"
300#include "globaldata/{{PARTICLE}}.h"
301"""
302 )
303 return result.render(**self.d)
304
305 def get_attributes(self):
306 return """
307 int _treeNumber;
308
309 int _numberOfRemainingLocalParticles;
310 int _numberOfExpiredHaloParticles;
311 int _numberOfParticlesThatHaveLeftTheirDomain;
312"""
313
315 template = jinja2.Template(
316 """
317 vertexdata::{{PARTICLES_CONTAINER}}::clearParticleStateStatistics();
318"""
319 )
320 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.