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

Fix algorithm in Manifold::normal_vector #5393

Merged
merged 4 commits into from Nov 7, 2017

Conversation

gassmoeller
Copy link
Member

I think the current algorithm to compute normal vectors in the Manifold class does not work, at least for some special cases (and this is the reason for the bug in geodynamics/aspect#1990).

Consider the following flat face (i.e. it is not a perspective drawing) of a 3D cell with vertices 0,1,2,3, and assume we want to compute the normal vector for the point at the center of the bottom line:

20171103_143913-1_preview

The current algorithm separates the face into a diamond close to the center, and triangles closest to the vertices. Our point in question here is closer to the center than to any vertex. The current algorithm then looks for the closest vertex (vertex 2 in this case), and based on this index selects two other vertices as the ones to form tangent lines to. But in the current version it will select vertices 0 and 1, which leads to the case that tangent lines t1 and t2 are linearly dependent. It then takes the cross product of t1 and t2, which leads to a zero normal vector, which leads to a division by zero when normalizing the normal vector.

I here propose a new algorithm that does not try to geometrically guess which vertices to use, but instead tries all available vertex combinations, and only ensures that the following conditions are met by the normal vector:

  • The two tangent lines are of approximately the same length (to avoid roundoff errors)
  • The two tangent lines are not linearly dependent
  • The normal vectors on a face can not differ by more than 90 degrees from the normal vector in the face center. Note that the algorithm only satisfies this condition if the curvature of the manifold does not 'curve' by more than 90 degrees between the face center and one vertex. This should be safe, as even in a HyperBall with 0 refinement 4 cells divide 360 degrees, i.e. the direction difference of the normal vector at a face center and the vertices is at most 45 degrees. I do not know if one could construct a case where this check fails.

The included test reconstructs exactly the case I have drawn above and crashes with a division by zero on current master, but produces the expected result with this patch.

@gassmoeller
Copy link
Member Author

I should add that the new algorithm uses the first combination of vertices that fulfills all three conditions, so it should not take much longer than the old algorithm.

@gassmoeller
Copy link
Member Author

It seems this creates some issues at other places, let me look into that first before going forward with this PR.

@gassmoeller
Copy link
Member Author

Ok, I figured it out, there were still issues with round-off and floating point comparisons. This should now work as intended.

@masterleinad
Copy link
Member

/run-tests

@bangerth
Copy link
Member

bangerth commented Nov 4, 2017

Test numerics/no_flux_09.debug fails -- can you take a look?

Copy link
Member

@bangerth bangerth left a comment

Choose a reason for hiding this comment

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

Ha, what a bizarre problem!

else
// if p is too close to vertex i try again with different i and j
if ((p - face->vertex(i)).norm_square() <
std::numeric_limits<double>::epsilon() * (p - face->vertex(j)).norm_square())
Copy link
Member

Choose a reason for hiding this comment

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

I think you should err on the safe side and make this 100 * eps or some such. In fact, since you're comparing squares, you may even want to use 10000 * eps.

if ((p-face->center()).norm_square() < min_distance)
// Look for a combination of tangent vectors that
// are of approximately equal length and not linearly dependent
for (unsigned int i=0,j=1 ; i<4 && j<4; ++j)
Copy link
Member

Choose a reason for hiding this comment

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

Can you use GeometryInfo<2>::vertices_per_cell here?


// if p is too close to vertex j try again with different j
if ((p - face->vertex(j)).norm_square() <
std::numeric_limits<double>::epsilon() * (p - face->vertex(i)).norm_square())
Copy link
Member

Choose a reason for hiding this comment

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

same here

normal = cross_product_3d(t1,t2);

// if t1 and t2 are (nearly) linearly dependent try again with different j / t2
if (normal.norm_square() < std::numeric_limits<double>::epsilon() *
Copy link
Member

Choose a reason for hiding this comment

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

and here

@@ -0,0 +1,64 @@
//---------------------------- manifold_id_01.cc ---------------------------
// Copyright (C) 2011 - 2015 by the mathLab team.
Copy link
Member

Choose a reason for hiding this comment

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

all not quite correct :-)

const Tensor<1,spacedim> reference_normal = cross_product_3d(reference_t1,reference_t2);

if (reference_normal * normal < 0.0)
normal *= -1;
Copy link
Member

Choose a reason for hiding this comment

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

Can you explain why you need this? Is it because you've lost track of whether vertices i and j are oriented in clockwise orientation with an angle of less than 180 degrees when seen from point p? That's when you get the sign flip. But I wonder whether you can detect that case without having to call get_tangent_vector twice as that might be an expensive operation.

continue;

t1 = get_tangent_vector(p, face->vertex(i));
t2 = get_tangent_vector(p, face->vertex(j));
Copy link
Member

Choose a reason for hiding this comment

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

get_tangent_vector may be an expensive operation. Do you think you could cache these vectors and only update them when you change i or j (and have decided that you actually need them)?

@gassmoeller gassmoeller force-pushed the fix_manifold_normal_vector branch 2 times, most recently from bcb4048 to f922ef2 Compare November 7, 2017 01:12
@gassmoeller
Copy link
Member Author

After a number of iterations I hope the new algorithm improves on the predecessors. In particular it should be nearly as fast, does not fail for special cases anymore, and preserves existing test results. Unfortunately, it does not preserve existing functionality completely. For points inside of faces of 3D triangulations that do not form a plane (i.e. they are twisted) this algorithm sometimes uses different vertices than the previous implementation to compute tangent vectors, and thus the resulting normal vectors are different. I added test numerics/no_flux_14 to document this behavior, it passes on this branch but fails on master.

@bangerth
Copy link
Member

bangerth commented Nov 7, 2017

Yes, excellent. Very nice and clean code, thank you!

@bangerth bangerth merged commit 5e9285a into dealii:master Nov 7, 2017
@gassmoeller gassmoeller deleted the fix_manifold_normal_vector branch November 7, 2017 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants