Feature: add neighbor subface helper#2216
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2216 +/- ##
==========================================
+ Coverage 78.27% 82.52% +4.24%
==========================================
Files 114 115 +1
Lines 19101 18561 -540
==========================================
+ Hits 14952 15318 +366
+ Misses 4149 3243 -906 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
spenke91
left a comment
There was a problem hiding this comment.
Thank you very much @dutkalex for another great contribution! :-) I am sorry it took us so long to reply...
I mostly have suggestions regarding variable names and code comments (which, I know, not everybody loves as much as I do 🤓 ). Aside from that, I only have a question regarding the hard-coded 4.
| t8_element_t *target = nullptr; | ||
| scheme->element_new (neighbor_tree_class, 1, &target); | ||
|
|
||
| int dummy; // Can't pass a nullptr to t8_forest_element_face_neighbor below (see #2214) | ||
| t8_forest_element_face_neighbor (forest, ltreeid, leaf, target, neighbor_tree_class, face, &dummy); |
There was a problem hiding this comment.
Maybe we can find a more expressive name than target? e.g.
| t8_element_t *target = nullptr; | |
| scheme->element_new (neighbor_tree_class, 1, &target); | |
| int dummy; // Can't pass a nullptr to t8_forest_element_face_neighbor below (see #2214) | |
| t8_forest_element_face_neighbor (forest, ltreeid, leaf, target, neighbor_tree_class, face, &dummy); | |
| // Determine the (virtual) face neighbor | |
| // Note: For this function to work properly, this face neighbor has to be a direct child of neighbor_leaf | |
| t8_element_t *virtual_face_neighbor= nullptr; | |
| scheme->element_new (neighbor_tree_class, 1, &virtual_face_neighbor); | |
| int dummy; // Can't pass a nullptr to t8_forest_element_face_neighbor below (see #2214) | |
| t8_forest_element_face_neighbor (forest, ltreeid, leaf, virtual_face_neighbor, neighbor_tree_class, face, &dummy); |
Or maybe we can even find something better? sub_neighbor?
There was a problem hiding this comment.
"There are only two hard things in computer science: cache invalidation and naming things." 😉
I see your point, and I guess since I could not come up with a better name at the time and went for the rather undescriptive but concise option. I feel like virtual_face_neighbor is more descriptive than sub_neighbor, but it looses the "it's what we're looking for" idea. It seems hard to find a (reasonably sized) name which encodes unambiguously all this information. Maybe this means a small comment is necessary to clarify exactly what this is... What do you think?
There was a problem hiding this comment.
Love the quote!😄
I think a comment is a good idea anyway (as I said, I will basically never say "no" to code comments 🙈 )
We all do not love long variable names, but what's the harm of using maybe target_virtual_neighbor or even target_virtual_face_neighbor -- after all, this isn't some old Fortran code with 72 characters per line 😉
With a comment, however, I also don't mind sticking to target if that's what you prefer.
|
|
||
| scheme->element_get_children_at_face (neighbor_tree_class, neighbor_leaf, neighbor_face, children.begin (), | ||
| num_children, nullptr); | ||
|
|
There was a problem hiding this comment.
| Iterate over the neighbor's children and compare with virtual face neighbor to find the sub-face ID. |
There was a problem hiding this comment.
I guess this is meant to be a comment. However, after re-reading with fresh eyes this piece of code, I think this is just a std::find_if and that refactoring the code to use this is the proper way to make the intent explicit here
There was a problem hiding this comment.
Ah, of course this should be a comment not C++ code 😬
There was a problem hiding this comment.
Good idea, std::find_if would make it nicer and clearer 👍
| result = i_child; | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
| // Free memory and return sub-face ID. |
There was a problem hiding this comment.
I think the "free memory" part is quite self evident here, but the "return sub-face ID" part is indeed not clear with the current state of the code. However maybe this just means that result is a bad name ? 😅
There was a problem hiding this comment.
Maybe target_face_id or something like that?
| int dummy; // Can't pass a nullptr to t8_forest_element_face_neighbor below (see #2214) | ||
| t8_forest_element_face_neighbor (forest, ltreeid, leaf, target, neighbor_tree_class, face, &dummy); | ||
|
|
||
| int const num_children = scheme->element_get_num_face_children (neighbor_tree_class, neighbor_leaf, neighbor_face); |
There was a problem hiding this comment.
| int const num_children = scheme->element_get_num_face_children (neighbor_tree_class, neighbor_leaf, neighbor_face); | |
| // Get the number of the neighbor's children touching the neighbor_face or, equivalenlty, the face's number of children. | |
| int const num_neighbor_face_children = scheme->element_get_num_face_children (neighbor_tree_class, neighbor_leaf, neighbor_face); |
There was a problem hiding this comment.
I can see why you'd want the variable name to be a little bit more explicit, but isn't the comment just paraphrasing what element_get_num_face_children's documentation already says?
I tend to think that comments should be used to explain why something is done a certain way, but not what the code does (that should be clear by reading the code, and if you think it's not the case maybe I should change the code then).
There was a problem hiding this comment.
I tend to think that comments should be used to explain why something is done a certain way, but not what the code does (that should be clear by reading the code...)
I guess I have a fundamentally different philosophy here, but that is definitely not a problem at all! :-) Because it is in fact just my personal preference and not part of any t8code coding guidelines. So if you feel like some of the comments I suggested are too much, don't worry and feel free not to add them 👍
In case you are interested, my philosphy is more that reading the doxygen header of a function along with the inline code comments should already give an idea about what the code does -- and how it does it, meaning the structure of the algorithm. For example, the line code comments I suggested alone would read
// Determine the (virtual) face neighbor
// Note: For this function to work properly, this face neighbor has to be a direct child of neighbor_leaf
// Get number of neighbor's children
// Allocate memory
// Iterate over the neighbor's subfaces and compare with target to find the sub-face ID.
// Free memory and return sub-face ID.
which, I'd argue, already gives you a rough but useful outline of the function without reading one single line of code.
The motivation behind this is that I am convinced even a proficient software developer can still read English faster than C++ 😉
(And as for the added work of writing the comments, I am certain the time is neglible compared to the time saved for future readers -- after all, while writing the code we know what we're doing anyway!)
So regarding your quote here
I get it, I often say that getting the compiler to understand the code is the easy part and that the real challenge it to write code that humans can comprehend 😉
this would be my answer, that is, paraphrasing the code in a language made for humans to understand, not compilers 😉
But as I said, I know not everybody would agree, which is fine 👍
| std::array<t8_element_t *, 4> children; // assumes a 2:1 balanced forest | ||
| scheme->element_new (neighbor_tree_class, 4, children.begin ()); | ||
|
|
||
| scheme->element_get_children_at_face (neighbor_tree_class, neighbor_leaf, neighbor_face, children.begin (), | ||
| num_children, nullptr); |
There was a problem hiding this comment.
It may well be that I missed something here, but shouldn't we use num_face_children rather than 4?
| std::array<t8_element_t *, 4> children; // assumes a 2:1 balanced forest | |
| scheme->element_new (neighbor_tree_class, 4, children.begin ()); | |
| scheme->element_get_children_at_face (neighbor_tree_class, neighbor_leaf, neighbor_face, children.begin (), | |
| num_children, nullptr); | |
| // Create an array containing the neighbor's children at neighbor_face. | |
| std::array<t8_element_t *, num_neighbor_face_children> neighbor_children_at_face; | |
| scheme->element_new (neighbor_tree_class, num_neighbor_face_children, neighbor_children_at_face.begin ()); | |
| scheme->element_get_children_at_face (neighbor_tree_class, neighbor_leaf, neighbor_face, neighbor_children_at_face.begin (), num_neighbor_face_children, nullptr); |
There was a problem hiding this comment.
There are two reasons for why we can't use num_face_children/num_neighbor_face_children here:
- because we need an integral constant expression here (i.e. a
constexpr int-like thing) - because what we need here is the upper-bound of the number of face neighbors (this happens to be 4 for a 2:1 balanced interface) so that this piece of code works for any element type
Maybe there is a properly named global constant which could be used instead of this magic number?
There was a problem hiding this comment.
Whoops, the integral constant expression point is in fact quite obvious here, sorry for missing that.
How about defining a new constant to t8_eclass.h
/** The maximal number of children a face may have.*/
#define T8_ECLASS_MAX_FACE_CHILDREN 4
or
/** The maximal number of children a 2D element may have.*/
#define T8_ECLASS_MAX_CHILDREN_2D 4
and using it here? Then it should also be clear where the number 4 comes from.
| for (int i_child = 0; i_child < num_children; ++i_child) { | ||
| if (scheme->element_compare (neighbor_tree_class, target, children[i_child]) == 0) { |
There was a problem hiding this comment.
| for (int i_child = 0; i_child < num_children; ++i_child) { | |
| if (scheme->element_compare (neighbor_tree_class, target, children[i_child]) == 0) { | |
| for (int i_child = 0; i_child < num_neighbor_face_children; ++i_child) { | |
| if (scheme->element_compare (neighbor_tree_class, virtual_face_neighbor, neighbor_children_at_face[i_child]) == 0) { |
There was a problem hiding this comment.
I'll make the adequate changes here when the previous comments are settled.
Co-authored-by: spenke91 <thomas.spenke@dlr.de>
Add assertion to ensure the face was found before destruction.
No worries @spenke91. Thank you for taking the time, I know you guys have a lot on your plate.
I get it, I often say that getting the compiler to understand the code is the easy part and that the real challenge it to write code that humans can comprehend 😉. I have either committed your suggestions directly, or answered your comments/questions to get your opinion. Let me know what you think and I'll revise the code accordingly. |
spenke91
left a comment
There was a problem hiding this comment.
Thanks a lot for your comments and replies!
| t8_element_t *target = nullptr; | ||
| scheme->element_new (neighbor_tree_class, 1, &target); | ||
|
|
||
| int dummy; // Can't pass a nullptr to t8_forest_element_face_neighbor below (see #2214) | ||
| t8_forest_element_face_neighbor (forest, ltreeid, leaf, target, neighbor_tree_class, face, &dummy); |
There was a problem hiding this comment.
Love the quote!😄
I think a comment is a good idea anyway (as I said, I will basically never say "no" to code comments 🙈 )
We all do not love long variable names, but what's the harm of using maybe target_virtual_neighbor or even target_virtual_face_neighbor -- after all, this isn't some old Fortran code with 72 characters per line 😉
With a comment, however, I also don't mind sticking to target if that's what you prefer.
|
|
||
| scheme->element_get_children_at_face (neighbor_tree_class, neighbor_leaf, neighbor_face, children.begin (), | ||
| num_children, nullptr); | ||
|
|
There was a problem hiding this comment.
Ah, of course this should be a comment not C++ code 😬
|
|
||
| scheme->element_get_children_at_face (neighbor_tree_class, neighbor_leaf, neighbor_face, children.begin (), | ||
| num_children, nullptr); | ||
|
|
There was a problem hiding this comment.
Good idea, std::find_if would make it nicer and clearer 👍
| int dummy; // Can't pass a nullptr to t8_forest_element_face_neighbor below (see #2214) | ||
| t8_forest_element_face_neighbor (forest, ltreeid, leaf, target, neighbor_tree_class, face, &dummy); | ||
|
|
||
| int const num_children = scheme->element_get_num_face_children (neighbor_tree_class, neighbor_leaf, neighbor_face); |
There was a problem hiding this comment.
I tend to think that comments should be used to explain why something is done a certain way, but not what the code does (that should be clear by reading the code...)
I guess I have a fundamentally different philosophy here, but that is definitely not a problem at all! :-) Because it is in fact just my personal preference and not part of any t8code coding guidelines. So if you feel like some of the comments I suggested are too much, don't worry and feel free not to add them 👍
In case you are interested, my philosphy is more that reading the doxygen header of a function along with the inline code comments should already give an idea about what the code does -- and how it does it, meaning the structure of the algorithm. For example, the line code comments I suggested alone would read
// Determine the (virtual) face neighbor
// Note: For this function to work properly, this face neighbor has to be a direct child of neighbor_leaf
// Get number of neighbor's children
// Allocate memory
// Iterate over the neighbor's subfaces and compare with target to find the sub-face ID.
// Free memory and return sub-face ID.
which, I'd argue, already gives you a rough but useful outline of the function without reading one single line of code.
The motivation behind this is that I am convinced even a proficient software developer can still read English faster than C++ 😉
(And as for the added work of writing the comments, I am certain the time is neglible compared to the time saved for future readers -- after all, while writing the code we know what we're doing anyway!)
So regarding your quote here
I get it, I often say that getting the compiler to understand the code is the easy part and that the real challenge it to write code that humans can comprehend 😉
this would be my answer, that is, paraphrasing the code in a language made for humans to understand, not compilers 😉
But as I said, I know not everybody would agree, which is fine 👍
| result = i_child; | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Maybe target_face_id or something like that?
| std::array<t8_element_t *, 4> children; // assumes a 2:1 balanced forest | ||
| scheme->element_new (neighbor_tree_class, 4, children.begin ()); | ||
|
|
||
| scheme->element_get_children_at_face (neighbor_tree_class, neighbor_leaf, neighbor_face, children.begin (), | ||
| num_children, nullptr); |
There was a problem hiding this comment.
Whoops, the integral constant expression point is in fact quite obvious here, sorry for missing that.
How about defining a new constant to t8_eclass.h
/** The maximal number of children a face may have.*/
#define T8_ECLASS_MAX_FACE_CHILDREN 4
or
/** The maximal number of children a 2D element may have.*/
#define T8_ECLASS_MAX_CHILDREN_2D 4
and using it here? Then it should also be clear where the number 4 comes from.
Describe your changes here:
Implements #2215
All these boxes must be checked by the AUTHOR before requesting review:
Documentation:,Bugfix:,Feature:,Improvement:orOther:.All these boxes must be checked by the REVIEWERS before merging the pull request:
As a reviewer please read through all the code lines and make sure that the code is fully understood, bug free, well-documented and well-structured.
General
Tests
If the Pull request introduces code that is not covered by the github action (for example coupling with a new library):
Scripts and Wiki
scripts/internal/find_all_source_files.shto check the indentation of these files.License
doc/(or already has one).