Skip to content

Commit

Permalink
UGRID Conformance, Grid Construction from Minimal Grid Variables (#628)
Browse files Browse the repository at this point in the history
* initial work

* work on convetnions

* topology reader

* work on topo, getitem for grid

* work on conventions

* run pre-commit

* fix from face vertices

* add sizes, dims, coordinates, and connectivity properties

* reader

* work on ugrid reader

* use ugrid conventions module in esmf reader

* update scrip reader

* add test for from topo

* update topology reader

* update mpas reader

* update exodus reader

* update ugrid reader

* fix tests after refactor

* fix tests

* update connectivity.py with refactor

* update connectivity.py with refactor

* investigate failing test

* investigate failing test

* investigate failing test

* fix failing connectivity tests

* testing

* update grid.py

* update user api

* update face areas and add grid.to_netcdf

* update ValueError

* update internall api

* add docstring to __getitem__

* add edge dimension to grid topology

* docstring for Grid.to_netcdf

* remove Grid.to_netcdf in favor of Grid.to_xarray

* update repr

* fix failing test

* update docstrings

* update repr

* coments

* fix failing test

* remove leftover method in api

* specify Cartesiain in xyz coordinates

* add cartesiain coordinate names

* update docstrings

* update docstrings

* update conventions

---------

Co-authored-by: Hongyu Chen <hyvchen@ucdavis.edu>
Co-authored-by: Orhan Eroglu <32553057+erogluorhan@users.noreply.github.com>
  • Loading branch information
3 people authored Apr 1, 2024
1 parent 556cdc7 commit 545a18d
Show file tree
Hide file tree
Showing 18 changed files with 1,073 additions and 677 deletions.
14 changes: 9 additions & 5 deletions docs/user_api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@ Dimensions
Grid.n_face
Grid.n_max_face_nodes
Grid.n_max_face_edges
Grid.n_max_face_faces
Grid.n_max_edge_edges
Grid.n_max_node_faces
Grid.n_max_node_edges
Grid.n_max_node_nodes
Grid.n_nodes_per_face

Spherical Coordinates
Expand Down Expand Up @@ -255,13 +260,12 @@ Connectivity
:toctree: generated/

Grid.face_node_connectivity
Grid.edge_node_connectivity
Grid.node_node_connectivity
Grid.face_edge_connectivity
Grid.edge_edge_connectivity
Grid.node_edge_connectivity
Grid.face_face_connectivity
Grid.edge_node_connectivity
Grid.edge_edge_connectivity
Grid.edge_face_connectivity
Grid.node_edge_connectivity
Grid.node_face_connectivity

Grid Descriptors
Expand All @@ -279,7 +283,7 @@ Attributes
:toctree: generated/

Grid.grid_spec
Grid.parsed_attrs
Grid.attrs


Plotting
Expand Down
2 changes: 1 addition & 1 deletion test/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
NNODES_outCSne8 = 386
NNODES_outCSne30 = 5402
NNODES_outRLL1deg = 64442
DATAVARS_outCSne30 = 2
DATAVARS_outCSne30 = 4
TRI_AREA = 1.047
# 4*Pi is 12.56
MESH30_AREA = 12.566
Expand Down
2 changes: 1 addition & 1 deletion test/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def test_open_dataset_grid_kwargs(self):
"""Drops ``Mesh2_face_nodes`` from the inputted grid file using
``grid_kwargs``"""

with self.assertRaises(KeyError):
with self.assertRaises(ValueError):
# attempt to open a dataset after dropping face nodes should raise a KeyError
uxds = ux.open_dataset(
self.gridfile_ne30,
Expand Down
87 changes: 87 additions & 0 deletions test/test_from_topology.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import uxarray as ux

from uxarray.constants import INT_FILL_VALUE
import numpy.testing as nt
import os

import pytest

from pathlib import Path

current_path = Path(os.path.dirname(os.path.realpath(__file__)))

GRID_PATHS = [
current_path / 'meshfiles' / "mpas" / "QU" / 'oQU480.231010.nc',
current_path / "meshfiles" / "ugrid" / "geoflow-small" / "grid.nc",
current_path / "meshfiles" / "ugrid" / "outCSne30" / "outCSne30.ug"
]




def test_minimal_class_method():
"""Tests the minimal required variables for constructing a grid using the
from topology class method."""

for grid_path in GRID_PATHS:
uxgrid = ux.open_grid(grid_path)

uxgrid_ft = ux.Grid.from_topology(node_lon=uxgrid.node_lon.values,
node_lat=uxgrid.node_lat.values,
face_node_connectivity=uxgrid.face_node_connectivity.values,
fill_value=INT_FILL_VALUE,
start_index=0)

nt.assert_array_equal(uxgrid.node_lon.values, uxgrid_ft.node_lon.values)
nt.assert_array_equal(uxgrid.node_lat.values, uxgrid_ft.node_lat.values)
nt.assert_array_equal(uxgrid.face_node_connectivity.values, uxgrid_ft.face_node_connectivity.values)


def test_minimal_api():
"""Tests the minimal required variables for constructing a grid using the
``ux.open_dataset`` method."""

for grid_path in GRID_PATHS:
uxgrid = ux.open_grid(grid_path)

uxgrid_ft = ux.Grid.from_topology(node_lon=uxgrid.node_lon.values,
node_lat=uxgrid.node_lat.values,
face_node_connectivity=uxgrid.face_node_connectivity.values,
fill_value=INT_FILL_VALUE,
start_index=0)

grid_topology = {'node_lon': uxgrid.node_lon.values,
'node_lat': uxgrid.node_lat.values,
'face_node_connectivity': uxgrid.face_node_connectivity.values,
'fill_value': INT_FILL_VALUE,
'start_index': 0}

uxgrid_ft = ux.open_grid(grid_topology)

nt.assert_array_equal(uxgrid.node_lon.values, uxgrid_ft.node_lon.values)
nt.assert_array_equal(uxgrid.node_lat.values, uxgrid_ft.node_lat.values)
nt.assert_array_equal(uxgrid.face_node_connectivity.values, uxgrid_ft.face_node_connectivity.values)


def test_dataset():
uxds = ux.open_dataset(GRID_PATHS[0], GRID_PATHS[0])

grid_topology = {'node_lon': uxds.uxgrid.node_lon.values,
'node_lat': uxds.uxgrid.node_lat.values,
'face_node_connectivity': uxds.uxgrid.face_node_connectivity.values,
'fill_value': INT_FILL_VALUE,
'start_index': 0,
"dims_dict" : {"nVertices": "n_node"}}


uxds_ft = ux.open_grid(grid_topology, GRID_PATHS[1])

uxgrid = uxds.uxgrid
uxgrid_ft = uxds_ft


nt.assert_array_equal(uxgrid.node_lon.values, uxgrid_ft.node_lon.values)
nt.assert_array_equal(uxgrid.node_lat.values, uxgrid_ft.node_lat.values)
nt.assert_array_equal(uxgrid.face_node_connectivity.values, uxgrid_ft.face_node_connectivity.values)

assert uxds_ft.dims == {'n_face', 'n_node', 'n_max_face_nodes'}
32 changes: 8 additions & 24 deletions test/test_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,8 @@ def test_build_face_edges_connectivity_mpas(self):
"""Tests the construction of (``Mesh2_edge_nodes``) on an MPAS grid
with known edge nodes."""

from uxarray.grid.connectivity import _build_edge_node_connectivity

# grid with known edge node connectivity
mpas_grid_ux = ux.open_grid(self.mpas_filepath)
edge_nodes_expected = mpas_grid_ux._ds['edge_node_connectivity'].values
Expand All @@ -652,8 +654,12 @@ def test_build_face_edges_connectivity_mpas(self):
edge_nodes_expected = np.unique(edge_nodes_expected, axis=0)

# construct edge nodes
_build_edge_node_connectivity(mpas_grid_ux, repopulate=True)
edge_nodes_output = mpas_grid_ux._ds['edge_node_connectivity'].values
edge_nodes_output, _, _ = _build_edge_node_connectivity(mpas_grid_ux.face_node_connectivity.values,
mpas_grid_ux.n_face,
mpas_grid_ux.n_max_face_nodes)

# _populate_face_edge_connectivity(mpas_grid_ux)
# edge_nodes_output = mpas_grid_ux._ds['edge_node_connectivity'].values

self.assertTrue(np.array_equal(edge_nodes_expected, edge_nodes_output))

Expand Down Expand Up @@ -705,28 +711,6 @@ def test_build_face_edges_connectivity(self):
np.array_equal(reverted_mesh2_edge_nodes[i],
original_face_nodes_connectivity[i]))

def test_build_face_edges_connectivity_mpas(self):
tgrid = ux.open_grid(self.mpas_filepath)

face_node_connectivity = tgrid._ds["face_node_connectivity"]

_populate_face_edge_connectivity(tgrid)
mesh2_face_edges = tgrid._ds.face_edge_connectivity
mesh2_edge_nodes = tgrid._ds.edge_node_connectivity

# Assert if the mesh2_face_edges sizes are correct.
self.assertEqual(mesh2_face_edges.sizes["n_face"],
face_node_connectivity.sizes["n_face"])
self.assertEqual(mesh2_face_edges.sizes["n_max_face_edges"],
face_node_connectivity.sizes["n_max_face_nodes"])

# Assert if the mesh2_edge_nodes sizes are correct.
# Euler formular for determining the edge numbers: n_face = n_edges - n_nodes + 2
num_edges = mesh2_face_edges.sizes["n_face"] + tgrid._ds[
"node_lon"].sizes["n_node"] - 2
size = mesh2_edge_nodes.sizes["n_edge"]
self.assertEqual(mesh2_edge_nodes.sizes["n_edge"], num_edges)

def test_build_face_edges_connectivity_fillvalues(self):
verts = [
self.f0_deg, self.f1_deg, self.f2_deg, self.f3_deg, self.f4_deg,
Expand Down
Empty file added uxarray/conventions/__init__.py
Empty file.
18 changes: 18 additions & 0 deletions uxarray/conventions/descriptors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
DESCRIPTOR_NAMES = ["face_areas", "edge_face_distances", "edge_node_distances"]


FACE_AREAS_DIMS = ["n_face"]

FACE_AREAS_ATTRS = {"cf_role": "face_areas"}

EDGE_FACE_DISTANCES_DIMS = ["n_edge"]
EDGE_FACE_DISTANCES_ATTRS = {
"cf_role": "edge_face_distances",
"long_name": "Distances between the face centers that " "saddle each edge",
}

EDGE_NODE_DISTANCES_DIMS = ["n_edge"]
EDGE_NODE_DISTANCES_ATTRS = {
"cf_role": "edge_node_distances",
"long_name": "Distances between the nodes that make up " "each edge.",
}
Loading

0 comments on commit 545a18d

Please sign in to comment.