Skip to content

Commit 50cc9d8

Browse files
committed
Support subdomains even when ghost margin > subdomain size
1 parent 777d69f commit 50cc9d8

File tree

7 files changed

+175
-55
lines changed

7 files changed

+175
-55
lines changed

Cargo.lock

Lines changed: 1 addition & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

splashsurf_lib/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ rayon = "1.7"
5353
dashmap = "6.1.0"
5454
fxhash = "0.2"
5555
bitflags = "2.4"
56-
arrayvec = "0.7"
5756
bytemuck = { version = "1.9", features = ["extern_crate_alloc"] }
5857
bytemuck_derive = "1.3"
5958
numeric_literals = "0.2"
@@ -80,6 +79,7 @@ sdfu = { git = "https://github.com/w1th0utnam3/sdfu", features = [
8079
clap = { version = "4.5", features = ["derive"] }
8180
fern = "0.7"
8281
chrono = "0.4.41"
82+
all_asserts = "2.3.3"
8383

8484
[[example]]
8585
name = "minimal_levelset"

splashsurf_lib/src/dense_subdomains.rs

Lines changed: 68 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
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+
16
use anyhow::{Context, anyhow};
2-
use arrayvec::ArrayVec;
37
use itertools::Itertools;
48
use log::{info, trace};
59
use nalgebra::Vector3;
@@ -34,27 +38,27 @@ type GlobalIndex = i64;
3438
pub(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

6064
impl<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 {

splashsurf_lib/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,8 @@ impl<R: Real> Parameters<R> {
247247
pub struct SurfaceReconstruction<I: Scalar, R: Scalar + Send + Default> {
248248
/// Background grid that was used as a basis for generating the density map for marching cubes
249249
pub grid: UniformGrid<I, R>,
250+
/// If spatial decomposition was used, this contains the grid of subdomains
251+
pub subdomain_grid: Option<UniformGrid<I, R>>,
250252
/// Per particle densities (contains only data of particles inside the domain)
251253
pub particle_densities: Option<Vec<R>>,
252254
/// If an AABB was specified to restrict the reconstruction, this stores per input particle whether they were inside
@@ -264,6 +266,7 @@ impl<I: Index, R: Real> Default for SurfaceReconstruction<I, R> {
264266
fn default() -> Self {
265267
Self {
266268
grid: UniformGrid::new_zero(),
269+
subdomain_grid: None,
267270
particle_densities: None,
268271
particle_neighbors: None,
269272
particle_inside_aabb: None,

splashsurf_lib/src/reconstruction.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::dense_subdomains::{
2-
compute_global_densities_and_neighbors, decomposition, initialize_parameters, reconstruction,
3-
stitching, subdomain_classification::GhostMarginClassifier,
2+
compute_global_densities_and_neighbors, decomposition, initialize_parameters,
3+
reconstruct_subdomains, stitching, subdomain_classification::GhostMarginClassifier,
44
};
55
use crate::mesh::TriMesh3d;
66
use crate::uniform_grid::UniformGrid;
@@ -23,6 +23,7 @@ pub(crate) fn reconstruct_surface_subdomain_grid<I: Index, R: Real>(
2323

2424
let internal_parameters =
2525
initialize_parameters(parameters, particle_positions, output_surface)?;
26+
output_surface.subdomain_grid = Some(internal_parameters.subdomain_grid.clone());
2627
output_surface.grid = internal_parameters
2728
.global_marching_cubes_grid()
2829
.context("failed to convert global marching cubes grid")?;
@@ -36,7 +37,7 @@ pub(crate) fn reconstruct_surface_subdomain_grid<I: Index, R: Real>(
3637
&subdomains,
3738
);
3839

39-
let surface_patches = reconstruction(
40+
let surface_patches = reconstruct_subdomains(
4041
&internal_parameters,
4142
particle_positions,
4243
&particle_densities,

splashsurf_lib/tests/integration_tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ pub mod test_full;
44
pub mod test_mesh;
55
pub mod test_neighborhood_search;
66
pub mod test_simple;
7+
pub mod test_subdomains;

0 commit comments

Comments
 (0)