Skip to content

Commit 29bcf7e

Browse files
authored
Merge branch 'master' into fix/inactive-patch-validation
2 parents 5c6ad39 + 4cebbb0 commit 29bcf7e

40 files changed

Lines changed: 1335 additions & 933 deletions

.github/scripts/prebuild-case-optimization.sh

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,44 @@ case "$cluster" in
2222
*) echo "ERROR: Unknown cluster '$cluster'"; exit 1 ;;
2323
esac
2424

25+
# Optional sharding (format "i/N", e.g. "1/2"), set by submit-slurm-job.sh's
26+
# [shard] argument via $job_shard: shard i builds every Nth case of the sorted
27+
# case list. Unset = build all cases in one job (default; other clusters).
28+
shard="${job_shard:-}"
29+
if [ -n "$shard" ]; then
30+
# Validate full shape: must be exactly "digits/digits" — one slash with
31+
# non-empty, purely numeric, non-leading-zero parts on both sides.
32+
# Split first, then validate each part independently so that inputs like
33+
# "1/" "/2" "//" "1/2/3" "a/b" "12" are all caught before any arithmetic.
34+
shard_idx="${shard%%/*}"
35+
shard_count="${shard##*/}"
36+
# Reject if no slash (idx and count are equal and equal to the whole string)
37+
case "$shard_idx" in
38+
''|*[!0-9]*|0*) echo "ERROR: bad shard '$shard' (expected i/N)"; exit 1 ;;
39+
esac
40+
case "$shard_count" in
41+
''|*[!0-9]*|0*) echo "ERROR: bad shard '$shard' (expected i/N)"; exit 1 ;;
42+
esac
43+
# Confirm the string is exactly "idx/count" — catches "12" (no slash) and
44+
# "1/2/3" (extra slash, where idx=1 and count=2/3 would have failed above,
45+
# but this is an extra safety net).
46+
if [ "$shard" != "$shard_idx/$shard_count" ]; then
47+
echo "ERROR: bad shard '$shard' (expected i/N)"; exit 1
48+
fi
49+
if [ "$shard_idx" -lt 1 ] || [ "$shard_idx" -gt "$shard_count" ]; then
50+
echo "ERROR: bad shard '$shard' (expected i/N with 1 <= i <= N)"; exit 1
51+
fi
52+
fi
53+
2554
# Phoenix starts fresh (no prior dep build); other clusters pre-build deps via
2655
# build.sh first, so we must preserve them and only clean MFC target staging.
56+
# Sharded jobs share one workspace and run concurrently, so the workflow
57+
# cleans once before submitting them — cleaning here would wipe a sibling
58+
# shard's in-progress build.
2759
if [ "$cluster" = "phoenix" ]; then
2860
source .github/scripts/clean-build.sh
2961
clean_build
30-
else
62+
elif [ -z "$shard" ]; then
3163
find build/staging -maxdepth 1 -regex '.*/[0-9a-f]+' -type d -exec rm -rf {} + 2>/dev/null || true
3264
find build/install -maxdepth 1 -regex '.*/[0-9a-f]+' -type d -exec rm -rf {} + 2>/dev/null || true
3365
fi
@@ -40,7 +72,49 @@ case "$job_interface" in
4072
*) echo "ERROR: prebuild requires gpu interface (acc or omp)"; exit 1 ;;
4173
esac
4274

