Skip to content

Commit 9717387

Browse files
committed
Update jigsaw_to_netcdf() for efficiency and robustness
This merge: * switches to xarray instead of netcdf4 * it does not hard-code `NetCDF3-Classic` format but instead uses `write_netcdf()` with the default format. This will be important for meshes with large numbers of vertices. * it uses numpy arrays directly rather than looping over vertices Some formatting has been cleaned up for better consistency with other parts of `mpas_tools`.
1 parent 8684b81 commit 9717387

File tree

1 file changed

+77
-106
lines changed

1 file changed

+77
-106
lines changed
Lines changed: 77 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
from __future__ import absolute_import, division, print_function, \
2-
unicode_literals
1+
import argparse
32

43
import numpy as np
4+
import xarray as xr
55

6-
from netCDF4 import Dataset as NetCDFFile
6+
from mpas_tools.io import write_netcdf
77
from mpas_tools.mesh.creation.open_msh import readmsh
88
from mpas_tools.mesh.creation.util import circumcenter
99

10-
import argparse
11-
1210

1311
def jigsaw_to_netcdf(msh_filename, output_name, on_sphere, sphere_radius=None):
1412
"""
@@ -18,150 +16,123 @@ def jigsaw_to_netcdf(msh_filename, output_name, on_sphere, sphere_radius=None):
1816
----------
1917
msh_filename : str
2018
A JIGSAW mesh file name
19+
2120
output_name: str
2221
The name of the output file
22+
2323
on_sphere : bool
2424
Whether the mesh is spherical or planar
25+
2526
sphere_radius : float, optional
26-
The radius of the sphere in meters. If ``on_sphere=True`` this argument
27-
is required, otherwise it is ignored.
27+
The radius of the sphere in meters. If ``on_sphere=True`` this
28+
argument is required, otherwise it is ignored.
2829
"""
29-
# Authors: Phillip J. Wolfram, Matthew Hoffman and Xylar Asay-Davis
30-
31-
grid = NetCDFFile(output_name, 'w', format='NETCDF3_CLASSIC')
3230

3331
# Get dimensions
3432
# Get nCells
3533
msh = readmsh(msh_filename)
3634
nCells = msh['POINT'].shape[0]
37-
38-
# Get vertexDegree and nVertices
39-
vertexDegree = 3 # always triangles with JIGSAW output
35+
# always triangles with JIGSAW output
36+
vertexDegree = 3
4037
nVertices = msh['TRIA3'].shape[0]
4138

4239
if vertexDegree != 3:
43-
ValueError("This script can only compute vertices with triangular "
44-
"dual meshes currently.")
45-
46-
grid.createDimension('nCells', nCells)
47-
grid.createDimension('nVertices', nVertices)
48-
grid.createDimension('vertexDegree', vertexDegree)
40+
raise ValueError(
41+
'This script can only compute vertices with triangular '
42+
'dual meshes currently.')
4943

5044
# Create cell variables and sphere_radius
5145
xCell_full = msh['POINT'][:, 0]
5246
yCell_full = msh['POINT'][:, 1]
53-
zCell_full = []
5447
if msh['NDIMS'] == 2:
5548
zCell_full = np.zeros(yCell_full.shape)
5649
elif msh['NDIMS'] == 3:
5750
zCell_full = msh['POINT'][:, 2]
5851
else:
59-
raise ValueError("NDIMS must be 2 or 3; input mesh has NDIMS={}"
60-
.format(msh['NDIMS']))
52+
raise ValueError(
53+
f'NDIMS must be 2 or 3; input mesh has NDIMS={msh["NDIMS"]}'
54+
)
6155
for cells in [xCell_full, yCell_full, zCell_full]:
62-
assert cells.shape[0] == nCells, 'Number of anticipated nodes is' \
63-
' not correct!'
56+
assert cells.shape[0] == nCells, \
57+
'Number of anticipated nodes is not correct!'
58+
59+
attrs = {}
6460
if on_sphere:
65-
grid.on_a_sphere = "YES"
66-
grid.sphere_radius = sphere_radius
61+
attrs['on_a_sphere'] = 'YES'
62+
attrs['sphere_radius'] = sphere_radius
6763
# convert from km to meters
68-
xCell_full *= 1e3
69-
yCell_full *= 1e3
70-
zCell_full *= 1e3
64+
xCell_full = xCell_full * 1e3
65+
yCell_full = yCell_full * 1e3
66+
zCell_full = zCell_full * 1e3
7167
else:
72-
grid.on_a_sphere = "NO"
73-
grid.sphere_radius = 0.0
68+
attrs['on_a_sphere'] = 'NO'
69+
attrs['sphere_radius'] = 0.0
7470

7571
# Create cellsOnVertex
7672
cellsOnVertex_full = msh['TRIA3'][:, :3] + 1
7773
assert cellsOnVertex_full.shape == (nVertices, vertexDegree), \
7874
'cellsOnVertex_full is not the right shape!'
7975

