Add hemisphere shell particle cloud packing - #1667
Conversation
|
I don't think this is implemented quite right. The example you added highlights this fact. It defines a hemispherical particle cloud using a cubic domain. I think instead you should add a Edit: Your |
|
Thanks Ben, that makes sense. I agree that the hemisphere shell should be treated as a particle-cloud geometry rather than a packing method. I’ll refactor this so that I’ll also remove or adjust the example so it does not imply that a cubic domain defines the hemispherical cloud. |
81e9038 to
933f66c
Compare
| do while (n_placed < particle_cloud(cloud_idx)%num_particles .and. n_attempts < max_attempts) | ||
| n_attempts = n_attempts + 1 | ||
|
|
||
| if (p == 0) then |
There was a problem hiding this comment.
num_dims < 3 peferred in general. This likely does not affect things, but num_dims is a case-optimization parameter, and therefore the compiler can optimize this away if you make it a check on num_dims.
There was a problem hiding this comment.
Updated this to use num_dims < 3 instead of checking p == 0.
| xdir = rho*cos(phi) | ||
| ydir = rho*sin(phi) | ||
| u = f_xorshift(seed) | ||
| r_shell = ((r_outer**3._wp - r_inner**3._wp)*u + r_inner**3._wp)**(1._wp/3._wp) |
There was a problem hiding this comment.
This seems like a lot fo compute for your QOI. You do not use the [xyz]dir parameter at all after computing it. Again, either use it or do not computed.
There was a problem hiding this comment.
That said, you using a probability distribution function to weight your random seed is correct here. Just you are computing redundant quantities here that seem pointless.
There was a problem hiding this comment.
-
Thanks for catching that! I completely missed that [xyz]dir wasn't being used later on. I've removed the redundant calculation to save compute.
-
Thanks for confirming the PDF logic. I've cleaned up all the redundant quantities you mentioned to optimize the compute.
933f66c to
4aff083
Compare
BCKim55
left a comment
There was a problem hiding this comment.
Thanks for the feedback. I updated the hemisphere-shell path to remove the redundant box-bounds logic and rely on the shell radii for the placement region. I also changed the dimensionality checks to use num_dims < 3 and removed the extra direction variables in the 3D sampling path.
| do while (n_placed < particle_cloud(cloud_idx)%num_particles .and. n_attempts < max_attempts) | ||
| n_attempts = n_attempts + 1 | ||
|
|
||
| if (p == 0) then |
There was a problem hiding this comment.
Updated this to use num_dims < 3 instead of checking p == 0.
| xdir = rho*cos(phi) | ||
| ydir = rho*sin(phi) | ||
| u = f_xorshift(seed) | ||
| r_shell = ((r_outer**3._wp - r_inner**3._wp)*u + r_inner**3._wp)**(1._wp/3._wp) |
There was a problem hiding this comment.
-
Thanks for catching that! I completely missed that [xyz]dir wasn't being used later on. I've removed the redundant calculation to save compute.
-
Thanks for confirming the PDF logic. I've cleaned up all the redundant quantities you mentioned to optimize the compute.
sbryngelson
left a comment
There was a problem hiding this comment.
Request changes
-
Missing regression coverage. This PR adds the hemisphere-shell execution path but removes the local example and adds no test fixture. The existing particle-cloud case is box-only, so CI never executes
geometry = 2. Please add a small deterministic 2D or 3D case that checks generated particle positions for shell/plane clearance and non-overlap. -
Documentation was not updated.
docs/documentation/case.mddoes not documentgeometry,shell_inner_radius, orshell_outer_radius, and itslength_[x,y,z]description is inaccurate forgeometry = 2, where those extents are ignored. Please update the Particle Clouds section accordingly.
|
Hi Spencer, I addressed the requested validation, documentation, and regression coverage changes. CI is now passing, and the PR is ready for another look when you have time. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1667 +/- ##
==========================================
- Coverage 61.04% 61.04% -0.01%
==========================================
Files 83 83
Lines 20978 21054 +76
Branches 3099 3109 +10
==========================================
+ Hits 12807 12853 +46
- Misses 6126 6150 +24
- Partials 2045 2051 +6 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
sbryngelson
left a comment
There was a problem hiding this comment.
Thanks for the revisions. Documentation and validation are genuinely addressed:
case.mdnow documentsgeometry,shell_inner_radius,shell_outer_radius, and qualifieslength_x[y,z]as box-only.- The Python validator and
m_checker.fppnow enforce the same condition, andshell_outer_radius > shell_inner_radius + 2*radiusis exactly the sampler's feasibility requirement (r_outer > r_innerafter the +/- radius insets), so thes_mpi_abortpath is no longer reachable from valid input. geometryand both new reals are broadcast inm_mpi_proxy.fpp.- The sampling math is right: the 2D
sqrtCDF and the 3D cube-root CDF with uniformcos(polar)both give uniform density in the shell.
The test coverage point is not yet addressed, though, and that is the blocking item. Details inline.
| } | ||
|
|
||
|
|
||
| def test_hemi_shell_random_packing_respects_clearance_and_overlap(): |
There was a problem hiding this comment.
This test does not exercise the code the PR adds. _pack_hemi_shell_3d is a Python reimplementation of s_particle_cloud_random_hemi_shell, and the assertions below check that the Python implementation satisfies the shell and overlap constraints. Nothing here reads from m_particle_cloud.fpp, so no regression in the Fortran sampler can make this test fail.
What I asked for was verification of the generated particle positions. Please parse ib_state_0.dat from an actual run and assert the shell clearance, plane clearance, and pairwise non-overlap on those coordinates. A mirrored implementation cannot serve that purpose no matter how faithful it is.
| return False | ||
|
|
||
|
|
||
| def _pack_hemi_shell_3d(count, radius, min_spacing, inner_radius, outer_radius, seed_value): |
There was a problem hiding this comment.
Separately from the point above: this model is not actually equivalent to the Fortran. _is_overlapping keys cells on exact bin tuples, whereas f_cloud_particle_overlaps folds bins through f_bin_hash into hash_size buckets. Collisions cause the Fortran to reject candidates this model accepts, so the two will diverge on particle placement for the same seed.
So even taken as documentation of the algorithm, it is misleading. Another reason to test the real output instead.
| cases.append( | ||
| define_case_d( | ||
| stack, | ||
| "IBM -> Particle Cloud -> Hemisphere Shell", |
There was a problem hiding this comment.
Two coverage gaps here.
1. 3D is not covered. This block sits inside if len(dimInfo[0]) == 2 and not viscous:, so CI only ever runs the 2D branch — which is a half-annulus, not a hemisphere. The 3D path (phi/zdir/rho with the cube-root radial CDF) is the one this feature is named for and it never executes. Please add a 3D golden.
2. The box path this PR rewrote has no coverage at all. grep particle_cloud toolchain/mfc/test/cases.py returns only this new case — it is the only particle-cloud golden in the suite. Meanwhile s_particle_cloud_random_box was rewritten here (p == 0 -> num_dims < 3, the inlined overlap loop replaced by f_cloud_particle_overlaps, bin coordinates now computed after acceptance rather than before). That refactor is unverified. Please add a box golden as well, or demonstrate bit-identical output before and after the change on an existing example.
Note also that the goldens hold cons.* fields rather than ib_state. Placement perturbs the flow, so this is a real regression lock, but it does not directly assert the geometric constraints.
| end if | ||
|
|
||
| if (num_dims < 3) then | ||
| geom = 2 |
There was a problem hiding this comment.
geometry is overloaded in a way that will bite users. Here particle_cloud(i)%geometry == 2 means hemisphere shell, while two lines down geom = 2 means circle in the patch_ib vocabulary (and geom = 8 means sphere). The same subroutine uses two different meanings of geometry = 2 within six lines.
Please rename the cloud field to something like region_shape or cloud_geometry, or reuse the existing patch geometry codes. As written, a user who reads particle_cloud(1)%geometry = 2 will reasonably expect a circle.
| end if | ||
|
|
||
| if (num_dims < 3) then | ||
| if (ry < particle_cloud(cloud_idx)%y_centroid + particle_cloud(cloud_idx)%radius) cycle |
There was a problem hiding this comment.
Two things about the region definition.
Orientation is hard-coded and unconfigurable. The flat face is at y_centroid in 2D and z_centroid in 3D, always opening toward +y / +z. That is a reasonable initial restriction, but it needs to be stated in case.md — right now nothing tells a user which way the hemisphere faces.
Nothing bounds the cloud to the domain. Dropping the [xyz]min/max logic was the right call per @danieljvickers, but nothing replaced it: x_centroid +/- shell_outer_radius can extend past x_domain, and s_add_cloud_particle performs no bounds check, so particles land off-grid silently. The validator already has x[y,z]_centroid, shell_outer_radius, and the domain extents, so this is a cheap check to add there.
| j = hash_head(slot) | ||
| do while (j > 0) | ||
| if (num_dims < 3) then | ||
| dist_sq = (px - placed(1, j))**2._wp + (py - placed(2, j))**2._wp |
There was a problem hiding this comment.
Minor: the extraction turned integer exponents into real ones. The original was (rx - placed(1, j))**2; this is now **2._wp, and min_dist_sq = min_dist**2._wp above is the same. gfortran folds x**2.0 to x*x (I checked at both -O0 and -O2), but that is a compiler courtesy rather than a guarantee, and this is the innermost loop of the rejection sampler. Please use **2.
Also minor, in the same area: s_get_cloud_bin runs twice per accepted particle — once inside f_cloud_particle_overlaps and again at the insertion site on line 227 — and it tests num_dims == 3 while everything else in the module tests num_dims < 3.
|
|
||
| - `geometry` selects the cloud region: | ||
| - `1` (box) uses `x[y,z]_centroid` and `length_x[y,z]` to define the region. | ||
| - `2` (hemisphere shell) uses `x[y,z]_centroid`, `shell_inner_radius`, and `shell_outer_radius` to define the region. Particle centres are sampled between `shell_inner_radius + radius` and `shell_outer_radius - radius`, and the flat hemisphere plane is kept clear by one particle radius. |
There was a problem hiding this comment.
This is a clear improvement, but two facts a user needs are still missing:
- Which way the hemisphere opens. The flat face lies at
y_centroidin 2D andz_centroidin 3D, and the filled half is +y / +z. This is not configurable and is not stated anywhere. - That the 2D region is a half-annulus, not a hemisphere. Calling both cases "hemisphere shell" is going to confuse people setting up 2D cases.
Description
Adds a hemisphere-shell particle cloud packing option for immersed-boundary particle clouds.
This introduces
particle_cloud(i)%packing_method = 3, which randomly places spherical/circular IBM particles inside a hemisphere-shell region while enforcing:This also adds
shell_inner_radiusandshell_outer_radiusparticle cloud parameters. The local verification example was removed from this PR to avoid introducing a new golden test in the same change.Type of change
Testing
./mfc.sh format./mfc.sh validate examples/*/case.py./mfc.sh validate examples/3D_mibm_particle_cloud_hemi_shell/case.py./mfc.sh run examples/3D_mibm_particle_cloud_hemi_shell/case.py --clean --no-debugAdditional local checks:
ib_state_0.datparticle positions.min_spacing=0.02.min_spacing=0.0.Checklist
GPU changes (expand if you modified
src/simulation/)