-
Notifications
You must be signed in to change notification settings - Fork 50
/
Copy pathCabana_VerletList.hpp
1082 lines (947 loc) · 42.9 KB
/
Cabana_VerletList.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/****************************************************************************
* Copyright (c) 2018-2023 by the Cabana authors *
* All rights reserved. *
* *
* This file is part of the Cabana library. Cabana is distributed under a *
* BSD 3-clause license. For the licensing terms see the LICENSE file in *
* the top-level directory. *
* *
* SPDX-License-Identifier: BSD-3-Clause *
****************************************************************************/
/*!
\file Cabana_VerletList.hpp
\brief Verlet grid-accelerated neighbor list
*/
#ifndef CABANA_VERLETLIST_HPP
#define CABANA_VERLETLIST_HPP
#include <Cabana_LinkedCellList.hpp>
#include <Cabana_NeighborList.hpp>
#include <Cabana_Parallel.hpp>
#include <Kokkos_Core.hpp>
#include <Kokkos_Profiling_ScopedRegion.hpp>
#include <cassert>
namespace Cabana
{
//---------------------------------------------------------------------------//
// Verlet List Memory Layout Tag.
//---------------------------------------------------------------------------//
//! CSR (compressed sparse row) neighbor list layout.
struct VerletLayoutCSR
{
};
//! 2D array neighbor list layout.
struct VerletLayout2D
{
};
//---------------------------------------------------------------------------//
// Verlet List Data.
//---------------------------------------------------------------------------//
template <class MemorySpace, class LayoutTag>
struct VerletListData;
//! Store the VerletList compressed sparse row (CSR) neighbor data.
template <class MemorySpace>
struct VerletListData<MemorySpace, VerletLayoutCSR>
{
//! Kokkos memory space.
using memory_space = MemorySpace;
//! Number of neighbors per particle.
Kokkos::View<int*, memory_space> counts;
//! Offsets into the neighbor list.
Kokkos::View<int*, memory_space> offsets;
//! Neighbor list.
Kokkos::View<int*, memory_space> neighbors;
//! Add a neighbor to the list.
KOKKOS_INLINE_FUNCTION
void addNeighbor( const int pid, const int nid ) const
{
neighbors( offsets( pid ) +
Kokkos::atomic_fetch_add( &counts( pid ), 1 ) ) = nid;
}
//! Modify a neighbor in the list.
KOKKOS_INLINE_FUNCTION
void setNeighbor( const int pid, const int nid, const int new_id ) const
{
neighbors( offsets( pid ) + nid ) = new_id;
}
};
//! Store the VerletList 2D neighbor data.
template <class MemorySpace>
struct VerletListData<MemorySpace, VerletLayout2D>
{
//! Kokkos memory space.
using memory_space = MemorySpace;
//! Number of neighbors per particle.
Kokkos::View<int*, memory_space> counts;
//! Neighbor list.
Kokkos::View<int**, memory_space> neighbors;
//! Actual maximum neighbors per particle (potentially less than allocated
//! space).
std::size_t max_n;
//! Add a neighbor to the list.
KOKKOS_INLINE_FUNCTION
void addNeighbor( const int pid, const int nid ) const
{
std::size_t count = Kokkos::atomic_fetch_add( &counts( pid ), 1 );
if ( count < neighbors.extent( 1 ) )
neighbors( pid, count ) = nid;
}
//! Modify a neighbor in the list.
KOKKOS_INLINE_FUNCTION
void setNeighbor( const int pid, const int nid, const int new_id ) const
{
neighbors( pid, nid ) = new_id;
}
};
//---------------------------------------------------------------------------//
//---------------------------------------------------------------------------//
namespace Impl
{
//! \cond Impl
//---------------------------------------------------------------------------//
// Verlet List Builder
//---------------------------------------------------------------------------//
template <class DeviceType, class RandomAccessPositionType, class RadiusType,
class AlgorithmTag, class LayoutTag, class BuildOpTag>
struct VerletListBuilder
{
// Types.
using device = DeviceType;
using PositionValueType = typename RandomAccessPositionType::value_type;
using memory_space = typename device::memory_space;
using execution_space = typename device::execution_space;
// List data.
VerletListData<memory_space, LayoutTag> _data;
// Background squared neighbor cutoff.
PositionValueType rsqr;
// Fixed or per-particle neighbor radius.
RadiusType radius;
// Positions.
RandomAccessPositionType _position;
std::size_t pid_begin, pid_end;
// Binning Data.
BinningData<memory_space> bin_data_1d;
LinkedCellList<memory_space, PositionValueType> linked_cell_list;
// Check to count or refill.
bool refill;
bool count;
// Maximum allocated neighbors per particle
std::size_t alloc_n;
// Constructor with a single cutoff radius.
template <class PositionType>
VerletListBuilder( PositionType positions, const std::size_t begin,
const std::size_t end,
const RadiusType neighborhood_radius,
const PositionValueType cell_size_ratio,
const PositionValueType grid_min[3],
const PositionValueType grid_max[3],
const std::size_t max_neigh )
: pid_begin( begin )
, pid_end( end )
, alloc_n( max_neigh )
{
init( positions, neighborhood_radius, cell_size_ratio, grid_min,
grid_max );
// This value is not currently used, but set to be consistent with the
// variable cutoff case below.
radius = neighborhood_radius;
}
// Constructor with a background radius (used for the LinkedCellList) and a
// per-particle radius.
template <class PositionType>
VerletListBuilder( PositionType positions, const std::size_t begin,
const std::size_t end,
const PositionValueType background_radius,
const RadiusType neighborhood_radius,
const PositionValueType cell_size_ratio,
const PositionValueType grid_min[3],
const PositionValueType grid_max[3],
const std::size_t max_neigh )
: pid_begin( begin )
, pid_end( end )
, alloc_n( max_neigh )
{
assert( positions.size() == neighborhood_radius.size() );
init( positions, background_radius, cell_size_ratio, grid_min,
grid_max );
// Store a shallow copy (not squared).
// TODO: for cases where the radii never change, this could be better
// optimized with a deep copy of the squared radius instead.
radius = neighborhood_radius;
}
template <class PositionType>
void init( PositionType positions,
const PositionValueType neighborhood_radius,
const PositionValueType cell_size_ratio,
const PositionValueType grid_min[3],
const PositionValueType grid_max[3] )
{
count = true;
refill = false;
// Create the count view.
_data.counts = Kokkos::View<int*, memory_space>( "num_neighbors",
size( positions ) );
// Make a guess for the number of neighbors per particle for 2D lists.
initCounts( LayoutTag() );
// Shallow copy for random access read-only memory.
_position = positions;
// Bin the particles in the grid. Don't actually sort them but make a
// permutation vector. Note that we are binning all particles here and
// not just the requested range. This is because all particles are
// treated as candidates for neighbors.
double grid_size = cell_size_ratio * neighborhood_radius;
PositionValueType grid_delta[3] = { grid_size, grid_size, grid_size };
linked_cell_list = createLinkedCellList<memory_space>(
_position, grid_delta, grid_min, grid_max, neighborhood_radius,
cell_size_ratio );
bin_data_1d = linked_cell_list.binningData();
// We will use the square of the distance for neighbor determination.
rsqr = neighborhood_radius * neighborhood_radius;
}
KOKKOS_INLINE_FUNCTION auto cutoff( [[maybe_unused]] const int p ) const
{
// Square the radius on the fly if using a per-particle field to avoid a
// deep copy.
if constexpr ( is_slice<RadiusType>::value ||
Kokkos::is_view<RadiusType>::value )
return radius( p ) * radius( p );
// This value is already squared.
else
return rsqr;
}
// Neighbor count team operator (only used for CSR lists).
struct CountNeighborsTag
{
};
using CountNeighborsPolicy =
Kokkos::TeamPolicy<execution_space, CountNeighborsTag,
Kokkos::IndexType<int>,
Kokkos::Schedule<Kokkos::Dynamic>>;
KOKKOS_INLINE_FUNCTION
void
operator()( const CountNeighborsTag&,
const typename CountNeighborsPolicy::member_type& team ) const
{
// The league rank of the team is the cardinal cell index we are
// working on.
int cell = team.league_rank();
// Get the stencil for this cell.
int imin, imax, jmin, jmax, kmin, kmax;
linked_cell_list.getStencilCells( cell, imin, imax, jmin, jmax, kmin,
kmax );
// Operate on the particles in the bin.
std::size_t b_offset = bin_data_1d.binOffset( cell );
Kokkos::parallel_for(
Kokkos::TeamThreadRange( team, 0, bin_data_1d.binSize( cell ) ),
[&]( const int bi )
{
// Get the true particle id. The binned particle index is the
// league rank of the team.
std::size_t pid = linked_cell_list.permutation( bi + b_offset );
if ( ( pid >= pid_begin ) && ( pid < pid_end ) )
{
// Cache the particle coordinates.
double x_p = _position( pid, 0 );
double y_p = _position( pid, 1 );
double z_p = _position( pid, 2 );
// Loop over the cell stencil.
int stencil_count = 0;
for ( int i = imin; i < imax; ++i )
for ( int j = jmin; j < jmax; ++j )
for ( int k = kmin; k < kmax; ++k )
{
// See if we should actually check this box for
// neighbors.
if ( linked_cell_list.cellStencil()
.grid.minDistanceToPoint(
x_p, y_p, z_p, i, j, k ) <= rsqr )
{
std::size_t n_offset =
linked_cell_list.binOffset( i, j, k );
std::size_t num_n =
linked_cell_list.binSize( i, j, k );
// Check the particles in this bin to see if
// they are neighbors. If they are add to
// the count for this bin.
int cell_count = 0;
neighbor_reduce( team, pid, x_p, y_p, z_p,
n_offset, num_n,
cell_count, BuildOpTag() );
stencil_count += cell_count;
}
}
Kokkos::single( Kokkos::PerThread( team ), [&]()
{ _data.counts( pid ) = stencil_count; } );
}
} );
}
// Neighbor count team vector loop (only used for CSR lists).
KOKKOS_INLINE_FUNCTION void
neighbor_reduce( const typename CountNeighborsPolicy::member_type& team,
const std::size_t pid, const double x_p, const double y_p,
const double z_p, const int n_offset, const int num_n,
int& cell_count, TeamVectorOpTag ) const
{
Kokkos::parallel_reduce(
Kokkos::ThreadVectorRange( team, num_n ),
[&]( const int n, int& local_count ) {
neighbor_kernel( pid, x_p, y_p, z_p, n_offset, n, local_count );
},
cell_count );
}
// Neighbor count serial loop (only used for CSR lists).
KOKKOS_INLINE_FUNCTION
void neighbor_reduce( const typename CountNeighborsPolicy::member_type,
const std::size_t pid, const double x_p,
const double y_p, const double z_p,
const int n_offset, const int num_n, int& cell_count,
TeamOpTag ) const
{
for ( int n = 0; n < num_n; n++ )
neighbor_kernel( pid, x_p, y_p, z_p, n_offset, n, cell_count );
}
// Neighbor count kernel
KOKKOS_INLINE_FUNCTION
void neighbor_kernel( const int pid, const double x_p, const double y_p,
const double z_p, const int n_offset, const int n,
int& local_count ) const
{
// Get the true id of the candidate neighbor.
std::size_t nid = linked_cell_list.permutation( n_offset + n );
// Cache the candidate neighbor particle coordinates.
double x_n = _position( nid, 0 );
double y_n = _position( nid, 1 );
double z_n = _position( nid, 2 );
// If this could be a valid neighbor, continue.
if ( NeighborDiscriminator<AlgorithmTag>::isValid(
pid, x_p, y_p, z_p, nid, x_n, y_n, z_n ) )
{
// Calculate the distance between the particle and its candidate
// neighbor.
PositionValueType dx = x_p - x_n;
PositionValueType dy = y_p - y_n;
PositionValueType dz = z_p - z_n;
PositionValueType dist_sqr = dx * dx + dy * dy + dz * dz;
// If within the cutoff add to the count.
if ( dist_sqr <= cutoff( pid ) )
local_count += 1;
}
}
// Process the CSR counts by computing offsets and allocating the neighbor
// list.
template <class KokkosMemorySpace>
struct OffsetScanOp
{
using kokkos_mem_space = KokkosMemorySpace;
Kokkos::View<int*, kokkos_mem_space> counts;
Kokkos::View<int*, kokkos_mem_space> offsets;
KOKKOS_INLINE_FUNCTION
void operator()( const int i, int& update, const bool final_pass ) const
{
if ( final_pass )
offsets( i ) = update;
update += counts( i );
}
};
void initCounts( VerletLayoutCSR ) {}
void initCounts( VerletLayout2D )
{
if ( alloc_n > 0 )
{
count = false;
_data.neighbors = Kokkos::View<int**, memory_space>(
Kokkos::ViewAllocateWithoutInitializing( "neighbors" ),
_data.counts.size(), alloc_n );
}
}
void processCounts( VerletLayoutCSR )
{
// Allocate offsets.
_data.offsets = Kokkos::View<int*, memory_space>(
Kokkos::ViewAllocateWithoutInitializing( "neighbor_offsets" ),
_data.counts.size() );
// Calculate offsets from counts and the total number of counts.
OffsetScanOp<memory_space> offset_op;
offset_op.counts = _data.counts;
offset_op.offsets = _data.offsets;
int total_num_neighbor;
Kokkos::RangePolicy<execution_space> range_policy(
0, _data.counts.extent( 0 ) );
Kokkos::parallel_scan( "Cabana::VerletListBuilder::offset_scan",
range_policy, offset_op, total_num_neighbor );
Kokkos::fence();
// Allocate the neighbor list.
_data.neighbors = Kokkos::View<int*, memory_space>(
Kokkos::ViewAllocateWithoutInitializing( "neighbors" ),
total_num_neighbor );
// Reset the counts. We count again when we fill.
Kokkos::deep_copy( _data.counts, 0 );
}
// Process 2D counts by computing the maximum number of neighbors and
// reallocating the 2D data structure if needed.
void processCounts( VerletLayout2D )
{
// Calculate the maximum number of neighbors.
auto counts = _data.counts;
int max;
Kokkos::Max<int> max_reduce( max );
Kokkos::parallel_reduce(
"Cabana::VerletListBuilder::reduce_max",
Kokkos::RangePolicy<execution_space>( 0, _data.counts.size() ),
KOKKOS_LAMBDA( const int i, int& value ) {
if ( counts( i ) > value )
value = counts( i );
},
max_reduce );
Kokkos::fence();
_data.max_n = static_cast<std::size_t>( max );
// Reallocate the neighbor list if previous size is exceeded.
if ( count or _data.max_n > _data.neighbors.extent( 1 ) )
{
refill = true;
Kokkos::deep_copy( _data.counts, 0 );
_data.neighbors = Kokkos::View<int**, memory_space>(
Kokkos::ViewAllocateWithoutInitializing( "neighbors" ),
_data.counts.size(), _data.max_n );
}
}
// Neighbor count team operator.
struct FillNeighborsTag
{
};
using FillNeighborsPolicy =
Kokkos::TeamPolicy<execution_space, FillNeighborsTag,
Kokkos::IndexType<int>,
Kokkos::Schedule<Kokkos::Dynamic>>;
KOKKOS_INLINE_FUNCTION
void
operator()( const FillNeighborsTag&,
const typename FillNeighborsPolicy::member_type& team ) const
{
// The league rank of the team is the cardinal cell index we are
// working on.
int cell = team.league_rank();
// Get the stencil for this cell.
int imin, imax, jmin, jmax, kmin, kmax;
linked_cell_list.getStencilCells( cell, imin, imax, jmin, jmax, kmin,
kmax );
// Operate on the particles in the bin.
std::size_t b_offset = bin_data_1d.binOffset( cell );
Kokkos::parallel_for(
Kokkos::TeamThreadRange( team, 0, bin_data_1d.binSize( cell ) ),
[&]( const int bi )
{
// Get the true particle id. The binned particle index is the
// league rank of the team.
std::size_t pid = linked_cell_list.permutation( bi + b_offset );
if ( ( pid >= pid_begin ) && ( pid < pid_end ) )
{
// Cache the particle coordinates.
double x_p = _position( pid, 0 );
double y_p = _position( pid, 1 );
double z_p = _position( pid, 2 );
// Loop over the cell stencil.
for ( int i = imin; i < imax; ++i )
for ( int j = jmin; j < jmax; ++j )
for ( int k = kmin; k < kmax; ++k )
{
// See if we should actually check this box for
// neighbors.
if ( linked_cell_list.cellStencil()
.grid.minDistanceToPoint(
x_p, y_p, z_p, i, j, k ) <= rsqr )
{
// Check the particles in this bin to see if
// they are neighbors.
std::size_t n_offset =
linked_cell_list.binOffset( i, j, k );
int num_n =
linked_cell_list.binSize( i, j, k );
neighbor_for( team, pid, x_p, y_p, z_p,
n_offset, num_n,
BuildOpTag() );
}
}
}
} );
}
// Neighbor fill team vector loop.
KOKKOS_INLINE_FUNCTION void
neighbor_for( const typename FillNeighborsPolicy::member_type& team,
const std::size_t pid, const double x_p, const double y_p,
const double z_p, const int n_offset, const int num_n,
TeamVectorOpTag ) const
{
Kokkos::parallel_for(
Kokkos::ThreadVectorRange( team, num_n ), [&]( const int n )
{ neighbor_kernel( pid, x_p, y_p, z_p, n_offset, n ); } );
}
// Neighbor fill serial loop.
KOKKOS_INLINE_FUNCTION
void neighbor_for( const typename FillNeighborsPolicy::member_type team,
const std::size_t pid, const double x_p,
const double y_p, const double z_p, const int n_offset,
const int num_n, TeamOpTag ) const
{
for ( int n = 0; n < num_n; n++ )
Kokkos::single(
Kokkos::PerThread( team ),
[&]() { neighbor_kernel( pid, x_p, y_p, z_p, n_offset, n ); } );
}
// Neighbor fill kernel.
KOKKOS_INLINE_FUNCTION
void neighbor_kernel( const int pid, const double x_p, const double y_p,
const double z_p, const int n_offset,
const int n ) const
{
// Get the true id of the candidate neighbor.
std::size_t nid = linked_cell_list.permutation( n_offset + n );
// Cache the candidate neighbor particle coordinates.
double x_n = _position( nid, 0 );
double y_n = _position( nid, 1 );
double z_n = _position( nid, 2 );
// If this could be a valid neighbor, continue.
if ( NeighborDiscriminator<AlgorithmTag>::isValid(
pid, x_p, y_p, z_p, nid, x_n, y_n, z_n ) )
{
// Calculate the distance between the particle and its candidate
// neighbor.
PositionValueType dx = x_p - x_n;
PositionValueType dy = y_p - y_n;
PositionValueType dz = z_p - z_n;
PositionValueType dist_sqr = dx * dx + dy * dy + dz * dz;
// If within the cutoff increment the neighbor count and add as a
// neighbor at that index.
if ( dist_sqr <= cutoff( pid ) )
{
_data.addNeighbor( pid, nid );
}
}
}
};
// Builder creation functions. This is only necessary to define the different
// random access types.
template <class DeviceType, class AlgorithmTag, class LayoutTag,
class BuildOpTag, class PositionType>
auto createVerletListBuilder(
PositionType x, const std::size_t begin, const std::size_t end,
const typename PositionType::value_type radius,
const typename PositionType::value_type cell_size_ratio,
const typename PositionType::value_type grid_min[3],
const typename PositionType::value_type grid_max[3],
const std::size_t max_neigh,
typename std::enable_if<( is_slice<PositionType>::value ), int>::type* = 0 )
{
using RandomAccessPositionType = typename PositionType::random_access_slice;
return VerletListBuilder<DeviceType, RandomAccessPositionType,
typename PositionType::value_type, AlgorithmTag,
LayoutTag, BuildOpTag>(
x, begin, end, radius, cell_size_ratio, grid_min, grid_max, max_neigh );
}
template <class DeviceType, class AlgorithmTag, class LayoutTag,
class BuildOpTag, class PositionType>
auto createVerletListBuilder(
PositionType x, const std::size_t begin, const std::size_t end,
const typename PositionType::value_type radius,
const typename PositionType::value_type cell_size_ratio,
const typename PositionType::value_type grid_min[3],
const typename PositionType::value_type grid_max[3],
const std::size_t max_neigh,
typename std::enable_if<( Kokkos::is_view<PositionType>::value ),
int>::type* = 0 )
{
using RandomAccessPositionType =
Kokkos::View<typename PositionType::value_type**, DeviceType,
Kokkos::MemoryTraits<Kokkos::RandomAccess>>;
return VerletListBuilder<DeviceType, RandomAccessPositionType,
typename PositionType::value_type, AlgorithmTag,
LayoutTag, BuildOpTag>(
x, begin, end, radius, cell_size_ratio, grid_min, grid_max, max_neigh );
}
template <class DeviceType, class AlgorithmTag, class LayoutTag,
class BuildOpTag, class PositionType, class RadiusType>
auto createVerletListBuilder(
PositionType x, const std::size_t begin, const std::size_t end,
const typename PositionType::value_type background_radius,
const RadiusType radius,
const typename PositionType::value_type cell_size_ratio,
const typename PositionType::value_type grid_min[3],
const typename PositionType::value_type grid_max[3],
const std::size_t max_neigh,
typename std::enable_if<( is_slice<PositionType>::value ), int>::type* = 0 )
{
using RandomAccessPositionType = typename PositionType::random_access_slice;
return VerletListBuilder<DeviceType, RandomAccessPositionType, RadiusType,
AlgorithmTag, LayoutTag, BuildOpTag>(
x, begin, end, background_radius, radius, cell_size_ratio, grid_min,
grid_max, max_neigh );
}
template <class DeviceType, class AlgorithmTag, class LayoutTag,
class BuildOpTag, class PositionType, class RadiusType>
auto createVerletListBuilder(
PositionType x, const std::size_t begin, const std::size_t end,
const typename PositionType::value_type background_radius,
const RadiusType radius,
const typename PositionType::value_type cell_size_ratio,
const typename PositionType::value_type grid_min[3],
const typename PositionType::value_type grid_max[3],
const std::size_t max_neigh,
typename std::enable_if<( Kokkos::is_view<PositionType>::value ),
int>::type* = 0 )
{
using RandomAccessPositionType =
Kokkos::View<typename PositionType::value_type**, DeviceType,
Kokkos::MemoryTraits<Kokkos::RandomAccess>>;
return VerletListBuilder<DeviceType, RandomAccessPositionType, RadiusType,
AlgorithmTag, LayoutTag, BuildOpTag>(
x, begin, end, background_radius, radius, cell_size_ratio, grid_min,
grid_max, max_neigh );
}
//---------------------------------------------------------------------------//
//! \endcond
} // end namespace Impl
//---------------------------------------------------------------------------//
/*!
\brief Neighbor list implementation based on binning particles on a 3d
Cartesian grid with cells of the same size as the interaction distance.
\tparam MemorySpace The Kokkos memory space for storing the neighbor list.
\tparam AlgorithmTag Tag indicating whether to build a full or half neighbor
list.
\tparam LayoutTag Tag indicating whether to use a CSR or 2D data layout.
\tparam BuildTag Tag indicating whether to use hierarchical team or team
vector parallelism when building neighbor lists.
Neighbor list implementation most appropriate for somewhat regularly
distributed particles due to the use of a Cartesian grid.
*/
template <class MemorySpace, class AlgorithmTag, class LayoutTag,
class BuildTag = TeamVectorOpTag>
class VerletList
{
public:
static_assert( Kokkos::is_memory_space<MemorySpace>::value, "" );
//! Kokkos memory space in which the neighbor list data resides.
using memory_space = MemorySpace;
//! Kokkos default execution space for this memory space.
using execution_space = typename memory_space::execution_space;
//! Verlet list data.
VerletListData<memory_space, LayoutTag> _data;
/*!
\brief Default constructor.
*/
VerletList() {}
/*!
\brief VerletList constructor. Given a list of particle positions and
a neighborhood radius calculate the neighbor list.
\param x The particle positions
\param begin The beginning particle index to compute neighbors for.
\param end The end particle index to compute neighbors for.
\param neighborhood_radius The radius of the neighborhood. Particles
within this radius are considered neighbors. This is effectively the
grid cell size in each dimension.
\param cell_size_ratio The ratio of the cell size in the Cartesian grid
to the neighborhood radius. For example, if the cell size ratio is 0.5
then the cells will be half the size of the neighborhood radius in each
dimension.
\param grid_min The minimum value of the grid containing the particles
in each dimension.
\param grid_max The maximum value of the grid containing the particles
in each dimension.
\param max_neigh Optional maximum number of neighbors per particle to
pre-allocate the neighbor list. Potentially avoids recounting with 2D
layout only.
Particles outside of the neighborhood radius will not be considered
neighbors. Only compute the neighbors of those that are within the given
range. All particles are candidates for being a neighbor, regardless of
whether or not they are in the range.
*/
template <class PositionType>
VerletList(
PositionType x, const std::size_t begin, const std::size_t end,
const typename PositionType::value_type neighborhood_radius,
const typename PositionType::value_type cell_size_ratio,
const typename PositionType::value_type grid_min[3],
const typename PositionType::value_type grid_max[3],
const std::size_t max_neigh = 0,
typename std::enable_if<( is_slice<PositionType>::value ||
Kokkos::is_view<PositionType>::value ),
int>::type* = 0 )
{
build( x, begin, end, neighborhood_radius, cell_size_ratio, grid_min,
grid_max, max_neigh );
}
/*!
\brief VerletList constructor. Given a list of particle positions and
a neighborhood radius calculate the neighbor list.
\param x The slice containing the particle positions
\param begin The beginning particle index to compute neighbors for.
\param end The end particle index to compute neighbors for.
\param background_radius The radius of the neighborhood used
for the background grid cells in each dimension.
\param neighborhood_radius The radius of the neighborhood per particle.
Particles within this radius are considered neighbors.
\param cell_size_ratio The ratio of the cell size in the Cartesian grid
to the neighborhood radius. For example, if the cell size ratio is 0.5
then the cells will be half the size of the neighborhood radius in each
dimension.
\param grid_min The minimum value of the grid containing the particles
in each dimension.
\param grid_max The maximum value of the grid containing the particles
in each dimension.
\param max_neigh Optional maximum number of neighbors per particle to
pre-allocate the neighbor list. Potentially avoids recounting with 2D
layout only.
Particles outside of the neighborhood radius will not be considered
neighbors. Only compute the neighbors of those that are within the given
range. All particles are candidates for being a neighbor, regardless of
whether or not they are in the range.
*/
template <class PositionSlice, class RadiusSlice>
VerletList( PositionSlice x, const std::size_t begin, const std::size_t end,
const typename PositionSlice::value_type background_radius,
RadiusSlice neighborhood_radius,
const typename PositionSlice::value_type cell_size_ratio,
const typename PositionSlice::value_type grid_min[3],
const typename PositionSlice::value_type grid_max[3],
const std::size_t max_neigh = 0,
typename std::enable_if<( is_slice<PositionSlice>::value ),
int>::type* = 0 )
{
build( x, begin, end, background_radius, neighborhood_radius,
cell_size_ratio, grid_min, grid_max, max_neigh );
}
/*!
\brief Given a list of particle positions and a neighborhood radius
calculate the neighbor list.
*/
template <class PositionType>
void
build( PositionType x, const std::size_t begin, const std::size_t end,
const typename PositionType::value_type neighborhood_radius,
const typename PositionType::value_type cell_size_ratio,
const typename PositionType::value_type grid_min[3],
const typename PositionType::value_type grid_max[3],
const std::size_t max_neigh = 0,
typename std::enable_if<( is_slice<PositionType>::value ||
Kokkos::is_view<PositionType>::value ),
int>::type* = 0 )
{
// Use the default execution space.
build( execution_space{}, x, begin, end, neighborhood_radius,
cell_size_ratio, grid_min, grid_max, max_neigh );
}
/*!
\brief Given a list of particle positions and a neighborhood radius
calculate the neighbor list.
*/
template <class PositionType, class ExecutionSpace>
void
build( ExecutionSpace, PositionType x, const std::size_t begin,
const std::size_t end,
const typename PositionType::value_type neighborhood_radius,
const typename PositionType::value_type cell_size_ratio,
const typename PositionType::value_type grid_min[3],
const typename PositionType::value_type grid_max[3],
const std::size_t max_neigh = 0,
typename std::enable_if<( is_slice<PositionType>::value ||
Kokkos::is_view<PositionType>::value ),
int>::type* = 0 )
{
Kokkos::Profiling::ScopedRegion region( "Cabana::VerletList::build" );
static_assert( is_accessible_from<memory_space, ExecutionSpace>{}, "" );
assert( end >= begin );
assert( end <= size( x ) );
using device_type = Kokkos::Device<ExecutionSpace, memory_space>;
// Create a builder functor.
auto builder = Impl::createVerletListBuilder<device_type, AlgorithmTag,
LayoutTag, BuildTag>(
x, begin, end, neighborhood_radius, cell_size_ratio, grid_min,
grid_max, max_neigh );
buildImpl( builder );
}
/*!
\brief Given a list of particle positions and a neighborhood radius
calculate the neighbor list.
*/
template <class PositionSlice, class RadiusSlice>
void build( PositionSlice x, const std::size_t begin, const std::size_t end,
const typename PositionSlice::value_type background_radius,
RadiusSlice neighborhood_radius,
const typename PositionSlice::value_type cell_size_ratio,
const typename PositionSlice::value_type grid_min[3],
const typename PositionSlice::value_type grid_max[3],
const std::size_t max_neigh = 0 )
{
// Use the default execution space.
build( execution_space{}, x, begin, end, background_radius,
neighborhood_radius, cell_size_ratio, grid_min, grid_max,
max_neigh );
}
/*!
\brief Given a list of particle positions and a neighborhood radius
calculate the neighbor list.
*/
template <class PositionSlice, class RadiusSlice, class ExecutionSpace>
void build( ExecutionSpace, PositionSlice x, const std::size_t begin,
const std::size_t end,
const typename PositionSlice::value_type background_radius,
RadiusSlice neighborhood_radius,
const typename PositionSlice::value_type cell_size_ratio,
const typename PositionSlice::value_type grid_min[3],
const typename PositionSlice::value_type grid_max[3],
const std::size_t max_neigh = 0 )
{
Kokkos::Profiling::ScopedRegion region( "Cabana::VerletList::build" );
static_assert( is_accessible_from<memory_space, ExecutionSpace>{}, "" );
assert( end >= begin );
assert( end <= x.size() );
// Create a builder functor.
using device_type = Kokkos::Device<ExecutionSpace, memory_space>;
auto builder = Impl::createVerletListBuilder<device_type, AlgorithmTag,
LayoutTag, BuildTag>(
x, begin, end, background_radius, neighborhood_radius,
cell_size_ratio, grid_min, grid_max, max_neigh );
buildImpl( builder );
}
//! \cond Impl
template <class BuilderType>
void buildImpl( BuilderType builder )
{
// For each particle in the range check each neighboring bin for
// neighbor particles. Bins are at least the size of the
// neighborhood radius so the bin in which the particle resides and
// any surrounding bins are guaranteed to contain the neighboring
// particles. For CSR lists, we count, then fill neighbors. For 2D
// lists, we count and fill at the same time, unless the array size
// is exceeded, at which point only counting is continued to
// reallocate and refill.
typename BuilderType::FillNeighborsPolicy fill_policy(
builder.bin_data_1d.numBin(), Kokkos::AUTO, 4 );
if ( builder.count )
{
typename BuilderType::CountNeighborsPolicy count_policy(
builder.bin_data_1d.numBin(), Kokkos::AUTO, 4 );
Kokkos::parallel_for( "Cabana::VerletList::count_neighbors",
count_policy, builder );
}
else
{
builder.processCounts( LayoutTag() );
Kokkos::parallel_for( "Cabana::VerletList::fill_neighbors",
fill_policy, builder );
}
Kokkos::fence();
// Process the counts by computing offsets and allocating the neighbor
// list, if needed.
builder.processCounts( LayoutTag() );
// For each particle in the range fill (or refill) its part of the
// neighbor list.
if ( builder.count or builder.refill )
{
Kokkos::parallel_for( "Cabana::VerletList::fill_neighbors",
fill_policy, builder );
Kokkos::fence();
}
// Get the data from the builder.
_data = builder._data;
}
//! \endcond
//! Modify a neighbor in the list; for example, mark it as a broken bond.
KOKKOS_INLINE_FUNCTION
void setNeighbor( const std::size_t particle_index,
const std::size_t neighbor_index,
const int new_index ) const
{
_data.setNeighbor( particle_index, neighbor_index, new_index );
}
};
//---------------------------------------------------------------------------//
// Neighbor list interface implementation.
//---------------------------------------------------------------------------//
//! CSR VerletList NeighborList interface.
template <class MemorySpace, class AlgorithmTag, class BuildTag>
class NeighborList<
VerletList<MemorySpace, AlgorithmTag, VerletLayoutCSR, BuildTag>>
{
public:
//! Kokkos memory space.
using memory_space = MemorySpace;
//! Neighbor list type.
using list_type =
VerletList<MemorySpace, AlgorithmTag, VerletLayoutCSR, BuildTag>;
//! Get the total number of neighbors across all particles.
KOKKOS_INLINE_FUNCTION
static std::size_t totalNeighbor( const list_type& list )
{
// Size of the allocated memory gives total neighbors.
return list._data.neighbors.extent( 0 );
}