-
Notifications
You must be signed in to change notification settings - Fork 32
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 & Get Faces Containing Point #1056
base: main
Are you sure you want to change the base?
Conversation
@ philipc2 do you think this should be an internal function or exposed to the user? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please include an ASV benchmark. I'd suggest doing a parameterized benchmark for the 120 and 480 km MPAS grids.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use the optimized functions from #1072 and try to write the function entirely in Numba. This may require us to pass in both the cartesian and spherical versions of point & polygon. Let me know if you have any questions!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
point_in_face(faces_edge_cartesian[0], point, inclusive=True)
this always requires calculation of edges, can we have another version of this -
point_in_face(face_id, point
orpoint_in_face(face_conn, point)
For the notebook example, we have 4 faces with id's say 0 to 4 and connectivity as shown below:
xarray.DataArray'face_node_connectivity'n_face: 4n_max_face_nodes: 6
array([[ 0, 1, 2, 3, 4, 5],
[15, 7, 6, 12, 0, 5],
[ 0, 12, 8, 9, 13, 1],
[ 4, 14, 11, 10, 15, 5]])
getting edges adds another step that might not always be necessary and can be done internally for these other signatures, the two functions above would be more user friendly IMO.
This function is not a grid function, so passing just the face connectivity isn’t enough. You have to pass the node coordinates of the face. Either way, you have to parse the coordinates of the face you want to use. We can pass the node coordinates as nodes into it if you like, but I originally went with this implementation for two reasons. First, the conversion will in theory impact performance. And second, if the user has to construct the node coordinates and send them in anyway, it would require less effort to use the edge coordinates Cartesian function to create the array for the user. It is easier and ensures the correct array shape is being passed to the function. Also, that is the shape of array the ‘faces_containing_point’ use, as well as all of Hongyu’s code. I definitely understand wanting to make them more user friendly, but I see ‘get_faces_containing_point’ as the more common user function. And as the point in face function is meant to be called without being attached to the grid, you will always have to construct some kind of coordinate information and pass it in, whether it is the nodes that make up faces, or the nodes that make up edges. Otherwise the function won’t have access to that information. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bug fix for conversion _lonlat_rad_to_xyz in coordinates.py, max_face_radius and get_faces_containing_points are critical changes. good work
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please fix the ASV benchmark, it is still failing on our CI.
if len(point) == 2: | ||
point_xyz = np.array(_lonlat_rad_to_xyz(*np.deg2rad(point))) | ||
point_lonlat = point | ||
elif len(point) == 3: | ||
point_xyz = point | ||
point_lonlat = np.array(_xyz_to_lonlat_deg(*point_xyz), dtype=np.float64) | ||
else: | ||
raise ValueError( | ||
"Point must either be in spherical or cartesian coordinates." | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran into an issue where if my points were float32
the Numba functionality would fail, since it would be trying to call Numba functions between float32
and float64
variables, which isn't possible.
Maybe doing a np.asaray(point, dtype=np.float64)
could fix it. May you add a test for this to replicate the functionality?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In some of my tests I have found that using lonlat with a dtype=np.float64
can cause issues on the nodes, it will not find all the faces nearby. I think this is possible due to rounding precission issues maybe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Casting to a higher precision shouldn't usually lead to issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does, doing dtype=np.float32
ensures that Hongyus algorithm recognizes that it is on the great circle arc. But when it is float64
it doesn't seem to realize it is on the arc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have a minimal example? That's interesting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I query with Cartesian coordinates in f64, I get the correct indices.
point = np.array([grid.node_x[0].values, grid.node_y[0].values, grid.node_z[0].values], dtype=np.float64)
Though, with f32 I get a Numba error.
point = np.array([grid.node_x[0].values, grid.node_y[0].values, grid.node_z[0].values], dtype=np.float32)
No implementation of function Function(<built-in function dot>) found for signature:
>>> dot(array(float64, 1d, C), array(float32, 1d, C))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does appear to be a precision issue when using spherical coordinates, since we internally convert them to cartesian. We should try to avoid as many of these conversions as possible. @hongyuchen1030 and I have encountered this a lot in our previous implementations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error you are getting happens within Hongyus code. I have had it error out sometimes when using cartesian coordinates that aren't in float64
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be fixed by ensuring that the only xyz coordinates are f64. But that still won't fix the precision issue of the spherical coordinates when they are changed to f64.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if adding a small eps
to increase the radius of each bounding circle might help?
EDIT: I see you did that below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we include a test for points along a line of constant latitude?
def test_point_along_arc():
node_lon = np.array([-40, -40, 40, 40])
node_lat = np.array([-20, 20, 20, -20])
face_node_connectivity = np.array([[0, 1, 2, 3]], dtype=np.int64)
uxgrid = ux.Grid.from_topology(node_lon, node_lat, face_node_connectivity)
# point at exactly 20 degrees latitude
out1 = uxgrid.get_faces_containing_point((0, 20))
# point at 25.41 degrees latitude (max along the great circle arc)
out2 = uxgrid.get_faces_containing_point((0, 25.41))
nt.assert_array_equal(out1, out2)
…/uxarray into zedwick/point-in-polygon
if len(point) == 2: | ||
point_xyz = np.array(_lonlat_rad_to_xyz(*np.deg2rad(point))) | ||
point_lonlat = point | ||
elif len(point) == 3: | ||
point_xyz = point | ||
point_lonlat = np.array(_xyz_to_lonlat_deg(*point_xyz), dtype=np.float64) | ||
else: | ||
raise ValueError( | ||
"Point must either be in spherical or cartesian coordinates." | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I query with Cartesian coordinates in f64, I get the correct indices.
point = np.array([grid.node_x[0].values, grid.node_y[0].values, grid.node_z[0].values], dtype=np.float64)
Though, with f32 I get a Numba error.
point = np.array([grid.node_x[0].values, grid.node_y[0].values, grid.node_z[0].values], dtype=np.float32)
No implementation of function Function(<built-in function dot>) found for signature:
>>> dot(array(float64, 1d, C), array(float32, 1d, C))
if len(point) == 2: | ||
point_xyz = np.array(_lonlat_rad_to_xyz(*np.deg2rad(point))) | ||
point_lonlat = point | ||
elif len(point) == 3: | ||
point_xyz = point | ||
point_lonlat = np.array(_xyz_to_lonlat_deg(*point_xyz), dtype=np.float64) | ||
else: | ||
raise ValueError( | ||
"Point must either be in spherical or cartesian coordinates." | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does appear to be a precision issue when using spherical coordinates, since we internally convert them to cartesian. We should try to avoid as many of these conversions as possible. @hongyuchen1030 and I have encountered this a lot in our previous implementations.
Closes #905, #1141
Overview
Expected Usage
PR Checklist
General
Testing
Documentation
_
) and have been added todocs/internal_api/index.rst
docs/user_api/index.rst