Skip to content

Commit 6d63b63

Browse files
committed
Implement local computation of densities written to global storage
1 parent 3a91c75 commit 6d63b63

6 files changed

Lines changed: 455 additions & 96 deletions

File tree

splashsurf/src/reconstruction.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ pub struct ReconstructSubcommandArgs {
8484
/// Safety factor applied to the kernel compact support radius when it's used as a margin to collect ghost particles in the leaf nodes
8585
#[structopt(long)]
8686
octree_ghost_margin_factor: Option<f64>,
87+
/// Whether to compute particle densities in a global step before domain decomposition (slower)
88+
#[structopt(long, default_value = "off", possible_values = &["on", "off"], case_insensitive = true)]
89+
octree_global_density: Switch,
90+
/// Whether to compute particle densities per subdomain but synchronize densities for ghost-particles (faster, recommended).
91+
/// Note: if both this and global particle density computation is disabled the ghost particle margin has to be increased to at least 2.0
92+
/// to compute correct density values for ghost particles.
93+
#[structopt(long, default_value = "on", possible_values = &["on", "off"], case_insensitive = true)]
94+
octree_sync_local_density: Switch,
8795
/// Optional filename for writing the point cloud representation of the intermediate density map to disk
8896
#[structopt(long, parse(from_os_str))]
8997
output_dm_points: Option<PathBuf>,
@@ -167,7 +175,7 @@ mod arguments {
167175
use anyhow::{anyhow, Context};
168176
use log::info;
169177
use splashsurf_lib::nalgebra::Vector3;
170-
use splashsurf_lib::AxisAlignedBoundingBox3d;
178+
use splashsurf_lib::{AxisAlignedBoundingBox3d, ParticleDensityComputationStrategy};
171179
use std::convert::TryFrom;
172180
use std::fs;
173181
use std::path::{Path, PathBuf};
@@ -232,10 +240,25 @@ mod arguments {
232240
let ghost_particle_safety_factor = args.octree_ghost_margin_factor;
233241
let enable_stitching = args.octree_stitch_subdomains.into_bool();
234242

243+
let particle_density_computation = if args.octree_global_density.into_bool()
244+
&& args.octree_sync_local_density.into_bool()
245+
{
246+
return Err(anyhow!("Cannot enable both global and merged local particle density computation at the same time. Switch off at least one."));
247+
} else {
248+
if args.octree_global_density.into_bool() {
249+
ParticleDensityComputationStrategy::Global
250+
} else if args.octree_sync_local_density.into_bool() {
251+
ParticleDensityComputationStrategy::SynchronizeSubdomains
252+
} else {
253+
ParticleDensityComputationStrategy::IndependentSubdomains
254+
}
255+
};
256+
235257
Some(splashsurf_lib::SpatialDecompositionParameters {
236258
subdivision_criterion,
237259
ghost_particle_safety_factor,
238260
enable_stitching,
261+
particle_density_computation,
239262
})
240263
};
241264

splashsurf_lib/src/density_map.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ pub fn compute_particle_densities_inplace<I: Index, R: Real>(
9696
fn init_density_storage<R: Real>(densities: &mut Vec<R>, new_len: usize) {
9797
// Ensure that length is correct
9898
densities.resize(new_len, R::zero());
99+
// Existing values don't have to be set to zero, as they are overwritten later anyway
99100
}
100101

101102
/// Computes the individual densities of particles using a standard SPH sum, sequential implementation

splashsurf_lib/src/lib.rs

Lines changed: 51 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ pub use uniform_grid::{GridConstructionError, UniformGrid};
2020

2121
use crate::mesh::TriMesh3d;
2222
use crate::octree::Octree;
23-
use crate::reconstruction::{
24-
reconstruct_single_surface_append, SurfaceReconstructionOctreeVisitor,
25-
};
2623
use crate::workspace::ReconstructionWorkspace;
2724

2825
#[cfg(feature = "profiling")]
@@ -103,6 +100,50 @@ pub struct SpatialDecompositionParameters<R: Real> {
103100
pub ghost_particle_safety_factor: Option<R>,
104101
/// Whether to enable stitching of all disjoint subdomain meshes to a global manifold mesh
105102
pub enable_stitching: bool,
103+
/// Which method to use for computing the densities of the particles
104+
pub particle_density_computation: ParticleDensityComputationStrategy,
105+
}
106+
107+
/// Available strategies for the computation of the particle densities
108+
#[derive(Copy, Clone, Debug)]
109+
pub enum ParticleDensityComputationStrategy {
110+
/// Compute the particle densities globally before performing domain decomposition.
111+
///
112+
/// With this approach the particle densities are computed globally on all particles before any
113+
/// domain decomposition is performed.
114+
///
115+
/// This approach is guaranteed to lead to consistent results and does not depend on the following
116+
/// decomposition. However, it is also by far the *slowest method* as global operations (especially
117+
/// the global neighborhood search) are much slower.
118+
Global,
119+
/// Compute particle densities for all particles locally followed by a synchronization step.
120+
///
121+
/// **This is the recommended approach.**
122+
/// The particle densities will be evaluated for all particles per subdomain, possibly in parallel.
123+
/// Afterwards, the values for all non-ghost particles are written to a global array.
124+
/// This happens in a separate step before performing any reconstructions
125+
/// For the following reconstruction procedure, each subdomain will update the densities of its ghost particles
126+
/// from this global array. This ensures that all ghost-particles receive correct density values
127+
/// without requiring to double the width of the ghost-particle margin just to ensure correct values
128+
/// for the actual inner ghost-particles (i.e. in contrast to the completely local approach).
129+
///
130+
/// The actual synchronization overhead is relatively low and this approach is often the fastest method.
131+
///
132+
/// This approach should always lead consistent results. Only in very rare cases when a particle is not
133+
/// uniquely assigned during domain decomposition this might lead to problems. If you encounter such
134+
/// problems with this approach please report it as a bug.
135+
SynchronizeSubdomains,
136+
/// Compute densities locally per subdomain without global synchronization.
137+
///
138+
/// The particle densities will be evaluated per subdomain on-the-fly just before the reconstruction
139+
/// of the subdomain happens. In order to compute correct densities for the ghost particles of each
140+
/// subdomain it is required that the ghost-particle margin is at least two times the kernel compact
141+
/// support radius. This may add a lot of additional ghost-particles to each subdomain.
142+
///
143+
/// If the ghost-particle margin is not set wide enough, this may lead to density differences on subdomain
144+
/// boundaries. Otherwise this approach robust with respect to the classification of particles into the
145+
/// subdomains.
146+
IndependentSubdomains,
106147
}
107148

108149
impl<R: Real> SpatialDecompositionParameters<R> {
@@ -115,6 +156,7 @@ impl<R: Real> SpatialDecompositionParameters<R> {
115156
r => r.try_convert()?
116157
),
117158
enable_stitching: self.enable_stitching,
159+
particle_density_computation: self.particle_density_computation,
118160
})
119161
}
120162
}
@@ -266,7 +308,6 @@ pub fn reconstruct_surface<I: Index, R: Real>(
266308
particle_positions: &[Vector3<R>],
267309
parameters: &Parameters<R>,
268310
) -> Result<SurfaceReconstruction<I, R>, ReconstructionError<I, R>> {
269-
profile!("reconstruct_surface");
270311
let mut surface = SurfaceReconstruction::default();
271312
reconstruct_surface_inplace(particle_positions, parameters, &mut surface)?;
272313
Ok(surface)
@@ -290,55 +331,17 @@ pub fn reconstruct_surface_inplace<'a, I: Index, R: Real>(
290331
parameters.domain_aabb.as_ref(),
291332
parameters.enable_multi_threading,
292333
)?;
293-
let grid = &output_surface.grid;
294-
grid.log_grid_info();
334+
335+
output_surface.grid.log_grid_info();
295336