75+
# Case-optimized simulation builds land in per-case hash-named staging dirs,
76+
# but syscheck/pre_process/post_process hash identically across these cases.
77+
# Concurrent shards must not build those shared staging dirs simultaneously:
78+
# shard 1 builds them first and drops a done marker; other shards wait for it,
79+
# after which their builds no-op in the shared dirs.
80+
if [ -n "$shard" ] && [ "$shard_count" -gt 1 ]; then
81+
shared_marker_done="build/.prebuild-shared-targets-done"
82+
shared_marker_failed="build/.prebuild-shared-targets-failed"
83+
set -- benchmarks/*/case.py
84+
first_case="$1"
85+
if [ "$shard_idx" -eq 1 ]; then
86+
# Remove both markers at the start so reruns and manual invocations
87+
# never observe stale state from a prior run.
88+
rm -f "$shared_marker_done" "$shared_marker_failed"
89+
echo "=== Shard 1/$shard_count: building shared targets ==="
90+
# Write the failure marker if the build exits non-zero so other shards
91+
# can detect the failure immediately instead of waiting 90 minutes.
92+
trap 'touch "$shared_marker_failed"' ERR
93+
./mfc.sh build -i "$first_case" -t syscheck pre_process post_process --case-optimization $gpu_opts -j 8
94+
trap - ERR
95+
touch "$shared_marker_done"
96+
else
97+
echo "=== Shard $shard_idx/$shard_count: waiting for shard 1 to build shared targets ==="
98+
waited=0
99+
until [ -f "$shared_marker_done" ]; do
100+
if [ -f "$shared_marker_failed" ]; then
101+
echo "ERROR: shard 1 failed to build shared targets; see shard 1 log"; exit 1
102+
fi
103+
if [ "$waited" -ge 5400 ]; then
104+
echo "ERROR: timed out waiting for $shared_marker_done"; exit 1
105+
fi
106+
sleep 30
107+
waited=$((waited + 30))
108+
done
109+
fi
110+
fi
111+
112+
idx=0
43113
for case in benchmarks/*/case.py; do
114+
idx=$((idx + 1))
115+
if [ -n "$shard" ] && [ $(((idx - 1) % shard_count)) -ne $((shard_idx - 1)) ]; then
116+
continue
117+
fi
44118
echo "=== Pre-building: $case ==="
45119
./mfc.sh run "$case" --case-optimization $gpu_opts -j 8 --dry-run
46120
done

.github/scripts/run_parallel_benchmarks.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ bash "${SCRIPT_DIR}/run_monitored_slurm_job.sh" "$pr_job_id" "pr/${job_slug}.out
6262
if [ "$pr_exit" -ne 0 ]; then
6363
echo "PR job exited with code: $pr_exit"
6464
tail -n 50 "pr/${job_slug}.out" 2>/dev/null || echo " Could not read PR log"
65+
# The PR benchmark run genuinely failed (cases crashed/hung/SIGTERM'd, not a
66+
# monitor false-positive -- run_monitored_slurm_job.sh re-checks sacct). Fail
67+
# the job instead of falling through to the YAML-exists check, which would let
68+
# a broken PR pass green as long as a partial YAML was written. Scoped to PR
69+
# only: a master/baseline infra flake stays a warning and does not red-cross.
70+
exit 1
6571
else
6672
echo "PR job completed successfully"
6773
fi

.github/workflows/test.yml

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,13 @@ jobs:
402402
cluster_name: 'Oak Ridge | Frontier (AMD)'
403403
device: 'cpu'
404404
interface: 'none'
405+
shard: '1/2'
406+
- runner: 'frontier'
407+
cluster: 'frontier_amd'
408+
cluster_name: 'Oak Ridge | Frontier (AMD)'
409+
device: 'cpu'
410+
interface: 'none'
411+
shard: '2/2'
405412
runs-on:
406413
group: phoenix
407414
labels: ${{ matrix.runner }}
@@ -420,7 +427,7 @@ jobs:
420427

421428
- name: Fetch Dependencies
422429
if: matrix.cluster != 'phoenix'
423-
timeout-minutes: 60
430+
timeout-minutes: 120
424431
run: bash .github/workflows/${{ matrix.cluster }}/build.sh ${{ matrix.device }} ${{ matrix.interface }}
425432

426433
- name: Build
@@ -523,7 +530,22 @@ jobs:
523530

