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}