296337
if parameters.spatial_decomposition.is_some() {
297-
SurfaceReconstructionOctreeVisitor::new(particle_positions, parameters, output_surface)
298-
.unwrap()
299-
.run(particle_positions, output_surface);
300-
} else {
301-
profile!("reconstruct_surface_inplace");
302-
303-
let mut workspace = output_surface
304-
.workspace
305-
.get_local_with_capacity(particle_positions.len())
306-
.borrow_mut();
307-
308-
// Clear the current mesh, as reconstruction will be appended to output
309-
output_surface.mesh.clear();
310-
// Perform global reconstruction without octree
311-
reconstruct_single_surface_append(
312-
&mut *workspace,
313-
grid,
314-
None,
338+
reconstruction::reconstruct_surface_domain_decomposition(
315339
particle_positions,
316340
parameters,
317-
&mut output_surface.mesh,
341+
output_surface,
318342
);
319-
320-
/*
321-
let particle_indices = splash_detection_radius.map(|splash_detection_radius| {
322-
let neighborhood_list = neighborhood_search::search::<I, R>(
323-
&grid.aabb(),
324-
particle_positions,
325-
splash_detection_radius,
326-
enable_multi_threading,
327-
);
328-
329-
let mut active_particles = Vec::new();
330-
for (particle_i, neighbors) in neighborhood_list.iter().enumerate() {
331-
if !neighbors.is_empty() {
332-
active_particles.push(particle_i);
333-
}
334-
}
335-
336-
active_particles
337-
});
338-
*/
339-
340-
// TODO: Set this correctly
341-
output_surface.density_map = None;
343+
} else {
344+
reconstruction::reconstruct_surface_global(particle_positions, parameters, output_surface);
342345
}
343346

344347
Ok(())

0 commit comments

Comments
 (0)