-
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
Apply function to points within circular neighborhood #941
base: main
Are you sure you want to change the base?
Conversation
# face centers, edge centers or nodes. | ||
tree = self.uxgrid.get_ball_tree(coordinates=data_mapping, reconstruct=True) |
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.
We should probably fix this logic in get_ball_tree()
, since we shouldn't need to manually set reconstruct=False
if self._ball_tree is None or reconstruct:
self._ball_tree = BallTree(
self,
coordinates=coordinates,
distance_metric=distance_metric,
coordinate_system=coordinate_system,
reconstruct=reconstruct,
)
else:
if coordinates != self._ball_tree._coordinates:
self._ball_tree.coordinates = coordinates
The coordinates != self._ball_tree._coordinates
check should be included in the first if
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.
That makes sense. So, move the coordinates check to the if-clause like this?
if (
self._ball_tree is None
or coordinates != self._ball_tree._coordinates
or reconstruct
):
self._ball_tree = BallTree(
self,
coordinates=coordinates,
distance_metric=distance_metric,
coordinate_system=coordinate_system,
reconstruct=reconstruct,
)
What if the coordinate_system is different? Would that also require a newly constructed tree?
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.
Whatever logic is fixed in Grid.get_ball_tree
should also be applied to Grid.get_kdtree
.
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.
checking coordinate system also (coordinate_system is not a hidden variable of _ball_tree; it has no underscore):
if (
self._ball_tree is None
or coordinates != self._ball_tree._coordinates
or coordinate_system != self._ball_tree.coordinate_system
or reconstruct
):
self._ball_tree = BallTree(
self,
coordinates=coordinates,
distance_metric=distance_metric,
coordinate_system=coordinate_system,
reconstruct=reconstruct,
)
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.
A few initial comments:
@@ -1104,3 +1103,116 @@ def _slice_from_grid(self, sliced_grid): | |||
dims=self.dims, | |||
attrs=self.attrs, | |||
) | |||
|
|||
def neighborhood_filter( |
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 implementation looks great! May we move the bulk of the logic into the uxarray.grid.neighbors
module and call that helper from here?
We can keep the data-mapping checks here, and anything related to constructing and returining the final data array but the bulk of the computations would go inside a helper in the module mentioned above.
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 have to think about how to do that, but I am happy to defer to you.
|
||
coordinate_system = tree.coordinate_system | ||
|
||
if coordinate_system == "spherical": | ||
if data_mapping == "nodes": | ||
lon, lat = ( | ||
self.uxgrid.node_lon.values, | ||
self.uxgrid.node_lat.values, | ||
) | ||
elif data_mapping == "face centers": | ||
lon, lat = ( | ||
self.uxgrid.face_lon.values, | ||
self.uxgrid.face_lat.values, | ||
) | ||
elif data_mapping == "edge centers": | ||
lon, lat = ( | ||
self.uxgrid.edge_lon.values, | ||
self.uxgrid.edge_lat.values, | ||
) | ||
else: | ||
raise ValueError( | ||
f"Invalid data_mapping. Expected 'nodes', 'edge centers', or 'face centers', " | ||
f"but received: {data_mapping}" | ||
) | ||
|
||
dest_coords = np.vstack((lon, lat)).T | ||
|
||
elif coordinate_system == "cartesian": | ||
if data_mapping == "nodes": | ||
x, y, z = ( | ||
self.uxgrid.node_x.values, | ||
self.uxgrid.node_y.values, | ||
self.uxgrid.node_z.values, | ||
) | ||
elif data_mapping == "face centers": | ||
x, y, z = ( | ||
self.uxgrid.face_x.values, | ||
self.uxgrid.face_y.values, | ||
self.uxgrid.face_z.values, | ||
) | ||
elif data_mapping == "edge centers": | ||
x, y, z = ( | ||
self.uxgrid.edge_x.values, | ||
self.uxgrid.edge_y.values, | ||
self.uxgrid.edge_z.values, | ||
) | ||
else: | ||
raise ValueError( | ||
f"Invalid data_mapping. Expected 'nodes', 'edge centers', or 'face centers', " | ||
f"but received: {data_mapping}" | ||
) |
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.
Use #974 's new remap.utils._remap_grid_parse
instead of this code block.
Apply a neighborhood filter within a circular radius r to a UxDataset or UxDataArray.
Issue #930
Overview
This is kind of like
uxarray.UxDataArray.inverse_distance_weighted_remap
, but the neighborhood is defined by distance, not a number of nearest neighbors. This is ideally suited for a variable resolution mesh, in which a constant of neighbors doesn't have a constant sized neighborhood. Another difference is that this neighborhood filter does not weight data by inverse distance.Just like
uxarray.UxDataArray.subset.bounding_circle
this function usesball_tree.query_radius
to select grid elements in a circular neighborhood, but this function finds the neighborhood for all elements in grid, not just one center_coordinate.The filter function
func
may be a user-defined function, but usesnp.mean
by default. It could bemin
,max
,np.median
. It can even use functions that require additional arguments, likenp.percentile
if you supply the argument(s) withfunctools.partial
(see below)Expected Usage
PR Checklist
General
Testing
Documentation
_
) and have been added todocs/internal_api/index.rst
docs/user_api/index.rst
Examples
docs/examples/
folderdocs/examples.rst
toctreedocs/gallery.yml
with appropriate thumbnail photo indocs/_static/thumbnails/