Angles entre deux vecteurs n-dimensionnels dans Python
Question
I besoin pour déterminer l'angle (s) entre deux vecteurs n-dimensionnels dans python. Par exemple, l'entrée peut être deux listes comme suit:. [1,2,3,4]
et [6,7,8,9]
La solution
import math
def dotproduct(v1, v2):
return sum((a*b) for a, b in zip(v1, v2))
def length(v):
return math.sqrt(dotproduct(v, v))
def angle(v1, v2):
return math.acos(dotproduct(v1, v2) / (length(v1) * length(v2)))
Remarque : cela ne fonctionne pas lorsque les vecteurs ont les mêmes ou la direction opposée. La mise en œuvre correcte est ici: https://stackoverflow.com/a/13849249/71522
Autres conseils
Remarque :. Toutes les autres réponses ici échouera si les deux vecteurs ont soit la même direction (ex, (1, 0, 0)
, (1, 0, 0)
) ou des directions opposées (ex, (-1, 0, 0)
, (1, 0, 0)
)
Voici une fonction qui gérer correctement ces cas:
import numpy as np
def unit_vector(vector):
""" Returns the unit vector of the vector. """
return vector / np.linalg.norm(vector)
def angle_between(v1, v2):
""" Returns the angle in radians between vectors 'v1' and 'v2'::
>>> angle_between((1, 0, 0), (0, 1, 0))
1.5707963267948966
>>> angle_between((1, 0, 0), (1, 0, 0))
0.0
>>> angle_between((1, 0, 0), (-1, 0, 0))
3.141592653589793
"""
v1_u = unit_vector(v1)
v2_u = unit_vector(v2)
return np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))
vous faire à l'aide numpy (fortement recommandé),:
from numpy import (array, dot, arccos, clip)
from numpy.linalg import norm
u = array([1.,2,3,4])
v = ...
c = dot(u,v)/norm(u)/norm(v) # -> cosine of the angle
angle = arccos(clip(c, -1, 1)) # if you really want the angle
L'autre possibilité est juste en utilisant numpy
et il vous donne l'angle intérieur
import numpy as np
p0 = [3.5, 6.7]
p1 = [7.9, 8.4]
p2 = [10.8, 4.8]
'''
compute angle (in degrees) for p0p1p2 corner
Inputs:
p0,p1,p2 - points in the form of [x,y]
'''
v0 = np.array(p0) - np.array(p1)
v1 = np.array(p2) - np.array(p1)
angle = np.math.atan2(np.linalg.det([v0,v1]),np.dot(v0,v1))
print np.degrees(angle)
et est ici la sortie:
In [2]: p0, p1, p2 = [3.5, 6.7], [7.9, 8.4], [10.8, 4.8]
In [3]: v0 = np.array(p0) - np.array(p1)
In [4]: v1 = np.array(p2) - np.array(p1)
In [5]: v0
Out[5]: array([-4.4, -1.7])
In [6]: v1
Out[6]: array([ 2.9, -3.6])
In [7]: angle = np.math.atan2(np.linalg.det([v0,v1]),np.dot(v0,v1))
In [8]: angle
Out[8]: 1.8802197318858924
In [9]: np.degrees(angle)
Out[9]: 107.72865519428085
Si vous travaillez avec des vecteurs 3D, vous pouvez le faire en utilisant de façon concise le toolbelt vg . Il est une légère couche au-dessus de numpy.
import numpy as np
import vg
vec1 = np.array([1, 2, 3])
vec2 = np.array([7, 8, 9])
vg.angle(vec1, vec2)
Vous pouvez également spécifier un angle de vue pour calculer l'angle par projection:
vg.angle(vec1, vec2, look=vg.basis.z)
Ou calculer l'angle signé par projection:
vg.signed_angle(vec1, vec2, look=vg.basis.z)
J'ai créé la bibliothèque de mon dernier démarrage, où il a été motivé par des utilisations comme ceci:. Idées simples qui sont verbeux ou opaques dans NumPy
Pour les rares qui peuvent avoir (en raison de complications SEO) a pris fin en essayant de calculer l'angle entre deux lignes en python, comme dans (x0, y0), (x1, y1)
lignes géométriques, il est le dessous minimum solution (utilise le module shapely
, mais peut être facilement modifié pas):
from shapely.geometry import LineString
import numpy as np
ninety_degrees_rad = 90.0 * np.pi / 180.0
def angle_between(line1, line2):
coords_1 = line1.coords
coords_2 = line2.coords
line1_vertical = (coords_1[1][0] - coords_1[0][0]) == 0.0
line2_vertical = (coords_2[1][0] - coords_2[0][0]) == 0.0
# Vertical lines have undefined slope, but we know their angle in rads is = 90° * π/180
if line1_vertical and line2_vertical:
# Perpendicular vertical lines
return 0.0
if line1_vertical or line2_vertical:
# 90° - angle of non-vertical line
non_vertical_line = line2 if line1_vertical else line1
return abs((90.0 * np.pi / 180.0) - np.arctan(slope(non_vertical_line)))
m1 = slope(line1)
m2 = slope(line2)
return np.arctan((m1 - m2)/(1 + m1*m2))
def slope(line):
# Assignments made purely for readability. One could opt to just one-line return them
x0 = line.coords[0][0]
y0 = line.coords[0][1]
x1 = line.coords[1][0]
y1 = line.coords[1][1]
return (y1 - y0) / (x1 - x0)
Et l'utilisation serait
>>> line1 = LineString([(0, 0), (0, 1)]) # vertical
>>> line2 = LineString([(0, 0), (1, 0)]) # horizontal
>>> angle_between(line1, line2)
1.5707963267948966
>>> np.degrees(angle_between(line1, line2))
90.0
Utilisation numpy et en prenant soin des erreurs d'arrondi de bande interdite:
from numpy.linalg import norm
from numpy import dot
import math
def angle_between(a,b):
arccosInput = dot(a,b)/norm(a)/norm(b)
arccosInput = 1.0 if arccosInput > 1.0 else arccosInput
arccosInput = -1.0 if arccosInput < -1.0 else arccosInput
return math.acos(arccosInput)
Note, cette fonction lancera une exception si l'un des vecteurs a une magnitude zéro (division par 0).
David solution de Wolever est bon , mais
Si vous voulez avoir signé angles vous devez déterminer si une paire donnée est droitier ou gaucher (voir wiki pour plus d'infos).
Ma solution pour cela est:
def unit_vector(vector):
""" Returns the unit vector of the vector"""
return vector / np.linalg.norm(vector)
def angle(vector1, vector2):
""" Returns the angle in radians between given vectors"""
v1_u = unit_vector(vector1)
v2_u = unit_vector(vector2)
minor = np.linalg.det(
np.stack((v1_u[-2:], v2_u[-2:]))
)
if minor == 0:
raise NotImplementedError('Too odd vectors =(')
return np.sign(minor) * np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))
Il est pas parfait à cause de cette NotImplementedError
mais pour mon cas, il fonctionne bien. Ce comportement pourrait être fixé (cause handness est déterminée pour une paire donnée) mais il faut plus de code que je veux et je dois écrire.