1- use std:: path:: Path ;
2-
3- use splashsurf_lib:: mesh:: PointCloud3d ;
1+ use splashsurf_lib:: generic_tree:: VisitableTree ;
42use splashsurf_lib:: nalgebra:: Vector3 ;
53use splashsurf_lib:: octree:: Octree ;
64use splashsurf_lib:: { grid_for_reconstruction, Index , Real , SubdivisionCriterion , UniformGrid } ;
7-
8- use vtkio:: model:: UnstructuredGridPiece ;
5+ use std:: path:: Path ;
96
107use super :: io;
11- use splashsurf_lib:: generic_tree:: VisitableTree ;
128
9+ /*
1310#[allow(dead_code)]
1411fn 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) ]
2523fn 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