Skip to content

Commit e534813

Browse files
EliEli
authored andcommitted
Output location works well for prepro.
1 parent ef0a030 commit e534813

6 files changed

Lines changed: 117 additions & 37 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ set_param = "schimpy.param:set_param_cli"
144144
bctide = "schimpy.bctide:bctide_cli"
145145
relocate_source_sink = "schimpy.relocate_source_sink:relocate_source_sink_cli"
146146
splice_netcdf = "schimpy.splice_netcdf:splice_netcdf_cli"
147+
check_vgrid = "schimpy.check_vgrid:check_vgrid_cli"
147148

148149
# check_skewness = "schimpy.check_skewness:check_skewness_cli"
149150
# combine_flux = "schimpy.combine_flux:combine_flux_cli"

schimpy/batch_metrics.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -567,10 +567,8 @@ def plot(self):
567567
s = simout.loc[:, (station_id, subloc)]
568568
s = s.squeeze()
569569
if isinstance(s, pd.DataFrame):
570-
raise (
571-
"Station,Sublocation pair ({},{}) not unique or some other uniqueness problem in station files".format(
572-
(station_id, subloc)
573-
)
570+
raise Exception(
571+
f"Station,Sublocation pair ({station_id},{subloc}) not unique or some other uniqueness problem in station files"
574572
)
575573
else:
576574
s = None

schimpy/create_vgrid_lsc2.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ def vgrid_gen(
194194
minmaxlayerfile,
195195
archive_nlayer="out",
196196
nlayer_gr3="nlayer.gr3",
197+
diagnostics_dir=None,
197198
):
198199

199200
if not vgrid_version in ["5.8", "5.10"]:
@@ -203,6 +204,8 @@ def vgrid_gen(
203204

204205
# specify nlayer output dir:
205206
out_dir = os.path.dirname(vgrid_out)
207+
if diagnostics_dir is None:
208+
diagnostics_dir = out_dir
206209
nlayer_gr3 = ensure_outdir(out_dir, nlayer_gr3)
207210

208211
meshfun = BilinearMeshDensity()
@@ -268,9 +271,13 @@ def vgrid_gen(
268271

269272
if archive_nlayer == "out":
270273
print("writing out number of layers")
274+
nlayer_default_path = ensure_outdir(
275+
diagnostics_dir,
276+
os.path.basename(nlayer_gr3).replace(".gr3", "_default.gr3"),
277+
)
271278
write_mesh(
272279
mesh,
273-
nlayer_gr3.replace(".gr3", "_default.gr3"),
280+
nlayer_default_path,
274281
node_attr=nlayer_default,
275282
)
276283
write_mesh(mesh, nlayer_gr3, node_attr=nlayer)
@@ -294,7 +301,7 @@ def vgrid_gen(
294301
maxlayer = nlayer * 0 + fixed_max # np.max(maxlayer)
295302

296303
sigma2, nlayer_revised = gen_sigma(
297-
nlayer, minlayer, maxlayer, eta, h0, mesh, meshfun, out_dir=out_dir
304+
nlayer, minlayer, maxlayer, eta, h0, mesh, meshfun, out_dir=diagnostics_dir
298305
)
299306
print("Returned nlayer revised: {}".format(np.max(nlayer_revised)))
300307
nlayer = nlayer_revised
@@ -309,7 +316,7 @@ def vgrid_gen(
309316
print("Done")
310317

311318

312-
def plot_vgrid(hgrid_file, vgrid0_file, vgrid_file, vgrid_version, eta, transectfiles):
319+
def plot_vgrid(hgrid_file, vgrid0_file, vgrid_file, vgrid_version, eta, transectfiles, out_dir="."):
313320
from schimpy.lsc2 import default_num_layers, plot_mesh
314321
from schimpy.schism_vertical_mesh import read_vmesh
315322
import matplotlib.pylab as plt
@@ -322,6 +329,9 @@ def plot_vgrid(hgrid_file, vgrid0_file, vgrid_file, vgrid_version, eta, transect
322329
h0 = mesh.nodes[:, 2]
323330
depth = eta + h0
324331

332+
images_dir = ospath.join(out_dir, "images")
333+
os.makedirs(images_dir, exist_ok=True)
334+
325335
zcor0 = vmesh0.build_z(mesh, eta)[:, ::-1]
326336
zcor1 = vmesh1.build_z(mesh, eta)[:, ::-1]
327337
for transectfile in transectfiles:
@@ -348,7 +358,7 @@ def plot_vgrid(hgrid_file, vgrid0_file, vgrid_file, vgrid_version, eta, transect
348358
plot_mesh(ax1, xpath, zcor1[path, :], 0, len(xpath), c="blue")
349359
ax0.plot(xpath, -h0[path], linewidth=2, c="black")
350360
ax1.plot(xpath, -h0[path], linewidth=2, c="black")
351-
plt.savefig(ospath.join("images", base + ".png"))
361+
plt.savefig(ospath.join(images_dir, base + ".png"))
352362
plt.show()
353363
except:
354364
print("Plotting of grid failed for transectfile: {}".format(transectfile))

schimpy/lsc2.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -522,9 +522,11 @@ def gen_sigma(
522522
latest_layer = np.maximum(old_layer + 0.5 * speed, new_layer)
523523
zsmooth[:, nsmoothlev - 2 - i] = latest_layer
524524
old_layer = latest_layer
525-
np.savetxt("zsmoothsave.txt", zsmooth)
525+
zsmooth_fn = ensure_outdir(out_dir, "zsmoothsave.txt")
526+
np.savetxt(zsmooth_fn, zsmooth)
526527
else:
527-
zsmooth = np.loadtxt("zsmoothsave.txt")
528+
zsmooth_fn = ensure_outdir(out_dir, "zsmoothsave.txt")
529+
zsmooth = np.loadtxt(zsmooth_fn)
528530

529531
print("Linking")
530532
# Now generate new mesh depths using the smoothed elevations as a pseudo-bed

schimpy/lsc2_v2.py

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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

716744
def _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

schimpy/nudging.py

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ def concatenate_nudge(
228228
# check to see if there is any overlap among the regions, if yes,
229229
# take the weighted average
230230
weights_merged = np.zeros_like(weights_var[0][0])
231-
values_merged = np.zeros((len(self.time), len(imap_merged), self.nvrt))
231+
values_merged = np.zeros((len(self.time), len(imap_merged), self.nvrt), dtype=np.float32)
232232
for im in range(len(imap_merged)):
233233
idx_r = np.where(~np.isnan(idx_list[im, :]))[0].astype(int)
234234
if len(idx_r) > 1: # overlapping regions
@@ -400,9 +400,11 @@ def gen_nudge_3dfield(self, region_info):
400400

401401
utm_xy = np.array([self.node_x, self.node_y])
402402

403-
temperature = []
404-
salinity = []
403+
temperature = None # pre-allocated after npout is known
404+
salinity = None
405405
output_day = []
406+
istep = 0
407+
max_steps = len(self.datetime)
406408
var_name = {
407409
"temperature": "temp",
408410
"salinity": "salt",
@@ -468,7 +470,7 @@ def gen_nudge_3dfield(self, region_info):
468470
ctime = pd.to_datetime(t)
469471
dt = ctime - self.start_date
470472
dt_in_days = dt.total_seconds() / 86400.0
471-
print(f"Time out at hr stage (days)={ctime}, dt ={dt_in_days} ")
473+
logger.debug("Time out at hr stage (days)=%s, dt =%s", ctime, dt_in_days)
472474
irecout += 1
473475

474476
# Make sure it2=ilo is output (output at precisely the time interval)
@@ -487,14 +489,14 @@ def gen_nudge_3dfield(self, region_info):
487489
.sel(time=t)
488490
.transpose("lon", "lat", "depth")
489491
.values[:, :, -1::-1]
490-
)
492+
).astype(np.float32)
491493
# if 'salt' in var_list:
492494
salt = (
493495
ncdata["salt"]
494496
.sel(time=t)
495497
.transpose("lon", "lat", "depth")
496498
.values[:, :, -1::-1]
497-
)
499+
).astype(np.float32)
498500
# Comments below for futhre implementation.
499501
# if 'uvel' in var_list:
500502
# uvel = ncdata['uvel'].sel(time=t).transpose(
@@ -694,19 +696,23 @@ def gen_nudge_3dfield(self, region_info):
694696
vrat = vrat.T
695697

696698
# initialize interpolation coefficients
697-
wild2 = np.zeros((self.nvrt, npout, 4, 2))
699+
wild2 = np.zeros((self.nvrt, npout, 4, 2), dtype=np.float32)
698700
ix = np.broadcast_to(ix, (self.nvrt, npout))
699701
iy = np.broadcast_to(iy, (self.nvrt, npout))
700702
intri1 = np.where(intri == 1)[0]
701703
intri2 = np.where(intri == 2)[0]
702704
i_in_1 = i_in[intri1]
703705
i_in_2 = i_in[intri2]
704706

705-
tempout = np.empty((self.nvrt, self.nnode))
706-
saltout = np.empty((self.nvrt, self.nnode))
707+
tempout = np.empty((self.nvrt, self.nnode), dtype=np.float32)
708+
saltout = np.empty((self.nvrt, self.nnode), dtype=np.float32)
707709
tempout[:, i_out] = tem_outside
708710
saltout[:, i_out] = sal_outside
709711

712+
# Pre-allocate output arrays as float32
713+
temperature = np.empty((max_steps, self.nvrt, npout), dtype=np.float32)
714+
salinity = np.empty((max_steps, self.nvrt, npout), dtype=np.float32)
715+
710716
id_dry = np.where(kbp[ix, iy] == -1)[0] # 3dfield dry cell
711717
lev[:, id_dry] = int(ilen - 1 - 1)
712718
vrat[:, id_dry] = 1
@@ -771,15 +777,18 @@ def gen_nudge_3dfield(self, region_info):
771777
tempout[idST[0], i_in[idST[1]]] = tempout[idST[0], i_in[idST[1]]] - 1
772778

773779
logger.debug("applying spatial interpolation!")
774-
logger.info("outputting at day , %s, %s", dt.total_seconds()/86400, npout)
780+
current_day = int(dt.total_seconds() / 86400)
781+
if not hasattr(self, '_last_logged_day') or current_day != self._last_logged_day:
782+
logger.info("outputting at day %s, npout=%s", current_day, npout)
783+
self._last_logged_day = current_day
784+
else:
785+
logger.debug("outputting at day %s, npout=%s", dt.total_seconds()/86400, npout)
775786

776787
# only save the nodes with valid values.
777-
tempout_in = [temp_t[imap[:npout]] for temp_t in tempout]
778-
saltout_in = [salt_t[imap[:npout]] for salt_t in saltout]
779-
780-
temperature.append(tempout_in)
781-
salinity.append(saltout_in)
788+
temperature[istep, :, :] = tempout[:, imap[:npout]]
789+
salinity[istep, :, :] = saltout[:, imap[:npout]]
782790
output_day.append(dt.total_seconds() / 86400)
791+
istep += 1
783792
nudge_step = int(pd.Timedelta(self.nudge_step).total_seconds() / 3600)
784793
if len(output_day) > 1:
785794
if output_day[-1] - output_day[-2] > (nudge_step + 0.1) / 24:
@@ -790,13 +799,13 @@ def gen_nudge_3dfield(self, region_info):
790799
"The difference between current and previous time step is greater than assigned stride"
791800
)
792801

793-
temperature = np.array(temperature)
794-
salinity = np.array(salinity)
802+
temperature = temperature[:istep]
803+
salinity = salinity[:istep]
795804
# [var,time,node_map, nvrt]
796805
temperature = np.transpose(temperature, (0, 2, 1))
797806
salinity = np.transpose(salinity, (0, 2, 1))
798807
# Enforce lower bound for temp. for eqstate
799-
temperature[temperature < 0] == 0
808+
temperature[temperature < 0] = 0
800809
logger.info("reorganizing matrix!")
801810
# print("time=%f"%(timer.time() - start))
802811
max_time = self.time[-1].total_seconds() / 3600 / 24

0 commit comments

Comments
 (0)