124 lines
4.0 KiB
Rust
124 lines
4.0 KiB
Rust
//! Tests for the batch visibility matrix.
|
|
//!
|
|
//! The parallel form must produce exactly the matrix the sequential form
|
|
//! does, in the same row major order. Every test compares the two rather than
|
|
//! against a constant, so a content or ordering difference is caught.
|
|
|
|
use apsis_isl::{visibility_matrix, visibility_matrix_parallel};
|
|
use apsis_math::Vec3;
|
|
|
|
/// The body radius used throughout, in kilometers.
|
|
const RADIUS: f64 = 6_378.137;
|
|
|
|
/// Returns a set of positions spread around a shell.
|
|
fn shell(count: usize) -> Vec<Vec3> {
|
|
(0..count)
|
|
.map(|index| {
|
|
let angle = index as f64 * 0.3;
|
|
let radius = 6_900.0;
|
|
Vec3::new(
|
|
radius * angle.cos(),
|
|
radius * angle.sin(),
|
|
radius * 0.2 * (index as f64 * 0.1).sin(),
|
|
)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
fn a_single_position_gives_a_one_by_one_matrix() {
|
|
let positions = shell(1);
|
|
assert_eq!(
|
|
visibility_matrix_parallel(&positions, RADIUS),
|
|
visibility_matrix(&positions, RADIUS)
|
|
);
|
|
// A satellite is visible to itself over a body radius below its own.
|
|
assert!(visibility_matrix(&positions, RADIUS)[0]);
|
|
}
|
|
|
|
#[test]
|
|
fn a_shell_matches_the_sequential_matrix() {
|
|
// Long enough that the parallel path takes the multi thread branch.
|
|
for count in [2_usize, 16, 64, 200] {
|
|
let positions = shell(count);
|
|
assert_eq!(
|
|
visibility_matrix_parallel(&positions, RADIUS),
|
|
visibility_matrix(&positions, RADIUS),
|
|
"count {count} differs"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn an_empty_set_gives_an_empty_matrix() {
|
|
assert!(visibility_matrix_parallel(&[], RADIUS).is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn the_matrix_is_row_major_and_symmetric() {
|
|
// Row major: the entry at row * len + column is the visibility from the
|
|
// row satellite to the column one. For a spherical body the geometry is
|
|
// symmetric, so the matrix is symmetric, which is what a wrong ordering
|
|
// of a row major layout would break.
|
|
let positions = shell(12);
|
|
let matrix = visibility_matrix_parallel(&positions, RADIUS);
|
|
let len = positions.len();
|
|
assert_eq!(matrix.len(), len * len);
|
|
for row in 0..len {
|
|
for column in 0..len {
|
|
assert_eq!(
|
|
matrix[row * len + column],
|
|
matrix[column * len + row],
|
|
"not symmetric at ({row}, {column})"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn the_matrix_matches_the_scalar_test() {
|
|
// Each entry must agree with calling the geometry directly, so a wrong
|
|
// pairing of row and column is caught even when the matrix is symmetric.
|
|
let positions = shell(10);
|
|
let matrix = visibility_matrix_parallel(&positions, RADIUS);
|
|
let len = positions.len();
|
|
for row in 0..len {
|
|
for column in 0..len {
|
|
let expected = apsis_isl::mutual_visibility(positions[row], positions[column], RADIUS)
|
|
.unwrap_or(false);
|
|
assert_eq!(matrix[row * len + column], expected, "at ({row}, {column})");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn an_unusable_body_radius_yields_all_false() {
|
|
// A non positive radius makes every test undefined, which the sequential
|
|
// form reports as false; the parallel form must do the same.
|
|
let positions = shell(8);
|
|
for radius in [0.0, -1.0] {
|
|
let matrix = visibility_matrix_parallel(&positions, radius);
|
|
assert!(matrix.iter().all(|&visible| !visible));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_position_that_is_not_finite_yields_false_in_its_row() {
|
|
// A satellite with a non finite position cannot see anything. The rest
|
|
// of the matrix is unaffected.
|
|
let mut positions = shell(6);
|
|
positions[2] = Vec3::new(f64::NAN, 0.0, 0.0);
|
|
let matrix = visibility_matrix_parallel(&positions, RADIUS);
|
|
let len = positions.len();
|
|
for column in 0..len {
|
|
assert!(
|
|
!matrix[2 * len + column],
|
|
"row 2 column {column} should be false"
|
|
);
|
|
assert!(
|
|
!matrix[column * len + 2],
|
|
"row {column} column 2 should be false"
|
|
);
|
|
}
|
|
}
|