-
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
Check Grid for Partial Spherical Coverage #899
Conversation
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.
looks good adds closes #141
def _mesh_contains_holes(edge_face_connectivity):
"""Check if a mesh has holes in it."""
# If an edge only has one face saddling it than the mesh has holes in it
return np.any(edge_face_connectivity[:, 1] == INT_FILL_VALUE) This function is basically checking if the |
Doesn’t this connective hold for each edge what face saddles it? If it is the case that an edge has only one face and the other side of the edge has a fill value, that means it lacks a face there and therefore there is a hole in the mesh? For each edge it can only ever have 2 faces that saddle it, and if there is only one face saddling it, the logic goes that there must be a hole. Is that assumption incorrect? |
Oh, so you want to see if a given mesh file contains holes, instead of a single face has holes inside it? However, why we need to run this check over the entire grid? So basically what happens is: you check the ENTIRE grid to see if it has a hole or not, which will still at least needs an O(log_2 N_Edges) time complexity. And then what will happen if we detect there's at least one hole in the face? What's the following procedure? Are you planning to do another query about where the hole is? |
Exactly. The specific use case for this is within the dual mesh. If the grid has holes (like say it doesn’t include landmasses) then the dual mesh of a data array would give incorrect results, as some face centers and nodes won’t be used in the dual mesh (imagine if there was a line of squares in a row, no dual mesh could be made from just that and therefore all data stored there would be lost). Also, for bilinear remapping, if the mesh has holes in it, the construction of the dual mesh needs to be handled differently, in accordance to what Paul does in his implementation. So inside the bilinear remapping a call to this function must be made to determine if a global dual mesh will work, or if a different local approach to constructing the dual must be taken. I hope that helps clear things up! |
So yes, my concern was true. After we call this boolean function to check the entire grid and return False, we need to check the entire grid again to find where the holes are missing. So two same traverse for the entire grid, which is very computationally expensive. Why don't we just return the clean-up meshes or identify the missing faces during the re-mapping process? During the |
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.
If all we are interested in knowing is whether there are empty patches with no geometry, such as continents, we should be able to check the total face area of the unit sphere.
If the area is less than 4pi, it means that not the entire surface is covered with geometries.
I'd also avoid referring to this behavior as "holes", as it can be confused with what we've discussed. We are essentially just checking if the entire sphere is covered with our grid. A grid with partial coverage, such as the grids used in ocean models, only cover a fraction of the sphere. |
And partial grid should not impact the re-gridding behavior as well. |
Paul does a check for it in his code, unless I am misunderstanding what he does there. Unless he is checking for holes inside a face but looking at his code for |
That's a fair point. However if we already have the face area, it would be an O(n) operation. |
I understand that you are trying to follow what the tempest extreme does here. My point is:
These two points are very important and introduce a recent important discussion for our developing process. |
uxarray/grid/validation.py
Outdated
"""Check if a mesh has holes in it.""" | ||
|
||
# If an edge only has one face saddling it than the mesh has holes in it | ||
return np.any(edge_face_connectivity[:, 1] == INT_FILL_VALUE) |
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 don't see an issue with this check, as the reasoning that @aaronzedwick explained is sound. If there exists any edge that does not have two neighboring faces, then there must exist some "empty" area adjacent to that edge. Illustration below for reference: the edges marked in red each neighbor an empty region, which means that we can't construct an edge between the centers of the two faces indicated in the edge_face_connectivity
This causes issues with the Dual Mesh construction, since node-centered data variables become face-centered. However, there will be no faces constructed centered around these values. Given our interpretation of the UGRID conventions, we have no way to construct a face for this data, meaning that the data is essentially "lost" from the dual mesh construction process.
Also, I will suggest caching the result once we've run it, so that we don't need to run the repeated searches over the edge_face_connectivity
. Something as simple as a Grid._partial_coverage
attribute would suffice.
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 don't see an issue with this check, as the reasoning that @aaronzedwick explained is sound. If there exists any edge that does not have two neighboring faces, then there must exist some "empty" area adjacent to that edge. Illustration below for reference: the edges marked in red each neighbor an empty region, which means that we can't construct an edge between the centers of the two faces indicated in the
edge_face_connectivity
We have already gone through this part in the previous discussion above. I understand what you mean by "missing a face" here. My current concern is: after we have examined the entire grid to check if there's a partial coverage, what's the next step to deal with the missing faces? Do we need to rerun a similar check to identify which faces don't have neighbors? So basically two O(N) operations?
Also, I will suggest caching the result once we've run it, so that we don't need to run the repeated searches over the
edge_face_connectivity
. Something as simple as aGrid._partial_coverage
attribute would suffice.
This is a good point. As we agreed in the long conversation from the zonal_mean
PR, we need to provide the sudo codes for the algorithm. So that we can better adapt it to the UXarray API and the python vectorization styles.
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.
My current concern is: after we have examined the entire grid to check if there's a partial coverage, what's the next step to deal with the missing faces? Do we need to rerun a similar check to identify which faces don't have neighbors? So basically two O(N) operations?
This is a good point that I overlooked. We should be keeping track of which indices are considered to border an empty area, which can be achieved by removing the np.any
call
edge_face_connectivity[:, 1] == INT_FILL_VALUE
Once we have this, we need to identify which nodes (indicated in black) will need to be removed from the new grid that is constructure from the Dual Grid.
The number of faces in the new Grid
should equal the original number of nodes (n_node
) minus the number of nodes that share one or more of the "invalid" edges shown 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.
edge_face_connectivity[:, 1] == INT_FILL_VALUE
This sounds good. If we need to check if there's a partial coverage, don't just return a boolean value, but instead directly return where is the partial coverage. And if the return is None
, then we know is all covered.
The number of faces in the new Grid should equal the original number of nodes (n_node) minus the number of nodes that share one or more of the "invalid" edges shown above.
It sounds good to me though I haven't thought deep into this yet. But I will keep you updated if anything other things comes into my head.
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.
Sounds perfect to me, @philipc2 @hongyuchen1030 thanks for the suggestions on this one! Let me know if you have anymore thoughts and I will get the changes mentioned here implemented!
And just some information from Computational Geometry, we normally assign the uncovered area a face index on the spherical surface as well. And when we said is neighboring with this face index, we know it is next by the uncovered area. Hope this point will help. |
This does make sense, however the meshes that we work with that have a partial spherical coverage (i.e. ocean meshes from climate models) don't assign an index to the uncovered area.
This is roughly what we are trying to achieve with the checks added by @aaronzedwick here. Since we don't have any explicit definitions of the geometry of the uncovered areas, the only way we are able to determine if a cell neighbors an empty region is by performing a search over the |
Oddly enough, even without the current checks for the data, the dual mesh construction does work with a partially covered data array. I am not sure exactly how the data array is constructed inside UXarray, but I was confused by this. |
Is it possible that it uses the |
@hongyuchen1030 It doesn't seem to, no, the values in the data array are all actual data points without any fill values, and the number of values matches the number of faces. |
Seconding this. |
…uxarray into zedwick/mesh-with-holes
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.
May we include a basic benchmark in the mpas_ocean
file for computing Grid.hole_edge_indices
Yeah, that's a good idea! |
Co-authored-by: Philip Chmielowiec <67855069+philipc2@users.noreply.github.com>
ASV BenchmarkingBenchmark Comparison ResultsBenchmarks that have improved:
Benchmarks that have stayed the same:
Benchmarks that have got worse:
|
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 implementation here looks great and the execution time is quick! Thanks for putting this together.
Closes #898
Overview
Allows for a grid to be checked for holes.
Expected Usage
PR Checklist
General
Testing
Documentation
_
) and have been added todocs/internal_api/index.rst
docs/user_api/index.rst