80-
# Create vertex variables
81-
xVertex_full = np.zeros((nVertices,))
82-
yVertex_full = np.zeros((nVertices,))
83-
zVertex_full = np.zeros((nVertices,))
84-
85-
for iVertex in np.arange(0, nVertices):
86-
cell1 = cellsOnVertex_full[iVertex, 0]
87-
cell2 = cellsOnVertex_full[iVertex, 1]
88-
cell3 = cellsOnVertex_full[iVertex, 2]
89-
90-
x1 = xCell_full[cell1 - 1]
91-
y1 = yCell_full[cell1 - 1]
92-
z1 = zCell_full[cell1 - 1]
93-
x2 = xCell_full[cell2 - 1]
94-
y2 = yCell_full[cell2 - 1]
95-
z2 = zCell_full[cell2 - 1]
96-
x3 = xCell_full[cell3 - 1]
97-
y3 = yCell_full[cell3 - 1]
98-
z3 = zCell_full[cell3 - 1]
99-
100-
pv = circumcenter(on_sphere, x1, y1, z1, x2, y2, z2, x3, y3, z3)
101-
xVertex_full[iVertex] = pv.x
102-
yVertex_full[iVertex] = pv.y
103-
zVertex_full[iVertex] = pv.z
104-
105-
meshDensity_full = grid.createVariable(
106-
'meshDensity', 'f8', ('nCells',))
107-
108-
for iCell in np.arange(0, nCells):
109-
meshDensity_full[iCell] = 1.0
110-
111-
del meshDensity_full
112-
113-
var = grid.createVariable('xCell', 'f8', ('nCells',))
114-
var[:] = xCell_full
115-
var = grid.createVariable('yCell', 'f8', ('nCells',))
116-
var[:] = yCell_full
117-
var = grid.createVariable('zCell', 'f8', ('nCells',))
118-
var[:] = zCell_full
119-
var = grid.createVariable('xVertex', 'f8', ('nVertices',))
120-
var[:] = xVertex_full
121-
var = grid.createVariable('yVertex', 'f8', ('nVertices',))
122-
var[:] = yVertex_full
123-
var = grid.createVariable('zVertex', 'f8', ('nVertices',))
124-
var[:] = zVertex_full
125-
var = grid.createVariable(
126-
'cellsOnVertex', 'i4', ('nVertices', 'vertexDegree',))
127-
var[:] = cellsOnVertex_full
128-
129-
grid.sync()
130-
grid.close()
131-
76+
# Vectorized circumcenter calculation
77+
# shape (nVertices, vertexDegree)
78+
cell_indices = cellsOnVertex_full - 1
79+
x_cells = xCell_full[cell_indices]
80+
y_cells = yCell_full[cell_indices]
81+
z_cells = zCell_full[cell_indices]
82+
83+
# Vectorized circumcenter computation
84+
x1, x2, x3 = x_cells[:, 0], x_cells[:, 1], x_cells[:, 2]
85+
y1, y2, y3 = y_cells[:, 0], y_cells[:, 1], y_cells[:, 2]
86+
z1, z2, z3 = z_cells[:, 0], z_cells[:, 1], z_cells[:, 2]
87+
xVertex_full, yVertex_full, zVertex_full = circumcenter(
88+
on_sphere, x1, y1, z1, x2, y2, z2, x3, y3, z3
89+
)
90+
91+
meshDensity_full = np.ones((nCells,), dtype=np.float64)
92+
93+
# Build xarray.Dataset
94+
ds = xr.Dataset(
95+
data_vars=dict(
96+
xCell=(('nCells',), xCell_full),
97+
yCell=(('nCells',), yCell_full),
98+
zCell=(('nCells',), zCell_full),
99+
xVertex=(('nVertices',), xVertex_full),
100+
yVertex=(('nVertices',), yVertex_full),
101+
zVertex=(('nVertices',), zVertex_full),
102+
cellsOnVertex=(('nVertices', 'vertexDegree'), cellsOnVertex_full),
103+
meshDensity=(('nCells',), meshDensity_full),
104+
),
105+
attrs=attrs
106+
)
107+
108+
# Write to NetCDF using write_netcdf
109+
write_netcdf(ds, output_name)
132110

133111
def main():
112+
"""
113+
Convert a JIGSAW mesh file to NetCDF format for MPAS-Tools.
114+
"""
134115
parser = argparse.ArgumentParser(
135116
description=__doc__,
136117
formatter_class=argparse.RawTextHelpFormatter)
137118
parser.add_argument(
138-
"-m",
139-
"--msh",
140-
dest="msh",
141-
required=True,
142-
help="input .msh file generated by JIGSAW.",
143-
metavar="FILE")
119+
'-m', '--msh', dest='msh', required=True,
120+
help='input .msh file generated by JIGSAW.', metavar='FILE')
144121
parser.add_argument(
145-
"-o",
146-
"--output",
147-
dest="output",
148-
default="grid.nc",
149-
help="output file name.",
150-
metavar="FILE")
122+
'-o', '--output', dest='output', default='grid.nc',
123+
help='output file name.', metavar='FILE')
151124
parser.add_argument(
152-
"-s",
153-
"--spherical",
154-
dest="spherical",
155-
action="store_true",
125+
'-s',
126+
'--spherical',
127+
dest='spherical',
128+
action='store_true',
156129
default=False,
157-
help="Determines if the input/output should be spherical or not.")
130+
help='Determines if the input/output should be spherical or not.')
158131
parser.add_argument(
159-
"-r",
160-
"--sphere_radius",
161-
dest="sphere_radius",
162-
type=float,
163-
help="The radius of the sphere in meters")
132+
'-r',
133+
'--sphere_radius',
134+
dest='sphere_radius', type=float,
135+
help='The radius of the sphere in meters')
164136

165137
args = parser.parse_args()
166-
167138
jigsaw_to_netcdf(args.msh, args.output, args.spherical, args.sphere_radius)

0 commit comments

Comments
 (0)