VM2D 1.14
Vortex methods for 2D flows simulation
Loading...
Searching...
No Matches
morton_tree.cpp
Go to the documentation of this file.
1#include "morton_tree.h"
2#include "../common/simple_math.h"
3
4#include <numeric>
5#include <algorithm>
6#include <execution>
7#include <cmath>
8#include "omp.h"
9#include <iostream>
10#include <oneapi/tbb/parallel_sort.h>
11#include <oneapi/tbb/parallel_for_each.h>
12#include <oneapi/tbb/scalable_allocator.h>
13#include <oneapi/tbb/concurrent_vector.h>
14#include <oneapi/tbb/parallel_reduce.h>
15#include <atomic>
16#include <cassert>
17//#include <boost/sort/sort.hpp>
18#include <iterator>
19#include <oneapi/tbb/enumerable_thread_specific.h>
20#include "../common/mpi_utils.h"
21
22namespace fmm {
23
24#ifdef max
25#undef max
26#endif
27
28#ifdef min
29#undef min
30#endif
31
32#define exec_policy std::execution::par
33
34template <typename point, typename value>
35std::pair<point, double> box_extent(const std::vector<particle<point, value>>& particles);
36
37template <int dim>
38inline size_t ExpandBits(double x, int tree_depth);
39
40template <int dim, typename point>
41inline size_t MortonCode(point val, int tree_depth, point shift, double scale);
42
43template <typename point>
44point PointFromMortonCode(size_t val, int depth, point shift, double scale);
45
46//#define deterministic_behaviour
47
48template <>
49inline size_t ExpandBits<2>(double x, int tree_depth)
50{
51 assert(x > 0.0 && x < 1.0);
52#ifdef deterministic_behaviour
53 size_t bit_shift = (1ull << 30);
54#else
55 size_t bit_shift = (1ull << tree_depth);
56#endif
57 size_t val = (size_t)(bit_shift * x);
58 val = ((val << 16) | val) & 0xffff0000ffff;
59 val = ((val << 8) | val) & 0xff00ff00ff00ff;
60 val = ((val << 4) | val) & 0xf0f0f0f0f0f0f0f;
61 val = ((val << 2) | val) & 0x3333333333333333;
62 val = ((val << 1) | val) & 0x5555555555555555;
63 return val;
64}
65
66template <>
67inline size_t ExpandBits<3>(double x, int tree_depth)
68{
69 assert(x > 0.0 && x < 1.0);
70#ifdef deterministic_behaviour
71 size_t bit_shift = (1ull << 20);
72#else
73 size_t bit_shift = (1ull << tree_depth);
74#endif
75 size_t val = (size_t)(bit_shift * x);
76 val = ((val << 32) | val) & 0xffff00000000ffff;
77 val = ((val << 16) | val) & 0xff0000ff0000ff;
78 val = ((val << 8) | val) & 0xf00f00f00f00f00f;
79 val = ((val << 4) | val) & 0x30c30c30c30c30c3;
80 val = ((val << 2) | val) & 0x9249249249249249;
81 return val;
82}
83
84template <>
85inline size_t MortonCode<2, point2d>(point2d val, int tree_depth, point2d shift, double scale)
86{
87 double x = (val.real() - shift.real()) / scale;
88 double y = (val.imag() - shift.imag()) / scale;
89 return (ExpandBits<2>(x, tree_depth) << 1) | ExpandBits<2>(y, tree_depth);
90}
91
92template <>
93inline size_t MortonCode<3, point3d>(point3d val, int tree_depth, point3d shift, double scale)
94{
95 double x = (val[0] - shift[0]) / scale;
96 double y = (val[1] - shift[1]) / scale;
97 double z = (val[2] - shift[2]) / scale;
98 return (ExpandBits<3>(x, tree_depth) << 2) | (ExpandBits<3>(y, tree_depth) << 1) | ExpandBits<3>(z, tree_depth);
99}
100
101template <>
102point2d PointFromMortonCode(size_t val, int depth, point2d shift, double scale)
103{
104 double step = 1.0 / (1 << (depth + 1));
105 double x = step, y = step;
106 step *= 2;
107 for (int i = 0; i < depth; ++i)
108 {
109 y += step * (val & 1);
110 val >>= 1;
111 x += step * (val & 1);
112 val >>= 1;
113 step *= 2;
114 }
115 x *= scale;
116 x += shift.real();
117 y *= scale;
118 y += shift.imag();
119 return { x, y };
120}
121
122template <>
123point3d PointFromMortonCode(size_t val, int depth, point3d shift, double scale)
124{
125 double step = 1.0 / (1 << (depth + 1));
126 double x = step, y = step, z = step;
127 step *= 2;
128 for (int i = 0; i < depth; ++i)
129 {
130 z += step * (val & 1);
131 val >>= 1;
132 y += step * (val & 1);
133 val >>= 1;
134 x += step * (val & 1);
135 val >>= 1;
136 step *= 2;
137 }
138 x *= scale;
139 x += shift[0];
140 y *= scale;
141 y += shift[1];
142 z *= scale;
143 z += shift[2];
144 return { x, y, z };
145}
146
147template <int dim, typename point, typename value>
149{
150 if (tree_depth > 1)
151 for (int i = 0; i < level_sizes[1]; ++i)
152 {
153 auto& cell = levels[1][i];
154 auto& [s, e] = levels[0][0].source_range;
155 std::vector<size_t> indexes(e - s);
156 std::iota(indexes.begin(), indexes.end(), s);
157 indexes.erase(indexes.begin() + i);
158 cell.closeneighbours = indexes;
159 }
160 for (int i = 2; i < tree_depth; ++i)
161 {
162 double dw = sqrt(dim + 1.e-5) * scale / (1 << i);
163 dw *= dw;
164 const auto& parent_level = levels[i - 1];
165 auto& current_level = levels[i];
166
167 tbb::parallel_for_each(current_level.begin(), current_level.begin() + level_sizes[i], [&](auto& cell)
168 {
169 if constexpr (dim == 2)
170 {
171 cell.closeneighbours.reserve(9);
172 cell.farneighbours.reserve(27);
173 }
174 else
175 {
176 cell.closeneighbours.reserve(27);
177 cell.farneighbours.reserve(189);
178 }
179
180 const auto& parent_cell = parent_level[cell.parent];
181 for (const auto& parent_neighbours : parent_cell.closeneighbours)
182 {
183 const auto& [s, e] = parent_level[parent_neighbours].source_range;
184 for (auto k = s; k < e; ++k)
185 {
186 if (norm(current_level[k].center - cell.center) < dw)
187 cell.closeneighbours.push_back(k);
188 else
189 cell.farneighbours.push_back(k);
190 }
191 }
192
193 const auto& [s, e] = parent_level[cell.parent].source_range;
194 for (auto k = s; k < e; ++k)
195 {
196 if (&cell != &(current_level[k]))
197 {
198 if (norm(current_level[k].center - cell.center) < dw)
199 cell.closeneighbours.push_back(k);
200 else
201 cell.farneighbours.push_back(k);
202 }
203 }
204 cell.farneighbours.shrink_to_fit();
205 cell.closeneighbours.shrink_to_fit();
206 });
207 }
208}
209
210template <int dim, typename value>
212{
213 double coords[dim];
214 value q;
216};
217
218template<typename value>
219std::pair<point2d, double> box_extent(const std::vector<particle<point2d, value>>& particles)
220{
221 tbb::enumerable_thread_specific<double> xmin(particles[0].center.real()), xmax(particles[0].center.real()),
222 ymin(particles[0].center.imag()), ymax(particles[0].center.imag());
223 tbb::parallel_for(tbb::blocked_range<size_t>(0, particles.size()), [&](const tbb::blocked_range<size_t>& r)
224 {
225 auto& Xmin = xmin.local();
226 auto& Xmax = xmax.local();
227 auto& Ymin = ymin.local();
228 auto& Ymax = ymax.local();
229 for (size_t i = r.begin(); i < r.end(); ++i)
230 {
231 double x = particles[i].center.real();
232 double y = particles[i].center.imag();
233 Xmin = std::min(Xmin, x);
234 Xmax = std::max(Xmax, x);
235 Ymin = std::min(Ymin, y);
236 Ymax = std::max(Ymax, y);
237 }
238 });
239 double Xmin = *std::min_element(xmin.begin(), xmin.end());
240 double Ymin = *std::min_element(ymin.begin(), ymin.end());
241 double Xmax = *std::max_element(xmax.begin(), xmax.end());
242 double Ymax = *std::max_element(ymax.begin(), ymax.end());
243
244 double dw = std::max(Xmax - Xmin, Ymax - Ymin);
245 return { {Xmin,Ymin}, dw };
246}
247
248template<typename value>
249std::pair<point3d, double> box_extent(const std::vector<particle<point3d, value>>& particles)
250{
251 tbb::enumerable_thread_specific<double> xmin(particles[0].center[0]), xmax(particles[0].center[0]),
252 ymin(particles[0].center[1]), ymax(particles[0].center[1]),
253 zmin(particles[0].center[2]), zmax(particles[0].center[2]);
254 tbb::parallel_for(tbb::blocked_range<size_t>(0, particles.size()), [&](const tbb::blocked_range<size_t>& r)
255 {
256 auto& Xmin = xmin.local();
257 auto& Xmax = xmax.local();
258 auto& Ymin = ymin.local();
259 auto& Ymax = ymax.local();
260 auto& Zmin = zmin.local();
261 auto& Zmax = zmax.local();
262 for (size_t i = r.begin(); i < r.end(); ++i)
263 {
264 double x = particles[i].center[0];
265 double y = particles[i].center[1];
266 double z = particles[i].center[2];
267 Xmin = std::min(Xmin, x);
268 Xmax = std::max(Xmax, x);
269 Ymin = std::min(Ymin, y);
270 Ymax = std::max(Ymax, y);
271 Zmin = std::min(Zmin, z);
272 Zmax = std::max(Zmax, z);
273 }
274 });
275 double Xmin = *std::min_element(xmin.begin(), xmin.end());
276 double Ymin = *std::min_element(ymin.begin(), ymin.end());
277 double Zmin = *std::min_element(zmin.begin(), zmin.end());
278 double Xmax = *std::max_element(xmax.begin(), xmax.end());
279 double Ymax = *std::max_element(ymax.begin(), ymax.end());
280 double Zmax = *std::max_element(zmax.begin(), zmax.end());
281
282 double dw = std::max({Xmax - Xmin, Ymax - Ymin, Zmax - Zmin});
283 return { {Xmin, Ymin, Zmin}, dw };
284}
285
286template <int dim, typename point, typename value>
287MortonTree<dim, point, value>::MortonTree(std::vector<particle_t>&& particles_, size_t tree_depth_) : tree_depth(tree_depth_), particles(particles_)
288{
289 double T = omp_get_wtime();
290 //if (IAmRoot()) std::cout << "\n************ Start Building Tree ************" << std::endl;
291 const int dim2 = MyPow(2, dim);
292 double t = omp_get_wtime();
293 {
294 auto be = box_extent(particles);
295 shift = be.first; scale = be.second;
296
297 while (scale < FORCE_EPS * (1 << tree_depth))
298 {
299 --tree_depth;
300 }
301
302 if constexpr (dim == 2)
303 shift -= std::complex<double> {1.e-7, 1.e-7}; // min particles shouldnt have coordinates equal zero
304 else
305 shift -= Vector3d {1.e-7, 1.e-7, 1.e-7}; // min particles shouldnt have coordinates equal zero
306 scale += 1.e-6; // max particles shouldnt have coordinate equal 1
307 }
308 //if (IAmRoot()) std::cout << "compute extent time: " << omp_get_wtime() - t << std::endl;
309 //if (IAmRoot()) std::cout << "scale factors: shift = " << shift << ", extent = " << scale << std::endl;
310
311 size_t num_particles = particles.size();
312 // if (IAmRoot()) std::cout << "particles num = " << num_particles << std::endl;
313
314 levels.resize(tree_depth);
315 tbb::parallel_for(size_t(0), tree_depth, [&](size_t i) {
316 levels[i].resize(std::min(num_particles, (size_t)MyPow(size_t(dim2), (unsigned int)i)));
317 });
318 level_sizes.resize(tree_depth);
319 positions_map.resize(num_particles);
320
321 t = omp_get_wtime();
322 tbb::parallel_for_each(particles.begin(), particles.end(), [&](particle_t& x) {
323 x.morton_code = MortonCode<dim, point>(x.center, (int)(tree_depth - 1), shift, scale);
324 assert(x.morton_code < MyPow(dim2, tree_depth - 1));
325 });
326 //if (IAmRoot()) std::cout << "compute morton codes time: " << omp_get_wtime() - t << std::endl;
327
328 assert(MyPow(dim2, tree_depth) > std::max_element(particles.begin(), particles.end(), [&](const particle_t& x, const particle_t& y) {return x.morton_code < y.morton_code; })->morton_code);
329 t = omp_get_wtime();
330 {
331#ifdef deterministic_behaviour
332 tbb::parallel_sort(particles.begin(), particles.end(), [](const auto& x, const auto& y) {return x.morton_code < y.morton_code; });
333 //boost::sort::block_indirect_sort(particles.begin(), particles.end(), [](const auto& x, const auto& y) {return x.morton_code < y.morton_code; }, 1);
334#else
335 if (IAmRoot())
336 {
337 // counting sort, using known number of unique numbers
338 tbb::concurrent_vector<std::atomic_size_t> counts(MyPow(dim2, (unsigned int)(tree_depth - 1)) + 1);
339 tbb::parallel_for_each(particles.begin(), particles.end(), [&](const auto& x) {counts[1 + x.morton_code]++; });
340 for (int i = 1; i < counts.size(); ++i)
341 counts[i] += counts[i - 1];
342 assert(counts.back() == num_particles);
343
344 tbb::scalable_allocator<small_particle<dim, value>> temp;
345 constexpr size_t memsize = sizeof(small_particle<dim, value>);
346 auto ptr = temp.allocate(num_particles);
347
348 //t = omp_get_wtime();
349 tbb::parallel_for(size_t(0), num_particles, [&](size_t i) {
350 const auto& x = particles[i];
351 size_t pos = counts[x.morton_code]++;
352 assert(pos < num_particles);
353 positions_map[i] = pos;
354 memcpy(&ptr[pos], &x, memsize);
355 });
356
357 tbb::parallel_for(size_t(0), num_particles, [&](size_t i) {
358 memcpy(&particles[i], &ptr[i], memsize);
359 });
360
361 temp.deallocate(ptr, num_particles);
362
363 assert(std::is_sorted(particles.begin(), particles.end(), [](const auto& x, const auto& y) {return x.morton_code < y.morton_code; }));
364 }
365#ifdef FMM_MPI
366 MPI_Bcast(particles.data(), num_particles * sizeof(particle_t), MPI_BYTE, RootID(), MPI_COMM_WORLD);
367 MPI_Bcast(positions_map.data(), num_particles * sizeof(size_t), MPI_BYTE, RootID(), MPI_COMM_WORLD);
368#endif
369
370#endif
371 }
372 //if (IAmRoot()) std::cout << "sort time: " << omp_get_wtime() - t << std::endl;
373 t = omp_get_wtime();
374#ifdef deterministic_behaviour
375 tbb::parallel_for_each(particles.begin(), particles.end(), [&](auto& x) {x.morton_code >>= dim * (30 - tree_depth); });
376#endif
377 {
378 auto& top_level = levels.back();
379 auto ptr = std::unique_copy(exec_policy, particles.begin(), particles.end(), top_level.begin(), [&](const auto& x, const auto& y) {return (x.morton_code) == (y.morton_code); });
380 level_sizes.back() = ptr - top_level.begin();
381
382 int idx_bottom = 0, idx_top = 0;
383 for (int j = 0; j < num_particles; ++j)
384 {
385 if ((particles[j].morton_code) != (top_level[idx_top].morton_code))
386 {
387 auto& cell = top_level[idx_top];
388 cell.source_range = { idx_bottom, j };
389 cell.center = PointFromMortonCode(cell.morton_code, (int)(tree_depth - 1), shift, scale);
390 ++idx_top;
391 idx_bottom = j;
392 }
393 }
394 auto& cell = top_level[idx_top];
395 cell.source_range = { idx_bottom, num_particles };
396 cell.center = PointFromMortonCode(cell.morton_code, (int)(tree_depth - 1), shift, scale);
397
398 //if (IAmRoot()) std::cout << "level " << tree_depth - 1 << " size: " << level_sizes.back() << std::endl;
399 }
400
401 for (int i = (int)(tree_depth - 2); i >= 0; --i)
402 {
403 auto& top_level = levels[i];
404 auto& bottom_level = levels[i + 1];
405
406 auto ptr = std::unique_copy(exec_policy, bottom_level.begin(), bottom_level.begin() + level_sizes[i + 1], top_level.begin(), [&](const auto& x, const auto& y) {return (x.morton_code >> dim) == (y.morton_code >> dim); });
407 level_sizes[i] = ptr - top_level.begin();
408
409 //if (IAmRoot()) std::cout << "level " << i << " size: " << level_sizes[i] << std::endl;
410
411 int idx_bottom = 0, idx_top = 0;
412 for (int j = 0; j < level_sizes[i + 1]; ++j)
413 {
414 if ((bottom_level[j].morton_code >> dim) != (top_level[idx_top].morton_code >> dim))
415 {
416 auto& cell = top_level[idx_top];
417 cell.source_range = { idx_bottom, j };
418 cell.morton_code >>= dim;
419 cell.center = PointFromMortonCode(cell.morton_code, i, shift, scale);
420 ++idx_top;
421 idx_bottom = j;
422 }
423 bottom_level[j].parent = idx_top;
424 }
425 auto& cell = top_level[idx_top];
426 cell.source_range = { idx_bottom, level_sizes[i + 1] };
427 cell.morton_code >>= dim;
428 cell.center = PointFromMortonCode(cell.morton_code, i, shift, scale);
429 }
430 //if (IAmRoot()) std::cout << "create levels time: " << omp_get_wtime() - t << std::endl;
431
432 t = omp_get_wtime();
433 {
434 std::vector<size_t> inverse_positions_map(num_particles);
435 tbb::parallel_for(size_t(0), num_particles, [&](size_t i){
436 inverse_positions_map[positions_map[i]] = i;
437 });
438
439 value Zero;
440 if constexpr (std::is_same_v<value, double>)
441 Zero = 0;
442 if constexpr (std::is_same_v<value, Vector3d>)
443 Zero = { 0,0,0 };
444 auto& leaves = levels.back();
445 tbb::enumerable_thread_specific<size_t> local_num(0);
446 tbb::parallel_for(size_t(0), level_sizes.back(), [&](size_t k){
447 auto& leaf = leaves[k];
448 auto& [s_begin, s_end] = leaf.source_range;
449 auto& [t_begin, t_end] = leaf.target_range;
450 t_end = s_end;
451 t_begin = s_begin;
452 for (size_t i = s_begin; i < s_end; ++i)
453 {
454 if (particles[i].q != Zero)
455 {
456 std::swap(particles[t_begin], particles[i]);
457 std::swap(inverse_positions_map[t_begin++], inverse_positions_map[i]);
458 }
459 }
460 s_end = t_begin;
461 local_num.local() += t_end - t_begin;
462
463 });
464
465 tbb::parallel_for(size_t(0), num_particles, [&](size_t i){
466 positions_map[inverse_positions_map[i]] = i;
467 });
468
469 for (const auto& x : local_num)
470 targets_num += x;
471 //if (IAmRoot()) std::cout << "targets num: " << targets_num << std::endl;
472
473 }
474 //if (IAmRoot()) std::cout << "source/target partition time: " << omp_get_wtime() - t << std::endl;
475
476 t = omp_get_wtime();
477 FillNeighbours();
478 //if (IAmRoot()) std::cout << "fill neighbours time: " << omp_get_wtime() - t << std::endl;
479
480 //������ ������ ������� ������������
481 /*
482 t = omp_get_wtime();
483 if (targets_num == 0)
484 {
485 auto& leaves = levels.back();
486 for (int i = 0; i < level_sizes.back(); ++i)
487 {
488 const auto& cell = leaves[i];
489 for (const auto& idx : cell.closeneighbours)
490 {
491 auto& neighbour_cell = leaves[idx];
492 auto ptr = std::find(neighbour_cell.closeneighbours.begin(), neighbour_cell.closeneighbours.end(), i);
493 neighbour_cell.closeneighbours.erase(ptr);
494 }
495 }
496 }
497 if (IAmRoot()) std::cout << "remove duplicate nbr indices time:" << omp_get_wtime() - t << std::endl;
498 */
499
500 //if (IAmRoot()) std::cout << "total build time: " << omp_get_wtime() - T << std::endl;
501 //if (IAmRoot()) std::cout << "************ End Building Tree ************\n\n";
502}
503
504template class MortonTree<2, point2d, double>;
505template class MortonTree<3, point3d, double>;
506template class MortonTree<3, point3d, point3d>;
507
508} // fmm
std::vector< std::vector< TreeCell_t > > levels
Definition morton_tree.h:33
std::vector< particle_t > particles
Definition morton_tree.h:37
std::vector< size_t > level_sizes
Definition morton_tree.h:34
std::vector< size_t > positions_map
Definition morton_tree.h:38
#define exec_policy
Definition avx.h:5
std::pair< point, double > box_extent(const std::vector< particle< point, value > > &particles)
constexpr T MyPow(T base, unsigned int exp)
Definition simple_math.h:85
point PointFromMortonCode(size_t val, int depth, point shift, double scale)
size_t ExpandBits< 2 >(double x, int tree_depth)
size_t ExpandBits< 3 >(double x, int tree_depth)
double norm(const Vector3< T > &vec)
Definition utils.h:119
size_t ExpandBits(double x, int tree_depth)
bool IAmRoot()
Definition mpi_utils.h:101
size_t MortonCode< 3, point3d >(point3d val, int tree_depth, point3d shift, double scale)
size_t MortonCode< 2, point2d >(point2d val, int tree_depth, point2d shift, double scale)
std::complex< double > point2d
Definition utils.h:153
constexpr double FORCE_EPS
Definition defs.h:28
size_t MortonCode(point val, int tree_depth, point shift, double scale)
size_t morton_code
Definition utils.h:34