Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Point In Face #1056

Draft
wants to merge 54 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
75b88e8
Initial work
aaronzedwick Oct 24, 2024
998aa99
Switched to projection method
aaronzedwick Nov 5, 2024
7d7be67
Merge branch 'main' into zedwick/point-in-polygon
aaronzedwick Nov 5, 2024
e38ca24
Updated test cases, fixed spherical bug
aaronzedwick Nov 6, 2024
92bab4a
Added test cases
aaronzedwick Nov 6, 2024
354f821
Increased performance
aaronzedwick Nov 6, 2024
5fc59aa
Update test_geometry.py
aaronzedwick Nov 7, 2024
c4a4979
Merge branch 'main' into zedwick/point-in-polygon
aaronzedwick Nov 7, 2024
3b09973
updated api
aaronzedwick Nov 7, 2024
f7cb51c
Merge branch 'zedwick/point-in-polygon' of https://github.com/UXARRAY…
aaronzedwick Nov 7, 2024
22e84d5
Merge branch 'main' into zedwick/point-in-polygon
aaronzedwick Nov 20, 2024
4043c8b
Updated detection method
aaronzedwick Nov 20, 2024
95a0dea
Merge branch 'main' into zedwick/point-in-polygon
aaronzedwick Nov 21, 2024
5dbed72
Merge branch 'zedwick/point-in-polygon' of https://github.com/UXARRAY…
aaronzedwick Nov 21, 2024
c1eae89
Implemented new ray casting method
aaronzedwick Nov 21, 2024
586c8ad
Merge branch 'main' into zedwick/point-in-polygon
aaronzedwick Nov 26, 2024
b33326d
Added node crossing and point on edge checks
aaronzedwick Nov 27, 2024
0acaa2b
Merge branch 'main' into zedwick/point-in-polygon
aaronzedwick Nov 27, 2024
807ce07
Added docstrings, test case
aaronzedwick Nov 27, 2024
b4eba94
Merge branch 'zedwick/point-in-polygon' of https://github.com/UXARRAY…
aaronzedwick Nov 27, 2024
fa6fd8c
Update test_geometry.py
aaronzedwick Nov 27, 2024
0092258
Fixed point conversion bug
aaronzedwick Nov 27, 2024
1f6f51b
Update geometry.py
aaronzedwick Nov 27, 2024
5e0477b
Merge branch 'main' into zedwick/point-in-polygon
aaronzedwick Dec 4, 2024
acce252
Merge branch 'zedwick/point-in-polygon' of https://github.com/UXARRAY…
aaronzedwick Dec 4, 2024
d88d69f
Converted functions to numba, updated tests accordingly
aaronzedwick Dec 5, 2024
a379a1a
Added max face radius function
aaronzedwick Dec 5, 2024
fefe1e7
Added baseplate code for polygon containing code
aaronzedwick Dec 5, 2024
cc9f943
Added reference point setter
aaronzedwick Dec 6, 2024
73a1be4
Updated to use constructed edges
aaronzedwick Dec 10, 2024
8392e2c
Debugging
aaronzedwick Dec 11, 2024
6fea25e
Merge branch 'main' into zedwick/point-in-polygon
aaronzedwick Dec 11, 2024
200f4bb
Merge branch 'main' into zedwick/point-in-polygon
aaronzedwick Dec 16, 2024
e86e81c
Merge branch 'main' into zedwick/point-in-polygon
philipc2 Dec 17, 2024
a6b0dcc
Added unique face test cases
aaronzedwick Dec 18, 2024
36ef299
Fixed test cases, changed to `point_in_face`
aaronzedwick Dec 18, 2024
dc153dc
Convert face containing point to use updated function
aaronzedwick Dec 18, 2024
c5ed371
Added inverse indices, complete get_faces_containing_point
aaronzedwick Jan 2, 2025
d7318de
Merge branch 'main' into zedwick/point-in-polygon
aaronzedwick Jan 2, 2025
ab18797
Merge branch 'zedwick/point-in-polygon' of https://github.com/UXARRAY…
aaronzedwick Jan 2, 2025
47843d8
Merge branch 'main' into zedwick/point-in-polygon
aaronzedwick Jan 9, 2025
845cca6
Merge branch 'main' into zedwick/point-in-polygon
aaronzedwick Jan 9, 2025
88a602f
Updated to use inverse face indices
aaronzedwick Jan 9, 2025
c28772a
Fixed failing tests
aaronzedwick Jan 9, 2025
e055327
Moved function location
aaronzedwick Jan 9, 2025
46cecc4
Fixed circular import
aaronzedwick Jan 10, 2025
5dba109
Update grid.py
aaronzedwick Jan 10, 2025
d297a9e
updated comment
aaronzedwick Jan 13, 2025
bff6cd5
updated max_face_radius to handle variable face sizes
aaronzedwick Jan 13, 2025
67ea7b2
updated haversine and added test
aaronzedwick Jan 13, 2025
ce354fd
Added benchmark, made max_face_radius attribute for better performance
aaronzedwick Jan 14, 2025
7eeef4e
Merge branch 'main' into zedwick/point-in-polygon
aaronzedwick Jan 14, 2025
98a676d
Fixed benchmark
aaronzedwick Jan 14, 2025
66fba3d
added property, update benchmark
aaronzedwick Jan 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions benchmarks/mpas_ocean.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,10 @@ def teardown(self, resolution, lat_step):
def time_const_lat(self, resolution, lat_step):
for lat in self.lats:
self.uxgrid.cross_section.constant_latitude(lat)


