@@ -521,19 +521,22 @@ def fix_sigma_pileups(
521521 if abs (s [- 1 ] - 1.0 ) <= TOL :
522522 s [- 1 ] = 1.0
523523
524- # 2) Bottom sliver fix (operate only if wet and we have an interior)
524+ # 2a) Surface sliver fix: ensure the first cell (near σ=0, physical
525+ # surface) is at least tmin thick.
525526 bottom_adjusted = False
526527 bottom_drops = 0
528+ bed_adjusted = False
529+ bed_drops = 0
527530 dz1_after = np .nan
528531 dz2_after = np .nan
529532
530533 if D > 0.0 and Ni >= 3 and tmin_i > 0.0 :
531534 # Keep trying until first success or run out of interfaces
532535 while True :
533- # current bottom thickness
536+ # current surface-cell thickness
534537 dz = D * (s [1 ] - s [0 ])
535538 if dz + 1e-15 >= tmin_i :
536- bottom_adjusted = changed # adjusted if we moved/dropped
539+ bottom_adjusted = changed
537540 break
538541 # try to raise s[1] to target
539542 target = s [0 ] + (tmin_i / max (D , 1e-12 ))
@@ -548,13 +551,35 @@ def fix_sigma_pileups(
548551 bottom_drops += 1
549552 changed = True
550553 if Ni < 3 :
551- # no more interior interface to adjust; infeasible
554+ break
555+
556+ # 2b) Bed sliver fix: ensure the last cell (near σ=1, physical
557+ # bottom / bed) is at least tmin thick.
558+ if D > 0.0 and Ni >= 3 and tmin_i > 0.0 :
559+ while True :
560+ dz = D * (s [- 1 ] - s [- 2 ])
561+ if dz + 1e-15 >= tmin_i :
562+ bed_adjusted = changed
563+ break
564+ # try to lower s[-2] to make room
565+ target = s [- 1 ] - (tmin_i / max (D , 1e-12 ))
566+ if target > s [- 3 ] + TOL :
567+ s [- 2 ] = target
568+ changed = True
569+ bed_adjusted = True
570+ break
571+ # blocked by s[-3] → drop s[-2] and retry
572+ s = np .delete (s , - 2 )
573+ Ni -= 1
574+ bed_drops += 1
575+ changed = True
576+ if Ni < 3 :
552577 break
553578
554579 # recompute dz1/dz2 after changes
555580 if D > 0.0 and Ni >= 3 :
556581 dz1_after = D * (s [1 ] - s [0 ])
557- dz2_after = D * (s [2 ] - s [1 ])
582+ dz2_after = D * (s [- 1 ] - s [- 2 ])
558583 elif D > 0.0 and Ni == 2 :
559584 dz1_after = D # single layer (bed→surface)
560585
@@ -569,7 +594,8 @@ def fix_sigma_pileups(
569594 "Ni_before" : Ni0 ,
570595 "Ni_after" : Ni ,
571596 "dupes_removed" : dupes_removed ,
572- "bottom_drops" : bottom_drops ,
597+ "surface_drops" : bottom_drops ,
598+ "bed_drops" : bed_drops ,
573599 "dz1_after_m" : dz1_after ,
574600 "dz2_after_m" : dz2_after ,
575601 "depth_m" : D ,
@@ -711,6 +737,8 @@ def smooth_interfaces_onepass_with_bed_fill_sparse(mesh,
711737
712738 # Jacobi blend; update only nodes that actually have this level and are not bottom
713739 h_col_new = (1.0 - alpha ) * hold [:, k ] + alpha * nbr_mean
740+ # Clamp: h must not exceed local depth (physically below bed)
741+ h_col_new = np .minimum (h_col_new , depth )
714742 h [upd , k ] = h_col_new [upd ]
715743
716744def _get_row_stochastic_W (mesh , n_nodes ):
@@ -1034,13 +1062,17 @@ def fit_interfaces(mesh,
10341062 valid : np .ndarray ,
10351063 tbottom : np .ndarray ,
10361064 params : FitParams = FitParams (),
1037- tmin_arr : Optional [np .ndarray ] = None ) -> Tuple [np .ndarray , np .ndarray ]:
1065+ tmin_arr : Optional [np .ndarray ] = None ,
1066+ uniform_sigma_mask : Optional [np .ndarray ] = None ) -> Tuple [np .ndarray , np .ndarray ]:
10381067 """Fit interfaces h_k(x) to targets with smoothing, bottom anchoring, and guards.
10391068
10401069 Parameters
10411070 ----------
10421071 tmin_arr : optional float array (n_nodes,)
10431072 Per-node minimum layer thickness. If None, uses params.tmin everywhere.
1073+ uniform_sigma_mask : optional bool array (n_nodes,)
1074+ If True for a node, reassert uniform sigma spacing in finalization
1075+ (protects shallow exception nodes from smoother cliff-pressure).
10441076
10451077 Returns
10461078 -------
@@ -1368,7 +1400,7 @@ def _dbg_levels(tag):
13681400 _assert_finite ("h post FINISH (exist)" , h , where_mask = exists )
13691401
13701402
1371- # Finalization: single forward guard + exact endpoints (+ strict nudge)
1403+ # Finalization: exact endpoints + guards (+ strict nudge)
13721404 for i in range (n ):
13731405 Ni = int (Nlevels [i ]); D = depth [i ]
13741406 if Ni < 2 :
@@ -1381,6 +1413,14 @@ def _dbg_levels(tag):
13811413 h [i , k ] = D_eff * k / (Ni - 1 )
13821414 continue
13831415
1416+ # Exception nodes (shallow, forced Nlevels by hysteresis): reassert
1417+ # uniform sigma. The smoother pulls these toward deep neighbors;
1418+ # uniform is the correct answer for them.
1419+ if uniform_sigma_mask is not None and uniform_sigma_mask [i ]:
1420+ for k in range (Ni ):
1421+ h [i , k ] = k * D / (Ni - 1 )
1422+ continue
1423+
13841424 # Surface exactly 0
13851425 h [i , 0 ] = 0.0
13861426
@@ -1393,6 +1433,25 @@ def _dbg_levels(tag):
13931433 # Pin bed exactly to D
13941434 h [i , Ni - 1 ] = D
13951435
1436+ # Overflow fix: if smoother pushed levels above D, or the bed
1437+ # cell would be thinner than tmin, redistribute proportionally
1438+ # between a valid base and the bed. Choose the split point so
1439+ # that the redistributed spacing >= tmin.
1440+ if Ni >= 3 and h [i , Ni - 2 ] > D - tmin_arr [i ]:
1441+ tmin_i = tmin_arr [i ]
1442+ # Scan downward: find the deepest level k whose value allows
1443+ # (Ni-1-k) layers of at least tmin between h[k] and D.
1444+ k_overflow = 1 # fallback: redistribute everything from level 1
1445+ for k in range (Ni - 2 , 0 , - 1 ):
1446+ if h [i , k ] <= D - (Ni - 1 - k ) * tmin_i :
1447+ k_overflow = k + 1
1448+ break
1449+ # Linearly redistribute from h[k_overflow-1] to D
1450+ h_base = h [i , k_overflow - 1 ]
1451+ n_redist = Ni - k_overflow # number of levels to redistribute
1452+ for j in range (n_redist ):
1453+ h [i , k_overflow + j ] = h_base + (j + 1 ) * (D - h_base ) / n_redist
1454+
13961455 # Strict separation nudge (fp safety)
13971456 for k in range (1 , Ni ):
13981457 if not (h [i , k ] > h [i , k - 1 ]):
@@ -1694,7 +1753,8 @@ def run_pipeline(mesh,
16941753 t3 = time .perf_counter ()
16951754 logger .info ("D) Fitting interfaces ..." )
16961755 h , valid2 = fit_interfaces (mesh , depth , Nlevels , hhat , valid , tb , pp .fit ,
1697- tmin_arr = tmin_arr )
1756+ tmin_arr = tmin_arr ,
1757+ uniform_sigma_mask = uniform_sigma_mask )
16981758 logger .info (" done in %.2f s" , time .perf_counter () - t3 )
16991759
17001760 # E) sigma
0 commit comments