apsis-rs/crates/network/apsis-isl/src/batch.rs

61 lines
2.8 KiB
Rust

//! Deciding many links at once, optionally across several threads.
//!
//! Building a constellation's topology asks the same question for every pair
//! of satellites, which is a quadratic number of independent geometry tests.
//! Each one is small, but there are so many that the loop is the dominant
//! cost of building the graph, and the tests do not depend on each other, so
//! they are the natural place to use several cores.
//!
//! The parallel entry point returns exactly what the sequential one does, in
//! the same order, so a caller can switch between them without changing the
//! graph it builds.
use crate::geometry::mutual_visibility;
use apsis_math::{Vec3, parallel::map_index};
/// Returns the visibility of every ordered pair of positions, in order.
///
/// The result is row major: the entry at `row * positions.len() + column` is
/// the visibility from `positions[row]` to `positions[column]`. A satellite is
/// always visible to itself, which is the caller's cue to skip the diagonal.
///
/// A pair whose test is undefined, because a position is not finite or the
/// body radius is not positive, yields `false`.
pub fn visibility_matrix(positions: &[Vec3], body_radius: f64) -> Vec<bool> {
let len = positions.len();
let mut matrix = vec![false; len * len];
for (row, first) in positions.iter().enumerate() {
for (column, second) in positions.iter().enumerate() {
matrix[row * len + column] =
mutual_visibility(*first, *second, body_radius).unwrap_or(false);
}
}
matrix
}
/// Returns the visibility of every ordered pair of positions, across several
/// threads.
///
/// The result matches [`visibility_matrix`] element for element and in the
/// same order. The pairs are independent, so splitting the flat index space
/// across threads needs no coordination and produces the same matrix however
/// the work is divided.
///
/// The gain from threading is modest here and shrinks as the matrix shrinks.
/// A single visibility test is a few nanoseconds, so the cost of starting the
/// threads is only repaid by a large matrix: on a sixteen core machine a few
/// hundred satellites already gain, while a few dozen lose. The sequential
/// form is the better choice for a small shell, and this one exists for the
/// large ones where the quadratic sweep dominates.
pub fn visibility_matrix_parallel(positions: &[Vec3], body_radius: f64) -> Vec<bool> {
let len = positions.len();
// The pairs are independent, so splitting the flat index space across
// threads needs no coordination. `map_index` owns the output buffer,
// which is what lets the threads write disjoint slots of one allocation.
map_index(len * len, |index| {
let row = index / len;
let column = index - row * len;
mutual_visibility(positions[row], positions[column], body_radius).unwrap_or(false)
})
}