class PointInPolygon(GridBenchmark):
def time_whole_grid(self, resolution):
point_xyz = np.array([self.uxgrid.face_x[0].values, self.uxgrid.face_y[0].values, self.uxgrid.face_z[0].values])

self.uxgrid.get_faces_containing_point(point_xyz=point_xyz)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the GH Actions report, it appears that a single Point in Face query is taking approximately 27.0±0.1ms

Ideally, we want to get this number significantly down.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did some digging:

Please include the following in a custom setup function:

_ = uxds.uxgrid.face_edge_connectivity

We don't want this to be included in the timing. I believe much of the 27ms is attributed to that.

This is for a 30km Grid

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, consider including a single sample point query in the setup, since there is also some overhead with the KD tree construction.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the performance I get for a 30km grid after doing the things mentioned above. pretty good for 30km grids

image

Only issue, is that it doesn't find any faces.

Here is my sample code I used.

import uxarray as ux
import numpy as np

import cProfile

profiler = cProfile.Profile()

grid_path = "/Users/philipc/PycharmProjects/uxarray/unstructured-grid-viz-cookbook/meshfiles/x1.655362.grid.nc"
data_path = "/Users/philipc/PycharmProjects/uxarray/unstructured-grid-viz-cookbook/meshfiles/x1.655362.data.nc"

uxds = ux.open_dataset(grid_path, data_path)
uxds.uxgrid.normalize_cartesian_coordinates()

_ = uxds.uxgrid.face_edge_connectivity

point = np.array([0.0, 0.0, 1.0])
res = uxds.uxgrid.get_faces_containing_point(point)


profiler.enable()
res = uxds.uxgrid.get_faces_containing_point(point)
profiler.disable()

profiler.dump_stats('pface.prof')

print(res)

Can do snakeviz pface.prof to view the profiler.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking into this for me! I am trying to implement these changes now. In terms of it not finding any faces, does the mesh have holes in it? There may not be a face at the pole possibly?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The grid is a global MPAS atmosphere grid with no holes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may have been on an older version of the code. Just ran it again after pulling and it works. A single face is detected. Going to run a few more tests.

Copy link
Member Author

@aaronzedwick aaronzedwick Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it working locally for you? It does for me, but the CI is failing. Not sure why.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like it is working for the poles at least on my end. Haven't tested other points

