Skip to content

Commit b713315

Browse files
committed
Add more tests for octree subdivision
1 parent 5a52172 commit b713315

File tree

3 files changed

+231
-48
lines changed

3 files changed

+231
-48
lines changed

data/cube_2366_particles.vtk

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:12fbbc4c82e0d5544047b7fd635a518433ad9edf95b3177d5988d315f7932950
3+
size 56944

splashsurf_lib/tests/integration_tests/test_full.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ fn params<R: Real>(
4949
Strategy::OctreeStitching => {
5050
parameters.spatial_decomposition = Some(SpatialDecompositionParameters {
5151
subdivision_criterion: SubdivisionCriterion::MaxParticleCountAuto,
52-
ghost_particle_safety_factor: Some(R::one()),
52+
ghost_particle_safety_factor: Some(R::one() + R::one()),
5353
enable_stitching: true,
5454
});
5555
}

splashsurf_lib/tests/integration_tests/test_octree.rs

Lines changed: 227 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
use std::path::Path;
2-
3-
use splashsurf_lib::mesh::PointCloud3d;
1+
use splashsurf_lib::generic_tree::VisitableTree;
42
use splashsurf_lib::nalgebra::Vector3;
53
use splashsurf_lib::octree::Octree;
64
use splashsurf_lib::{grid_for_reconstruction, Index, Real, SubdivisionCriterion, UniformGrid};
7-
8-
use vtkio::model::UnstructuredGridPiece;
5+
use std::path::Path;
96

107
use super::io;
11-
use splashsurf_lib::generic_tree::VisitableTree;
128

9+
/*
1310
#[allow(dead_code)]
1411
fn particles_to_file<P: AsRef<Path>, R: Real>(particles: Vec<Vector3<R>>, path: P) {
1512
let points = PointCloud3d { points: particles };
@@ -20,6 +17,7 @@ fn particles_to_file<P: AsRef<Path>, R: Real>(particles: Vec<Vector3<R>>, path:
2017
)
2118
.unwrap();
2219
}
20+
*/
2321

2422
#[allow(dead_code)]
2523
fn octree_to_file<P: AsRef<Path>, I: Index, R: Real>(
@@ -28,7 +26,7 @@ fn octree_to_file<P: AsRef<Path>, I: Index, R: Real>(
2826
path: P,
2927
) {
3028
let mesh = octree.hexmesh(&grid, false);
31-
io::vtk::write_vtk(UnstructuredGridPiece::from(&mesh), path.as_ref(), "octree").unwrap();
29+
io::vtk::write_vtk(mesh.to_dataset(), path.as_ref(), "octree").unwrap();
3230
}
3331

3432
#[test]
@@ -139,7 +137,7 @@ fn build_octree_from_vtk() {
139137
particle_count += node_particles.len();
140138

141139
// Ensure that all particles are within extents of octree cell
142-
let aabb = node.aabb(&grid);
140+
let aabb = node.aabb();
143141
for idx in node_particles.iter().copied() {
144142
let particle = particles[idx];
145143
assert!(aabb.contains_point(&particle));
@@ -152,45 +150,121 @@ fn build_octree_from_vtk() {
152150
//octree_to_file(&octree, &grid, "U:\\double_dam_break_frame_26_4732_particles_octree.vtk");
153151
}
154152

155-
#[test]
156-
fn build_octree_par_consistency() {
157-
let file = "../data/double_dam_break_frame_26_4732_particles.vtk";
158-
let particles = io::vtk::particles_from_vtk::<f64, _>(file).unwrap();
159-
//println!("Loaded {} particles from {}", particles.len(), file);
153+
struct TestParameters<R: Real> {
154+
particle_radius: R,
155+
compact_support_radius: R,
156+
cube_size: R,
157+
margin: Option<R>,
158+
max_particles_per_cell: Option<usize>,
159+
compare_seq_par: bool,
160+
}
160161

161-
let grid = grid_for_reconstruction::<i64, _>(
162-
particles.as_slice(),
163-
0.025,
164-
4.0 * 0.025,
165-
0.2,
166-
None,
167-
true,
168-
)
169-
.unwrap();
162+
impl<R: Real> Default for TestParameters<R> {
163+
fn default() -> Self {
164+
let particle_radius = R::from_f64(0.025).unwrap();
165+
let compact_support_radius = particle_radius.times_f64(4.0);
166+
let cube_size = particle_radius.times_f64(0.5);
167+
168+
Self {
169+
particle_radius,
170+
compact_support_radius,
171+
cube_size,
172+
margin: None,
173+
max_particles_per_cell: None,
174+
compare_seq_par: true,
175+
}
176+
}
177+
}
170178

171-
let mut octree_seq = Octree::new(&grid, particles.as_slice().len());
172-
octree_seq.subdivide_recursively_margin(
173-
&grid,
174-
particles.as_slice(),
175-
SubdivisionCriterion::MaxParticleCount(20),
176-
0.0,
177-
false,
178-
);
179+
impl<R: Real> TestParameters<R> {
180+
fn new(particle_radius: f64, compact_support_factor: f64, cube_size_factor: f64) -> Self {
181+
let params = Self::default();
182+
params.with_parameters(particle_radius, compact_support_factor, cube_size_factor)
183+
}
179184

180-
let mut octree_par = Octree::new(&grid, particles.as_slice().len());
181-
octree_par.subdivide_recursively_margin_par(
182-
&grid,
183-
particles.as_slice(),
184-
SubdivisionCriterion::MaxParticleCount(20),
185-
0.0,
186-
false,
185+
fn with_margin(mut self, margin: Option<f64>) -> Self {
186+
self.margin = margin.map(|m| self.particle_radius.times_f64(m));
187+
self
188+
}
189+
fn with_max_particles_per_cell(mut self, max_particles_per_cell: Option<usize>) -> Self {
190+
self.max_particles_per_cell = max_particles_per_cell;
191+
self
192+
}
193+
194+
fn with_compare_seq_par(mut self, compare_seq_par: bool) -> Self {
195+
self.compare_seq_par = compare_seq_par;
196+
self
197+
}
198+
199+
fn with_parameters(
200+
mut self,
201+
particle_radius: f64,
202+
compact_support_factor: f64,
203+
cube_size_factor: f64,
204+
) -> Self {
205+
self.particle_radius = R::from_f64(particle_radius).unwrap();
206+
self.compact_support_radius = self.particle_radius.times_f64(compact_support_factor);
207+
self.cube_size = self.particle_radius.times_f64(cube_size_factor);
208+
self
209+
}
210+
211+
fn build_grid<I: Index>(&self, particle_positions: &[Vector3<R>]) -> UniformGrid<I, R> {
212+
grid_for_reconstruction(
213+
particle_positions,
214+
self.particle_radius,
215+
self.compact_support_radius,
216+
self.cube_size,
217+
None,
218+
true,
219+
)
220+
.unwrap()
221+
}
222+
}
223+
224+
/// Returns a vector containing per particle how often it is a non-ghost particle in the octree
225+
fn count_non_ghost_particles<I: Index, R: Real>(
226+
particle_positions: &[Vector3<R>],
227+
octree: &Octree<I, R>,
228+
) -> Vec<usize> {
229+
let mut non_ghost_particles = vec![0; particle_positions.len()];
230+
for node in octree.root().dfs_iter() {
231+
if let Some(particle_set) = node.data().particle_set() {
232+
for idx in particle_set.particles.iter().copied() {
233+
if node.aabb().contains_point(&particle_positions[idx]) {
234+
non_ghost_particles[idx] += 1;
235+
}
236+
}
237+
}
238+
}
239+
240+
non_ghost_particles
241+
}
242+
243+
/// Asserts whether each particle has a unique octree node where it is not a ghost particle
244+
fn assert_unique_node_per_particle<I: Index, R: Real>(
245+
particle_positions: &[Vector3<R>],
246+
octree: &Octree<I, R>,
247+
) {
248+
let non_ghost_particles_counts = count_non_ghost_particles(particle_positions, octree);
249+
let no_unique_node_particles: Vec<_> = non_ghost_particles_counts
250+
.into_iter()
251+
.enumerate()
252+
.filter(|&(_, count)| count != 1)
253+
.collect();
254+
255+
assert_eq!(
256+
no_unique_node_particles,
257+
vec![],
258+
"There are nodes that don't have a unique octree node where they are not ghost particles!"
187259
);
260+
}
188261

189-
let mut particle_count = 0;
190-
for (node_seq, node_par) in octree_seq
262+
/// Asserts whether both trees have equivalent particle sets in the same octree nodes
263+
fn assert_tree_equivalence<I: Index, R: Real>(left_tree: &Octree<I, R>, right_tree: &Octree<I, R>) {
264+
for (node_seq, node_par) in left_tree
191265
.root()
192266
.dfs_iter()
193-
.zip(octree_par.root().dfs_iter())
267+
.zip(right_tree.root().dfs_iter())
194268
{
195269
match (
196270
node_seq.data().particle_set(),
@@ -201,8 +275,7 @@ fn build_octree_par_consistency() {
201275
let particles_par = &particles_par.particles;
202276

203277
// Ensure that we have the same number of particles for sequential and parallel result
204-
assert_eq!(particles_seq.len(), particles_par.len());
205-
particle_count += particles_seq.len();
278+
assert_eq!(particles_seq.len(), particles_par.len(), "Found corresponding leaves in the trees that don't have the same number of particles!");
206279

207280
// Sort the particle lists
208281
let mut particles_seq = particles_seq.to_vec();
@@ -211,19 +284,126 @@ fn build_octree_par_consistency() {
211284
particles_par.sort_unstable();
212285

213286
// Ensure that the particle lists are identical
214-
assert_eq!(particles_seq, particles_par);
287+
assert_eq!(particles_seq, particles_par, "Found corresponding leaves in the trees that don't have the same sorted particle lists!");
215288
}
216289
(None, None) => {
217290
// Both nodes are leaves
218291
continue;
219292
}
220293
_ => {
221-
// Parallel and sequential node do not match (one is leaf, one has children)
222-
assert!(false);
294+
// Parallel and sequential nodes do not match (one is leaf, one has children)
295+
panic!("Encountered a node where one octree has a particle set but the other does not!");
223296
}
224297
}
225298
}
299+
}
226300

227-
// Check if all particles were visited once
228-
assert_eq!(particle_count, particles.len());
301+
fn build_octree_par_consistency<I: Index, R: Real, P: AsRef<Path>>(
302+
file: P,
303+
parameters: TestParameters<R>,
304+
) {
305+
let particles = io::vtk::particles_from_vtk::<R, _>(file).unwrap();
306+
307+
let grid = parameters.build_grid::<I>(particles.as_slice());
308+
309+
let octree_seq = if parameters.compare_seq_par {
310+
let mut octree_seq = Octree::new(&grid, particles.as_slice().len());
311+
octree_seq.subdivide_recursively_margin(
312+
&grid,
313+
particles.as_slice(),
314+
parameters
315+
.max_particles_per_cell
316+
.map(|n| SubdivisionCriterion::MaxParticleCount(n))
317+
.unwrap_or(SubdivisionCriterion::MaxParticleCountAuto),
318+
parameters.margin.unwrap_or(R::zero()),
319+
false,
320+
);
321+
322+
assert_unique_node_per_particle(particles.as_slice(), &octree_seq);
323+
324+
Some(octree_seq)
325+
} else {
326+
None
327+
};
328+
329+
let mut octree_par = Octree::new(&grid, particles.as_slice().len());
330+
octree_par.subdivide_recursively_margin_par(
331+
&grid,
332+
particles.as_slice(),
333+
parameters
334+
.max_particles_per_cell
335+
.map(|n| SubdivisionCriterion::MaxParticleCount(n))
336+
.unwrap_or(SubdivisionCriterion::MaxParticleCountAuto),
337+
parameters.margin.unwrap_or(R::zero()),
338+
false,
339+
);
340+
341+
assert_unique_node_per_particle(particles.as_slice(), &octree_par);
342+
343+
if let Some(octree_seq) = octree_seq {
344+
assert_tree_equivalence(&octree_seq, &octree_par)
345+
}
346+
}
347+
348+
#[test]
349+
fn build_octree_cube() {
350+
build_octree_par_consistency::<i64, f64, _>(
351+
"../data/cube_2366_particles.vtk",
352+
TestParameters::default()
353+
.with_margin(Some(1.0))
354+
.with_max_particles_per_cell(Some(70)),
355+
);
356+
}
357+
358+
#[test]
359+
fn build_octree_double_dam_break() {
360+
build_octree_par_consistency::<i64, f64, _>(
361+
"../data/double_dam_break_frame_26_4732_particles.vtk",
362+
TestParameters::default()
363+
.with_margin(Some(1.0))
364+
.with_max_particles_per_cell(Some(200)),
365+
);
366+
}
367+
368+
#[test]
369+
fn build_octree_dam_break() {
370+
build_octree_par_consistency::<i64, f64, _>(
371+
"../data/dam_break_frame_23_24389_particles.vtk",
372+
TestParameters::default()
373+
.with_margin(Some(1.0))
374+
.with_max_particles_per_cell(Some(1000)),
375+
);
376+
}
377+
378+
#[test]
379+
fn build_octree_bunny() {
380+
build_octree_par_consistency::<i64, f64, _>(
381+
"../data/bunny_frame_14_7705_particles.vtk",
382+
TestParameters::default()
383+
.with_margin(Some(1.0))
384+
.with_max_particles_per_cell(Some(200)),
385+
);
386+
}
387+
388+
#[test]
389+
fn build_octree_hilbert() {
390+
build_octree_par_consistency::<i64, f64, _>(
391+
"../data/hilbert_46843_particles.vtk",
392+
TestParameters::default()
393+
.with_margin(Some(1.0))
394+
.with_max_particles_per_cell(Some(1000)),
395+
);
396+
}
397+
398+
/*
399+
#[test]
400+
fn build_octree_canyon() {
401+
build_octree_par_consistency::<i64, f64, _>(
402+
"../../canyon_13353401_particles.vtk",
403+
TestParameters::new(0.011, 4.0, 1.5)
404+
.with_margin(Some(1.0))
405+
.with_max_particles_per_cell(Some(52161))
406+
.with_compare_seq_par(false),
407+
);
229408
}
409+
*/

0 commit comments

Comments
 (0)