Check if a point is inside an arbitrary hexahedron - geometry

I am working on a 3D finite element code, where i face the following problem:
If I take an arbitrary point (say x), how do I figure out, which element it is in?
This can be simplified to: How do I check if an arbitrary point (x) lies inside or outside of an (hexahedral) element?
What I already found:
Limited to cubes: How to determine a point is inside or outside a cube?
Limited to rectangular shapes: https://math.stackexchange.com/questions/1472049/check-if-a-point-is-inside-a-rectangular-shaped-area-3d
Contrary to the two approaches above, my problem does not assume right angles nor parallel faces.
Problem sketch:
Notation: (again: though the sketch shows a regular shape, our hexahedron is assumed to be of general shape)
8-node hexahedron topology, nodes: 0,..,7
axis: r,s,t
t
|
4--------|-------------7
/| | /|
/ | | / |
/ | | / |
/ | | / |
/ | | / |
/ | | / |
5----------------------6 |
| | | | |
| | o------|---------s
| | / | |
| 0------/--------|------3
| / / | /
| / / | /
| / / | /
| / / | /
| / r | /
|/ |/
1----------------------2
Data that we have available:
coordinates of the nodes (vectors P0 to P7)
coordinates of the point we want to check (lets say Px)
Additionaly we assume the nodes are ordered as sketched above.
My approach/solution so far:
calculate the surface (outward) normal vectors
Use cross products, eg. for the r_pos_normal_vec (pointing out of the plane)
r_pos_normvec = (P2-P1) x (P5-P1)
and for the r_neg_normal_vec
r_neg_normvec = (P4-P0) x (P3-P0)
similarly for the s and t directions
check two opposite corner nodes (I chose node0 and node 6)
For node0
calculate vector from P0 to Px:
P0x = Px - P0
calculate inner prodcut of P0x and surfaces adjacent to node 0
<P0x, r_neg_normal_vec>
<P0x, s_neg_normal_vec>
<P0x, t_neg_normal_vec>
For node1
same scheme as for node 0, whereas P1 instead of P0 and the positive counterparts of the normal vectors are used
Iff all 6 (3 from node0 and 3 from node1) inner products result in negative value -> the point is inside the hexahedron.
Question:
I implemented the functionality described above in my code and ran some tests.
It seems to work, from the math side I am quite confident.
Please discuss my approach, I am happy for any hints/clues/recommendations/bug fixes ...
Is there some way to make this faster?
Alternative solutions?
Note:
To speed up the algorithm a box check can be done first:
Construct a rectangular box around the hexahedron:
Get the min and max values of the node coordinates in each direction.
If the point to check (x) is outside this (larger) box, it cannot be inside the hexahedron.

For any convex polyhedron, establish the implicit equations of the faces (f.i. plane by three points), of the form ax+by+cz+d=0.
When you plug the coordinates of a known point inside the volume (such as the center) in the expression ax+by+cz+d, you will get a set of signs. An arbitrary point is inside if it yields the same signs.
Update:
For maximum performance, you can consider also using an axis-aligned bounding box for quick rejection. This only makes sense if many of the points are outside. Make sur to use a shortcut evaluation so that early rejection can happen.
Note that a rejection test such as X<Xmin is nothing but the above sign test against the plane of equation X-Xmin=0.

I personnally prefer your method, however there also is a way to approach the problem if the hexahedral restricted to parallelepiped. So you can transfer the coordinate of P in the frame $(0; e_1; e_2; e_3)$ to $(P_0, P_0P_1,P_0P_3,P_0P_4)$. We call it $(a,b,c)$, then the point is in that parallelepiped if $a,b,c > 0 a \in [0,1], a+b+c \in [0,1]$.

Because you mentioned that you want to be able to handle arbitrary hexahedrons, I think that your process might be improved if you adjust your s, r, and t normals to account for having slightly warped faces. I would do this by making the following change to r normals (and similar for s and t):
r_pos_normvec = (P6-P1) x (P5-P2)
r_neg_normvec = (P7-P0) x (P4-P3)
This would be important for a case where you shifted node 6 towards node 7 (say 0.9xP6) and had a point at 0.95xP6. Without the warping correction, I believe you would erroneously determine the point as inside the hexahedron.

Here is a python example :
def point_is_in_hexa(point,centers,normals):
vect=[]
prod=[]
for i in range(6):
vect.append(point-centers[i])
vect= np.array(vect)
for i in range(6):
prod.append(np.dot(vect[i],normals[i]))
prod=np.array(prod)
if all(prod <= 0):
is_in_hexa = 1
else:
is_in_hexa = -1
return is_in_hexa
https://github.com/fgomez03/hexacheck

Related

understanding the torch.nn.functional.grid_sample op by concrete example

