Skip to content

Commit eac503b

Browse files
committed
add envelope computation
1 parent 1dd355e commit eac503b

2 files changed

Lines changed: 102 additions & 60 deletions

File tree

pydomcfg/domzgr/sco.py

Lines changed: 70 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing import Optional # , Tuple
88

99
import numpy as np
10+
import xarray as xr
1011
from xarray import DataArray, Dataset
1112

1213
from pydomcfg.utils import _smooth_MB06 # , _calc_rmax
@@ -121,15 +122,14 @@ def __call__(
121122

122123
bathy = self._bathy["Bathymetry"]
123124

124-
# Land-Sea mask of the domain:
125+
# compute land-sea mask of the domain:
125126
# 0 = land
126127
# 1 = ocean
127-
self._ocean = bathy.where(bathy == 0, 1)
128+
self._lsm = xr.where(bathy > 0, 1, 0)
128129

129130
# set maximum and minumum depths of model bathymetry
130-
bathy = bathy.where(bathy < self._max_dep, self._max_dep)
131-
bathy = bathy.where(bathy > self._min_dep, self._min_dep)
132-
bathy *= self._ocean
131+
bathy = np.minimum(bathy, self._max_dep)
132+
bathy = np.maximum(bathy, self._min_dep) * self._lsm
133133

134134
# compute envelope bathymetry DataArray
135135
self._envlp = self._compute_env(bathy)
@@ -193,46 +193,79 @@ def _compute_env(self, depth: DataArray) -> DataArray:
193193
----------
194194
depth: DataArray
195195
xarray DataArray of the 2D bottom topography
196+
it MUST have only two dimensions named "x" and "y"
196197
Returns
197198
-------
198199
DataArray
199200
xarray DataArray of the 2D envelope bathymetry
200201
"""
201202

202-
da_zenv = depth.copy()
203-
204203
if self._rmax:
205204

206-
# getting the actual numpy array
207-
# TO BE OPTIMISED
208-
zenv = da_zenv.data
209-
nj = zenv.shape[0]
210-
ni = zenv.shape[1]
205+
lsm = self._lsm
211206

212207
# set first land point adjacent to a wet cell to
213208
# min_dep as this needs to be included in smoothing
214-
for j in range(nj - 1):
215-
for i in range(ni - 1):
216-
if not self._ocean[j, i]:
217-
ip1 = np.minimum(i + 1, ni)
218-
jp1 = np.minimum(j + 1, nj)
219-
im1 = np.maximum(i - 1, 0)
220-
jm1 = np.maximum(j - 1, 0)
221-
if (
222-
depth[jp1, im1]
223-
+ depth[jp1, i]
224-
+ depth[jp1, ip1]
225-
+ depth[j, im1]
226-
+ depth[j, ip1]
227-
+ depth[jm1, im1]
228-
+ depth[jm1, i]
229-
+ depth[jm1, ip1]
230-
) > 0.0:
231-
zenv[j, i] = self._min_dep
232-
233-
da_zenv.data = zenv
234-
# print(np.nanmax(_calc_rmax(da_zenv)*self._ocean))
235-
da_zenv = _smooth_MB06(da_zenv, self._rmax)
236-
da_zenv = da_zenv.where(da_zenv > self._min_dep, self._min_dep)
237-
238-
return da_zenv
209+
210+
# ------------------------------------------------------------
211+
# This is the original NEMO Fortran90 code: translated
212+
# in python it is very inefficient
213+
# ------------------------------------------------------------
214+
# zenv = depth.copy()
215+
# env = zenv.data
216+
#
217+
# nj = env.shape[0]
218+
# ni = env.shape[1]
219+
#
220+
# for j in range(nj - 1):
221+
# for i in range(ni - 1):
222+
# if not lsm[j, i]:
223+
# ip1 = np.minimum(i + 1, ni)
224+
# jp1 = np.minimum(j + 1, nj)
225+
# im1 = np.maximum(i - 1, 0)
226+
# jm1 = np.maximum(j - 1, 0)
227+
# if (
228+
# depth[jp1, im1]
229+
# + depth[jp1, i]
230+
# + depth[jp1, ip1]
231+
# + depth[j, im1]
232+
# + depth[j, ip1]
233+
# + depth[jm1, im1]
234+
# + depth[jm1, i]
235+
# + depth[jm1, ip1]
236+
# ) > 0.0:
237+
# env[j, i] = min_dep
238+
#
239+
# zenv.data = env
240+
# ------------------------------------------------------------
241+
242+
# ------------------------------------------------------------
243+
# This is my translation into xarray. I think it does what
244+
# it should, a part on the boundaries: I tested with AMM7,
245+
# and zenv computed with NEMO-like code (above) and this
246+
# one are perfectly identical apart for two single different
247+
# points just on the border ... I don't think it will make
248+
# a huge difference but if there is a better way to manage
249+
# the borders with xarray and obtain exactly the same results
250+
# of the original NEMO-like code, happy to use it.
251+
# ------------------------------------------------------------
252+
cst_lsm = lsm * 0.0
253+
ngb_pnt = [-1, 0, 1]
254+
for j in ngb_pnt:
255+
for i in ngb_pnt:
256+
if j != 0 or i != 0:
257+
lsm_sft = lsm.shift({lsm.dims[1]: i, lsm.dims[0]: j})
258+
cst_lsm += lsm_sft
259+
260+
cst_lsm = cst_lsm.where(lsm == 0, 0)
261+
cst_lsm = cst_lsm.where(cst_lsm == 0, 1)
262+
zenv = depth.where(cst_lsm == 0, self._min_dep)
263+
for dim in lsm.dims:
264+
for indx in [0, -1]:
265+
zenv[{dim: indx}] = depth[{dim: indx}]
266+
# ------------------------------------------------------------
267+
268+
zenv = _smooth_MB06(zenv, self._rmax)
269+
zenv = zenv.where(zenv > self._min_dep, self._min_dep)
270+
271+
return zenv

pydomcfg/utils.py

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,12 @@ def _are_nemo_none(var: Iterable) -> Iterator[bool]:
2323
yield _is_nemo_none(v)
2424

2525

26-
# -----------------------------------------------------------------------------
2726
def _calc_rmax(depth: DataArray) -> DataArray:
2827
"""
2928
Calculate rmax: measure of steepness
30-
This function returns the maximum slope paramater
29+
This function returns the slope paramater field
3130
32-
rmax = abs(Hb - Ha) / (Ha + Hb)
31+
r = abs(Hb - Ha) / (Ha + Hb)
3332
3433
where Ha and Hb are the depths of adjacent grid cells (Mellor et al 1998).
3534
@@ -44,33 +43,43 @@ def _calc_rmax(depth: DataArray) -> DataArray:
4443
Returns
4544
-------
4645
DataArray
47-
2D maximum slope parameter (units: None)
48-
"""
46+
2D slope parameter (units: None)
4947
50-
depth = DataArray(depth.reset_index(list(depth.dims)))
48+
Notes
49+
-----
50+
This function uses a "conservative approach" and rmax is overestimated.
51+
rmax at T points is the maximum rmax estimated at any adjacent U/V point.
52+
"""
53+
# Mask land
54+
depth = depth.where(depth > 0)
5155

56+
# Loop over x and y
5257
both_rmax = []
5358
for dim in depth.dims:
5459

55-
# (Hb - Ha) / (Ha + Hb)
56-
depth_diff = depth.diff(dim)
57-
depth_rolling_sum = depth.rolling({dim: 2}).sum().dropna(dim)
58-
rmax = depth_diff / depth_rolling_sum
59-
# dealing with nans at land points
60-
rmax = rmax.where(np.isfinite(rmax), 0)
60+
# Compute rmax
61+
rolled = depth.rolling({dim: 2}).construct("window_dim")
62+
diff = rolled.diff("window_dim").squeeze("window_dim")
63+
rmax = np.abs(diff) / rolled.sum("window_dim")
64+
65+
# Construct dimension with velocity points adjacent to any T point
66+
# We need to shift as we rolled twice
67+
rmax = rmax.rolling({dim: 2}).construct("vel_points")
68+
rmax = rmax.shift({dim: -1})
6169

62-
# (rmax_a + rmax_b) / 2
63-
rmax = rmax.rolling({dim: 2}).mean().dropna(dim)
70+
both_rmax.append(rmax)
6471

65-
# Fill first row and column
66-
rmax = rmax.pad({dim: (1, 1)}, constant_values=0)
72+
# Find maximum rmax at adjacent U/V points
73+
rmax = xr.concat(both_rmax, "vel_points")
74+
rmax = rmax.max("vel_points", skipna=True)
6775

68-
both_rmax.append(np.abs(rmax))
76+
# Mask halo points
77+
for dim in rmax.dims:
78+
rmax[{dim: [0, -1]}] = 0
6979

70-
return np.maximum(*both_rmax)
80+
return rmax.fillna(0)
7181

7282

73-
# -----------------------------------------------------------------------------
7483
def _smooth_MB06(depth: DataArray, rmax: float) -> DataArray:
7584
"""
7685
This is NEMO implementation of the direct iterative method
@@ -120,9 +129,9 @@ def _smooth_MB06(depth: DataArray, rmax: float) -> DataArray:
120129
ztmpj2 = zenv.copy()
121130

122131
# Computing the initial maximum slope parameter
123-
zrmax = np.nanmax(_calc_rmax(depth))
124-
zri = np.ones(zenv.shape) * zrmax
125-
zrj = np.ones(zenv.shape) * zrmax
132+
zrmax = 1.0 # np.nanmax(_calc_rmax(depth))
133+
zri = np.ones(zenv.shape) # * zrmax
134+
zrj = np.ones(zenv.shape) # * zrmax
126135

127136
tol = 1.0e-8
128137
itr = 0
@@ -144,6 +153,7 @@ def _smooth_MB06(depth: DataArray, rmax: float) -> DataArray:
144153

145154
zri *= 0.0
146155
zrj *= 0.0
156+
147157
for j in range(nj - 1):
148158
for i in range(ni - 1):
149159
ip1 = np.minimum(i + 1, ni)
@@ -173,7 +183,6 @@ def _smooth_MB06(depth: DataArray, rmax: float) -> DataArray:
173183
return da_zenv
174184

175185

176-
# -----------------------------------------------------------------------------
177186
def generate_cartesian_grid(
178187
ppe1_m,
179188
ppe2_m,

0 commit comments

Comments
 (0)