Мне нужно определить угол (ы) между двумя n-мерными векторами в Python. Например, вход может состоять из двух списков: [1,2,3,4]
и [6,7,8,9]
.Углы между двумя n-мерными векторами в Python
ответ
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
Использование 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
Последняя строка может привести к ошибке, как я нашел из-за ошибок округления. Таким образом, если вы расставляете точку (u, u)/norm (u) ** 2, это приводит к 1.0000000002, а arccos затем терпит неудачу (также «работает» для антипараллельных векторов) – BandGap
Я тестировал с u = [1,1,1 ]. u = [1,1,1,1] работает отлично, но каждое добавленное измерение возвращает немного больше или уменьшает значения, чем 1 ... – BandGap
Примечание: ** это не сработает ** (выход 'nan'), когда направление двух векторы либо идентичны, либо противоположны. См. Мой ответ для более правильной версии. –
Примечание: все другие ответы здесь не получится, если два вектора имеют либо в том же направлении (напр, (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' вместо одного из математической библиотеки? Теоретически они должны быть идентичными, но я не совсем уверен на практике. В любом случае, я бы подумал, что это будет безопаснее. – Hooked
Единственное отличие состоит в том, что 'np.isnan' будет делать что-то разумное, если вход представляет собой массив, который здесь никогда не будет иметь места. Однако использование 'np.isnan' определенно будет более чистым (не уверен, почему я использовал' math.isnan' ...), поэтому я переключу его. –
Вы также можете использовать 'angle = np.arccos (np.clip (np.dot (v1_u, v2_u), - 1,1))' и пропустить бизнес if-else. – letmaik
Использование NumPy и уход за ошибок округления ширины запрещенной зоны в:
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).
Другая возможность использования только 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
Кроме того, если вам нужны только cos, sin, tan угла, а не сам угол, тогда вы можете пропустить math.acos для получения косинуса и использовать кросс-продукт для получения синуса. – mbeckish
Это именно то, что я искал, спасибо! – Peter
Учитывая, что 'math.sqrt (x)' эквивалентно 'x ** 0.5', а' math.pow (x, y) 'эквивалентно' x ** y', я удивлен, что они остались в аудре избыточности во время перехода Python 2.x-> 3.0. На практике я обычно делаю эти виды числовых функций как часть более интенсивного процесса с интенсивным вычислением, а поддержка интерпретатора «**» переходит непосредственно к байт-коду BINARY_POWER по сравнению с поиском «математики», доступ к его атрибуту «sqrt», а затем к мучительно медленному байт-коду CALL_FUNCTION, может сделать измеримое улучшение скорости без каких-либо затрат на кодирование или считывание. – PaulMcG