Skip to main content

bempp_octree/
octree.rs

1//! Definition of Octree.
2mod implementation;
3use std::collections::HashMap;
4
5pub(crate) use implementation::*;
6use mpi::{
7    collective::SystemOperation,
8    traits::{CommunicatorCollectives, Root},
9};
10use rand::SeedableRng;
11use rand_chacha::ChaCha8Rng;
12
13use crate::{
14    constants::DEEPEST_LEVEL,
15    geometry::{PhysicalBox, Point},
16    morton::MortonKey,
17    tools::gather_to_root,
18};
19
20/// Stores the type of the key relative to the octree.
21#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
22pub enum KeyType {
23    /// A local leaf.
24    LocalLeaf,
25    /// A local interior key.
26    LocalInterior,
27    /// A global key.
28    Global,
29    /// A ghost key from a specific process.
30    Ghost(usize),
31}
32
33/// A general structure for octrees.
34pub struct Octree<'o, C> {
35    points: Vec<Point>,
36    point_keys: Vec<MortonKey>,
37    coarse_tree_leafs: Vec<MortonKey>,
38    leaf_keys: Vec<MortonKey>,
39    coarse_tree_bounds: Vec<MortonKey>,
40    all_keys: HashMap<MortonKey, KeyType>,
41    neighbours: HashMap<MortonKey, Vec<MortonKey>>,
42    leaf_keys_to_local_point_indices: HashMap<MortonKey, Vec<usize>>,
43    bounding_box: PhysicalBox,
44    comm: &'o C,
45}
46
47impl<'o, C: CommunicatorCollectives> Octree<'o, C> {
48    /// Create a new distributed Octree.
49    ///
50    /// # Arguments
51    /// - `max_level`: The maximum level of the tree. The maximum level is 16.
52    /// - `max_leaf_points`: The maximum number of points per leaf.
53    /// - `comm`: The communicator.
54    ///
55    /// # Returns
56    /// A new Octree.
57    ///
58    /// # Note
59    /// The points are redistributed during construction of the octree. The tree stores
60    /// the redistributed points and the corresponding Morton keys.
61    pub fn new(points: &[Point], max_level: usize, max_leaf_points: usize, comm: &'o C) -> Self {
62        // We need a random number generator for sorting. For simplicity we use a ChaCha8 random number generator
63        // seeded with the rank of the process.
64        let mut rng = ChaCha8Rng::seed_from_u64(comm.rank() as u64);
65
66        // First compute the Morton keys of the points.
67        let (point_keys, bounding_box) = points_to_morton(points, DEEPEST_LEVEL as usize, comm);
68
69        // Generate the coarse tree
70
71        let (coarse_tree, leaf_tree) = {
72            // Linearize the keys.
73            let linear_keys = linearize(&point_keys, &mut rng, comm);
74
75            // Compute the first version of the coarse tree without load balancing.
76            // We want to ensure that it is 2:1 balanced.
77            let coarse_tree = compute_coarse_tree(&linear_keys, comm);
78
79            let coarse_tree = balance(&coarse_tree, &mut rng, comm);
80            debug_assert!(is_complete_linear_tree(&coarse_tree, comm));
81
82            // We now compute the weights for the initial coarse tree.
83
84            let weights = compute_coarse_tree_weights(&linear_keys, &coarse_tree, comm);
85
86            // We now load balance the initial coarse tree. This forms our final coarse tree
87            // that is used from now on.
88
89            let coarse_tree = load_balance(&coarse_tree, &weights, comm);
90            // We also want to redistribute the fine keys with respect to the load balanced coarse trees.
91
92            let fine_keys =
93                redistribute_with_respect_to_coarse_tree(&linear_keys, &coarse_tree, comm);
94
95            // We now create the refined tree by recursing the coarse tree until we are at max level
96            // or the fine tree keys per coarse tree box is small enough.
97            let refined_tree =
98                create_local_tree(&fine_keys, &coarse_tree, max_level, max_leaf_points);
99
100            // We now need to 2:1 balance the refined tree and then redistribute again with respect to the coarse tree.
101
102            let refined_tree = redistribute_with_respect_to_coarse_tree(
103                &balance(&refined_tree, &mut rng, comm),
104                &coarse_tree,
105                comm,
106            );
107
108            (coarse_tree, refined_tree)
109
110            // redistribute the balanced tree according to coarse tree
111        };
112
113        let (points, point_keys) = redistribute_points_with_respect_to_coarse_tree(
114            points,
115            &point_keys,
116            &coarse_tree,
117            comm,
118        );
119
120        let coarse_tree_bounds = get_tree_bins(&coarse_tree, comm);
121
122        // Duplicate the coarse tree across all nodes
123
124        // let coarse_tree = gather_to_all(&coarse_tree, comm);
125
126        let all_keys = generate_all_keys(&leaf_tree, &coarse_tree, &coarse_tree_bounds, comm);
127        let neighbours = compute_neighbours(&all_keys);
128
129        let leaf_keys_to_points = assign_points_to_leaf_keys(&point_keys, &leaf_tree);
130
131        Self {
132            points: points.to_vec(),
133            point_keys,
134            coarse_tree_leafs: coarse_tree,
135            leaf_keys: leaf_tree,
136            coarse_tree_bounds,
137            all_keys,
138            neighbours,
139            leaf_keys_to_local_point_indices: leaf_keys_to_points,
140            bounding_box,
141            comm,
142        }
143    }
144
145    /// Return the Morton keys associated with points.
146    pub fn point_keys(&self) -> &Vec<MortonKey> {
147        &self.point_keys
148    }
149
150    /// Return the bounding box.
151    ///
152    /// The bounding box is computed globally for the distributed octree.
153    pub fn bounding_box(&self) -> &PhysicalBox {
154        &self.bounding_box
155    }
156
157    /// Return the coarse tree leafs.
158    pub fn coarse_tree_leafs(&self) -> &Vec<MortonKey> {
159        &self.coarse_tree_leafs
160    }
161
162    /// Return the points.
163    ///
164    /// Points are distributed across the nodes as part of the tree generation.
165    /// This function returns the redistributed points.
166    pub fn points(&self) -> &Vec<Point> {
167        &self.points
168    }
169
170    /// Return the leaf nodes.
171    pub fn leaf_keys(&self) -> &Vec<MortonKey> {
172        &self.leaf_keys
173    }
174
175    /// Return the map from leaf keys to local point indices.
176    ///
177    /// This allows to find the points associated with a given key.
178    /// # Example
179    /// ```ignore
180    /// let leaf_map = octree.leaf_keys_to_local_point_indices();
181    /// let indices = leaf_map.get(&key);
182    /// let points_for_key = indices.iter().map(|&i| octree.points()[i]).collect::<Vec<_>>();
183    /// ```
184    /// Each point in `points_for_key` is contained in the leaf box defined by `key`.
185    pub fn leaf_keys_to_local_point_indices(&self) -> &HashMap<MortonKey, Vec<usize>> {
186        &self.leaf_keys_to_local_point_indices
187    }
188
189    /// Get the coarse tree bounds.
190    ///
191    /// This returns an array of size the number of ranks,
192    /// where each element consists of the smallest Morton key in
193    /// the corresponding rank.
194    ///
195    /// If a Morton key is on rank i with i not the last rank then
196    /// ```text
197    /// coarse_tree_bounds[i] <= key < coarse_tree_bounds[i+1]
198    /// ```
199    /// where as if i is the last rank then
200    /// ```text
201    /// coarse_tree_bounds[i] <= key
202    /// ```
203    /// This allows to find the rank of a given Morton key.
204    pub fn coarse_tree_bounds(&self) -> &Vec<MortonKey> {
205        &self.coarse_tree_bounds
206    }
207
208    /// Return the communicator.
209    pub fn comm(&self) -> &C {
210        self.comm
211    }
212
213    /// Return a map of all leaf and interior keys.
214    ///
215    /// The map assigns each key a [KeyType] identifier. It is one of:
216    /// - [KeyType::LocalLeaf] for leaf keys
217    /// - [KeyType::LocalInterior] for interior keys
218    /// - [KeyType::Global] for global keys
219    /// - [KeyType::Ghost], a typed enum for keys that are adjacent to keys
220    ///   on the current rank but live on a different rank.
221    ///
222    /// Leaf keys have no children. Interior keys have children within the local rank.
223    /// Global keys are keys that are not uniquely assigned to a rank but exist on all ranks.
224    /// The global keys are those that are close to the root of the tree. By construction these
225    /// are the ancestors of the coarse tree leafs, where as the coarse tree leafs themselves are
226    /// the first level of keys distributed across ranks. Ghost keys are keys that are not local to
227    /// the current rank but lie along the interface to the current rank. Their identifiers store the value
228    /// of the rank that they originate from.
229    pub fn all_keys(&self) -> &HashMap<MortonKey, KeyType> {
230        &self.all_keys
231    }
232
233    /// Get the neighbour map.
234    ///
235    /// Returns a hash map that contains as keys all the keys obtained from [Octree::all_keys] except
236    /// those that are of type [KeyType::Ghost]. The values are the neighbours of the key.
237    pub fn neighbour_map(&self) -> &HashMap<MortonKey, Vec<MortonKey>> {
238        &self.neighbours
239    }
240
241    /// Return the local number of points in the octree.
242    pub fn local_number_of_points(&self) -> usize {
243        self.points.len()
244    }
245
246    /// Return the global number of points in the octree.
247    pub fn global_number_of_points(&self) -> usize {
248        let mut global_num_points = 0;
249        self.comm.all_reduce_into(
250            &self.local_number_of_points(),
251            &mut global_num_points,
252            SystemOperation::sum(),
253        );
254        global_num_points
255    }
256
257    /// Return the local maximum level
258    pub fn local_max_level(&self) -> usize {
259        self.leaf_keys
260            .iter()
261            .map(|key| key.level())
262            .max()
263            .unwrap_or(0)
264    }
265
266    /// Return the global maximum level
267    pub fn global_max_level(&self) -> usize {
268        let mut global_max_level = 0;
269        self.comm.all_reduce_into(
270            &self.local_max_level(),
271            &mut global_max_level,
272            SystemOperation::max(),
273        );
274        global_max_level
275    }
276}
277
278/// Test if an array of keys are the leafs of a complete linear and balanced tree.
279pub fn is_complete_linear_and_balanced<C: CommunicatorCollectives>(
280    arr: &[MortonKey],
281    comm: &C,
282) -> bool {
283    // Send the tree to the root node and check there that it is balanced.
284
285    let mut balanced = false;
286
287    if let Some(arr) = gather_to_root(arr, comm) {
288        balanced = MortonKey::is_complete_linear_and_balanced(&arr);
289    }
290
291    comm.process_at_rank(0).broadcast_into(&mut balanced);
292
293    balanced
294}
295
296/// Compute the global bounding box across all points on all processes.
297pub fn compute_global_bounding_box<C: CommunicatorCollectives>(
298    points: &[Point],
299    comm: &C,
300) -> PhysicalBox {
301    // Make sure that the points array is a multiple of 3.
302
303    // Now compute the minimum and maximum across each dimension.
304
305    let mut xmin = f64::MAX;
306    let mut xmax = f64::MIN;
307
308    let mut ymin = f64::MAX;
309    let mut ymax = f64::MIN;
310
311    let mut zmin = f64::MAX;
312    let mut zmax = f64::MIN;
313
314    for point in points {
315        let x = point.coords()[0];
316        let y = point.coords()[1];
317        let z = point.coords()[2];
318
319        xmin = f64::min(xmin, x);
320        xmax = f64::max(xmax, x);
321
322        ymin = f64::min(ymin, y);
323        ymax = f64::max(ymax, y);
324
325        zmin = f64::min(zmin, z);
326        zmax = f64::max(zmax, z);
327    }
328
329    let mut global_xmin = 0.0;
330    let mut global_xmax = 0.0;
331
332    let mut global_ymin = 0.0;
333    let mut global_ymax = 0.0;
334
335    let mut global_zmin = 0.0;
336    let mut global_zmax = 0.0;
337
338    comm.all_reduce_into(&xmin, &mut global_xmin, SystemOperation::min());
339    comm.all_reduce_into(&xmax, &mut global_xmax, SystemOperation::max());
340
341    comm.all_reduce_into(&ymin, &mut global_ymin, SystemOperation::min());
342    comm.all_reduce_into(&ymax, &mut global_ymax, SystemOperation::max());
343
344    comm.all_reduce_into(&zmin, &mut global_zmin, SystemOperation::min());
345    comm.all_reduce_into(&zmax, &mut global_zmax, SystemOperation::max());
346
347    let xdiam = global_xmax - global_xmin;
348    let ydiam = global_ymax - global_ymin;
349    let zdiam = global_zmax - global_zmin;
350
351    let xmean = global_xmin + 0.5 * xdiam;
352    let ymean = global_ymin + 0.5 * ydiam;
353    let zmean = global_zmin + 0.5 * zdiam;
354
355    // We increase diameters by box size on deepest level
356    // and use the maximum diameter to compute a
357    // cubic bounding box.
358
359    let deepest_box_diam = 1.0 / (1 << DEEPEST_LEVEL) as f64;
360
361    let max_diam = [xdiam, ydiam, zdiam].into_iter().reduce(f64::max).unwrap();
362
363    let max_diam = max_diam * (1.0 + deepest_box_diam);
364
365    PhysicalBox::new([
366        xmean - 0.5 * max_diam,
367        ymean - 0.5 * max_diam,
368        zmean - 0.5 * max_diam,
369        xmean + 0.5 * max_diam,
370        ymean + 0.5 * max_diam,
371        zmean + 0.5 * max_diam,
372    ])
373}