Мне нужно определить угол (ы) между двумя n-мерными векторами в Python. Например, входными данными могут быть два списка, например: [1,2,3,4]
и [6,7,8,9]
.
Мне нужно определить угол (ы) между двумя n-мерными векторами в Python. Например, входными данными могут быть два списка, например: [1,2,3,4]
и [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)))
Примечание : это не сработает, если векторы имеют одинаковое или противоположное направление. Правильная реализация здесь: https://stackoverflow.com/a/13849249/71522
math.sqrt(x)
это эквивалентно x**0.5
и math.pow(x,y)
эквивалентно x**y
, я удивлен, что они пережили топор избыточности, использованный во время перехода Python 2.x-> 3.0. На практике я обычно выполняю такие числовые операции как часть более крупного вычислительно-интенсивного процесса, и поддержка интерпретатором «**», идущего непосредственно к байт-коду BINARY_POWER, вместо поиска «математики», доступа его атрибуту sqrt, а затем мучительно медленному байт-коду CALL_FUNCTION, можно ощутимо улучшить скорость без затрат на кодирование или читаемость.
angle((1., 1., 1.), (1., 1., 1.))
). См. Мой ответ для чуть более правильной версии.
Примечание : все другие ответы здесь не получится, если два вектора имеют либо в одном направлении (например, (1, 0, 0)
, (1, 0, 0)
) или в противоположных направлениях (например, (-1, 0, 0)
, (1, 0, 0)
).
Вот функция, которая будет правильно обрабатывать эти случаи:
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))
np.isnan
вместо математической библиотеки? Теоретически они должны быть идентичными, но на практике я не совсем уверен. В любом случае, я полагаю, это будет безопаснее.
arccos
напрямую и безопасно. : In [140]: np.arccos (np.dot (np.array ([1,0,0]), np.array ([- 1,0,0]))) Out [140]: 3.1415926535897931 In [ 141]: np.arccos (np.dot (np.array ([1,0,0]), np.array ([1,0,0]))) Out [141]: 0.0
unit_vector
. Одна из возможностей - просто вернуть входной вектор в этой функции, когда это так.
Используя numpy (настоятельно рекомендуется), вы должны:
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
nan
), если направление двух векторов одинаковое или противоположное. См. Мой ответ для более правильной версии.
angle = arccos(clip(c, -1, 1))
во избежание проблем с округлением. Это решает проблему @DavidWolever.
clip
следует добавить в список импорта numpy.
Другой вариант - использовать просто, numpy
и он дает вам внутренний угол
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)
и вот результат:
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
Если вы работаете с 3D-векторами, вы можете сделать это кратко, используя toolbelt vg . Это легкий слой поверх numpy.
import numpy as np
import vg
vec1 = np.array([1, 2, 3])
vec2 = np.array([7, 8, 9])
vg.angle(vec1, vec2)
Вы также можете указать угол обзора для вычисления угла через проекцию:
vg.angle(vec1, vec2, look=vg.basis.z)
Или вычислите знаковый угол через проекцию:
vg.signed_angle(vec1, vec2, look=vg.basis.z)
Я создал библиотеку при моем последнем запуске, где она была мотивирована таким использованием: простые идеи, которые многословны или непрозрачны в NumPy.
Решение Дэвида Волевера хорошее, но
Если вы хотите иметь подписанные углы, вы должны определить, является ли данная пара правой или левой (см. Вики для получения дополнительной информации).
Мое решение для этого:
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))
Из-за этого он не идеален, NotImplementedError
но в моем случае работает хорошо. Такое поведение можно исправить (поскольку для каждой пары определяется удобство работы), но для этого требуется больше кода, который я хочу и должен написать.
Простой способ найти угол между двумя векторами (работает для n-мерного вектора),
Код Python:
import numpy as np
vector1 = [1,0,0]
vector2 = [0,1,0]
unit_vector1 = vector1 / np.linalg.norm(vector1)
unit_vector2 = vector2 / np.linalg.norm(vector2)
dot_product = np.dot(unit_vector1, unit_vector2)
angle = np.arccos(dot_product) #angle in radian
Основываясь на отличном ответе sgt pepper и добавляя поддержку выровненных векторов, а также добавляя ускорение более чем в 2 раза с помощью Numba
@njit(cache=True, nogil=True)
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:
sign = 1
else:
sign = -np.sign(minor)
dot_p = np.dot(v1_u, v2_u)
dot_p = min(max(dot_p, -1.0), 1.0)
return sign * np.arccos(dot_p)
@njit(cache=True, nogil=True)
def unit_vector(vector):
""" Returns the unit vector of the vector. """
return vector / np.linalg.norm(vector)
def test_angle():
def npf(x):
return np.array(x, dtype=float)
assert np.isclose(angle(npf((1, 1)), npf((1, 0))), pi / 4)
assert np.isclose(angle(npf((1, 0)), npf((1, 1))), -pi / 4)
assert np.isclose(angle(npf((0, 1)), npf((1, 0))), pi / 2)
assert np.isclose(angle(npf((1, 0)), npf((0, 1))), -pi / 2)
assert np.isclose(angle(npf((1, 0)), npf((1, 0))), 0)
assert np.isclose(angle(npf((1, 0)), npf((-1, 0))), pi)
%%timeit
результаты без Numba
И с
Использование numpy и устранение ошибок округления 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)
Обратите внимание, что эта функция вызовет исключение, если один из векторов имеет нулевую величину (делится на 0).
Для тех немногих, кто, возможно, (из-за осложнений с SEO) закончил здесь попытки вычислить угол между двумя линиями в python, как и в случае с (x0, y0), (x1, y1)
геометрическими линиями, существует следующее минимальное решение (использует shapely
модуль, но его можно легко изменить, чтобы не использовать):
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)
И польза была бы
>>> 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