Skip to content

Commit 8ee9da8

Browse files
committed
Make entry point for define_landice_cull_mask
The contents of the script have been moved into the `mpas_tools` package and cleaned up for PEP8 formatting. The original script `define_cullMask.py` is now a stub that calls the entry point.
1 parent 6b6cf66 commit 8ee9da8

2 files changed

Lines changed: 256 additions & 221 deletions

File tree

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
import sys
2+
import numpy as np
3+
from optparse import OptionParser
4+
from netCDF4 import Dataset as NetCDFFile
5+
from datetime import datetime
6+
import matplotlib.pyplot as plt
7+
8+
9+
def define_cull_mask():
10+
"""
11+
Script for adding a field named cullMask to an MPAS land ice grid for use
12+
with the MpasCellCuller tool that actually culls the unwanted cells.
13+
"""
14+
15+
print("** Gathering information.")
16+
parser = OptionParser()
17+
parser.add_option(
18+
"-f", "--file", dest="file",
19+
help="grid file to modify; default: landice_grid.nc",
20+
metavar="FILE")
21+
parser.add_option(
22+
"-m", "--method", dest="method",
23+
help="method to use for marking cells to cull. Supported methods: "
24+
"'noIce', 'numCells', 'distance', 'radius', 'edgeFraction'",
25+
metavar="METHOD")
26+
parser.add_option(
27+
"-n", "--numCells", dest="numCells", default=5,
28+
help="number of cells to keep beyond ice extent",
29+
metavar="NUM")
30+
parser.add_option(
31+
"-d", "--distance", dest="distance", default=50,
32+
help="numeric value to use for the various methods: distance "
33+
"method->distance (km), radius method->radius (km), edgeFraction "
34+
"method->fraction of width or height",
35+
metavar="DIST")
36+
parser.add_option(
37+
"-p", "--plot", dest="makePlot",
38+
help="Include to have the script generate a plot of the resulting "
39+
"mask, default=false",
40+
default=False, action="store_true")
41+
options, args = parser.parse_args()
42+
43+
if not options.file:
44+
print("No grid filename provided. Using landice_grid.nc.")
45+
options.file = "landice_grid.nc"
46+
47+
if not options.method:
48+
raise ValueError("No method selected for choosing cells to mark for "
49+
"culling")
50+
else:
51+
maskmethod = options.method
52+
53+
f = NetCDFFile(options.file, 'r+')
54+
55+
xCell = f.variables['xCell'][:]
56+
yCell = f.variables['yCell'][:]
57+
nCells = len(f.dimensions['nCells'])
58+
59+
# -- Get needed fields from the file --
60+
61+
# Initialize to cull no cells
62+
cullCell = np.zeros((nCells, ), dtype=np.int8)
63+
64+
thicknessMissing = True
65+
try:
66+
thickness = f.variables['thickness'][0, :]
67+
print('Using thickness field at time 0')
68+
thicknessMissing = False
69+
except KeyError:
70+
print("The field 'thickness' is not available. Some culling methods "
71+
"will not work.")
72+
73+
# ===== Various methods for defining the mask ====
74+
75+
# =========
76+
# only keep cells with ice
77+
if maskmethod == 'noIce':
78+
print("Method: remove cells without ice")
79+
if thicknessMissing:
80+
raise ValueError("Unable to perform 'numCells' method because "
81+
"thickness field was missing.")
82+
83+
cullCell[thickness == 0.0] = 1
84+
85+
# =========
86+
# add a buffer of X cells around the ice
87+
elif maskmethod == 'numCells':
88+
print("Method: remove cells beyond a certain number of cells from "
89+
"existing ice")
90+
91+
if thicknessMissing:
92+
raise ValueError("Unable to perform 'numCells' method because "
93+
"thickness field was missing.")
94+
95+
buffersize = int(options.numCells) # number of cells to expand
96+
print("Using a buffer of {} cells".format(buffersize))
97+
98+
keepCellMask = np.copy(cullCell[:])
99+
keepCellMask[:] = 0
100+
cellsOnCell = f.variables['cellsOnCell'][:]
101+
nEdgesOnCell = f.variables['nEdgesOnCell'][:]
102+
103+
# mark the cells with ice first
104+
keepCellMask[thickness > 0.0] = 1
105+
print('Num of cells with ice: {}'.format(sum(keepCellMask)))
106+
107+
for i in range(buffersize):
108+
print(f'Starting buffer loop {i + 1}')
109+
# make a copy to edit that can be edited without changing the
110+
# original
111+
keepCellMaskNew = np.copy(keepCellMask)
112+
ind = np.nonzero(keepCellMask == 0)[0]
113+
for i in range(len(ind)):
114+
iCell = ind[i]
115+
neighbors = cellsOnCell[iCell, :nEdgesOnCell[iCell]] - 1
116+
keep = False
117+
for n in neighbors:
118+
if n >= 0:
119+
if keepCellMask[n] == 1:
120+
keepCellMaskNew[iCell] = 1
121+
# after we've looped over all cells assign the new mask to the variable
122+
# we need (either for another loop around the domain or to write out)
123+
keepCellMask = np.copy(keepCellMaskNew)
124+
print(f'Num of cells to keep: {keepCellMask.sum()}')
125+
126+
# Now convert the keepCellMask to the cullMask
127+
# Flip the mask for which ones to cull
128+
cullCell[:] = np.absolute(keepCellMask[:]-1)
129+
130+
# =========
131+
# remove cells beyond a certain distance of ice extent
132+
elif maskmethod == 'distance':
133+
134+
print("Method: remove cells beyond a certain distance from existing "
135+
"ice")
136+
137+
if thicknessMissing:
138+
raise ValueError("Unable to perform 'numCells' method because "
139+
"thickness field was missing.")
140+
141+
dist = float(options.distance)
142+
print(f"Using a buffer distance of {dist} km")
143+
# convert to m
144+
dist = dist * 1000.0
145+
146+
keepCellMask = np.copy(cullCell[:])
147+
keepCellMask[:] = 0
148+
cellsOnCell = f.variables['cellsOnCell'][:]
149+
nEdgesOnCell = f.variables['nEdgesOnCell'][:]
150+
xCell = f.variables['xCell'][:]
151+
yCell = f.variables['yCell'][:]
152+
153+
# mark the cells with ice first
154+
keepCellMask[thickness > 0.0] = 1
155+
print('Num of cells with ice: {}'.format(sum(keepCellMask)))
156+
157+
# find list of margin cells
158+
iceCells = np.nonzero(keepCellMask == 1)[0]
159+
marginMask = np.zeros((nCells, ), dtype=np.int8)
160+
for i in range(len(iceCells)):
161+
iCell = iceCells[i]
162+
# the -1 converts from the fortran indexing in the variable to
163+
# python indexing
164+
for neighbor in cellsOnCell[iCell, :nEdgesOnCell[iCell]] - 1:
165+
if thickness[neighbor] == 0.0:
166+
marginMask[iCell] = 1
167+
continue # stop searching neighbors
168+
169+
# loop over margin cells
170+
marginCells = np.nonzero(marginMask == 1)[0]
171+
for i in range(len(marginCells)):
172+
iCell = marginCells[i]
173+
# for each margin cell, find all cells within specified distance
174+
ind = np.nonzero(((xCell - xCell[iCell])**2 + (yCell - yCell[iCell])**2)**0.5 < dist)[0]
175+
keepCellMask[ind] = 1
176+
177+
print(f'Num of cells to keep: {keepCellMask.sum()}')
178+
179+
# Now convert the keepCellMask to the cullMask
180+
# Flip the mask for which ones to cull
181+
cullCell[:] = np.absolute(keepCellMask[:] - 1)
182+
183+
# =========
184+
# cut out beyond some radius (good for the dome)
185+
elif maskmethod == 'radius':
186+
dist = float(options.distance)
187+
print(f"Method: remove cells beyond a radius of {dist} km from center "
188+
f"of mesh")
189+
xc = (xCell.max() - xCell.min()) / 2.0 + xCell.min()
190+
yc = (yCell.max() - yCell.min()) / 2.0 + yCell.min()
191+
ind = np.nonzero(((xCell[:] - xc)**2 + (yCell[:] - yc)**2)**0.5 > dist*1000.0)
192+
cullCell[ind] = 1
193+
194+
# =========
195+
# cut off some fraction of the height/width on all 4 sides - useful for
196+
# cleaning up a mesh from periodic_general
197+
elif maskmethod == 'edgeFraction':
198+
frac = float(options.distance)
199+
print("Method: remove a fraction from all 4 edges of {}".format(frac))
200+
if frac >= 0.5:
201+
raise ValueError("fraction cannot be >=0.5.")
202+
if frac < 0.0:
203+
raise ValueError("fraction cannot be <0.")
204+
205+
cullCell[:] = 0
206+
width = xCell.max() - xCell.min()
207+
height = yCell.max() - yCell.min()
208+
ind = np.nonzero(xCell[:] < (xCell.min() + width * frac))
209+
cullCell[ind] = 1
210+
ind = np.nonzero(xCell[:] > (xCell.max() - width * frac))
211+
cullCell[ind] = 1
212+
ind = np.nonzero(yCell[:] < (yCell.min() + height * frac))
213+
cullCell[ind] = 1
214+
ind = np.nonzero(yCell[:] > (yCell.max() - height * frac))
215+
cullCell[ind] = 1
216+
217+
# =========
218+
else:
219+
raise ValueError("no valid culling method selected.")
220+
# =========
221+
222+
print(f'Num of cells to cull: {sum(cullCell[:])}')
223+
224+
# =========
225+
# Try to add the new variable
226+
if 'cullCell' not in f.variables:
227+
# Get the datatype for integer
228+
datatype = f.variables['indexToCellID'].dtype
229+
f.createVariable('cullCell', datatype, ('nCells',))
230+
f.variables['cullCell'][:] = cullCell
231+
232+
# Update history attribute of netCDF file
233+
thiscommand = datetime.now().strftime("%a %b %d %H:%M:%S %Y") + ": " + \
234+
" ".join(sys.argv[:])
235+
if hasattr(f, 'history'):
236+
newhist = '\n'.join([thiscommand, getattr(f, 'history')])
237+
else:
238+
newhist = thiscommand
239+
setattr(f, 'history', newhist)
240+
241+
# --- make a plot only if requested ---
242+
if options.makePlot:
243+
fig = plt.figure(1, facecolor='w')
244+
ax = fig.add_subplot(111, aspect='equal')
245+
plt.scatter(xCell[:], yCell[:], 50, cullCell[:], edgecolors='none') #, vmin=minval, vmax=maxval)
246+
plt.colorbar()
247+
plt.draw()
248+
plt.show()
249+
250+
f.close()
251+
print("cullMask generation complete.")

0 commit comments

Comments
 (0)