I am debugging a neural network which has a torch.nn.functional.grid.sample operator inside. Using the Pycharm IDE, I can watch the values during debugging. My grid is a 1*15*2 tensor, here are the values in the first batch.
My input is a 1*128*16*16 tensor, here are the values in the first channel of the first batch:.
My output is 1*128*1*15 tensor, here are the values in the first channel of the first batch.
align_corners = False, mode = 'bilinear', padding_mode = 'zero'.
For gird coordinates (-1,-1), I can understand that the value(-4.74179) is sampled from 4 values on the top-left corner with 3 of them being the padded '0's and 1 of them being the value '-18.96716'.(-18.96716/4 = -4.74179).
But for other grid coordinates, I am confused. Taking the value '84.65594' for example, it's corresponding grid coordinate is (-0.45302, 0.53659). I firstly convert them from (-1,1) to (0,15) by adding 1 and then dividing by 2 and then multiplying 15(see official implementation). The converted coordinate is then (4.10235, 11.524425), Upon which I see the four values that should be sampled from are :
(x)44.20010---0.10235---------(y)26.68777
| | |
| | |
0.524425---(a,b)--------------------
| | |
| | |
(w)102.18765---------------------(z)30.03996
here are my calculation by hand step, Let:
a = 0.10235
b = 0.524425
x = 44.20010
y = 26.68777
z = 30.03996
w = 102.18765
The interpolated value should then be:
output = a*b*z + (1 - a)*(1 - b)*x + (1 - a)*b*w + (1-b)*a*y
= 0.10235*0.524425*30.03996 + (1-0.10235)*(1-0.524425)*44.20010 + (1-
0.10235)*0.524425*102.18765 + (1-0.524425)*0.10235*26.68777
= 69.8852865171
which isn't 84.65594, I cant't figure out how the value '84.65594' in the output is calculated, please help!
I answer my own question, it turns out that the inconsistency is due to the 'align_corners' flag. My way of calculation is actually under the case when 'align_corners' is true while in the program, this flag is set to be false. For how to calculate sample coordinates, please see this

Calculating a custom probability distribution in python (numerically)

