use super::{
super::{
Class,
test::{box_surface, sphere},
},
Lattice,
};
use crate::{
geometry::mesh::{Connectivity, Fitting, Verdict},
math::{Quantity, Scalar},
};
#[test]
fn box_encloses_its_interior_cells() {
let lattice = box_surface([0.0, 0.0, 0.0], [1.0, 1.0, 1.0])
.lattice_cells(Quantity::new(0.1))
.unwrap();
let cells = lattice.cells();
assert!(cells.iter().any(|&(_, class)| class == Class::Cut));
assert!(cells.iter().any(|&(_, class)| class == Class::Inside));
let inside = cells
.iter()
.filter(|&&(_, class)| class == Class::Inside)
.count();
assert!(inside >= 6 * 6 * 6, "only {inside} enclosed cells");
}
#[test]
fn the_exterior_is_only_ever_one_cell_deep() {
let lattice = sphere(2).lattice_cells(Quantity::new(0.15)).unwrap();
lattice
.cells()
.iter()
.filter(|&&(_, class)| class == Class::Outside)
.for_each(|&(index, _)| {
assert!(
lattice.neighbors(index).any(|next| lattice
.cells
.get(&next)
.is_some_and(|&class| class != Class::Outside)),
"{index:?} is outside but touches nothing"
)
});
}
fn bracket(lattice: &Lattice, spacing: Scalar) -> (Scalar, Scalar) {
let cells = lattice.cells();
let inside = cells
.iter()
.filter(|&&(_, class)| class == Class::Inside)
.count();
let occupied = cells
.iter()
.filter(|&&(_, class)| class != Class::Outside)
.count();
(
inside as Scalar * spacing.powi(3),
occupied as Scalar * spacing.powi(3),
)
}
#[test]
fn box_volume_is_bracketed_ever_more_tightly() {
let mut previous = Scalar::INFINITY;
for spacing in [0.2, 0.1, 0.05] {
let lattice = box_surface([0.0, 0.0, 0.0], [1.0, 1.0, 1.0])
.lattice_cells(Quantity::new(spacing))
.unwrap();
let (low, high) = bracket(&lattice, spacing);
assert!(low <= 1.0 && 1.0 <= high, "[{low}, {high}] at {spacing}");
assert!(high - low < previous);
previous = high - low;
}
}
#[test]
fn cells_are_ascending_and_unique() {
let lattice = sphere(2).lattice_cells(Quantity::new(0.15)).unwrap();
let cells = lattice.cells();
let keys: Vec<_> = cells.iter().map(|&([i, j, k], _)| (k, j, i)).collect();
assert!(keys.windows(2).all(|pair| pair[0] < pair[1]));
}
#[test]
fn sphere_fill_does_not_leak() {
let lattice = sphere(2).lattice_cells(Quantity::new(0.15)).unwrap();
let [nx, ny, nz] = lattice.nel;
lattice.cells().iter().for_each(|&([i, j, k], _)| {
assert!(i > 0 && j > 0 && k > 0);
assert!(i < nx - 1 && j < ny - 1 && k < nz - 1);
});
}
#[test]
fn rasterized_classes_agree_with_classifying() {
let fixtures = [
sphere(2),
sphere(3),
box_surface([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
box_surface([-2.0, -2.0, -0.05], [2.0, 2.0, 0.05]),
];
fixtures
.iter()
.enumerate()
.for_each(|(fixture, tessellation)| {
[0.3, 0.14].iter().for_each(|&spacing| {
let (mesh, classes) = tessellation
.lattice_cells(Quantity::new(spacing))
.unwrap()
.mesh();
assert_eq!(classes, tessellation.classify(&mesh), "fixture {fixture}");
})
})
}
#[test]
fn meshes_one_hexahedron_per_cell() {
let lattice = box_surface([0.0, 0.0, 0.0], [1.0, 1.0, 1.0])
.lattice_cells(Quantity::new(0.2))
.unwrap();
let (mesh, classes) = lattice.mesh();
assert_eq!(mesh.number_of_elements(), lattice.cells().len());
assert_eq!(classes.len(), lattice.cells().len());
}
#[test]
fn rejects_nonpositive_spacing() {
assert!(
box_surface([0.0; 3], [1.0; 3])
.lattice_cells(Quantity::new(0.0))
.is_err()
);
assert!(
box_surface([0.0; 3], [1.0; 3])
.lattice_cells(Quantity::new(-1.0))
.is_err()
);
assert!(
box_surface([0.0; 3], [1.0; 3])
.lattice_background(Quantity::new(0.0))
.is_err()
);
}
#[test]
fn lattice_trimmed_and_buffered_is_all_hexahedral_and_not_inverted() {
let tessellation = sphere(3);
let mut mesh = tessellation
.lattice_background(Quantity::new(0.15))
.unwrap()
.0;
let background = mesh.number_of_elements();
tessellation.trim(&mut mesh).unwrap();
assert!(mesh.number_of_elements() < background);
let mesh = mesh.buffer(&tessellation, Fitting::Snap).unwrap();
assert!(
mesh.connectivities()
.iter()
.all(|block| matches!(block, Connectivity::Hexahedral(_))),
"expected an all-hexahedral mesh"
);
let worst = mesh
.minimum_scaled_jacobians()
.into_iter()
.flatten()
.fold(Scalar::INFINITY, Scalar::min);
assert!(worst > 0.0, "{worst}");
}
#[test]
fn enclosed_cells_of_a_box_are_exactly_its_interior() {
let lattice = box_surface([0.0, 0.0, 0.0], [1.0, 1.0, 1.0])
.lattice_cells(Quantity::new(0.2))
.unwrap();
assert_eq!(
lattice
.cells()
.iter()
.filter(|&&(_, class)| class == Class::Inside)
.count(),
3 * 3 * 3
);
}
#[test]
fn a_plate_thinner_than_a_cell_is_all_cut_and_does_not_leak() {
let lattice = box_surface([0.0, 0.0, 0.0], [4.0, 4.0, 0.3])
.lattice_cells(Quantity::new(0.5))
.unwrap();
let cells = lattice.cells();
let [nx, ny, nz] = lattice.nel;
assert!(cells.iter().all(|&(_, class)| class != Class::Inside));
assert!(cells.len() < nx * ny * nz);
}
#[test]
fn sphere_volume_is_bracketed_ever_more_tightly() {
let exact = 4.0 * std::f64::consts::PI / 3.0;
let mut previous = Scalar::INFINITY;
for spacing in [0.3, 0.15, 0.075] {
let lattice = sphere(3).lattice_cells(Quantity::new(spacing)).unwrap();
let (low, high) = bracket(&lattice, spacing);
assert!(
low <= exact && exact <= high,
"[{low}, {high}] at {spacing}"
);
assert!(high - low < previous);
previous = high - low;
}
}