Skip to content

Commit 2776ef6

Browse files
authored
Merge pull request #220 from underworldcode/bugfix/advdiff-estimate-dt-percentile
estimate_dt: opt-in median/percentile cell reduction (sliver-robust dt)
2 parents 022241e + f0cdd36 commit 2776ef6

1 file changed

Lines changed: 22 additions & 8 deletions

File tree

src/underworld3/systems/solvers.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3399,7 +3399,7 @@ def delta_t(self, value):
33993399
self._delta_t.sym = value
34003400

34013401
@timing.routine_timer_decorator
3402-
def estimate_dt(self, direction_aware: bool = False):
3402+
def estimate_dt(self, direction_aware: bool = False, percentile: float = 0.0):
34033403
r"""
34043404
Estimate an appropriate timestep for the advection-diffusion solver.
34053405
@@ -3509,12 +3509,28 @@ def estimate_dt(self, direction_aware: bool = False):
35093509
## dt_adv_i = h_i / |v_i| for advection
35103510
## dt_diff_i = h_i^2 / κ for diffusion (using global κ for now)
35113511

3512+
# Reduce per-element dt to one global value. Default (percentile=0) =
3513+
# strict global MINIMUM — one cell sets the limit. percentile>0 takes the
3514+
# Nth global percentile (50 = median) of the per-element dt instead, so a
3515+
# few anisotropic SLIVER cells (velocity ACROSS a thin cell) don't collapse
3516+
# dt. SLCN is unconditionally stable, and ``direction_aware`` already
3517+
# credits cells stretched ALONG the flow — together they give the
3518+
# orientation-aware + sliver-robust timestep.
3519+
def _reduce_dt(per_elem):
3520+
fin = per_elem[np.isfinite(per_elem)] if len(per_elem) else per_elem
3521+
if percentile and percentile > 0:
3522+
gathered = comm.allgather(np.ascontiguousarray(fin, dtype=float))
3523+
allv = (np.concatenate([a for a in gathered if a.size])
3524+
if any(a.size for a in gathered) else np.empty(0))
3525+
return float(np.percentile(allv, percentile)) if allv.size else np.inf
3526+
loc = float(np.min(fin)) if len(fin) else np.inf
3527+
return comm.allreduce(loc, op=MPI.MIN)
3528+
35123529
# Per-element diffusive timestep (all elements use same diffusivity)
35133530
if diffusivity_glob > 0:
35143531
dt_diff_per_element = (element_radii ** 2) / diffusivity_glob
3515-
min_dt_diff_local = np.min(dt_diff_per_element) if len(dt_diff_per_element) > 0 else np.inf
35163532
else:
3517-
min_dt_diff_local = np.inf
3533+
dt_diff_per_element = np.array([np.inf])
35183534

35193535
# Per-element advective timestep — either isotropic
35203536
# (mesh._radii / |v|) or direction-aware (v-aligned cell
@@ -3552,11 +3568,9 @@ def estimate_dt(self, direction_aware: bool = False):
35523568
h_per_element / vel_magnitudes,
35533569
np.inf
35543570
)
3555-
min_dt_adv_local = np.min(dt_adv_per_element) if len(dt_adv_per_element) > 0 else np.inf
3556-
3557-
# Get global minimum timesteps (parallel-safe)
3558-
min_dt_diff_glob = comm.allreduce(min_dt_diff_local, op=MPI.MIN)
3559-
min_dt_adv_glob = comm.allreduce(min_dt_adv_local, op=MPI.MIN)
3571+
# Global reduction — strict min (percentile=0) or Nth percentile (median).
3572+
min_dt_diff_glob = _reduce_dt(dt_diff_per_element)
3573+
min_dt_adv_glob = _reduce_dt(dt_adv_per_element)
35603574

35613575
# Store for user inspection
35623576
self.dt_adv = min_dt_adv_glob if not np.isinf(min_dt_adv_glob) else 0.0

0 commit comments

Comments
 (0)