Skip to main content

bempp_octree/
tools.rs

1//! Utility routines.
2
3use itertools::{izip, Itertools};
4use mpi::{
5    collective::{SystemOperation, UserOperation},
6    datatype::{Partition, PartitionMut},
7    point_to_point as p2p,
8    traits::{CommunicatorCollectives, Destination, Equivalence, Root, Source},
9};
10use num::traits::Zero;
11use rand::{Rng, SeedableRng};
12use rand_chacha::ChaCha8Rng;
13
14use crate::{
15    constants::{DEEPEST_LEVEL, LEVEL_SIZE},
16    geometry::Point,
17    morton::MortonKey,
18};
19
20/// Gather array to all processes
21pub fn gather_to_all<T: Equivalence, C: CommunicatorCollectives>(arr: &[T], comm: &C) -> Vec<T> {
22    // First we need to broadcast the individual sizes on each process.
23
24    let size = comm.size();
25
26    let local_len = arr.len() as i32;
27
28    let mut sizes = vec![0; size as usize];
29
30    comm.all_gather_into(&local_len, &mut sizes);
31
32    let recv_len = sizes.iter().sum::<i32>() as usize;
33
34    // Now we have the size of each local contribution.
35    // let mut recvbuffer =
36    //     vec![T: Default; counts_from_processor.iter().sum::<i32>() as usize];
37    let mut recvbuffer = Vec::<T>::with_capacity(recv_len);
38    let buf: &mut [T] = unsafe { std::mem::transmute(recvbuffer.spare_capacity_mut()) };
39
40    let recv_displs: Vec<i32> = sizes
41        .iter()
42        .scan(0, |acc, &x| {
43            let tmp = *acc;
44            *acc += x;
45            Some(tmp)
46        })
47        .collect();
48
49    let mut receiv_partition = PartitionMut::new(buf, sizes, &recv_displs[..]);
50
51    comm.all_gather_varcount_into(arr, &mut receiv_partition);
52
53    unsafe { recvbuffer.set_len(recv_len) };
54
55    recvbuffer
56}
57/// Array to root
58
59/// Gather distributed array to the root rank.
60///
61/// The result is a `Vec<T>` on root and `None` on all other ranks.
62pub fn gather_to_root<T: Equivalence, C: CommunicatorCollectives>(
63    arr: &[T],
64    comm: &C,
65) -> Option<Vec<T>> {
66    let n = arr.len() as i32;
67    let rank = comm.rank();
68    let size = comm.size();
69    let root_process = comm.process_at_rank(0);
70
71    // We first communicate the length of the array to root.
72
73    if rank == 0 {
74        // We are at root.
75
76        let mut counts = vec![0_i32; size as usize];
77        root_process.gather_into_root(&n, &mut counts);
78
79        // We now have all ranks at root. Can now a varcount gather to get
80        // the array elements.
81
82        let nelements = counts.iter().sum::<i32>();
83        let mut new_arr = Vec::<T>::with_capacity(nelements as usize);
84        let new_arr_buf: &mut [T] = unsafe { std::mem::transmute(new_arr.spare_capacity_mut()) };
85
86        let displs = displacements(counts.as_slice());
87
88        let mut partition = PartitionMut::new(new_arr_buf, counts, &displs[..]);
89
90        root_process.gather_varcount_into_root(arr, &mut partition);
91
92        unsafe { new_arr.set_len(nelements as usize) };
93        Some(new_arr)
94    } else {
95        root_process.gather_into(&n);
96        root_process.gather_varcount_into(arr);
97        None
98    }
99}
100
101/// Get global size of a distributed array.
102///
103/// Computes the size and broadcoasts it to all ranks.
104pub fn global_size<T, C: CommunicatorCollectives>(arr: &[T], comm: &C) -> usize {
105    let local_size = arr.len();
106    let mut global_size = 0;
107
108    comm.all_reduce_into(&local_size, &mut global_size, SystemOperation::sum());
109
110    global_size
111}
112
113/// Get the maximum value across all ranks
114pub fn global_max<T: Equivalence + Copy + Ord, C: CommunicatorCollectives>(
115    arr: &[T],
116    comm: &C,
117) -> T {
118    let local_max = arr.iter().max().unwrap();
119
120    // Just need to initialize global_max with something.
121    let mut global_max = *local_max;
122
123    comm.all_reduce_into(
124        local_max,
125        &mut global_max,
126        &UserOperation::commutative(|x, y| {
127            let x: &[T] = x.downcast().unwrap();
128            let y: &mut [T] = y.downcast().unwrap();
129            for (&x_i, y_i) in x.iter().zip(y) {
130                *y_i = x_i.max(*y_i);
131            }
132        }),
133    );
134
135    global_max
136}
137
138/// Get the minimum value across all ranks
139pub fn global_min<T: Equivalence + Copy + Ord, C: CommunicatorCollectives>(
140    arr: &[T],
141    comm: &C,
142) -> T {
143    let local_min = *arr.iter().min().unwrap();
144
145    // Just need to initialize global_min with something.
146    let mut global_min = local_min;
147
148    comm.all_reduce_into(
149        &local_min,
150        &mut global_min,
151        &UserOperation::commutative(|x, y| {
152            let x: &[T] = x.downcast().unwrap();
153            let y: &mut [T] = y.downcast().unwrap();
154            for (&x_i, y_i) in x.iter().zip(y) {
155                *y_i = x_i.min(*y_i);
156            }
157        }),
158    );
159
160    global_min
161}
162
163/// Communicate the first element of each local array back to the previous rank.
164pub fn communicate_back<T: Equivalence, C: CommunicatorCollectives>(
165    arr: &[T],
166    comm: &C,
167) -> Option<T> {
168    let rank = comm.rank();
169    let size = comm.size();
170
171    if size == 1 {
172        return None;
173    }
174
175    if rank == size - 1 {
176        comm.process_at_rank(rank - 1).send(arr.first().unwrap());
177        None
178    } else {
179        let (new_last, _status) = if rank > 0 {
180            p2p::send_receive(
181                arr.first().unwrap(),
182                &comm.process_at_rank(rank - 1),
183                &comm.process_at_rank(rank + 1),
184            )
185        } else {
186            comm.process_at_rank(1).receive::<T>()
187        };
188        Some(new_last)
189    }
190}
191
192/// Check if an array is sorted.
193pub fn is_sorted_array<T: Equivalence + PartialOrd, C: CommunicatorCollectives>(
194    arr: &[T],
195    comm: &C,
196) -> bool {
197    let mut sorted = true;
198    for (elem1, elem2) in arr.iter().tuple_windows() {
199        if elem1 > elem2 {
200            sorted = false;
201        }
202    }
203
204    if comm.size() == 1 {
205        return sorted;
206    }
207
208    if let Some(next_first) = communicate_back(arr, comm) {
209        sorted = *arr.last().unwrap() <= next_first;
210    }
211
212    let mut global_sorted: bool = false;
213    comm.all_reduce_into(&sorted, &mut global_sorted, SystemOperation::logical_and());
214
215    global_sorted
216}
217
218/// Redistribute an array via an all_to_all_varcount operation.
219pub fn redistribute<T: Equivalence, C: CommunicatorCollectives>(
220    arr: &[T],
221    counts: &[i32],
222    comm: &C,
223) -> Vec<T> {
224    assert_eq!(counts.len(), comm.size() as usize);
225
226    // First send the counts around via an alltoall operation.
227
228    let mut recv_counts = vec![0; counts.len()];
229
230    comm.all_to_all_into(counts, &mut recv_counts);
231
232    // We have the recv_counts. Allocate space and setup the partitions.
233
234    let nelems = recv_counts.iter().sum::<i32>() as usize;
235
236    let mut output = Vec::<T>::with_capacity(nelems);
237    let out_buf: &mut [T] = unsafe { std::mem::transmute(output.spare_capacity_mut()) };
238
239    let send_partition = Partition::new(arr, counts, displacements(counts));
240    let mut recv_partition =
241        PartitionMut::new(out_buf, &recv_counts[..], displacements(&recv_counts));
242
243    comm.all_to_all_varcount_into(&send_partition, &mut recv_partition);
244
245    unsafe { output.set_len(nelems) };
246
247    output
248}
249
250/// Perform a global inclusive cumulative sum operation.
251///
252/// For the array `[1, 3, 5, 7]` the output will be `[1, 4, 9, 16]`.
253pub fn global_inclusive_cumsum<T: Equivalence + Zero + Copy, C: CommunicatorCollectives>(
254    arr: &[T],
255    comm: &C,
256) -> Vec<T> {
257    let mut scan: Vec<T> = arr
258        .iter()
259        .scan(<T as Zero>::zero(), |state, x| {
260            *state = *x + *state;
261            Some(*state)
262        })
263        .collect_vec();
264    let scan_last = *scan.last().unwrap();
265    let mut scan_result = T::zero();
266    comm.exclusive_scan_into(&scan_last, &mut scan_result, SystemOperation::sum());
267    for elem in &mut scan {
268        *elem = *elem + scan_result;
269    }
270
271    scan
272}
273
274/// Distribute a sorted sequence into bins.
275///
276/// For an array with n elements to be distributed into p bins,
277/// the array `bins` has p elements. The bins are defined by half-open intervals
278/// of the form [b_j, b_{j+1})). The final bin is the half-open interval [b_{p-1}, \infty).
279/// It is assumed that the bins and the elements are both sorted sequences and that
280/// every element has an associated bin.
281/// The function returns a p element array with the counts of how many elements go to each bin.
282/// Since the sequence is sorted this fully defines what element goes into which bin.
283pub fn sort_to_bins<T: Ord>(sorted_keys: &[T], bins: &[T]) -> Vec<usize> {
284    let nbins = bins.len();
285
286    // Make sure that the smallest element of the sorted keys fits into the bins.
287    assert!(bins.first().unwrap() <= sorted_keys.first().unwrap());
288
289    // Deal with the special case that there is only one bin.
290    // This means that all elements are in the one bin.
291    if nbins == 1 {
292        return vec![sorted_keys.len(); 1];
293    }
294
295    let mut bin_counts = vec![0; nbins];
296
297    // This iterates over each possible bin and returns also the associated rank.
298    // The last bin position is not iterated over since for an array with p elements
299    // there are p-1 tuple windows.
300    let mut bin_iter = izip!(
301        bin_counts.iter_mut(),
302        bins.iter().tuple_windows::<(&T, &T)>(),
303    );
304
305    // We take the first element of the bin iterator. There will always be at least one since
306    // there are at least two bins (an actual one, and the last half infinite one)
307    let mut r: &mut usize;
308    let mut bin_start: &T;
309    let mut bin_end: &T;
310    (r, (bin_start, bin_end)) = bin_iter.next().unwrap();
311
312    let mut count = 0;
313    'outer: for key in sorted_keys.iter() {
314        if bin_start <= key && key < bin_end {
315            *r += 1;
316            count += 1;
317        } else {
318            // Move the bin forward until it fits. There will always be a fitting bin.
319            loop {
320                if let Some((rn, (bsn, ben))) = bin_iter.next() {
321                    if bsn <= key && key < ben {
322                        // We have found the next fitting bin for our current element.
323                        // Can register it and go back to the outer for loop.
324                        *rn += 1;
325                        r = rn;
326                        bin_start = bsn;
327                        bin_end = ben;
328                        count += 1;
329                        break;
330                    }
331                } else {
332                    // We have no more fitting bin. So break the outer loop.
333                    break 'outer;
334                }
335            }
336        }
337    }
338
339    // We now have everything but the last bin. Just bunch the remaining elements to
340    // the last count.
341    *bin_counts.last_mut().unwrap() = sorted_keys.len() - count;
342
343    bin_counts
344}
345
346/// Redistribute locally sorted keys with respect to bins.
347///
348/// - The array `sorted_keys` is assumed to be sorted within each process. It needs not be globally sorted.
349/// - If there are `r` ranks in the communicator, the size of `bins` must be `r`.
350/// - The bins are defined through half-open intervals `(bin[0], bin[1])`, .... This defines r-1 bins. The
351///   last bin is the half-open interval `[bin[r-1], \infty)`.
352/// - All array elements must be larger or equal `bin[0]`. This means that each element can be sorted into a bin.
353pub fn redistribute_by_bins<T: Equivalence + Ord, C: CommunicatorCollectives>(
354    sorted_keys: &[T],
355    bins: &[T],
356    comm: &C,
357) -> Vec<T> {
358    let counts = sort_to_bins(sorted_keys, bins);
359    let counts = counts.iter().map(|elem| *elem as i32).collect_vec();
360    redistribute(sorted_keys, &counts, comm)
361}
362
363/// Generate random keys for testing.
364pub fn generate_random_keys<R: Rng>(nkeys: usize, rng: &mut R) -> Vec<MortonKey> {
365    let mut result = Vec::<MortonKey>::with_capacity(nkeys);
366
367    let xindices = rand::seq::index::sample(rng, LEVEL_SIZE as usize, nkeys);
368    let yindices = rand::seq::index::sample(rng, LEVEL_SIZE as usize, nkeys);
369    let zindices = rand::seq::index::sample(rng, LEVEL_SIZE as usize, nkeys);
370
371    for (xval, yval, zval) in izip!(xindices.iter(), yindices.iter(), zindices.iter()) {
372        result.push(MortonKey::from_index_and_level(
373            [xval, yval, zval],
374            DEEPEST_LEVEL as usize,
375        ));
376    }
377
378    result
379}
380
381/// Generate random points for testing.
382pub fn generate_random_points<R: Rng, C: CommunicatorCollectives>(
383    npoints: usize,
384    rng: &mut R,
385    comm: &C,
386) -> Vec<Point> {
387    let mut points = Vec::<Point>::with_capacity(npoints);
388    let rank = comm.rank() as usize;
389
390    for index in 0..npoints {
391        points.push(Point::new(
392            [rng.gen(), rng.gen(), rng.gen()],
393            npoints * rank + index,
394        ));
395    }
396
397    points
398}
399
400/// Get a seeded rng
401pub fn seeded_rng(seed: usize) -> ChaCha8Rng {
402    ChaCha8Rng::seed_from_u64(seed as u64)
403}
404
405/// Compute displacements from a vector of counts.
406///
407/// This is useful for global MPI varcount operations. Let
408/// count [ 3, 4, 5]. Then the corresponding displacements are
409// [0, 3, 7]. Note that the last element `5` is ignored.
410pub fn displacements(counts: &[i32]) -> Vec<i32> {
411    counts
412        .iter()
413        .scan(0, |acc, &x| {
414            let tmp = *acc;
415            *acc += x;
416            Some(tmp)
417        })
418        .collect()
419}
420
421#[cfg(test)]
422mod test {
423    use itertools::Itertools;
424
425    use super::sort_to_bins;
426
427    #[test]
428    fn test_sort_to_bins() {
429        let elems = (0..100).collect_vec();
430        let bins = [0, 17, 55];
431
432        let counts = sort_to_bins(&elems, &bins);
433
434        assert_eq!(counts[0], 17);
435        assert_eq!(counts[1], 38);
436        assert_eq!(counts[2], 45);
437    }
438}