1+ //! Internal functions related to the subdomain-based parallel surface reconstruction
2+ //!
3+ //! Note that we do not guarantee API stability of these functions, they are only exposed for
4+ //! testing and benchmarking purposes.
5+
16use anyhow:: { Context , anyhow} ;
2- use arrayvec:: ArrayVec ;
37use itertools:: Itertools ;
48use log:: { info, trace} ;
59use nalgebra:: Vector3 ;
@@ -34,27 +38,27 @@ type GlobalIndex = i64;
3438pub ( crate ) struct ParametersSubdomainGrid < I : Index , R : Real > {
3539 /// SPH particle radius (in simulation units)
3640 #[ allow( unused) ]
37- particle_radius : R ,
41+ pub particle_radius : R ,
3842 /// Rest mass of each particle
39- particle_rest_mass : R ,
43+ pub particle_rest_mass : R ,
4044 /// SPH kernel compact support radius (in simulation units)
41- compact_support_radius : R ,
45+ pub compact_support_radius : R ,
4246 /// Density value for the iso-surface
43- surface_threshold : R ,
47+ pub surface_threshold : R ,
4448 /// MC cube size (in simulation units)
45- cube_size : R ,
49+ pub cube_size : R ,
4650 /// Size of a subdomain in multiples of MC cubes
47- subdomain_cubes : I ,
51+ pub subdomain_cubes : I ,
4852 /// Margin for ghost particles around each subdomain
49- ghost_particle_margin : R ,
53+ pub ghost_particle_margin : R ,
5054 /// Implicit global MC background grid (required to compute consistent float coordinates at domain boundaries)
51- global_marching_cubes_grid : UniformCartesianCubeGrid3d < GlobalIndex , R > ,
55+ pub global_marching_cubes_grid : UniformCartesianCubeGrid3d < GlobalIndex , R > ,
5256 /// Implicit subdomain grid
53- subdomain_grid : UniformCartesianCubeGrid3d < I , R > ,
57+ pub subdomain_grid : UniformCartesianCubeGrid3d < I , R > ,
5458 /// Chunk size for chunked parallel processing
55- chunk_size : usize ,
59+ pub chunk_size : usize ,
5660 /// Whether to return the global particle neighborhood list instead of only using per-domain lists internally
57- global_neighborhood_list : bool ,
61+ pub global_neighborhood_list : bool ,
5862}
5963
6064impl < I : Index , R : Real > ParametersSubdomainGrid < I , R > {
@@ -142,13 +146,6 @@ pub(crate) fn initialize_parameters<I: Index, R: Real>(
142146 ghost_particle_margin / cube_size,
143147 ghost_particle_margin / ( cube_size * subdomain_cubes. to_real_unchecked( ) )
144148 ) ;
145-
146- if ghost_margin_cubes > subdomain_cubes {
147- panic ! (
148- "The ghost margin is {ghost_margin_cubes} cubes wide (rounded up), while the subdomain only has an extent of {subdomain_cubes} cubes. The subdomain has to have at least the same number of cubes ({})!" ,
149- ghost_margin_cubes
150- ) ;
151- }
152149 }
153150
154151 // AABB of the particles
@@ -559,6 +556,7 @@ pub(crate) fn compute_global_densities_and_neighbors<I: Index, R: Real>(
559556 . expect ( "Subdomain cell does not exist" ) ;
560557 let subdomain_aabb = parameters. subdomain_grid . cell_aabb ( & subdomain_idx) ;
561558
559+ // AABB used for neighborhood search, must encompass all particles, including ghost particles
562560 let margin_aabb = {
563561 let mut margin_aabb = subdomain_aabb. clone ( ) ;
564562 // TODO: Verify if we can omit this extra margin?
@@ -1214,7 +1212,7 @@ pub fn density_grid_loop_sparse<I: Index, R: Real, K: SymmetricKernel3d<R>>(
12141212 }
12151213}
12161214
1217- pub ( crate ) fn reconstruction < I : Index , R : Real > (
1215+ pub ( crate ) fn reconstruct_subdomains < I : Index , R : Real > (
12181216 parameters : & ParametersSubdomainGrid < I , R > ,
12191217 global_particles : & [ Vector3 < R > ] ,
12201218 global_particle_densities : & [ R ] ,
@@ -1473,14 +1471,22 @@ pub(crate) fn reconstruction<I: Index, R: Real>(
14731471 let cell = mc_grid. try_unflatten_cell_index ( flat_cell_idx) . unwrap ( ) ;
14741472
14751473 let mut vertices_inside = [ true ; 8 ] ;
1474+ let mut any_inside = false ;
14761475 for local_point_index in 0 ..8 {
14771476 let point = cell. global_point_index_of ( local_point_index) . unwrap ( ) ;
14781477 let flat_point_idx = mc_grid. flatten_point_index ( & point) ;
14791478 let flat_point_idx = flat_point_idx. to_usize ( ) . unwrap ( ) ;
14801479 // Get value of density map
14811480 let density_value = levelset_grid[ flat_point_idx] ;
14821481 // Update inside/outside surface flag
1483- vertices_inside[ local_point_index] = density_value > parameters. surface_threshold ;
1482+ let vertex_inside = density_value > parameters. surface_threshold ;
1483+ any_inside |= vertex_inside;
1484+ vertices_inside[ local_point_index] = vertex_inside;
1485+ }
1486+
1487+ if !any_inside {
1488+ // No vertices inside surface, skip triangulation
1489+ return ;
14841490 }
14851491
14861492 for triangle in marching_cubes_triangulation_iter ( & vertices_inside) {
@@ -1762,15 +1768,15 @@ pub(crate) mod subdomain_classification {
17621768 fn get ( & self , i : usize ) -> I ;
17631769 }
17641770
1765- /// Classifier that assign a particle to its owning subdomain and all subdomains where it's a ghost particle
1771+ /// Classifier that assign a particle to its " owning" subdomain and all subdomains where it's a ghost particle
17661772 pub struct GhostMarginClassifier < I : Index > {
1767- subdomains : ArrayVec < I , 27 > ,
1773+ subdomains : Vec < I > ,
17681774 }
17691775
17701776 impl < I : Index , R : Real > ParticleToSubdomainClassifier < I , R > for GhostMarginClassifier < I > {
17711777 fn new ( ) -> Self {
17721778 GhostMarginClassifier {
1773- subdomains : ArrayVec :: new ( ) ,
1779+ subdomains : Vec :: with_capacity ( 27 ) ,
17741780 }
17751781 }
17761782
@@ -1805,15 +1811,26 @@ pub(crate) mod subdomain_classification {
18051811 particle : & Vector3 < R > ,
18061812 subdomain_grid : & UniformCartesianCubeGrid3d < I , R > ,
18071813 ghost_particle_margin : R ,
1808- subdomains : & mut ArrayVec < I , 27 > ,
1814+ subdomains : & mut Vec < I > ,
18091815 ) {
18101816 // Find the owning subdomain of the particle
18111817 let subdomain_ijk = subdomain_grid. enclosing_cell ( particle) ;
18121818 // Make sure particle is part of computational domain
18131819 if subdomain_grid. get_cell ( subdomain_ijk) . is_none ( ) {
1820+ // TODO: When/why does this occur?
18141821 return ;
18151822 }
18161823
1824+ let dx = subdomain_grid. cell_size ( ) ;
1825+
1826+ // The number of subdomains that have to be checked in each direction
1827+ let subdomain_radius = I :: from ( ( ghost_particle_margin / dx) . ceil ( ) )
1828+ . map ( |i| i. to_i32 ( ) )
1829+ . flatten ( )
1830+ . expect (
1831+ "ghost particle margin relative to subdomains has to fit in index type and i32" ,
1832+ ) ;
1833+
18171834 // Get corner points spanning the owning subdomain
18181835 let subdomain_aabb = subdomain_grid. cell_aabb (
18191836 & subdomain_grid
@@ -1824,47 +1841,54 @@ pub(crate) mod subdomain_classification {
18241841 let max_corner = subdomain_aabb. max ( ) ;
18251842
18261843 // Checks whether the current particle is within the ghost particle margin of the half plane reached by the given step and along an axis
1827- let is_in_ghost_margin_single_dim = |step : i8 , axis : usize | -> bool {
1828- match step {
1829- -1 => particle[ axis] - min_corner[ axis] < ghost_particle_margin,
1830- 0 => true ,
1831- 1 => max_corner[ axis] - particle[ axis] < ghost_particle_margin,
1832- _ => unsafe { std:: hint:: unreachable_unchecked ( ) } ,
1844+ let is_in_ghost_margin_single_dim = |step : i32 , axis : usize | -> bool {
1845+ let step_offset_real = R :: from ( step. abs ( ) - 1 ) . unwrap ( ) ;
1846+ if step > 0 {
1847+ ( ( max_corner[ axis] + step_offset_real * dx) - particle[ axis] )
1848+ < ghost_particle_margin
1849+ } else if step < 0 {
1850+ ( particle[ axis] - ( min_corner[ axis] - step_offset_real * dx) )
1851+ < ghost_particle_margin
1852+ } else {
1853+ // step == 0
1854+ true
18331855 }
18341856 } ;
18351857
18361858 // Checks whether the current particle is within the ghost particle margin of the neighbor subdomain reached by the given steps
1837- let is_in_ghost_margin = |x_step : i8 , y_step : i8 , z_step : i8 | -> bool {
1859+ let is_in_ghost_margin = |x_step : i32 , y_step : i32 , z_step : i32 | -> bool {
18381860 is_in_ghost_margin_single_dim ( x_step, 0 )
18391861 && is_in_ghost_margin_single_dim ( y_step, 1 )
18401862 && is_in_ghost_margin_single_dim ( z_step, 2 )
18411863 } ;
18421864
1843- let checked_apply_step = |index : I , step : i8 | -> Option < I > {
1844- let direction = match step {
1845- -1 => Some ( Direction :: Negative ) ,
1846- 0 => None ,
1847- 1 => Some ( Direction :: Positive ) ,
1848- _ => unsafe { std:: hint:: unreachable_unchecked ( ) } ,
1865+ let checked_apply_step = |index : I , step : i32 | -> Option < I > {
1866+ let direction = if step > 0 {
1867+ Some ( Direction :: Positive )
1868+ } else if step < 0 {
1869+ Some ( Direction :: Negative )
1870+ } else {
1871+ None
18491872 } ;
18501873 direction
1851- . map ( |d| d. checked_apply_step ( index, I :: one ( ) ) )
1874+ . map ( |d| d. checked_apply_step ( index, I :: from ( step . abs ( ) ) . unwrap ( ) ) )
18521875 . unwrap_or ( Some ( index) )
18531876 } ;
18541877
18551878 let checked_apply_step_ijk =
1856- |ijk : [ I ; 3 ] , x_step : i8 , y_step : i8 , z_step : i8 | -> Option < [ I ; 3 ] > {
1879+ |ijk : [ I ; 3 ] , x_step : i32 , y_step : i32 , z_step : i32 | -> Option < [ I ; 3 ] > {
18571880 Some ( [
18581881 checked_apply_step ( ijk[ 0 ] , x_step) ?,
18591882 checked_apply_step ( ijk[ 1 ] , y_step) ?,
18601883 checked_apply_step ( ijk[ 2 ] , z_step) ?,
18611884 ] )
18621885 } ;
18631886
1864- for & i in & [ -1 , 0 , 1 ] {
1865- for & j in & [ -1 , 0 , 1 ] {
1866- for & k in & [ -1 , 0 , 1 ] {
1867- // Check if the particle is in the ghost particle margin of the current subdomain
1887+ let r = subdomain_radius;
1888+ for i in -r..=r {
1889+ for j in -r..=r {
1890+ for k in -r..=r {
1891+ // Check if the particle is in the ghost particle margin of the subdomain i,j,k
18681892 let in_ghost_margin = is_in_ghost_margin ( i, j, k) ;
18691893
18701894 if in_ghost_margin {
0 commit comments