304 changes: 286 additions & 18 deletions test/test_geometry.py

Large diffs are not rendered by default.

86 changes: 74 additions & 12 deletions test/test_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from uxarray.grid.connectivity import _populate_face_edge_connectivity, _build_edge_face_connectivity, \
_build_edge_node_connectivity, _build_face_face_connectivity, _populate_face_face_connectivity

from uxarray.grid.coordinates import _populate_node_latlon, _lonlat_rad_to_xyz
from uxarray.grid.coordinates import _populate_node_latlon, _lonlat_rad_to_xyz, _xyz_to_lonlat_rad_scalar

from uxarray.constants import INT_FILL_VALUE, ERROR_TOLERANCE

Expand Down Expand Up @@ -44,13 +44,10 @@

shp_filename = current_path / "meshfiles" / "shp" / "grid_fire.shp"



grid_CSne30 = ux.open_grid(gridfile_CSne30)
grid_RLL1deg = ux.open_grid(gridfile_RLL1deg)
grid_RLL10deg_CSne4 = ux.open_grid(gridfile_RLL10deg_CSne4)


mpas_filepath = current_path / "meshfiles" / "mpas" / "QU" / "mesh.QU.1920km.151026.nc"
exodus_filepath = current_path / "meshfiles" / "exodus" / "outCSne8" / "outCSne8.g"
ugrid_filepath_01 = current_path / "meshfiles" / "ugrid" / "outCSne30" / "outCSne30.ug"
Expand Down Expand Up @@ -89,6 +86,7 @@ def test_grid_validate():
grid_mpas = ux.open_grid(gridfile_mpas)
assert grid_mpas.validate()


def test_grid_with_holes():
"""Test _holes_in_mesh function."""
grid_without_holes = ux.open_grid(gridfile_mpas)
Expand All @@ -97,6 +95,7 @@ def test_grid_with_holes():
assert grid_with_holes.partial_sphere_coverage
assert grid_without_holes.global_sphere_coverage


def test_grid_encode_as():
"""Reads a ugrid file and encodes it as `xarray.Dataset` in various types."""
grid_CSne30.encode_as("UGRID")
Expand All @@ -107,6 +106,7 @@ def test_grid_encode_as():
grid_RLL1deg.encode_as("Exodus")
grid_RLL10deg_CSne4.encode_as("Exodus")


def test_grid_init_verts():
"""Create a uxarray grid from multiple face vertices with duplicate nodes and saves a ugrid file."""
cart_x = [
Expand All @@ -131,7 +131,7 @@ def test_grid_init_verts():
[5, 4, 7, 6], # back face
[4, 0, 3, 7], # left face
[3, 2, 6, 7], # top face
[4, 5, 1, 0] # bottom face
[4, 5, 1, 0] # bottom face
]

faces_coords = []
Expand Down Expand Up @@ -163,6 +163,7 @@ def test_grid_init_verts():
assert vgrid.n_node == 6
vgrid.encode_as("UGRID")


