Related
The line is not a line segment, but should extend to infinity. I tried using this guys answer but I just got nan's because I was dividing by 0. Preferably I want something quick and easy with dot and cross products and such.
x = np.array([-1,-1,-1])
y = np.array([4,4,4])
a = np.array([2,2,2])
epsilon = 0.0001
#algorithm
print((x[2] - x[0]) / (x[1] - x[0]))
print((y[2] - y[0]) / (y[1] - y[0]))
print((a[2] - a[0]) / (a[1] - a[0]))
Ouput:
nan
nan
nan
Here is one possibility, depending on the application the code can be simplified, but I preferred to give a solution with a clear interpretation rather than a clear code.
def colinear(p1, p2, p3, rtol=1e-5):
'''
Test colinearity making sure that sin(angle) < rtol
'''
# twice the area of the triangle (p1, p2, p3)
A = np.linalg.norm(np.cross(p2 - p1, p3 - p1))
# length of two sides meeting the vertice p1
a = np.linalg.norm(p2 - p1)
b = np.linalg.norm(p3 - p1)
c = np.linalg.norm(p2 - p3)
# pick the two largest sides
_, a, b = sorted([a,b,c])
# the angle at the vertice where those points meet
# will be A / (a * b)
return A < rtol * a * b
x = np.array([-1,-1,-1])
y = np.array([4,4,4])
a = np.array([2,2,2])
assert colinear(x, y, a)
For a straight line, Slope k is known, one point(x1,y1) is know, how to get the other point (x2,y2) using python?
I know the way to do calculation, but have no idea to code using python.
(y2-y1)/(x2-x1)=k
sqrt((x2-x1)^2+(y2-y1)^2)= length
if two unknown equations, both two variables with power 1, linalg-solve should work, but now the power of 2rd function is 2, how to deal with that?
I tried to simplify as following, but seems I cannot apply linalg-solve
kx2-y2-kx1+y1 = 0
(y2-y1)^2 + (x2-x1)^2 = length^2
Supplement:
thanks to all of your answer...Posh_Pumpkin's code is exactly what I want, previously I thought I need to apply linalg-solve which I usually use.
here is a test code based on his answer:suppose P1 = (1,1) p2=(x,y), p1p2= sqrt(2), k=1,then p2 must be = (2,2)
import numpy as np
import math
k = 1
d = math.sqrt(2)
p1 = (1,1)
r_sq = d**2 / (1 + k**2)
r = math.sqrt(r_sq)
p2 = (p1[0] + r, p1[1] + k*r)
print(p2)
Why use a complicated solution at all? I would approach it under the assumption that P1 = (0, 0).
Assuming P1 is at the origin, and knowing that the slope = k, you know that P2 = (r, k*r) for a constant r. The r can be computed by calculating the distance P1P2. Since you said you already know the distance, we can then simply do:
r^2 + (k*r)^2 = d^2
To find r. Once you find r, you can get the coordinates of P2 when P1 = (0, 0). So to find the actual coordinates, you'd just do P2 = P1 + P2. Check below:
import math
# Obviously assume k and d are known constants, and P1 is a known point
k = # given k
d = # given d
p1 = # (given x coord, given y coord)
r_sq = d**2 / (1 + k**2)
r = math.sqrt(r_sq)
p2 = (p1[0] + r, p1[1] + k*r)
print(p2)
If you have to use numpy, you should show us your code and what exactly is troubling you. What is preventing you from using the module, whether it's an error or an unexpected behavior?
You can solve this with basic trig. Here is the general derivation.
let p1 = (x1,y1) & p2 = (x2 = x1+d, y2 = y1+h),
let L be the distance between p1 & p2
* note for p1 & p2 such that x1 != x2 && y1 != y2, a triangle can be formed Ldh such that tan(theta) = h/d
h/d is the slope of the line (m) connecting points p1 & p2, so tan(theta) = m
=> theta = atan(m), from the law of sines ( sin(a)/A = sin(b)/B )
=> sin(90)/L = sin(atan(theta))/y2
=> y2 = L*sin( atan(theta) )
now get x from the point slope form of a line y= y1+m(x-x1) = (y-y1)/m +x1
so x2 = (y2-y1)/m + x1
Here is this expressed in python:
from math import sin, atan
from random import randint
# This is the formula for (x2,y2) = p2
x = lambda y2, m, x1, y1: (y2 - y1)/float(m) + x1
y = lambda l, m, y1: l*sin( atan(m) ) + y1
# p2 constraints ( x2 > x1, y2 > y1 or x2 > x1, y2 < y1 )
p1 = [randint(1,1000),randint(1,1000)]
p2 = [randint(p1[0],1001),randint(0,p1[1])]
# calculate distance between p1 & p2 (L), also calculate slope (m)
slope = lambda x1,y1,x2,y2: (y2-y1)/float(x2-x1)
dist = lambda x1,y1,x2,y2: ( (y2-y1)**2 + (x2-x1)**2 )**(0.5)
L = dist(p1[0],p1[1],p2[0],p2[1])
m = slope(p1[0],p1[1],p2[0],p2[1])
# now see if our functions for x & y yield p2
y2 = y(L,m,p1[1])
p_derived = [ x(y2,m,p1[0],p1[1]),y2 ]
print "p1: ",p1 , "p2 actual: ",p2, "p2 derived: ",p_derived
So here I generate two random points p1 and p2, and verify that p2 can be calculated from the slope, distance, and p1 by comparing my derived result to the actual result.
I'm using Python+Numpy (can maybe also use Scipy) and have three 2D points
(P1, P2, P3);
I am trying to get the distance from P3 perpendicular to a line drawn between P1 and P2. Let P1=(x1,y1), P2=(x2,y2) and P3=(x3,y3)
In vector notation this would be pretty easy, but I'm fairly new to python/numpy and can't get anythng that works (or even close).
Any tips appreciated, thanks!
Try using the norm function from numpy.linalg
d = norm(np.cross(p2-p1, p1-p3))/norm(p2-p1)
np.cross returns the z-coordinate of the cross product only for 2D vectors. So the first norm in the accepted answer is not needed, and is actually dangerous if p3 is an array of vectors rather than a single vector. Best just to use
d=np.cross(p2-p1,p3-p1)/norm(p2-p1)
which for an array of points p3 will give you an array of distances from the line.
For the above-mentioned answers to work, the points need to be numpy arrays, here's a working example:
import numpy as np
p1=np.array([0,0])
p2=np.array([10,10])
p3=np.array([5,7])
d=np.cross(p2-p1,p3-p1)/np.linalg.norm(p2-p1)
To find distance to line from point if you have slope and intercept you can use formula from wiki
https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
Python:
def distance(point,coef):
return abs((coef[0]*point[0])-point[1]+coef[1])/math.sqrt((coef[0]*coef[0])+1)
coef is a tuple with slope and intercept
Based on the accepted answer
Test with below line equation -
Find the perpendicular distance from the point (5, 6) to the line −2x + 3y + 4 = 0
x-intercept p1 = [0, -4/3]
y-intercept p2 = [2, 0]
shortest distance from p3 = [5, 6] = 3.328
import numpy as np
norm = np.linalg.norm
p1 = np.array([0,-4/3])
p2 = np.array([2, 0])
p3 = np.array([5, 6])
d = np.abs(norm(np.cross(p2-p1, p1-p3)))/norm(p2-p1)
# output d = 3.328201177351375
abs((x2-x1)*(y1-y0) - (x1-x0)*(y2-y1)) / np.sqrt(np.square(x2-x1) + np.square(y2-y1))
Can be used directly through the formula, just have to plug in the values and boom it will work.
Shortest Distance from Point to a Line
This is the code I got from https://www.geeksforgeeks.org:
import math
# Function to find distance
def shortest_distance(x1, y1, a, b, c):
d = abs((a * x1 + b * y1 + c)) / (math.sqrt(a * a + b * b))
print("Perpendicular distance is", d)
Now you have to find A, B, C, x, and y.
import numpy as np
closest = []
x = (x ,y)
y = (x, y)
coef = np.polyfit(x, y, 1)
A = coef[0]
B = coef[1]
C = A*x[0] + B*x[1]
Now you can plug in the values:
shortest_dis = shortest_distance(x, y, A, B, C)
The full code may look like this:
import math
import numpy as np
def shortest_distance(x1, y1, a, b, c):
d = abs((a * x1 + b * y1 + c)) / (math.sqrt(a * a + b * b))
print("Perpendicular distance is", d)
closest = []
x = (x ,y)
y = (x, y)
coef = np.polyfit(x, y, 1)
A = coef[0]
B = coef[1]
C = A*x[0] + B*x[1]
shortest_dis = shortest_distance(x, y, A, B, C)
Please let me know if any of this is unclear.
Cross products are helpful for the 2D case, but they do not generalize well to other dimensions. Dot products do however. The dot product of two orthogonal vectors is zero in any space, which you can use to come up with a simple solution.
Let's say you have P4 on the same line as P1-P2. You could parametrize it with parameter t such that
P4 = P1 + t * (P2 - P1)
The goal is to find P4 such that
(P3 - P4) . (P2 - P1) == 0
Expanding P4 in terms of t and simplifying:
(P3 - P1 - t * (P2 - P1)) . (P2 - P1) == 0
(P3 - P1) . (P2 - P1) == t * ||P2 - P1||^2
t = (P3 - P1) . (P2 - P1) / ||P2 - P1||^2
You therefore have
D = ||P3 - P4|| = ||P3 - (P3 - P1) . (P2 - P1) / (||P2 - P1||^2)||
I've written a function in my library of utility routines called haggis. You can use haggis.math.segment_distance to compute the distance to the entire line (not just the bounded line segment) like this:
d = haggis.math.segment_distance(P3, P1, P2, segment=False)
3D distance should use np.dot
def threeD_corres(points_3_d,pre_points_3_d,points_camera):
for j in range (0,len(pre_points_3_d)):
vec1 = list(map(lambda x:x[0]- x[1],zip(pre_points_3_d[j], points_camera)))
vec2 = list(map(lambda x:x[0]- x[1],zip(pre_points_3_d[j], points_3_d[j])))
vec3 = list(map(lambda x:x[0]- x[1],zip(points_3_d[j], points_camera)))
distance = np.abs(np.dot(vec1_1,vec2_2))/np.linalg.norm(vec3)
print("#########distance:\n",distance)
return distance
Given 3 points in space (3D): A = (x1, y1, z1), B = (x2, y2, z2) C = (x3, y3, z3); then how to find the center and radius of the circle (arc) that passes through these three points, i.e. find circle equation? Using Python and Numpy here is my initial code
import numpy as np
A = np.array([x1, y1, z1])
B = np.array([x2, y2, z2])
C = np.array([x3, y3, z3])
#Find vectors connecting the three points and the length of each vector
AB = B - A
BC = C - B
AC = C - A
# Triangle Lengths
a = np.linalg.norm(AB)
b = np.linalg.norm(BC)
c = np.linalg.norm(AC)
From the Circumradius definition, the radius can be found using:
R = (a * b * c) / np.sqrt(2.0 * a**2 * b**2 +
2.0 * b**2 * c**2 +
2.0 * c**2 * a**2 -
a**4 - b**4 - c**4)
However, I am having problems finding the Cartesian coordinates of the center. One possible solution is to use the "Barycentric Coordinates" of the triangle points to find the trilinear coordinates of the circumcenter (Circumcenter).
First (using this source) we find the circumcenter barcyntric coordinates:
#barcyntric coordinates of center
b1 = a**2 * (b**2 + c**2 - a**2)
b2 = b**2 * (c**2 + a**2 - b**2)
b3 = c**2 * (a**2 + b**2 - c**2)
Then the Cartesian coordinates of the center (P) would be:
Px = (b1 * A[0]) + (b2 * B[0]) + (b3 * C[0])
Py = (b1 * A[1]) + (b2 * B[1]) + (b3 * C[1])
Pz = (b1 * A[2]) + (b2 * B[2]) + (b3 * C[2])
However, the barcyntric coordinates values above do not seem to be correct. When solved with an example of known values, the radius is correct, but the coordinates of the center are not.
Example: For these three points:
A = np.array([2.0, 1.5, 0.0])
B = np.array([6.0, 4.5, 0.0])
C = np.array([11.75, 6.25, 0.0])
The radius and center coordinates are:
R = 15.899002930062595
P = [13.4207317073, -9.56097560967, 0]
Any ideas on how to find the center coordinates?
There are two issues with your code.
The first is in the naming convention. For all the formulas you are using to hold, the side of length a has to be the one opposite the point A, and similarly for b and B and c and C. You can solve that by computing them as:
a = np.linalg.norm(C - B)
b = np.linalg.norm(C - A)
c = np.linalg.norm(B - A)
The second has to do with the note in your source for the barycentric coordinates of the circumcenter: not necessarily homogeneous. That is, they need not be normalized in any way, and the formula you are using to compute the Cartesian coordinates from the barycentric ones is only valid when they add up to one.
Fortunately, you only need to divide the resulting Cartesian coordinates by b1 + b2 + b3 to get the result you are after. Streamlining a little bit your code for efficiency, I get the results you expect:
>>> A = np.array([2.0, 1.5, 0.0])
>>> B = np.array([6.0, 4.5, 0.0])
>>> C = np.array([11.75, 6.25, 0.0])
>>> a = np.linalg.norm(C - B)
>>> b = np.linalg.norm(C - A)
>>> c = np.linalg.norm(B - A)
>>> s = (a + b + c) / 2
>>> R = a*b*c / 4 / np.sqrt(s * (s - a) * (s - b) * (s - c))
>>> b1 = a*a * (b*b + c*c - a*a)
>>> b2 = b*b * (a*a + c*c - b*b)
>>> b3 = c*c * (a*a + b*b - c*c)
>>> P = np.column_stack((A, B, C)).dot(np.hstack((b1, b2, b3)))
>>> P /= b1 + b2 + b3
>>> R
15.899002930062531
>>> P
array([ 13.42073171, -9.56097561, 0. ])
As an extension to the original problem: Now suppose we have an arc of known length (e.g. 1 unit) extending from point A as defined above (away from point B). How to find the 3D coordinates of the end point (N)? The new point N lies on the same circle passing through points A, B & C.
This can be solved by first finding the angle between the two vectors PA & PN:
# L = Arc Length
theta = L / R
Now all we need to do is rotate the vector PA (Radius) by this angle theta in the correct direction. In order to do that, we need the 3D rotation matrix. For that we use the Euler–Rodrigues formula:
def rotation_matrix_3d(axis, theta):
axis = axis / np.linalg.norm(axis)
a = np.cos(theta / 2.0)
b, c, d = axis * np.sin(theta / 2.0)
rot = np.array([[a*a+b*b-c*c-d*d, 2*(b*c-a*d), 2*(b*d+a*c)],
[ 2*(b*c+a*d), a*a+c*c-b*b-d*d, 2*(c*d-a*b)],
[ 2*(b*d-a*c), 2*(c*d+a*b), a*a+d*d-b*b-c*c]])
return rot
Next, we need to find the rotation axis to rotate PA around. This can be achieved by finding the axis normal to the plane of the circle passing through A, B & C. The coordinates of point N can then be found from the center of the circle.
PA = P - A
PB = P - B
xx = np.cross(PB, PA)
r3d = rotation_matrix_3d(xx, theta)
PN = np.dot(r3d, PA)
N = P - PN
as an example, we want to find coordinates of point N that are 3 degrees away from point A, the coordinates would be
N = (1.43676498, 0.8871264, 0.)
Which is correct! (manually verified using CAD program)
What suggestions do people have for quickly calculating dihedral angles in Python?
In the diagrams, phi is the dihedral angle:
What's your best for calculating angles in the range 0 to pi? What about 0 to 2pi?
"Best" here means some mix of fast and numerically stable. Methods that return values over the full range 0 to 2pi are preferred but if you have an incredibly fast way of calculating the dihedral over 0 to pi share that too.
Here are my 3 best efforts. Only the 2nd one returns angles between 0 and 2pi. It's also the slowest.
General comments about my approaches:
arccos() in Numpy seems plenty stable but since people raise this issue I may just not fully understand it.
The use of einsum came from here. Why is numpy's einsum faster than numpy's built in functions?
The diagrams and some inspiration came from here. How do I calculate a dihedral angle given Cartesian coordinates?
The 3 approaches with comments:
import numpy as np
from time import time
# This approach tries to minimize magnitude and sqrt calculations
def dihedral1(p):
# Calculate vectors between points, b1, b2, and b3 in the diagram
b = p[:-1] - p[1:]
# "Flip" the first vector so that eclipsing vectors have dihedral=0
b[0] *= -1
# Use dot product to find the components of b1 and b3 that are not
# perpendicular to b2. Subtract those components. The resulting vectors
# lie in parallel planes.
v = np.array( [ v - (v.dot(b[1])/b[1].dot(b[1])) * b[1] for v in [b[0], b[2]] ] )
# Use the relationship between cos and dot product to find the desired angle.
return np.degrees(np.arccos( v[0].dot(v[1])/(np.linalg.norm(v[0]) * np.linalg.norm(v[1]))))
# This is the straightforward approach as outlined in the answers to
# "How do I calculate a dihedral angle given Cartesian coordinates?"
def dihedral2(p):
b = p[:-1] - p[1:]
b[0] *= -1
v = np.array( [ v - (v.dot(b[1])/b[1].dot(b[1])) * b[1] for v in [b[0], b[2]] ] )
# Normalize vectors
v /= np.sqrt(np.einsum('...i,...i', v, v)).reshape(-1,1)
b1 = b[1] / np.linalg.norm(b[1])
x = np.dot(v[0], v[1])
m = np.cross(v[0], b1)
y = np.dot(m, v[1])
return np.degrees(np.arctan2( y, x ))
# This one starts with two cross products to get a vector perpendicular to
# b2 and b1 and another perpendicular to b2 and b3. The angle between those vectors
# is the dihedral angle.
def dihedral3(p):
b = p[:-1] - p[1:]
b[0] *= -1
v = np.array( [np.cross(v,b[1]) for v in [b[0], b[2]] ] )
# Normalize vectors
v /= np.sqrt(np.einsum('...i,...i', v, v)).reshape(-1,1)
return np.degrees(np.arccos( v[0].dot(v[1]) ))
dihedrals = [ ("dihedral1", dihedral1), ("dihedral2", dihedral2), ("dihedral3", dihedral3) ]
Benchmarking:
# Testing arccos near 0
# Answer is 0.000057
p1 = np.array([
[ 1, 0, 0 ],
[ 0, 0, 0 ],
[ 0, 0, 1 ],
[ 0.999999, 0.000001, 1 ]
])
# +x,+y
p2 = np.array([
[ 1, 0, 0 ],
[ 0, 0, 0 ],
[ 0, 0, 1 ],
[ 0.1, 0.6, 1 ]
])
# -x,+y
p3 = np.array([
[ 1, 0, 0 ],
[ 0, 0, 0 ],
[ 0, 0, 1 ],
[-0.3, 0.6, 1 ]
])
# -x,-y
p4 = np.array([
[ 1, 0, 0 ],
[ 0, 0, 0 ],
[ 0, 0, 1 ],
[-0.3, -0.6, 1 ]
])
# +x,-y
p5 = np.array([
[ 1, 0, 0 ],
[ 0, 0, 0 ],
[ 0, 0, 1 ],
[ 0.6, -0.6, 1 ]
])
for d in dihedrals:
name = d[0]
f = d[1]
print "%s: %12.6f %12.6f %12.6f %12.6f %12.6f" \
% (name, f(p1), f(p2), f(p3), f(p4), f(p5))
print
def profileDihedrals(f):
t0 = time()
for i in range(20000):
p = np.random.random( (4,3) )
f(p)
p = np.random.randn( 4,3 )
f(p)
return(time() - t0)
print "dihedral1: ", profileDihedrals(dihedral1)
print "dihedral2: ", profileDihedrals(dihedral2)
print "dihedral3: ", profileDihedrals(dihedral3)
Benchmarking output:
dihedral1: 0.000057 80.537678 116.565051 116.565051 45.000000
dihedral2: 0.000057 80.537678 116.565051 -116.565051 -45.000000
dihedral3: 0.000057 80.537678 116.565051 116.565051 45.000000
dihedral1: 2.79781794548
dihedral2: 3.74271392822
dihedral3: 2.49604296684
As you can see in the benchmarking, the last one tends to be the fastest while the second one is the only one that returns angles from the full range of 0 to 2pi since it uses arctan2.
Here's an implementation for torsion angle over the full 2pi range that is a bit faster, doesn't resort to numpy quirks (einsum being mysteriously faster than logically equivalent code), and is easier to read.
There's even a bit more than just hacks going on here -- the math is different too. The formula used in the question's dihedral2 uses 3 square roots and 1 cross product, the formula on Wikipedia uses 1 square root and 3 cross products, but the formula used in the function below uses only 1 square root and 1 cross product. This is probably as simple as the math can get.
Functions with 2pi range function from question, Wikipedia formula for comparison, and the new function:
dihedrals.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
def old_dihedral2(p):
"""http://stackoverflow.com/q/20305272/1128289"""
b = p[:-1] - p[1:]
b[0] *= -1
v = np.array( [ v - (v.dot(b[1])/b[1].dot(b[1])) * b[1] for v in [b[0], b[2]] ] )
# Normalize vectors
v /= np.sqrt(np.einsum('...i,...i', v, v)).reshape(-1,1)
b1 = b[1] / np.linalg.norm(b[1])
x = np.dot(v[0], v[1])
m = np.cross(v[0], b1)
y = np.dot(m, v[1])
return np.degrees(np.arctan2( y, x ))
def wiki_dihedral(p):
"""formula from Wikipedia article on "Dihedral angle"; formula was removed
from the most recent version of article (no idea why, the article is a
mess at the moment) but the formula can be found in at this permalink to
an old version of the article:
https://en.wikipedia.org/w/index.php?title=Dihedral_angle&oldid=689165217#Angle_between_three_vectors
uses 1 sqrt, 3 cross products"""
p0 = p[0]
p1 = p[1]
p2 = p[2]
p3 = p[3]
b0 = -1.0*(p1 - p0)
b1 = p2 - p1
b2 = p3 - p2
b0xb1 = np.cross(b0, b1)
b1xb2 = np.cross(b2, b1)
b0xb1_x_b1xb2 = np.cross(b0xb1, b1xb2)
y = np.dot(b0xb1_x_b1xb2, b1)*(1.0/np.linalg.norm(b1))
x = np.dot(b0xb1, b1xb2)
return np.degrees(np.arctan2(y, x))
def new_dihedral(p):
"""Praxeolitic formula
1 sqrt, 1 cross product"""
p0 = p[0]
p1 = p[1]
p2 = p[2]
p3 = p[3]
b0 = -1.0*(p1 - p0)
b1 = p2 - p1
b2 = p3 - p2
# normalize b1 so that it does not influence magnitude of vector
# rejections that come next
b1 /= np.linalg.norm(b1)
# vector rejections
# v = projection of b0 onto plane perpendicular to b1
# = b0 minus component that aligns with b1
# w = projection of b2 onto plane perpendicular to b1
# = b2 minus component that aligns with b1
v = b0 - np.dot(b0, b1)*b1
w = b2 - np.dot(b2, b1)*b1
# angle between v and w in a plane is the torsion angle
# v and w may not be normalized but that's fine since tan is y/x
x = np.dot(v, w)
y = np.dot(np.cross(b1, v), w)
return np.degrees(np.arctan2(y, x))
The new function would probably be a bit more conveniently called with 4 separate arguments but it to match the signature in the original question it simply immediately unpacks the argument.
Code for testing:
test_dihedrals.ph
from dihedrals import *
# some atom coordinates for testing
p0 = np.array([24.969, 13.428, 30.692]) # N
p1 = np.array([24.044, 12.661, 29.808]) # CA
p2 = np.array([22.785, 13.482, 29.543]) # C
p3 = np.array([21.951, 13.670, 30.431]) # O
p4 = np.array([23.672, 11.328, 30.466]) # CB
p5 = np.array([22.881, 10.326, 29.620]) # CG
p6 = np.array([23.691, 9.935, 28.389]) # CD1
p7 = np.array([22.557, 9.096, 30.459]) # CD2
# I guess these tests do leave 1 quadrant (-x, +y) untested, oh well...
def test_old_dihedral2():
assert(abs(old_dihedral2(np.array([p0, p1, p2, p3])) - (-71.21515)) < 1E-4)
assert(abs(old_dihedral2(np.array([p0, p1, p4, p5])) - (-171.94319)) < 1E-4)
assert(abs(old_dihedral2(np.array([p1, p4, p5, p6])) - (60.82226)) < 1E-4)
assert(abs(old_dihedral2(np.array([p1, p4, p5, p7])) - (-177.63641)) < 1E-4)
def test_new_dihedral1():
assert(abs(wiki_dihedral(np.array([p0, p1, p2, p3])) - (-71.21515)) < 1E-4)
assert(abs(wiki_dihedral(np.array([p0, p1, p4, p5])) - (-171.94319)) < 1E-4)
assert(abs(wiki_dihedral(np.array([p1, p4, p5, p6])) - (60.82226)) < 1E-4)
assert(abs(wiki_dihedral(np.array([p1, p4, p5, p7])) - (-177.63641)) < 1E-4)
def test_new_dihedral2():
assert(abs(new_dihedral(np.array([p0, p1, p2, p3])) - (-71.21515)) < 1E-4)
assert(abs(new_dihedral(np.array([p0, p1, p4, p5])) - (-171.94319)) < 1E-4)
assert(abs(new_dihedral(np.array([p1, p4, p5, p6])) - (60.82226)) < 1E-4)
assert(abs(new_dihedral(np.array([p1, p4, p5, p7])) - (-177.63641)) < 1E-4)
Code for timing:
time_dihedrals.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from dihedrals import *
from time import time
def profileDihedrals(f):
t0 = time()
for i in range(20000):
p = np.random.random( (4,3) )
f(p)
p = np.random.randn( 4,3 )
f(p)
return(time() - t0)
print("old_dihedral2: ", profileDihedrals(old_dihedral2))
print("wiki_dihedral: ", profileDihedrals(wiki_dihedral))
print("new_dihedral: ", profileDihedrals(new_dihedral))
The functions can be tested with pytest as pytest ./test_dihedrals.py.
Timing results:
./time_dihedrals.py
old_dihedral2: 1.6442952156066895
wiki_dihedral: 1.3895585536956787
new_dihedral: 0.8703620433807373
new_dihedral is about twice as fast as old_dihedral2.
...you can also see that the hardware used for this answer is a lot beefier than the hardware used in the question (3.74 vs 1.64 for dihedral2) ;-P
If you want to get even more aggressive you can use pypy. At the time of writing pypy doesn't support numpy.cross but you can just use a cross product implemented in python instead. For a 3-vector cross product the C pypy generates is probably at least as good as what numpy uses. Doing so gets the time down to 0.60 for me but at this we're wading into silly hax.
Same benchmark but with same hardware as used in the question:
old_dihedral2: 3.0171279907226562
wiki_dihedral: 3.415065050125122
new_dihedral: 2.086946964263916
My approach:
Form the vectors b4 = b1/\b2 and b5 = b2/\b4. These form an orthogonal frame with b2 and the length of b5 is that of b2 times that of b4 (as they are orthogonal). Projecting b3 on these two vectors gives you (scaled) 2D coordinates of the tip of b3 as on your second figure. The angle is given by atan2 in the -Pi..+Pi range.
b4= cross(b1, b2);
b5= cross(b2, b4);
Dihedral= atan2(dot(b3, b4), dot(b3, b5) * sqrt(dot(b2, b2)));
Similar to your dihedral2. 12 adds, 21 multiplies, 1 square root, 1 arctangent. You can rewrite the expression for b5 using the expulsion formula, but this does not really help.
CAUTION: I didn't thoroughly check the signs/quadrants issues!
My fastest approach is based on Praxeolitic's "new_dihedral" function, improved by using the njit decorator of the numba module. This will force an alternative route for compiling the function to run ~50 to 60 times faster on my machine than the original approach. NOTE, that the first run of the function will take around 1-2 seconds for compiling when you do a speedtest.
The code runs faster, the more function calls you avoid, so sorry for the decreased readability of the code.
import numpy as np
from numba import njit
#njit()
def fastest_dihedral(p):
b1 = p[2] - p[1]
b0, b1, b2 = -(p[1] - p[0]), b1 / np.sqrt((b1 * b1).sum()), p[3] - p[2]
v = b0 - (b0[0] * b1[0] + b0[1] * b1[1] + b0[2] * b1[2]) * b1
w = b2 - (b2[0] * b1[0] + b2[1] * b1[1] + b2[2] * b1[2]) * b1
x = v[0] * w[0] + v[1] * w[1] + v[2] * w[2]
y = (b1[1]*v[2] - b1[2]*v[1]) * w[0] + \
(b1[2]*v[0] - b1[0]*v[2]) * w[1] + \
(b1[0]*v[1] - b1[1]*v[0]) * w[2]
return 180 * np.arctan2(y, x) / np.pi
In case you want to calculate a big amount of dihedrals, I recommend a 'parallel version' of this function as follows:
import numpy as np
from numba import njit, prange
#njit(parallel=True)
def fastest_dihedral_multi(p, target_array):
for i in prange(target_array.shape[0]):
ind = i<<2
b1 = p[ind + 2] - p[ind + 1]
b0, b1, b2 = -(p[ind + 1] - p[ind]), b1 / np.sqrt((b1 * b1).sum()), p[ind + 3] - p[ind + 2]
v = b0 - (b0[0] * b1[0] + b0[1] * b1[1] + b0[2] * b1[2]) * b1
w = b2 - (b2[0] * b1[0] + b2[1] * b1[1] + b2[2] * b1[2]) * b1
x = v[0] * w[0] + v[1] * w[1] + v[2] * w[2]
y = (b1[1] * v[2] - b1[2] * v[1]) * w[0] + \
(b1[2] * v[0] - b1[0] * v[2]) * w[1] + \
(b1[0] * v[1] - b1[1] * v[0]) * w[2]
target_array[i] = np.arctan2(y, x)* 180 / np.pi
Here's the definitive answer. The authors have python versions available as well as the C version.
http://onlinelibrary.wiley.com/doi/10.1002/jcc.20237/abstract
Practical conversion from torsion space to Cartesian space for in silico protein synthesis
First published: 16 May 2005