Shamrock 2025.10.0
Astrophysical Code
Loading...
Searching...
No Matches
NeighbourCache.cpp
Go to the documentation of this file.
1// -------------------------------------------------------//
2//
3// SHAMROCK code for hydrodynamics
4// Copyright (c) 2021-2026 Timothée David--Cléris <tim.shamrock@proton.me>
5// SPDX-License-Identifier: CeCILL Free Software License Agreement v2.1
6// Shamrock is licensed under the CeCILL 2.1 License, see LICENSE for more information
7//
8// -------------------------------------------------------//
9
17
19#include "shambase/assert.hpp"
20#include "shambase/memory.hpp"
29
30template<class Tvec, class Tmorton, template<class> class SPHKernel>
31void shammodels::sph::modules::NeighbourCache<Tvec, Tmorton, SPHKernel>::start_neighbors_cache() {
32
33 // interface_control
34 using GhostHandle = sph::BasicSPHGhostHandler<Tvec>;
35 using GhostHandleCache = typename GhostHandle::CacheMap;
37
38 shambase::Timer time_neigh;
39 time_neigh.start();
40
41 StackEntry stack_loc{};
42
43 // do cache
44 auto build_neigh_cache = [&](u64 patch_id) {
45 shamlog_debug_ln("BasicSPH", "build particle cache id =", patch_id);
46
47 NamedStackEntry cache_build_stack_loc{"build cache"};
48
49 auto &mfield = storage.merged_xyzh.get().get(patch_id);
50
51 sham::DeviceBuffer<Tvec> &buf_xyz = mfield.template get_field_buf_ref<Tvec>(0);
52 sham::DeviceBuffer<Tscal> &buf_hpart = mfield.template get_field_buf_ref<Tscal>(1);
53
54 sham::DeviceBuffer<Tscal> &tree_field_rint
55 = storage.rtree_rint_field.get().get(patch_id).buf_field;
56
57 RTree &tree = storage.merged_pos_trees.get().get(patch_id);
58 auto obj_it = tree.get_object_iterator();
59
60 u32 obj_cnt = shambase::get_check_ref(storage.part_counts).indexes.get(patch_id);
61
62 sycl::range range_npart{obj_cnt};
63
64 Tscal h_tolerance = solver_config.htol_up_coarse_cycle;
65
66 NamedStackEntry stack_loc1{"init cache"};
67
68 using namespace shamrock;
69
70 sham::DeviceQueue &q = shamsys::instance::get_compute_scheduler().get_queue();
71
72 sham::DeviceBuffer<u32> neigh_count(
73 obj_cnt, shamsys::instance::get_compute_scheduler_ptr());
74
75 shamlog_debug_sycl_ln("Cache", "generate cache for N=", obj_cnt);
77 q,
78 sham::MultiRef{buf_xyz, buf_hpart, tree_field_rint, obj_it},
79 sham::MultiRef{neigh_count},
80 obj_cnt,
81 [h_tolerance](
82 u32 id_a,
83 const Tvec *__restrict xyz,
84 const Tscal *__restrict hpart,
85 const Tscal *__restrict rint_tree,
86 auto particle_looper,
87 u32 *__restrict neigh_cnt) {
88 constexpr Tscal Rker2 = Kernel::Rkern * Kernel::Rkern;
89
90 Tscal rint_a = hpart[id_a] * h_tolerance;
91
92 Tvec xyz_a = xyz[id_a];
93
94 Tvec inter_box_a_min = xyz_a - rint_a * Kernel::Rkern;
95 Tvec inter_box_a_max = xyz_a + rint_a * Kernel::Rkern;
96
97 u32 cnt = 0;
98
99 particle_looper.rtree_for(
100 [&](u32 node_id, shammath::AABB<Tvec> node_aabb) -> bool {
101 Tscal int_r_max_cell = rint_tree[node_id] * Kernel::Rkern;
102
103 using namespace walker::interaction_crit;
104
105 return sph_radix_cell_crit(
106 xyz_a,
107 inter_box_a_min,
108 inter_box_a_max,
109 node_aabb.lower,
110 node_aabb.upper,
111 int_r_max_cell);
112 },
113 [&](u32 id_b) {
114 // compute only omega_a
115 Tvec dr = xyz_a - xyz[id_b];
116 Tscal rab2 = sycl::dot(dr, dr);
117 Tscal rint_b = hpart[id_b] * h_tolerance;
118
119 bool no_interact
120 = rab2 > rint_a * rint_a * Rker2 && rab2 > rint_b * rint_b * Rker2;
121
122 cnt += (no_interact) ? 0 : 1;
123 });
124
125 neigh_cnt[id_a] = cnt;
126 });
127
128 tree::ObjectCache pcache = tree::prepare_object_cache(std::move(neigh_count), obj_cnt);
129
130 NamedStackEntry stack_loc2{"fill cache"};
132 q,
133 sham::MultiRef{buf_xyz, buf_hpart, tree_field_rint, pcache.scanned_cnt, obj_it},
134 sham::MultiRef{pcache.index_neigh_map},
135 obj_cnt,
136 [h_tolerance](
137 u32 id_a,
138 const Tvec *__restrict xyz,
139 const Tscal *__restrict hpart,
140 const Tscal *__restrict rint_tree,
141 const u32 *__restrict scanned_neigh_cnt,
142 auto particle_looper,
143 u32 *__restrict neigh) {
144 constexpr Tscal Rker2 = Kernel::Rkern * Kernel::Rkern;
145
146 Tscal rint_a = hpart[id_a] * h_tolerance;
147
148 Tvec xyz_a = xyz[id_a];
149
150 Tvec inter_box_a_min = xyz_a - rint_a * Kernel::Rkern;
151 Tvec inter_box_a_max = xyz_a + rint_a * Kernel::Rkern;
152
153 u32 cnt = scanned_neigh_cnt[id_a];
154
155 particle_looper.rtree_for(
156 [&](u32 node_id, shammath::AABB<Tvec> node_aabb) -> bool {
157 Tscal int_r_max_cell = rint_tree[node_id] * Kernel::Rkern;
158
159 using namespace walker::interaction_crit;
160
161 return sph_radix_cell_crit(
162 xyz_a,
163 inter_box_a_min,
164 inter_box_a_max,
165 node_aabb.lower,
166 node_aabb.upper,
167 int_r_max_cell);
168 },
169 [&](u32 id_b) {
170 // compute only omega_a
171 Tvec dr = xyz_a - xyz[id_b];
172 Tscal rab2 = sycl::dot(dr, dr);
173 Tscal rint_b = hpart[id_b] * h_tolerance;
174
175 bool no_interact
176 = rab2 > rint_a * rint_a * Rker2 && rab2 > rint_b * rint_b * Rker2;
177
178 if (!no_interact) {
179 neigh[cnt] = id_b;
180 }
181 cnt += (no_interact) ? 0 : 1;
182 });
183 });
184
185 return pcache;
186 };
187
188 shambase::get_check_ref(storage.neigh_cache).free_alloc();
189
190 using namespace shamrock::patch;
191 scheduler().for_each_patchdata_nonempty([&](Patch cur_p, PatchDataLayer &pdat) {
192 auto &ncache = shambase::get_check_ref(storage.neigh_cache);
193 ncache.neigh_cache.add_obj(cur_p.id_patch, build_neigh_cache(cur_p.id_patch));
194 });
195
196 time_neigh.stop();
197 storage.timings_details.neighbors += time_neigh.elapsed_sec();
198}
199
200template<class Tvec, class Tmorton, template<class> class SPHKernel>
203
204 // interface_control
205 using GhostHandle = sph::BasicSPHGhostHandler<Tvec>;
206 using GhostHandleCache = typename GhostHandle::CacheMap;
208
209 shambase::Timer time_neigh;
210 time_neigh.start();
211
212 StackEntry stack_loc{};
213
214 // do cache
215 auto build_neigh_cache = [&](u64 patch_id) {
216 shamlog_debug_ln("BasicSPH", "build particle cache id =", patch_id);
217
218 NamedStackEntry cache_build_stack_loc{"build cache"};
219
220 auto &mfield = storage.merged_xyzh.get().get(patch_id);
221
222 sham::DeviceBuffer<Tvec> &buf_xyz = mfield.template get_field_buf_ref<Tvec>(0);
223 sham::DeviceBuffer<Tscal> &buf_hpart = mfield.template get_field_buf_ref<Tscal>(1);
224
225 sham::DeviceBuffer<Tscal> &tree_field_rint
226 = storage.rtree_rint_field.get().get(patch_id).buf_field;
227
228 RTree &tree = storage.merged_pos_trees.get().get(patch_id);
229 auto obj_it = tree.get_object_iterator();
230 auto leaf_it = tree.get_traverser();
231
232 u32 leaf_cnt = tree.get_leaf_cell_count();
233 u32 intnode_cnt = tree.get_internal_cell_count();
234
235 u32 obj_cnt = shambase::get_check_ref(storage.part_counts).indexes.get(patch_id);
236
237 sycl::range range_nleaf{leaf_cnt};
238 sycl::range range_nobj{obj_cnt};
239 using namespace shamrock;
240
241 sham::DeviceQueue &q = shamsys::instance::get_compute_scheduler().get_queue();
242
243 Tscal h_tolerance = solver_config.htol_up_coarse_cycle;
244
245 NamedStackEntry stack_loc1{"init cache"};
246
247 // start by counting number of leaf neighbours
248
249 sham::DeviceBuffer<u32> neigh_count_leaf(
250 leaf_cnt, shamsys::instance::get_compute_scheduler_ptr());
251
252 shamlog_debug_sycl_ln("Cache", "generate cache for Nleaf=", leaf_cnt);
253
255 q,
256 sham::MultiRef{tree_field_rint, leaf_it},
257 sham::MultiRef{neigh_count_leaf},
258 leaf_cnt,
259 [intnode_cnt](
260 u32 id_a,
261 const Tscal *__restrict rint_tree,
262 auto leaf_looper,
263 u32 *__restrict neigh_cnt) {
264 u32 offset_leaf = intnode_cnt;
265
266 Tscal leaf_a_rint = rint_tree[offset_leaf + id_a] * Kernel::Rkern;
267 Tvec leaf_a_bmin = leaf_looper.aabb_min[offset_leaf + id_a];
268 Tvec leaf_a_bmax = leaf_looper.aabb_max[offset_leaf + id_a];
269 Tvec leaf_a_bmin_ext = leaf_a_bmin - leaf_a_rint;
270 Tvec leaf_a_bmax_ext = leaf_a_bmax + leaf_a_rint;
271
272 u32 cnt = 0;
273
274 leaf_looper.rtree_for(
275 [&](u32 node_id, shammath::AABB<Tvec> node_aabb) -> bool {
276 Tscal int_r_max_cell = rint_tree[node_id] * Kernel::Rkern;
277
278 Tvec ext_bmin = node_aabb.lower - int_r_max_cell;
279 Tvec ext_bmax = node_aabb.upper + int_r_max_cell;
280
281 return BBAA::cella_neigh_b(leaf_a_bmin, leaf_a_bmax, ext_bmin, ext_bmax)
282 || BBAA::cella_neigh_b(
283 leaf_a_bmin_ext,
284 leaf_a_bmax_ext,
285 node_aabb.lower,
286 node_aabb.upper);
287 },
288 [&](u32 leaf_b) {
289 cnt++;
290 });
291
292 neigh_cnt[id_a] = cnt;
293 });
294
295 //{
296 // u32 offset_leaf = intnode_cnt;
297 // sycl::host_accessor neigh_cnt{neigh_count_leaf};
298 // sycl::host_accessor pos_min_cell
299 // {shambase::get_check_ref(tree.tree_cell_ranges.buf_pos_min_cell_flt)};
300 // sycl::host_accessor pos_max_cell
301 // {shambase::get_check_ref(tree.tree_cell_ranges.buf_pos_max_cell_flt)};
302 //
303 // for (u32 i = 0; i < 1000; i++) {
304 // if(neigh_cnt[i] > 30){
305 // logger::raw_ln(i, neigh_cnt[i], pos_max_cell[i+offset_leaf] -
306 // pos_min_cell[i+offset_leaf]);
307 // }
308 // }
309 //}
310
311 tree::ObjectCache pleaf_cache
312 = tree::prepare_object_cache(std::move(neigh_count_leaf), leaf_cnt);
313
314 // fill ids of leaf neighbours
315
316 NamedStackEntry stack_loc2{"fill cache"};
317
319 q,
320 sham::MultiRef{tree_field_rint, pleaf_cache.scanned_cnt, leaf_it},
321 sham::MultiRef{pleaf_cache.index_neigh_map},
322 leaf_cnt,
323 [intnode_cnt](
324 u32 id_a,
325 const Tscal *__restrict rint_tree,
326 const u32 *__restrict scanned_neigh_cnt,
327 auto leaf_looper,
328 u32 *__restrict neigh) {
329 u32 offset_leaf = intnode_cnt;
330
331 Tscal leaf_a_rint = rint_tree[offset_leaf + id_a] * Kernel::Rkern;
332 Tvec leaf_a_bmin = leaf_looper.aabb_min[offset_leaf + id_a];
333 Tvec leaf_a_bmax = leaf_looper.aabb_max[offset_leaf + id_a];
334 Tvec leaf_a_bmin_ext = leaf_a_bmin - leaf_a_rint;
335 Tvec leaf_a_bmax_ext = leaf_a_bmax + leaf_a_rint;
336
337 u32 cnt = scanned_neigh_cnt[id_a];
338
339 leaf_looper.rtree_for(
340 [&](u32 node_id, shammath::AABB<Tvec> node_aabb) -> bool {
341 Tscal int_r_max_cell = rint_tree[node_id] * Kernel::Rkern;
342
343 Tvec ext_bmin = node_aabb.lower - int_r_max_cell;
344 Tvec ext_bmax = node_aabb.upper + int_r_max_cell;
345
346 return BBAA::cella_neigh_b(leaf_a_bmin, leaf_a_bmax, ext_bmin, ext_bmax)
347 || BBAA::cella_neigh_b(
348 leaf_a_bmin_ext,
349 leaf_a_bmax_ext,
350 node_aabb.lower,
351 node_aabb.upper);
352 },
353 [&](u32 leaf_b) {
354 neigh[cnt] = leaf_b;
355 cnt++;
356 });
357 });
358
359 // search in which leaf each parts are
360 sham::DeviceBuffer<u32> leaf_part_id(
361 obj_cnt, shamsys::instance::get_compute_scheduler_ptr());
362
364 q,
365 sham::MultiRef{buf_xyz, leaf_it},
366 sham::MultiRef{leaf_part_id},
367 obj_cnt,
368 [intnode_cnt](
369 u32 id_a, const Tvec *__restrict xyz, auto leaf_looper, u32 *__restrict found_id) {
370 u32 offset_leaf = intnode_cnt;
371
372 Tvec r_a = xyz[id_a];
373
374 u32 found_id_ = i32_max; // to ensure a crash because of out of bound
375 // access if not found
376
377 leaf_looper.rtree_for(
378 [&](u32 node_id, shammath::AABB<Tvec> node_aabb) -> bool {
379 return BBAA::is_coord_in_range_incl_max(
380 r_a, node_aabb.lower, node_aabb.upper);
381 },
382 [&](u32 leaf_b) {
383 found_id_ = leaf_b - offset_leaf;
384 });
385
386 SHAM_ASSERT(found_id_ < offset_leaf + 1);
387
388 found_id[id_a] = found_id_;
389 });
390
391 //{
392 // sycl::host_accessor xyz{buf_xyz};
393 // sycl::host_accessor acc {leaf_part_id};
394 //
395 // for(u32 i = 0; i < obj_cnt; i++){
396 // u32 leaf_id = acc[i];
397 // if(leaf_id >= leaf_cnt){
398 // logger::raw_ln("error : i=",i,"r=",xyz[i],"leaf_id=",leaf_id);
399 // }
400 // }
401 //}
402
403 sham::DeviceBuffer<u32> neigh_count(
404 obj_cnt, shamsys::instance::get_compute_scheduler_ptr());
405
406 shamlog_debug_sycl_ln("Cache", "generate cache for N=", obj_cnt);
407
409 q,
410 sham::MultiRef{buf_xyz, buf_hpart, pleaf_cache, obj_it.cell_iterator, leaf_part_id},
411 sham::MultiRef{neigh_count},
412 obj_cnt,
413 [intnode_cnt, h_tolerance](
414 u32 id_a,
415 const Tvec *__restrict xyz,
416 const Tscal *__restrict hpart,
417 auto acc_neigh_leaf_looper,
418 auto particle_looper,
419 const u32 *__restrict leaf_owner,
420 u32 *__restrict neigh_cnt) {
421 tree::ObjectCacheIterator neigh_leaf_looper(acc_neigh_leaf_looper);
422
423 u32 offset_leaf = intnode_cnt;
424
425 constexpr Tscal Rker2 = Kernel::Rkern * Kernel::Rkern;
426
427 Tscal rint_a = hpart[id_a] * h_tolerance;
428
429 Tvec xyz_a = xyz[id_a];
430
431 u32 cnt = 0;
432
433 u32 leaf_own_a = leaf_owner[id_a];
434
435 neigh_leaf_looper.for_each_object(leaf_own_a, [&](u32 leaf_b) {
436 SHAM_ASSERT(leaf_b >= offset_leaf);
437
438 particle_looper.for_each_in_leaf_cell(leaf_b - offset_leaf, [&](u32 id_b) {
439 Tvec dr = xyz_a - xyz[id_b];
440 Tscal rab2 = sycl::dot(dr, dr);
441 Tscal rint_b = hpart[id_b] * h_tolerance;
442
443 bool no_interact
444 = rab2 > rint_a * rint_a * Rker2 && rab2 > rint_b * rint_b * Rker2;
445
446 cnt += (no_interact) ? 0 : 1;
447 });
448 });
449
450 neigh_cnt[id_a] = cnt;
451 });
452
453 tree::ObjectCache pcache = tree::prepare_object_cache(std::move(neigh_count), obj_cnt);
454
455 NamedStackEntry stack_loc3{"fill cache"};
456
458 q,
460 buf_xyz,
461 buf_hpart,
462 pleaf_cache,
463 pcache.scanned_cnt,
464 obj_it.cell_iterator,
465 leaf_part_id},
466 sham::MultiRef{pcache.index_neigh_map},
467 obj_cnt,
468 [intnode_cnt, h_tolerance](
469 u32 id_a,
470 const Tvec *__restrict xyz,
471 const Tscal *__restrict hpart,
472 auto acc_neigh_leaf_looper,
473 const u32 *__restrict scanned_neigh_cnt,
474 auto particle_looper,
475 const u32 *__restrict leaf_owner,
476 u32 *__restrict neigh) {
477 tree::ObjectCacheIterator neigh_leaf_looper(acc_neigh_leaf_looper);
478
479 u32 offset_leaf = intnode_cnt;
480
481 constexpr Tscal Rker2 = Kernel::Rkern * Kernel::Rkern;
482
483 Tscal rint_a = hpart[id_a] * h_tolerance;
484
485 Tvec xyz_a = xyz[id_a];
486
487 u32 cnt = scanned_neigh_cnt[id_a];
488
489 u32 leaf_own_a = leaf_owner[id_a];
490
491 neigh_leaf_looper.for_each_object(leaf_own_a, [&](u32 leaf_b) {
492 SHAM_ASSERT(leaf_b >= offset_leaf);
493
494 particle_looper.for_each_in_leaf_cell(leaf_b - offset_leaf, [&](u32 id_b) {
495 Tvec dr = xyz_a - xyz[id_b];
496 Tscal rab2 = sycl::dot(dr, dr);
497 Tscal rint_b = hpart[id_b] * h_tolerance;
498
499 bool no_interact
500 = rab2 > rint_a * rint_a * Rker2 && rab2 > rint_b * rint_b * Rker2;
501
502 if (!no_interact) {
503 neigh[cnt] = id_b;
504 }
505 cnt += (no_interact) ? 0 : 1;
506 });
507 });
508 });
509 return pcache;
510 };
511
512 shambase::get_check_ref(storage.neigh_cache).free_alloc();
513
514 using namespace shamrock::patch;
515 scheduler().for_each_patchdata_nonempty([&](Patch cur_p, PatchDataLayer &pdat) {
516 auto &ncache = shambase::get_check_ref(storage.neigh_cache);
517 ncache.neigh_cache.add_obj(cur_p.id_patch, build_neigh_cache(cur_p.id_patch));
518 });
519
520 time_neigh.stop();
521 storage.timings_details.neighbors += time_neigh.elapsed_sec();
522}
523
524using namespace shammath;
528
constexpr const char * xyz
Position field (3D coordinates).
constexpr const char * hpart
Smoothing length field.
std::uint32_t u32
32 bit unsigned integer
std::uint64_t u64
64 bit unsigned integer
Shamrock assertion utility.
#define SHAM_ASSERT(x)
Shorthand for SHAM_ASSERT_NAMED without a message.
Definition assert.hpp:67
A buffer allocated in USM (Unified Shared Memory).
A SYCL queue associated with a device and a context.
Class Timer measures the time elapsed since the timer was started.
Definition Timer.hpp:36
f64 elapsed_sec() const
Converts the stored nanosecond time to a floating point representation in seconds.
Definition Timer.hpp:88
void start()
Starts the timer.
Definition Timer.hpp:51
void stop()
Stops the timer and stores the elapsed time in nanoseconds.
Definition Timer.hpp:65
PatchDataLayer container class, the layout is described in patchdata_layout.
A Compressed Leaf Bounding Volume Hierarchy (CLBVH) for neighborhood queries.
void kernel_call(sham::DeviceQueue &q, RefIn in, RefOut in_out, u32 n, Functor &&func, SourceLocation &&callsite=SourceLocation{})
Submit a kernel to a SYCL queue.
T & get_check_ref(const std::unique_ptr< T > &ptr, SourceLocation loc=SourceLocation())
Takes a std::unique_ptr and returns a reference to the object it holds. It throws a std::runtime_erro...
Definition memory.hpp:112
namespace for math utility
Definition AABB.hpp:26
namespace for the main framework
Definition __init__.py:1
constexpr i32 i32_max
i32 max value
sph kernels
shambase::details::NamedBasicStackEntry NamedStackEntry
Alias for shambase::details::NamedBasicStackEntry.
shambase::details::BasicStackEntry StackEntry
Alias for shambase::details::BasicStackEntry.
A class that references multiple buffers or similar objects.
Definition MultiRef.hpp:33
Axis-Aligned bounding box.
Definition AABB.hpp:99
T lower
Lower bound of the AABB.
Definition AABB.hpp:104
T upper
Upper bound of the AABB.
Definition AABB.hpp:105
Patch object that contain generic patch information.
Definition Patch.hpp:33
u64 id_patch
unique key that identify the patch
Definition Patch.hpp:86