def test_grid_init_verts_different_input_datatype():
"""Create a uxarray grid from multiple face vertices with different datatypes (ndarray, list, tuple) and saves a ugrid file."""
faces_verts_ndarray = np.array([
Expand All @@ -176,8 +177,8 @@ def test_grid_init_verts_different_input_datatype():
vgrid.encode_as("UGRID")

faces_verts_list = [[[150, 10], [160, 20], [150, 30], [135, 30], [125, 20], [135, 10]],
[[125, 20], [135, 30], [125, 60], [110, 60], [100, 30], [105, 20]],
[[95, 10], [105, 20], [100, 30], [85, 30], [75, 20], [85, 10]]]
[[125, 20], [135, 30], [125, 60], [110, 60], [100, 30], [105, 20]],
[[95, 10], [105, 20], [100, 30], [85, 30], [75, 20], [85, 10]]]
vgrid = ux.open_grid(faces_verts_list, latlon=True)
assert vgrid.n_face == 3
assert vgrid.n_node == 14
Expand All @@ -195,6 +196,7 @@ def test_grid_init_verts_different_input_datatype():
assert vgrid.validate()
vgrid.encode_as("UGRID")


def test_grid_init_verts_fill_values():
faces_verts_filled_values = [[[150, 10], [160, 20], [150, 30],
[135, 30], [125, 20], [135, 10]],
Expand All @@ -208,6 +210,7 @@ def test_grid_init_verts_fill_values():
assert vgrid.n_face == 3
assert vgrid.n_node == 12


def test_grid_properties():
"""Tests to see if accessing variables through set properties is equal to using the dict."""
xr.testing.assert_equal(grid_CSne30.node_lon, grid_CSne30._ds["node_lon"])
Expand All @@ -234,27 +237,32 @@ def test_grid_properties():
assert n_faces == grid_geoflow.n_face
assert n_face_nodes == grid_geoflow.n_max_face_nodes


def test_read_shpfile():
"""Reads a shape file and write ugrid file."""
with pytest.raises(ValueError):
grid_shp = ux.open_grid(shp_filename)


def test_read_scrip():
"""Reads a scrip file."""
grid_CSne8 = ux.open_grid(gridfile_CSne8) # tests from scrip


def test_operators_eq():
"""Test Equals ('==') operator."""
grid_CSne30_01 = ux.open_grid(gridfile_CSne30)
grid_CSne30_02 = ux.open_grid(gridfile_CSne30)
assert grid_CSne30_01 == grid_CSne30_02


def test_operators_ne():
"""Test Not Equals ('!=') operator."""
grid_CSne30_01 = ux.open_grid(gridfile_CSne30)
grid_RLL1deg = ux.open_grid(gridfile_RLL1deg)
assert grid_CSne30_01 != grid_RLL1deg


def test_face_areas_calculate_total_face_area_triangle():
"""Create a uxarray grid from vertices and saves an exodus file."""
verts = [[[0.57735027, -5.77350269e-01, -0.57735027],
Expand All @@ -275,11 +283,13 @@ def test_face_areas_calculate_total_face_area_triangle():
quadrature_rule="triangular", order=4)
nt.assert_almost_equal(area_triangular, constants.TRI_AREA, decimal=1)


def test_face_areas_calculate_total_face_area_file():
"""Create a uxarray grid from vertices and saves an exodus file."""
area = ux.open_grid(gridfile_CSne30).calculate_total_face_area()
nt.assert_almost_equal(area, constants.MESH30_AREA, decimal=3)


def test_face_areas_calculate_total_face_area_sphere():
"""Computes the total face area of an MPAS mesh that lies on a unit sphere, with an expected total face area of 4pi."""
mpas_grid_path = current_path / 'meshfiles' / "mpas" / "QU" / 'mesh.QU.1920km.151026.nc'
Expand All @@ -293,11 +303,13 @@ def test_face_areas_calculate_total_face_area_sphere():
nt.assert_almost_equal(primal_face_area, constants.UNIT_SPHERE_AREA, decimal=3)
nt.assert_almost_equal(dual_face_area, constants.UNIT_SPHERE_AREA, decimal=3)


def test_face_areas_compute_face_areas_geoflow_small():
"""Checks if the GeoFlow Small can generate a face areas output."""
grid_geoflow = ux.open_grid(gridfile_geoflow)
grid_geoflow.compute_face_areas()


def test_face_areas_verts_calc_area():
faces_verts_ndarray = np.array([
np.array([[150, 10, 0], [160, 20, 0], [150, 30, 0], [135, 30, 0],
Expand All @@ -311,11 +323,12 @@ def test_face_areas_verts_calc_area():
face_verts_areas = verts_grid.face_areas
nt.assert_almost_equal(face_verts_areas.sum(), constants.FACE_VERTS_AREA, decimal=3)


def test_populate_coordinates_populate_cartesian_xyz_coord():
# The following testcases are generated through the matlab cart2sph/sph2cart functions
lon_deg = [
45.0001052295749, 45.0001052295749, 360 - 45.0001052295749,
360 - 45.0001052295749
360 - 45.0001052295749
]
lat_deg = [
35.2655522903022, -35.2655522903022, 35.2655522903022,
Expand All @@ -342,10 +355,11 @@ def test_populate_coordinates_populate_cartesian_xyz_coord():
nt.assert_almost_equal(vgrid.node_y.values[i], cart_y[i], decimal=12)
nt.assert_almost_equal(vgrid.node_z.values[i], cart_z[i], decimal=12)


def test_populate_coordinates_populate_lonlat_coord():
lon_deg = [
45.0001052295749, 45.0001052295749, 360 - 45.0001052295749,
360 - 45.0001052295749
360 - 45.0001052295749
]
lat_deg = [
35.2655522903022, -35.2655522903022, 35.2655522903022,
Expand Down Expand Up @@ -374,8 +388,8 @@ def test_populate_coordinates_populate_lonlat_coord():


def _revert_edges_conn_to_face_nodes_conn(edge_nodes_connectivity: np.ndarray,
face_edges_connectivity: np.ndarray,
original_face_nodes_connectivity: np.ndarray):
face_edges_connectivity: np.ndarray,
original_face_nodes_connectivity: np.ndarray):
"""Utilize the edge_nodes_connectivity and face_edges_connectivity to
generate the res_face_nodes_connectivity in the counter-clockwise
order. The counter-clockwise order will be enforced by the passed in
Expand Down Expand Up @@ -440,6 +454,7 @@ def _revert_edges_conn_to_face_nodes_conn(edge_nodes_connectivity: np.ndarray,

return np.array(res_face_nodes_connectivity)


def test_connectivity_build_n_nodes_per_face():
"""Tests the construction of the ``n_nodes_per_face`` variable."""
grids = [grid_mpas, grid_exodus, grid_ugrid]
Expand All @@ -457,6 +472,7 @@ def test_connectivity_build_n_nodes_per_face():
expected_nodes_per_face = np.array([6, 3, 4, 6, 6, 4, 4], dtype=int)
nt.assert_equal(grid_from_verts.n_nodes_per_face.values, expected_nodes_per_face)


def test_connectivity_edge_nodes_euler():
"""Verifies that (``n_edge``) follows euler's formula."""
grid_paths = [exodus_filepath, ugrid_filepath_01, ugrid_filepath_02, ugrid_filepath_03]
Expand All @@ -470,6 +486,7 @@ def test_connectivity_edge_nodes_euler():

assert (n_face == n_edge - n_node + 2)


def test_connectivity_build_face_edges_connectivity_mpas():
"""Tests the construction of (``Mesh2_edge_nodes``) on an MPAS grid with known edge nodes."""
from uxarray.grid.connectivity import _build_edge_node_connectivity
Expand All @@ -492,6 +509,7 @@ def test_connectivity_build_face_edges_connectivity_mpas():

assert (n_face == n_edge - n_node + 2)


def test_connectivity_build_face_edges_connectivity():
"""Generates Grid.Mesh2_edge_nodes from Grid.face_node_connectivity."""
ug_filename_list = [ugrid_filepath_01, ugrid_filepath_02, ugrid_filepath_03]
Expand Down Expand Up @@ -522,6 +540,7 @@ def test_connectivity_build_face_edges_connectivity():
for i in range(len(reverted_mesh2_edge_nodes)):
assert np.array_equal(reverted_mesh2_edge_nodes[i], original_face_nodes_connectivity[i])


def test_connectivity_build_face_edges_connectivity_fillvalues():
verts = [f0_deg, f1_deg, f2_deg, f3_deg, f4_deg, f5_deg, f6_deg]
uds = ux.open_grid(verts)
Expand All @@ -543,6 +562,7 @@ def test_connectivity_build_face_edges_connectivity_fillvalues():

assert np.array_equal(res_face_nodes_connectivity, uds._ds["face_node_connectivity"].values)


def test_connectivity_node_face_connectivity_from_verts():
"""Test generating Grid.Mesh2_node_faces from array input."""
face_nodes_conn_lonlat_degree = [[162., 30], [216., 30], [70., 30],
Expand Down Expand Up @@ -573,6 +593,7 @@ def test_connectivity_node_face_connectivity_from_verts():

assert np.array_equal(vgrid.node_face_connectivity.values, expected)


def test_connectivity_node_face_connectivity_from_files():
"""Test generating Grid.Mesh2_node_faces from file input."""
grid_paths = [exodus_filepath, ugrid_filepath_01, ugrid_filepath_02, ugrid_filepath_03]
Expand All @@ -593,12 +614,14 @@ def test_connectivity_node_face_connectivity_from_files():

for i in range(grid_ux.n_node):
face_index_from_sparse_matrix = grid_ux.node_face_connectivity.values[i]
valid_face_index_from_sparse_matrix = face_index_from_sparse_matrix[face_index_from_sparse_matrix != grid_ux.node_face_connectivity.attrs["_FillValue"]]
valid_face_index_from_sparse_matrix = face_index_from_sparse_matrix[
face_index_from_sparse_matrix != grid_ux.node_face_connectivity.attrs["_FillValue"]]
valid_face_index_from_sparse_matrix.sort()
face_index_from_dict = node_face_connectivity[i]
face_index_from_dict.sort()
assert np.array_equal(valid_face_index_from_sparse_matrix, face_index_from_dict)


def test_connectivity_edge_face_connectivity_mpas():
"""Tests the construction of ``Mesh2_face_edges`` to the expected results of an MPAS grid."""
uxgrid = ux.open_grid(mpas_filepath)
Expand All @@ -611,6 +634,7 @@ def test_connectivity_edge_face_connectivity_mpas():

nt.assert_array_equal(edge_faces_output, edge_faces_gold)


def test_connectivity_edge_face_connectivity_sample():
"""Tests the construction of ``Mesh2_face_edges`` on an example with one shared edge, and the remaining edges only being part of one face."""
verts = [[(0.0, -90.0), (180, 0.0), (0.0, 90)],
Expand All @@ -633,6 +657,7 @@ def test_connectivity_edge_face_connectivity_sample():
assert n_solo == uxgrid.n_edge - n_shared
assert n_invalid == 0


def test_connectivity_face_face_connectivity_construction():
"""Tests the construction of face-face connectivity."""
grid = ux.open_grid(mpas_filepath)
Expand All @@ -645,6 +670,7 @@ def test_connectivity_face_face_connectivity_construction():

nt.assert_array_equal(face_face_conn_new_sorted, face_face_conn_old_sorted)


def test_class_methods_from_dataset():
# UGRID
xrds = xr.open_dataset(gridfile_ugrid)
Expand All @@ -663,6 +689,7 @@ def test_class_methods_from_dataset():
xrds = xr.open_dataset(gridfile_scrip)
uxgrid = ux.Grid.from_dataset(xrds)


def test_class_methods_from_face_vertices():
single_face_latlon = [(0.0, 90.0), (-180, 0.0), (0.0, -90)]
uxgrid = ux.Grid.from_face_vertices(single_face_latlon, latlon=True)
Expand All @@ -673,6 +700,7 @@ def test_class_methods_from_face_vertices():

single_face_cart = [(0.0,)]


def test_latlon_bounds_populate_bounds_GCA_mix():
gridfile_mpas = current_path / "meshfiles" / "mpas" / "QU" / "oQU480.231010.nc"
face_1 = [[10.0, 60.0], [10.0, 10.0], [50.0, 10.0], [50.0, 60.0]]
Expand All @@ -691,11 +719,13 @@ def test_latlon_bounds_populate_bounds_GCA_mix():
bounds_xarray = grid.bounds
nt.assert_allclose(bounds_xarray.values, expected_bounds, atol=ERROR_TOLERANCE)


def test_latlon_bounds_populate_bounds_MPAS():
gridfile_mpas = current_path / "meshfiles" / "mpas" / "QU" / "oQU480.231010.nc"
uxgrid = ux.open_grid(gridfile_mpas)
bounds_xarray = uxgrid.bounds


def test_dual_mesh_mpas():
grid = ux.open_grid(gridfile_mpas, use_dual=False)
mpas_dual = ux.open_grid(gridfile_mpas, use_dual=True)
Expand All @@ -708,11 +738,13 @@ def test_dual_mesh_mpas():

nt.assert_equal(dual.face_node_connectivity.values, mpas_dual.face_node_connectivity.values)


def test_dual_duplicate():
dataset = ux.open_dataset(gridfile_geoflow, gridfile_geoflow)
with pytest.raises(RuntimeError):
dataset.get_dual()


def test_normalize_existing_coordinates_non_norm_initial():
gridfile_mpas = current_path / "meshfiles" / "mpas" / "QU" / "mesh.QU.1920km.151026.nc"
from uxarray.grid.validation import _check_normalization
Expand All @@ -726,9 +758,39 @@ def test_normalize_existing_coordinates_non_norm_initial():
uxgrid.normalize_cartesian_coordinates()
assert _check_normalization(uxgrid)


def test_normalize_existing_coordinates_norm_initial():
gridfile_CSne30 = current_path / "meshfiles" / "ugrid" / "outCSne30" / "outCSne30.ug"
from uxarray.grid.validation import _check_normalization
uxgrid = ux.open_grid(gridfile_CSne30)

assert _check_normalization(uxgrid)


def test_number_of_faces_found():
grid = ux.open_grid(gridfile_mpas)

# For a face center only one face should be found
point_xyz = np.array([grid.face_x[100].values, grid.face_y[100].values, grid.face_z[100].values])

assert len(grid.get_faces_containing_point(point_xyz=point_xyz)) == 1

# For an edge two faces should be found
point_xyz = np.array([grid.edge_x[100].values, grid.edge_y[100].values, grid.edge_z[100].values])

assert len(grid.get_faces_containing_point(point_xyz=point_xyz)) == 2

# For a node three faces should be found
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not always true, a node can have 1, 2, 3 or higher number of faces.
For 1, consider a node on the boundary, not on any other face.

For 2, consider a node on the boundary that is the actual node on the face, on both faces.

For 4/higher, consider a node that is the intersection of 4 faces or higher.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is true, in general. However this is a MPAS grid. Which is always going to have each node connected to 3 faces. So I think it’s an OK assumption here that it should result in 3 faces being found.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is true, in general. However this is a MPAS grid. Which is always going to have each node connected to 3 faces. So I think it’s an OK assumption here that it should result in 3 faces being found.

Ok, you can keep it, but if the grid has holes and the node is along the boundary. I think, it'd be good to test these edge cases.

point_xyz = np.array([grid.node_x[100].values, grid.node_y[100].values, grid.node_z[100].values])

assert len(grid.get_faces_containing_point(point_xyz=point_xyz)) == 3


def test_whole_grid():
grid = ux.open_grid(gridfile_mpas)

# Ensure a face is found on the grid for every face center
for i in range(len(grid.face_x.values)):
point_xyz = np.array([grid.face_x[i].values, grid.face_y[i].values, grid.face_z[i].values])

assert len(grid.get_faces_containing_point(point_xyz=point_xyz)) == 1
4 changes: 2 additions & 2 deletions uxarray/grid/coordinates.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ def _xyz_to_lonlat_rad(
z: Union[np.ndarray, float],
normalize: bool = True,
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Converts Cartesian x, y, z coordinates in Spherical latitude and
longitude coordinates in degrees.
"""Converts Cartesian x, y, z coordinates in Spherical longitude and
latitude coordinates in radians.
Parameters
----------
Expand Down
Loading
Loading