#!/usr/bin/env python
from __future__ import absolute_import, division, print_function, \
unicode_literals
import numpy
import xarray
import argparse
from mpas_tools.io import write_netcdf
[docs]
def make_planar_hex_mesh(nx, ny, dc, nonperiodic_x,
nonperiodic_y, outFileName=None,
compareWithFileName=None,
format=None, engine=None):
"""
Builds an MPAS periodic, planar hexagonal mesh with the requested
dimensions, optionally saving it to a file, and returns it as an
``xarray.Dataset``.
Parameters
----------
nx : int
The number of cells in the x direction
ny : even int
The number of cells in the y direction (must be an even number for
periodicity to work out)
dc : float
The distance in meters between adjacent cell centers.
nonperiodic_x, nonperiodic_y : bool
is the mesh non-periodic in x and y directions?
outFileName : str, optional
The name of a file to save the mesh to. The mesh is not saved to a
file if no file name is supplied.
compareWithFileName : str, optional
The name of a grid file to compare with to see if they are identical,
used for testing purposes
format : {'NETCDF4', 'NETCDF4_CLASSIC', 'NETCDF3_64BIT', '
NETCDF3_CLASSIC'}, optional
The NetCDF format to use for output
engine : {'netcdf4', 'scipy', 'h5netcdf'}, optional
The library to use for NetCDF output
Returns
-------
mesh : xarray.Dataset
The mesh data set, available for further manipulation such as culling
cells or removing periodicity.
"""
mesh = initial_setup(nx, ny, dc, nonperiodic_x, nonperiodic_y)
compute_indices_on_cell(mesh)
if nonperiodic_x:
mark_cull_cell_nonperiodic_x(mesh)
if nonperiodic_y:
mark_cull_cell_nonperiodic_y(mesh)
compute_indices_on_edge(mesh)
compute_indices_on_vertex(mesh)
compute_weights_on_edge(mesh)
compute_coordinates(mesh)
add_one_to_indices(mesh)
# drop some arrays that aren't standard for MPAS but were used to compute
# the hex mesh
mesh = mesh.drop_vars(['cellIdx', 'cellRow', 'cellCol'])
if outFileName is not None:
write_netcdf(mesh, outFileName, format=format, engine=engine)
if compareWithFileName is not None:
# used to make sure results are exactly identical to periodic_hex
make_diff(mesh, compareWithFileName, 'diff.nc')
return mesh
def initial_setup(nx, ny, dc, nonperiodic_x, nonperiodic_y):
"""Setup the dimensions and add placeholders for some index variables"""
if ny % 2 != 0:
raise ValueError('ny must be divisible by 2 for the grid\'s '
'periodicity to work properly.')
mesh = xarray.Dataset()
if nonperiodic_x and nonperiodic_y:
mesh.attrs['is_periodic'] = 'NO'
else:
mesh.attrs['is_periodic'] = 'YES'
if nonperiodic_x:
mesh.attrs['x_period'] = 0.
else:
mesh.attrs['x_period'] = nx * dc
if nonperiodic_y:
mesh.attrs['y_period'] = 0.
else:
mesh.attrs['y_period'] = ny * dc * numpy.sqrt(3.) / 2.
mesh.attrs['dc'] = dc
mesh.attrs['nx'] = nx
mesh.attrs['ny'] = ny
mesh.attrs['on_a_sphere'] = 'NO'
mesh.attrs['sphere_radius'] = 0.
if nonperiodic_x:
nx = nx + 2
if nonperiodic_y:
ny = ny + 2
nCells = nx * ny
nEdges = 3 * nCells
nVertices = 2 * nCells
vertexDegree = 3
maxEdges = 6
# add some basic arrays to get all the dimensions in place
indexToCellID = numpy.arange(nCells, dtype='i4')
indexToEdgeID = numpy.arange(nEdges, dtype='i4')
indexToVertexID = numpy.arange(nVertices, dtype='i4')
cellIdx = indexToCellID.reshape(ny, nx)
cellCol, cellRow = numpy.meshgrid(numpy.arange(nx, dtype='i4'),
numpy.arange(ny, dtype='i4'))
mesh['cellIdx'] = (('ny', 'nx'), cellIdx)
mesh['cellRow'] = (('nCells',), cellRow.ravel())
mesh['cellCol'] = (('nCells',), cellCol.ravel())
mesh['indexToCellID'] = (('nCells',), indexToCellID)
mesh['indexToEdgeID'] = (('nEdges',), indexToEdgeID)
mesh['indexToVertexID'] = (('nVertices',), indexToVertexID)
mesh['cullCell'] = (('nCells',), numpy.zeros(nCells, 'i4'))
mesh['nEdgesOnCell'] = (('nCells',), 6 * numpy.ones((nCells,), 'i4'))
mesh['cellsOnCell'] = (('nCells', 'maxEdges'),
numpy.zeros((nCells, maxEdges), 'i4'))
mesh['edgesOnCell'] = (('nCells', 'maxEdges'),
numpy.zeros((nCells, maxEdges), 'i4'))
mesh['verticesOnCell'] = (('nCells', 'maxEdges'),
numpy.zeros((nCells, maxEdges), 'i4'))
mesh['nEdgesOnEdge'] = (('nEdges',), 10 * numpy.ones((nEdges,), 'i4'))
mesh['cellsOnEdge'] = (('nEdges', 'TWO'),
numpy.zeros((nEdges, 2), 'i4'))
mesh['edgesOnEdge'] = (('nEdges', 'maxEdges2'),
-1 * numpy.ones((nEdges, 2 * maxEdges), 'i4'))
mesh['verticesOnEdge'] = (('nEdges', 'TWO'),
numpy.zeros((nEdges, 2), 'i4'))
mesh['cellsOnVertex'] = (('nVertices', 'vertexDegree'),
numpy.zeros((nVertices, vertexDegree), 'i4'))
mesh['edgesOnVertex'] = (('nVertices', 'vertexDegree'),
numpy.zeros((nVertices, vertexDegree), 'i4'))
return mesh
def mark_cull_cell_nonperiodic_y(mesh):
cullCell = mesh.cullCell
nCells = mesh.sizes['nCells']
nx = mesh.sizes['nx']
cullCell[0:nx] = 1
cullCell[nCells - nx:nCells + 1] = 1
def mark_cull_cell_nonperiodic_x(mesh):
cullCell = mesh.cullCell
nCells = mesh.sizes['nCells']
nx = mesh.sizes['nx']
cullCell[::nx] = 1
cullCell[nx - 1:nCells + 1:nx] = 1
def compute_indices_on_cell(mesh):
cellIdx = mesh.cellIdx
cellRow = mesh.cellRow
cellCol = mesh.cellCol
indexToCellID = mesh.indexToCellID
nx = mesh.sizes['nx']
ny = mesh.sizes['ny']
mx = numpy.mod(cellCol - 1, nx)
my = numpy.mod(cellRow - 1, ny)
px = numpy.mod(cellCol + 1, nx)
py = numpy.mod(cellRow + 1, ny)
mask = numpy.mod(cellRow, 2) == 0
cellsOnCell = mesh.cellsOnCell
cellsOnCell[:, 0] = cellIdx[cellRow, mx]
cellsOnCell[:, 1] = cellIdx[my, mx].where(mask, cellIdx[my, cellCol])
cellsOnCell[:, 2] = cellIdx[my, cellCol].where(mask, cellIdx[my, px])
cellsOnCell[:, 3] = cellIdx[cellRow, px]
cellsOnCell[:, 4] = cellIdx[py, cellCol].where(mask, cellIdx[py, px])
cellsOnCell[:, 5] = cellIdx[py, mx].where(mask, cellIdx[py, cellCol])
edgesOnCell = mesh.edgesOnCell
edgesOnCell[:, 0] = 3 * indexToCellID
edgesOnCell[:, 1] = 3 * indexToCellID + 1
edgesOnCell[:, 2] = 3 * indexToCellID + 2
edgesOnCell[:, 3] = 3 * cellsOnCell[:, 3]
edgesOnCell[:, 4] = 3 * cellsOnCell[:, 4] + 1
edgesOnCell[:, 5] = 3 * cellsOnCell[:, 5] + 2
verticesOnCell = mesh.verticesOnCell
verticesOnCell[:, 0] = 2 * indexToCellID
verticesOnCell[:, 1] = 2 * indexToCellID + 1
verticesOnCell[:, 2] = 2 * cellsOnCell[:, 2]
verticesOnCell[:, 3] = 2 * cellsOnCell[:, 3] + 1
verticesOnCell[:, 4] = 2 * cellsOnCell[:, 3]
verticesOnCell[:, 5] = 2 * cellsOnCell[:, 4] + 1
def compute_indices_on_edge(mesh):
edgesOnCell = mesh.edgesOnCell
verticesOnCell = mesh.verticesOnCell
indexToCellID = mesh.indexToCellID
cellsOnEdge = mesh.cellsOnEdge
for j in range(3):
cellsOnEdge[edgesOnCell[:, j], 1] = indexToCellID
for j in range(3, 6):
cellsOnEdge[edgesOnCell[:, j], 0] = indexToCellID
verticesOnEdge = mesh.verticesOnEdge
verticesOnEdge[edgesOnCell[:, 0], 0] = verticesOnCell[:, 1]
verticesOnEdge[edgesOnCell[:, 0], 1] = verticesOnCell[:, 0]
verticesOnEdge[edgesOnCell[:, 1], 0] = verticesOnCell[:, 2]
verticesOnEdge[edgesOnCell[:, 1], 1] = verticesOnCell[:, 1]
verticesOnEdge[edgesOnCell[:, 2], 0] = verticesOnCell[:, 3]
verticesOnEdge[edgesOnCell[:, 2], 1] = verticesOnCell[:, 2]
edgesOnEdge = mesh.edgesOnEdge
edgesOnEdge[edgesOnCell[:, 3], 0] = edgesOnCell[:, 4]
edgesOnEdge[edgesOnCell[:, 3], 1] = edgesOnCell[:, 5]
edgesOnEdge[edgesOnCell[:, 3], 2] = edgesOnCell[:, 0]
edgesOnEdge[edgesOnCell[:, 3], 3] = edgesOnCell[:, 1]
edgesOnEdge[edgesOnCell[:, 3], 4] = edgesOnCell[:, 2]
edgesOnEdge[edgesOnCell[:, 4], 0] = edgesOnCell[:, 5]
edgesOnEdge[edgesOnCell[:, 4], 1] = edgesOnCell[:, 0]
edgesOnEdge[edgesOnCell[:, 4], 2] = edgesOnCell[:, 1]
edgesOnEdge[edgesOnCell[:, 4], 3] = edgesOnCell[:, 2]
edgesOnEdge[edgesOnCell[:, 4], 4] = edgesOnCell[:, 3]
edgesOnEdge[edgesOnCell[:, 5], 0] = edgesOnCell[:, 0]
edgesOnEdge[edgesOnCell[:, 5], 1] = edgesOnCell[:, 1]
edgesOnEdge[edgesOnCell[:, 5], 2] = edgesOnCell[:, 2]
edgesOnEdge[edgesOnCell[:, 5], 3] = edgesOnCell[:, 3]
edgesOnEdge[edgesOnCell[:, 5], 4] = edgesOnCell[:, 4]
edgesOnEdge[edgesOnCell[:, 0], 5] = edgesOnCell[:, 1]
edgesOnEdge[edgesOnCell[:, 0], 6] = edgesOnCell[:, 2]
edgesOnEdge[edgesOnCell[:, 0], 7] = edgesOnCell[:, 3]
edgesOnEdge[edgesOnCell[:, 0], 8] = edgesOnCell[:, 4]
edgesOnEdge[edgesOnCell[:, 0], 9] = edgesOnCell[:, 5]
edgesOnEdge[edgesOnCell[:, 1], 5] = edgesOnCell[:, 2]
edgesOnEdge[edgesOnCell[:, 1], 6] = edgesOnCell[:, 3]
edgesOnEdge[edgesOnCell[:, 1], 7] = edgesOnCell[:, 4]
edgesOnEdge[edgesOnCell[:, 1], 8] = edgesOnCell[:, 5]
edgesOnEdge[edgesOnCell[:, 1], 9] = edgesOnCell[:, 0]
edgesOnEdge[edgesOnCell[:, 2], 5] = edgesOnCell[:, 3]
edgesOnEdge[edgesOnCell[:, 2], 6] = edgesOnCell[:, 4]
edgesOnEdge[edgesOnCell[:, 2], 7] = edgesOnCell[:, 5]
edgesOnEdge[edgesOnCell[:, 2], 8] = edgesOnCell[:, 0]
edgesOnEdge[edgesOnCell[:, 2], 9] = edgesOnCell[:, 1]
def compute_indices_on_vertex(mesh):
edgesOnCell = mesh.edgesOnCell
verticesOnCell = mesh.verticesOnCell
indexToCellID = mesh.indexToCellID
cellsOnVertex = mesh.cellsOnVertex
cellsOnVertex[verticesOnCell[:, 1], 2] = indexToCellID
cellsOnVertex[verticesOnCell[:, 3], 0] = indexToCellID
cellsOnVertex[verticesOnCell[:, 5], 1] = indexToCellID
cellsOnVertex[verticesOnCell[:, 0], 0] = indexToCellID
cellsOnVertex[verticesOnCell[:, 2], 1] = indexToCellID
cellsOnVertex[verticesOnCell[:, 4], 2] = indexToCellID
edgesOnVertex = mesh.edgesOnVertex
edgesOnVertex[verticesOnCell[:, 0], 0] = edgesOnCell[:, 0]
edgesOnVertex[verticesOnCell[:, 1], 0] = edgesOnCell[:, 0]
edgesOnVertex[verticesOnCell[:, 2], 2] = edgesOnCell[:, 1]
edgesOnVertex[verticesOnCell[:, 1], 2] = edgesOnCell[:, 1]
edgesOnVertex[verticesOnCell[:, 2], 1] = edgesOnCell[:, 2]
edgesOnVertex[verticesOnCell[:, 3], 1] = edgesOnCell[:, 2]
def compute_weights_on_edge(mesh):
edgesOnCell = mesh.edgesOnCell
nEdges = mesh.sizes['nEdges']
maxEdges2 = mesh.sizes['maxEdges2']
mesh['weightsOnEdge'] = (('nEdges', 'maxEdges2'),
numpy.zeros((nEdges, maxEdges2), 'f8'))
weightsOnEdge = mesh.weightsOnEdge
weights = (1. / numpy.sqrt(3.)) * numpy.array(
[[1. / 3., 1. / 6., 0., 1. / 6., 1. / 3.],
[1. / 3., -1. / 6., 0., 1. / 6., -1. / 3.],
[-1. / 3., -1. / 6., 0., -1. / 6., -1. / 3.]])
for i in range(3):
for j in range(5):
weightsOnEdge[edgesOnCell[:, i + 3], j] = weights[i, j]
for i in range(3):
for j in range(5):
weightsOnEdge[edgesOnCell[:, i], j + 5] = weights[i, j]
def compute_coordinates(mesh):
dc = mesh.attrs['dc']
edgesOnCell = mesh.edgesOnCell
verticesOnCell = mesh.verticesOnCell
nCells = mesh.sizes['nCells']
nEdges = mesh.sizes['nEdges']
nVertices = mesh.sizes['nVertices']
vertexDegree = mesh.sizes['vertexDegree']
mesh['latCell'] = (('nCells',), numpy.zeros((nCells,), 'f8'))
mesh['lonCell'] = (('nCells',), numpy.zeros((nCells,), 'f8'))
mesh['latEdge'] = (('nEdges',), numpy.zeros((nEdges,), 'f8'))
mesh['lonEdge'] = (('nEdges',), numpy.zeros((nEdges,), 'f8'))
mesh['latVertex'] = (('nVertices',), numpy.zeros((nVertices,), 'f8'))
mesh['lonVertex'] = (('nVertices',), numpy.zeros((nVertices,), 'f8'))
cellRow = mesh.cellRow
cellCol = mesh.cellCol
mask = numpy.mod(cellRow, 2) == 0
mesh['xCell'] = (dc * (cellCol + 0.5)).where(mask, dc * (cellCol + 1))
mesh['yCell'] = dc * (cellRow + 1) * numpy.sqrt(3.) / 2.
mesh['zCell'] = (('nCells',), numpy.zeros((nCells,), 'f8'))
mesh['xEdge'] = (('nEdges',), numpy.zeros((nEdges,), 'f8'))
mesh['yEdge'] = (('nEdges',), numpy.zeros((nEdges,), 'f8'))
mesh['zEdge'] = (('nEdges',), numpy.zeros((nEdges,), 'f8'))
mesh.xEdge[edgesOnCell[:, 0]] = mesh.xCell - 0.5 * dc
mesh.yEdge[edgesOnCell[:, 0]] = mesh.yCell
mesh.xEdge[edgesOnCell[:, 1]] = mesh.xCell - \
0.5 * dc * numpy.cos(numpy.pi / 3.)
mesh.yEdge[edgesOnCell[:, 1]] = mesh.yCell - \
0.5 * dc * numpy.sin(numpy.pi / 3.)
mesh.xEdge[edgesOnCell[:, 2]] = mesh.xCell + \
0.5 * dc * numpy.cos(numpy.pi / 3.)
mesh.yEdge[edgesOnCell[:, 2]] = mesh.yCell - \
0.5 * dc * numpy.sin(numpy.pi / 3.)
mesh['xVertex'] = (('nVertices',), numpy.zeros((nVertices,), 'f8'))
mesh['yVertex'] = (('nVertices',), numpy.zeros((nVertices,), 'f8'))
mesh['zVertex'] = (('nVertices',), numpy.zeros((nVertices,), 'f8'))
mesh.xVertex[verticesOnCell[:, 0]] = mesh.xCell - 0.5 * dc
mesh.yVertex[verticesOnCell[:, 0]] = mesh.yCell + dc * numpy.sqrt(3.) / 6.
mesh.xVertex[verticesOnCell[:, 1]] = mesh.xCell - 0.5 * dc
mesh.yVertex[verticesOnCell[:, 1]] = mesh.yCell - dc * numpy.sqrt(3.) / 6.
mesh['angleEdge'] = (('nEdges',), numpy.zeros((nEdges,), 'f8'))
mesh.angleEdge[edgesOnCell[:, 1]] = numpy.pi / 3.
mesh.angleEdge[edgesOnCell[:, 2]] = 2. * numpy.pi / 3.
mesh['dcEdge'] = (('nEdges',), dc * numpy.ones((nEdges,), 'f8'))
mesh['dvEdge'] = mesh.dcEdge * numpy.sqrt(3.) / 3.
mesh['areaCell'] = \
(('nCells',), dc**2 * numpy.sqrt(3.) / 2. * numpy.ones((nCells,), 'f8'))
mesh['areaTriangle'] = \
(('nVertices',), dc**2 * numpy.sqrt(3.) /
4. * numpy.ones((nVertices,), 'f8'))
mesh['kiteAreasOnVertex'] = \
(('nVertices', 'vertexDegree'),
dc**2 * numpy.sqrt(3.) / 12. * numpy.ones((nVertices, vertexDegree),
'f8'))
mesh['meshDensity'] = (('nCells',), numpy.ones((nCells,), 'f8'))
def add_one_to_indices(mesh):
"""Needed to adhere to Fortran indexing"""
indexVars = ['indexToCellID', 'indexToEdgeID', 'indexToVertexID',
'cellsOnCell', 'edgesOnCell', 'verticesOnCell',
'cellsOnEdge', 'edgesOnEdge', 'verticesOnEdge',
'cellsOnVertex', 'edgesOnVertex']
for var in indexVars:
mesh[var] = mesh[var] + 1
def make_diff(mesh, refMeshFileName, diffFileName):
refMesh = xarray.open_dataset(refMeshFileName)
diff = xarray.Dataset()
for variable in mesh.data_vars:
if variable in refMesh:
diff[variable] = mesh[variable] - refMesh[variable]
print(diff[variable].name, float(numpy.abs(diff[variable]).max()))
else:
print('mesh has extra variable {}'.format(mesh[variable].name))
for variable in refMesh.data_vars:
if variable not in mesh:
print('mesh mising variable {}'.format(refMesh[variable].name))
for attr in refMesh.attrs:
if attr not in mesh.attrs:
print('mesh mising attribute {}'.format(attr))
for attr in mesh.attrs:
if attr not in refMesh.attrs:
print('mesh has extra attribute {}'.format(attr))
write_netcdf(diff, diffFileName)
def main():
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('--nx', dest='nx', type=int, required=True,
help='Cells in x direction')
parser.add_argument('--ny', dest='ny', type=int, required=True,
help='Cells in y direction')
parser.add_argument('--dc', dest='dc', type=float, required=True,
help='Distance between cell centers in meters')
parser.add_argument('--npx', '--nonperiodic_x', dest='nonperiodic_x',
action="store_true",
help='non-periodic in x direction')
parser.add_argument('--npy', '--nonperiodic_y', dest='nonperiodic_y',
action="store_true",
help='non-periodic in y direction')
parser.add_argument('-o', '--outFileName', dest='outFileName', type=str,
required=False, default='grid.nc',
help='The name of the output file')
args = parser.parse_args()
make_planar_hex_mesh(args.nx, args.ny, args.dc,
args.nonperiodic_x, args.nonperiodic_y,
args.outFileName)
if __name__ == '__main__':
main()