Je dois déterminer l'angle ou les angles entre deux vecteurs à n dimensions en Python. Par exemple, l'entrée peut être deux listes comme celle-ci: [1,2,3,4]
et [6,7,8,9]
.
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 échouera lorsque les vecteurs auront la même direction ou la direction opposée. La mise en œuvre correcte est la suivante: https://stackoverflow.com/a/13849249/71522
Note: toutes les autres réponses ici échoueront si les deux vecteurs ont 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érera 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))
En utilisant numpy (hautement recommandé), vous feriez:
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 d’utiliser numpy
et cela 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 voici 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 de manière concise en utilisant la palette d'outils vg . C'est une couche légère 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 le calculer via projection:
vg.angle(vec1, vec2, look=vg.basis.z)
Ou calculez l'angle signé par projection:
vg.signed_angle(vec1, vec2, look=vg.basis.z)
J'ai créé la bibliothèque lors de mon dernier démarrage, où elle était motivée par des utilisations telles que celle-ci: des idées simples qui sont verbeuses ou opaques dans NumPy.
Pour les rares qui peuvent avoir (en raison de complications SEO) terminé ici en essayant de calculer l'angle entre deux lignes en python, comme dans (x0, y0), (x1, y1)
lignes géométriques, voici la solution ci-dessous minimale (utilise le module shapely
, mais peut être facilement modifiée):
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
la solution de David Wolever c'est bien, mais
Si vous voulez avoir des angles signés , vous devez déterminer si une paire donnée est droitier ou gaucher (voir wiki pour plus d'informations. Info).
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))
Ce n'est pas parfait pour cette raison NotImplementedError
mais, dans mon cas, cela fonctionne bien. Ce comportement peut être corrigé (la cause est déterminée pour une paire donnée) mais nécessite plus de code que je souhaite et que je dois écrire.
En utilisant numpy et en prenant soin des erreurs d'arrondi de BandGap:
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)
Notez que cette fonction lève une exception si l'un des vecteurs a une magnitude nulle (division par 0).