524531
- name: Pre-Build (SLURM)
525532
if: matrix.cluster == 'frontier_amd'
526-
run: bash .github/scripts/submit-slurm-job.sh .github/scripts/prebuild-case-optimization.sh gpu ${{ matrix.interface }} ${{ matrix.cluster }}
533+
# AMD flang is slow enough that one serial pre-build job exceeds its
534+
# walltime, so split the case list across two concurrent SLURM jobs.
535+
# The shards share this workspace and skip their in-job staging clean,
536+
# so clean once here on the login node before submitting.
537+
run: |
538+
find build/staging -maxdepth 1 -regex '.*/[0-9a-f]+' -type d -exec rm -rf {} + 2>/dev/null || true
539+
find build/install -maxdepth 1 -regex '.*/[0-9a-f]+' -type d -exec rm -rf {} + 2>/dev/null || true
540+
rm -f build/.prebuild-shared-targets-done
541+
bash .github/scripts/submit-slurm-job.sh .github/scripts/prebuild-case-optimization.sh gpu ${{ matrix.interface }} ${{ matrix.cluster }} 1/2 &
542+
pid1=$!
543+
bash .github/scripts/submit-slurm-job.sh .github/scripts/prebuild-case-optimization.sh gpu ${{ matrix.interface }} ${{ matrix.cluster }} 2/2 &
544+
pid2=$!
545+
rc=0
546+
wait "$pid1" || rc=1
547+
wait "$pid2" || rc=1
548+
exit $rc
527549
528550
- name: Build & Run Case-Optimization Tests
529551
if: matrix.cluster != 'phoenix' && matrix.cluster != 'frontier_amd'
@@ -546,6 +568,8 @@ jobs:
546568
if: always()
547569
run: |
548570
for f in prebuild-case-optimization-${{ matrix.device }}-${{ matrix.interface }}.out \
571+
prebuild-case-optimization-${{ matrix.device }}-${{ matrix.interface }}-1-of-2.out \
572+
prebuild-case-optimization-${{ matrix.device }}-${{ matrix.interface }}-2-of-2.out \
549573
run-case-optimization-${{ matrix.device }}-${{ matrix.interface }}.out; do
550574
[ -f "$f" ] && echo "=== $f ===" && cat "$f"
551575
done
@@ -556,5 +580,5 @@ jobs:
556580
with:
557581
name: case-opt-${{ strategy.job-index }}-${{ matrix.cluster }}-${{ matrix.interface }}
558582
path: |
559-
prebuild-case-optimization-${{ matrix.device }}-${{ matrix.interface }}.out
583+
prebuild-case-optimization-${{ matrix.device }}-${{ matrix.interface }}*.out
560584
run-case-optimization-${{ matrix.device }}-${{ matrix.interface }}.out

benchmarks/igr/case.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,11 @@
102102
"patch_icpp(1)%length_x": 2 * math.pi * L,
103103
"patch_icpp(1)%length_y": 2 * math.pi * L,
104104
"patch_icpp(1)%length_z": 2 * math.pi * L,
105-
"patch_icpp(1)%vel(1)": f"{V0}*sin(x/{L})*cos(y/{L})*sin(z/{L})",
106-
"patch_icpp(1)%vel(2)": f"-{V0}*cos(x/{L})*sin(y/{L})*sin(z/{L})",
105+
"patch_icpp(1)%vel(1)": 0.0,
106+
"patch_icpp(1)%vel(2)": 0.0,
107107
"patch_icpp(1)%vel(3)": 0,
108-
"patch_icpp(1)%pres": f"{P0} + ({rho0}*{V0}**2/16)*(cos(2*x/{L}) + cos(2*y/{L}))*(cos(2*z/{L}) + 2)",
108+
"patch_icpp(1)%pres": 0.0,
109+
"patch_icpp(1)%hcid": 380,
109110
"patch_icpp(1)%alpha_rho(1)": 1,
110111
"patch_icpp(1)%alpha(1)": 1,
111112
# Fluids Physical Parameters