I have a custom (discrete) probability distribution defined somewhat in the form: f(x)/(sum(f(x')) for x' in a given discrete set X). Also, 0<=x<=1.
So I have been trying to implement it in python 3.8.2, and the problem is that the numerator and denominator both come out to be really small and python's floating point representation just takes them as 0.0.
After calculating these probabilities, I need to sample a random element from an array, whose each index may be selected with the corresponding probability in the distribution. So if my distribution is [p1,p2,p3,p4], and my array is [a1,a2,a3,a4], then probability of selecting a2 is p2 and so on.
So how can I implement this in an elegant and efficient way?
Is there any way I could use the np.random.beta() in this case? Since the difference between the beta distribution and my actual distribution is only that the normalization constant differs and the domain is restricted to a few points.
Note: The Probability Mass function defined above is actually in the form given by the Bayes theorem and f(x)=x^s*(1-x)^f, where s and f are fixed numbers for a given iteration. So the exact problem is that, when s or f become really large, this thing goes to 0.
You could well compute things by working with logs. The point is that while both the numerator and denominator might underflow to 0, their logs won't unless your numbers are really astonishingly small.
You say
f(x) = x^s*(1-x)^t
so
logf (x) = s*log(x) + t*log(1-x)
and you want to compute, say
p = f(x) / Sum{ y in X | f(y)}
so
p = exp( logf(x) - log sum { y in X | f(y)}
= exp( logf(x) - log sum { y in X | exp( logf( y))}
The only difficulty is in computing the second term, but this is a common problem, for example here
On the other hand computing logsumexp is easy enough to to by hand.
We want
S = log( sum{ i | exp(l[i])})
if L is the maximum of the l[i] then
S = log( exp(L)*sum{ i | exp(l[i]-L)})
= L + log( sum{ i | exp( l[i]-L)})
The last sum can be computed as written, because each term is now between 0 and 1 so there is no danger of overflow, and one of the terms (the one for which l[i]==L) is 1, and so if other terms underflow, that is harmless.
This may however lose a little accuracy. A refinement would be to recognize the set A of indices where
l[i]>=L-eps (eps a user set parameter, eg 1)
And then compute
N = Sum{ i in A | exp(l[i]-L)}
B = log1p( Sum{ i not in A | exp(l[i]-L)}/N)
S = L + log( N) + B

Greedy Algorithms and Time Complexity #2

We have a bomb that is ticking and may explode. This bomb has n switches, that can be moved up or down. Certain combinations of these switches trigger the bomb, but only one combination disables it.
Our task is to move the switches from the current position to a position that disables the bomb, without exploding it in the meantime. The switches are big and awkward, so we can move only one switch at a time.
We have, lets say, n = 4 switches currently in position ^vvv. We need to get them to the position ^v^^. Forbidden positions are vvv^, ^vv^, ^v^v, and ^^^v.
a.) I had to draw this by hand and find the shortest sequence of switch movements that solves the task - result I got was 4 ...and I found two such sequences, if i am right...
b.) this is where it gets a hard - write a code that answers the above question/questions (the shortest sequence and how many). The code should be generalized so that it would work with another number of switches and other starting, targeted, and forbidden combinations; targeted and forbidden combinations may be multiple or even fewer. Only thing we know for sure is that the switches have only two positions. It should also provide the possibility that the desired condition is unavailable; in this case, the program should of course tell.
c.) Next questions is the time complexity of the code this but for now I think I will just stop here...
I used '0' and '1' instead, because it is easier for me to imagine this.
So my approach towards this was something of a greedy algorithm (I think) - starting position, you think of all the possible (allowed) positions, you ignore the forbidden ones, then pick the one that the sequence of positions has the fewest difference from our targeting sequence.
The key part of the code I am yet to write and that's the part I need help with.
all_combinations = ['0000', '0001', '0010', '0011', '0100', '0101', '0110', '0111', '1000', '1001', '1010', '1011' , '1100', '1101', '1110', '1111']
def distance (position1, position2):
distance = 0
for i in range (len (position1)):
if position1 [i]! = position2 [i]:
distance + = 1
return distance
def allowed_positions (current, all_combinations):
allowed = set ()
for combination and all combinations:
if the distance (current, combination) == 1:
allowed.add (combination)
return allowed
def best_name (current, all_combinations, target):
list = []
for option and permitted_mood (current, all_combinations):
list.append (distance (option, target), option)
The task at hand is finding a shortest path in a graph. For this there is one typical approach and that is a breadth-first search algorithm (https://en.wikipedia.org/wiki/Breadth-first_search).
There is no real need to go into the details of how this is done because it can be read elsewhere in more detail and far better explained than I can do this in a StackOverflow answer.
But what might need to be explained is how the switch-combinations you have at hand are represented by a graph.
Imagine you have just two switches. Then you have exactly this graph:
^^---^v
| |
| |
v^---vv
If your starting position is ^^ and your ending (defusing) position is vv while the position ^v is an exploding position, then your graph is reduced to this:
^^ ^v
|
|
v^---vv
In this small example the shortest path is obvious and simple.
The graph at hand is easily sketched out in 2D, each dimension (x and y) representing one of the switches. If you have more switches, then you just add one dimension for each switch. For three switches this would look like this:
^^^--------^^v
|\ |\
| \ | \
| \ | \
| \ | \
| ^v^--- | --^vv
| | | |
| | | |
v^^--------v^v |
\ | \ |
\ | \ |
\ | \ |
\| \|
vv^--------vvv
If the positions ^^v, v^^, and vv^ are forbidden, then this graph is reduced to this:
^^^ ^^v
\
\
\
\
^v^--------^vv
|
|
v^^ v^v |
\ |
\ |
\ |
\|
vv^ vvv
Which already shows the clear way and the breadth-first search will easily find it. It gets interesting only for many dimensions/switches, though.
Drawing this for more dimensions/switches gets confusing of course (look up tesseracts for 4D). But it isn't necessary to have a visual image. Once you have written the algorithm for creating the graph in 2D and 3D in a general way it easily scales to n dimensions/switches without adding any complexity.
start = 8
target = 11
forbidden = {1: -1 , 9: -1, 10: -1, 14: -1}
dimensions = 4
def distance(start, target, forbidden, dimensions):
stack1 = []
stack1.append(start)
forbidden[start] = -1
while(len(stack1) > 0):
top = stack1.pop()
for i in range(dimensions):
testVal = top ^ (1 << i)
if testVal is target:
forbidden[testVal] = top
result = [testVal]
while testVal is not start:
testVal = forbidden[testVal]
result.insert(0, testVal)
return result
if testVal not in forbidden:
forbidden[testVal] = top
stack1.append(testVal)
return [-1]
print(distance(start, target, forbidden, dimensions))
Here is my code for your example in your question. Instead of using bits, I went ahead and used the base 10 number to represent the codes. Forbidden codes are mapped to a hashmap which is used later to trace the path upwards after the target is found. I use a stack to keep track of which code to try. Each time the while loop passes, the last code added is popped and it's unvisited neighbors are added to the stack. Importantly, to prevent cycles, codes on the stack or seen before are added to the list of forbidden nodes. When the target code is found for the first time, an early return is called and the path is traced through the hashmap.
This solution uses breadth first search and returns the first time the target is found. That means it does not guarantee the shortest path from start to target, but it does guarantee a working path if it's available. Since all possible codes are possibly traversed and there are 2^dimensions number of nodes, the time complexity of this algorithm is also O(2^n)

Is there a graph-drawing tool that will allow me to constrain x, and automatically lay out y?

I am looking for a tool similar to graphviz that can render graphs, but that will allow me to constrain just the x coordinate of each node. Then, the tool will automatically choose y coordinates to make the graph look neat.
Basically, I want to make a timeline.
Language / platform / rendering medium are not very important.
If you want a neat-looking graph a force-directed algorithm is going to be your best bet. One of the best ones is SFDP (developed by AT&T, included in graphviz) though I can't seem to find pseudocode or an easy implementation. I don't think there are any algorithms this specialized. Thankfully, it's easy to code your own. I'll present some pseudocode mostly lifted form Wikipedia, but with suitably one-dimensional modifications. I'll assume you have n vertices and the vector of x-positions is x, subscripted by x.i.
set all vertex velocities to (0,0)
set all vertex positions to (x.i, random)
while (KE > epsilon)
KE = 0
for each vertex v
force = (0,0)
for each vertex u != v
force = force + (0, coulomb(u, v).y)
if u is incident to v
force = force + (0, hooke(u, v).y)
v.velocity = (v.velocity + timestep * force) * damping
v.position = v.position + timestep * v.velocity
KE = KE + |v.velocity| ^ 2
here the .y denotes getting the y-component of the force. This ensures that the x-components of the positions of the vertices never change from what you set them to be. The epsilon parameter is to be set by you, and should be something small compared to what you expect KE (the kinetic energy) to be. Also, |v| denotes the magnitude of the vector v (all computations are of 2-vectors in the above, except the KE). Note I set the mass of all the nodes to be 1, but you can change that if you want.
The Hooke and Coulomb functions calculate the respective forces between nodes; the first is linear in distance between vertices, the second is quadratic, so there is a guaranteed equilibrium. These functions look something like
def hooke(u, v)
return -k * |u.position - v.position|
def coulomb(u, v)
return C * |u.position - v.position|
where again most computations are in vector form. C and k have real values but experiment to get the graph you want. This isn't usually necessary because the scaling factors will, in two dimensions, pretty much expand or contract the whole graph, but here the x-distances are set so to get a good-looking graph you will have to change the values a bit.

Simple Trigonometry?

EDIT - Thanks for all the answers everyone. I think I accidentally led you slightly wrong as the square in the picture below should be a rectangle (I see most of you are referencing squares which seems like it would make my life a lot easier). Also, the x/y lines could go in any direction, so the red dot won't always be at the top y boundary. I was originally going for a y = mx + b solution, but then I got stuck trying to figure out how I know whether to plug in the x or the y (one of them has to be known, obviously).
I have a very simple question (I think) that I'm currently struggling with for some reason. I'm trying to have a type of minimap in my game which shows symbols around the perimeter of the view, pointing towards objectives off-screen.
Anyway, I'm trying to find the value of the red point (while the black borders and everything in green is known):
It seems like simple trigonometry, but for some reason I can't wrap my head around it. I just need to find the "new" x value from the green point to the red point, then I can utilize basic math to get the red point, but how I go about finding that new x is puzzling me.
Thanks in advance!
scale = max(abs(x), abs(y))
x = x / scale
y = y / scale
This is the simple case, for a square from (-1, -1) to (1, 1). If you want a different sized square, multiply the coordinates by sidelen / 2.
If you want a rectangle instead of a square, use the following formula. (This is another solution to the arbitrarily-sized square version)
scale = max(abs(x) / (width / 2), abs(y) / (height / 2))
x = x / scale
y = y / scale
Let's call the length of one side of the square l. The slope of the line is -y/x. That means, if you move along the line and rise a distance y toward the top of the square, then you'll move a distance x to the left. But since the green point is at the center of the square, you can rise only l/2. You can express this as a ratio:
-y -l/2
——— = ———
x d
Where d is the distance you'll move to the left. Solving for d, we have
d = xl/2y
So if the green dot is at (0, 0), the red dot is at (-l/2, xl/2y).
All you need is the angle and the width of the square w.
If the green dot is at (0,0), then the angle is a = atan(y/x), the y-coordinate of the dot is w/2, and therefore the x-coordinate of the dot is tan(1/a) * (w/2). Note that tan(1/a) == pi/2 - tan(a), or in other words the angle you really want to plug into tan is the one outside the box.
Edit: yes, this can be done without trig, too. All you need is to interpolate the x-coordinate of the dot on the line. So you know the y-coordinate is w/2, then the x-coordinate is (w/2) * x/y. But, be careful which quadrant of the square you're working with. That formula is only valid for -y<x<y, otherwise you want to reverse x and y.

Resources