docs/documentation/case.md

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,8 @@ This is enabled by adding ``'elliptic_smoothing': "T",`` and ``'elliptic_smoothi
320320
| `num_ibs` | Integer | Number of immersed boundary patches |
321321
| `num_stl_models` | Integer | Number of STL/OBJ model entries in the `stl_models` array |
322322
| `num_particle_clouds` | Integer | Number of particle bed specifications to generate immersed boundary patches from |
323-
| `ib_neighborhood_radius` | Integer | Parameter that controls the neighborhood size for IB detection. |
323+
| `ib_neighborhood_radius` | Integer | Parameter that controls the neighborhood size for IB detection. |
324+
| `many_ib_patch_parallelism` | Logical | Parallelize over IB patches instead of grid cells (better for many small patches). |
324325
| `geometry` | Integer | Geometry configuration of the patch.|
325326
| `x[y,z]_centroid` | Real | Centroid of the applied geometry in the [x,y,z]-direction. |
326327
| `length_x[y,z]` | Real | Length, if applicable, in the [x,y,z]-direction. |
@@ -368,7 +369,7 @@ Additional details on this specification can be found in [NACA airfoil](https://
368369

369370
- For STL/OBJ geometry (geometry 5 or 12), set `model_id` to index into the `stl_models` array and specify `model_filepath`, `model_scale`, `model_translate`, and `model_threshold` on that entry.
370371

371-
- `moving_ibm` sets the method by which movement will be applied to the immersed boundary. Using 0 will result in no movement. Using 1 will result 1-way coupling where the boundary moves at a constant rate and applied forces to the fluid based upon it's own motion. In 1-way coupling, the fluid does not apply forces back onto the IB. Using 2 will result in 2-way coupling, where the boundary pushes on the fluid and the fluid pushes back on the boundary via pressure and viscous forces. If external forces are applied, the boundary will also experience those forces.
372+
- `moving_ibm` sets the method by which movement will be applied to the immersed boundary. Using 0 will result in no movement. Using 1 will result 1-way coupling where the boundary moves at a constant rate and applied forces to the fluid based upon its own motion. In 1-way coupling, the fluid does not apply forces back onto the IB. Using 2 will result in 2-way coupling, where the boundary pushes on the fluid and the fluid pushes back on the boundary via pressure and viscous forces. If external forces are applied, the boundary will also experience those forces.
372373

373374
- `vel(i)` is the initial linear velocity of the IB in the x, y, z direction for i=1, 2, 3. When `moving_ibm` equals 2, this velocity is just the starting speed of the object, which will then accelerate due to external forces. If `moving_ibm` equals 1, then this is constant if it is a number, or can be described analytically with an expression.
374375

@@ -378,15 +379,35 @@ Additional details on this specification can be found in [NACA airfoil](https://
378379
Available variables: `x` (`x_cc(i)`), `y` (`y_cc(j)`), `z` (`z_cc(k)`), `t` (current simulation time), and `r` (the IB patch radius).
379380
The same intrinsic functions and `pi` constant apply; bare `e` is not available.
380381

381-
- `coefficient_of_restitution` is a number from 0 (exclusive) to 1 (inclusive) describing how elastic IB collisions are. 0 is for perfectly inellastic collisions while 1 is for perfectly ellastic collisions.
382+
- `coefficient_of_restitution` is a number from 0 (exclusive) to 1 (inclusive) describing how elastic IB collisions are. 0 is for perfectly inelastic collisions while 1 is for perfectly elastic collisions.
382383

383-
- `collision_model` is an integer to select the collision model being used for IB collisions. Using 0 disables collisions and collisiono checking. 1 enables the soft-sphere collision model, where all IBs must be circles or sphere and those IBs can collide with each other as well as walls.
384+
- `collision_model` is an integer to select the collision model being used for IB collisions. Using 0 disables collisions and collision checking. 1 enables the soft-sphere collision model, where all IBs must be circles or sphere and those IBs can collide with each other as well as walls.
384385

385-
- `collision_time` is approximately the amount of simulation time used to resolve collisions. This is handled by modifying the spring gonstant used to apply collision forces.
386+
- `collision_time` is approximately the amount of simulation time used to resolve collisions. This is handled by modifying the spring constant used to apply collision forces.
386387

387388
- `ib_coefficient_of_friction` is the coefficient of friction used in IB collisions.
388389

389-
- `ib_neighborhood_radius` controls the size of the neighborhood size. This value defaults to 1, which indicates that any given rank is aware of IB's up to 1 ranks away. This parameter is required to strong-scale a case when IB's eventually grow to be larger than one full processor domain wide.
390+
- `ib_neighborhood_radius` controls the size of the neighborhood size. This value defaults to 1, which indicates that any given rank is aware of IBs up to 1 ranks away. This parameter is required to strong-scale a case when IBs eventually grow to be larger than one full processor domain wide.
391+
392+
#### Particle Clouds
393+
394+
A particle cloud is a compact specification of a bed of identical circular (2D) or spherical (3D) immersed boundaries; each cloud is expanded into individual `patch_ib` particles at startup. Set `num_particle_clouds` to the number of beds and prepend the parameters below with `particle_cloud(j)%` where $j$ is the cloud index.
395+
396+
| Parameter | Type | Description |
397+
| ---: | :----: | :--- |
398+
| `x[y,z]_centroid` | Real | Centre of the cloud region in the [x,y,z]-direction. |
399+
| `length_x[y,z]` | Real | Extent of the cloud region in the [x,y,z]-direction. |
400+
| `num_particles` | Integer | Number of particles to place in the region. |
401+
| `radius` | Real | Radius of every particle in the cloud. |
402+
| `mass` | Real | Mass of every particle in the cloud. |
403+
| `min_spacing` | Real | Minimum surface-to-surface gap between particles (centres are `2*radius + min_spacing` apart). |
404+
| `moving_ibm` | Integer | Motion flag applied to every particle (see `patch_ib(j)%%moving_ibm`). |
405+
| `seed` | Integer | Random seed for reproducible placement (used by `packing_method = 1`). |
406+
| `packing_method` | Integer | Algorithm used to place the particles. |
407+
408+
- `packing_method` selects how the `num_particles` are positioned within the cloud region:
409+
- `1` (rejection sampling) draws random positions and rejects any that violate `min_spacing`, producing a disordered bed. `seed` makes the placement reproducible.
410+
- `2` (lattice) places the particles on the optimally dense lattice for the geometry — a triangular lattice in 2D and a face-centered cubic lattice in 3D. The lattice spacing is derived from the particle density (`num_particles` over the region area/volume); if that spacing is below the required `2*radius + min_spacing`, the region is too dense and the run aborts.
390411

391412
### 5. Fluid Material's {#sec-fluid-materials}
392413

@@ -1248,7 +1269,7 @@ Boundary is at polar angle \f$\theta = \mathrm{atan2}(y - y_{\mathrm{centroid}},
12481269
| 3 | 2D Rectangle | 2 |
12491270
| 4 | 2D Airfoil | 2 |
12501271
| 8 | 3D Sphere | 3 |
1251-
| 10 | 3D Cylinder | 3 |
1272+
| 10 | 3D Cylinder | 3 | `length_x` sets the axial length of the cylinder. |
12521273
| 11 | 3D Airfoil | 3 |
12531274

12541275
### Acoustic Supports {#acoustic-supports}

docs/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
{ name: "Burstwave lithotripsy", image: "res/simulations/k.png", computer: "Delta", computerUrl: "https://www.ncsa.illinois.edu/research/project-highlights/delta/", accelerators: "128 A100s", walltime: "30m", source: "https://www.youtube.com/watch?v=XWsUTaJXGF8" },
6464
{ name: "Cavitation fragments kidney stone", image: "res/simulations/d.png", computer: "Summit", computerUrl: "https://www.olcf.ornl.gov/summit/", accelerators: "576 V100s", walltime: "30m", source: "https://doi.org/10.48550/arXiv.2305.09163" },
6565
{ name: "Focused ultrasound on kidney stone", image: "res/simulations/v.png", computer: "Bridges2", computerUrl: "https://www.psc.edu/resources/bridges-2/", accelerators: "10 V100s", walltime: "2h", source: "https://www.youtube.com/watch?v=z8j3NH-Y6i0" },
66+
{ name: "Microbubble-enhanced HIFU in a liver", image: "res/simulations/y.png", computer: "Delta", computerUrl: "https://www.ncsa.illinois.edu/research/project-highlights/delta/", accelerators: "128 CPU cores", walltime: "26m", source: "https://www.youtube.com/watch?v=TbR0MEdG8OU" },
6667
{ name: "Kidney stone stress waves", image: "res/simulations/l.png", computer: "Bridges2", computerUrl: "https://www.psc.edu/resources/bridges-2/", accelerators: "8 V100s", walltime: "20m", source: "https://www.youtube.com/watch?v=Q2L0J68qnRw" },
6768
{ name: "Whale bubble net feeding", image: "res/simulations/p.png", computer: "Delta", computerUrl: "https://www.ncsa.illinois.edu/research/project-highlights/delta/", accelerators: "128 A100s", walltime: "30m", source: "https://www.youtube.com/watch?v=6EpP6tdCZSA" },
6869
{ name: "Earplug acoustics (kinetic energy)", image: "res/simulations/q.png", computer: "Delta", computerUrl: "https://www.ncsa.illinois.edu/research/project-highlights/delta/", accelerators: "8 A100s", walltime: "5h", source: "https://www.youtube.com/watch?v=xSW5wZkdbrc" },

docs/module_categories.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@
8181
"m_checker",
8282
"m_checker_common",
8383
"m_sim_helpers",
84-
"m_derived_variables"
84+
"m_derived_variables",
85+
"m_patch_geometries"
8586
]
8687
},
8788
{

docs/res/simulations/y.png

169 KB
Loading

examples/2D_mibm_particle_cloud/case.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"ib": "T",
8080
"num_ibs": 0,
8181
"viscous": "T",
82+
"many_ib_patch_parallelism": "T",
8283
# Collision model (soft-sphere, from 3D_mibm_sphere_head_on_collision)
8384
"collision_model": 1,
8485
"coefficient_of_restitution": 0.9,

0 commit comments

Comments
 (0)