2010-05-13 5 views

ответ

41
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

+2

Кроме того, если вам нужны только cos, sin, tan угла, а не сам угол, тогда вы можете пропустить math.acos для получения косинуса и использовать кросс-продукт для получения синуса. – mbeckish

+0

Это именно то, что я искал, спасибо! – Peter

+7

Учитывая, что 'math.sqrt (x)' эквивалентно 'x ** 0.5', а' math.pow (x, y) 'эквивалентно' x ** y', я удивлен, что они остались в аудре избыточности во время перехода Python 2.x-> 3.0. На практике я обычно делаю эти виды числовых функций как часть более интенсивного процесса с интенсивным вычислением, а поддержка интерпретатора «**» переходит непосредственно к байт-коду BINARY_POWER по сравнению с поиском «математики», доступ к его атрибуту «sqrt», а затем к мучительно медленному байт-коду CALL_FUNCTION, может сделать измеримое улучшение скорости без каких-либо затрат на кодирование или считывание. – PaulMcG

22

Использование 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 
+2

Последняя строка может привести к ошибке, как я нашел из-за ошибок округления. Таким образом, если вы расставляете точку (u, u)/norm (u) ** 2, это приводит к 1.0000000002, а arccos затем терпит неудачу (также «работает» для антипараллельных векторов) – BandGap

+0

Я тестировал с u = [1,1,1 ]. u = [1,1,1,1] работает отлично, но каждое добавленное измерение возвращает немного больше или уменьшает значения, чем 1 ... – BandGap

+1

Примечание: ** это не сработает ** (выход 'nan'), когда направление двух векторы либо идентичны, либо противоположны. См. Мой ответ для более правильной версии. –

68

Примечание: все другие ответы здесь не получится, если два вектора имеют либо в том же направлении (напр, (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)) 
+0

Не было бы лучше использовать 'np.isnan' вместо одного из математической библиотеки? Теоретически они должны быть идентичными, но я не совсем уверен на практике. В любом случае, я бы подумал, что это будет безопаснее. – Hooked

+0

Единственное отличие состоит в том, что 'np.isnan' будет делать что-то разумное, если вход представляет собой массив, который здесь никогда не будет иметь места. Однако использование 'np.isnan' определенно будет более чистым (не уверен, почему я использовал' math.isnan' ...), поэтому я переключу его. –

+9

Вы также можете использовать 'angle = np.arccos (np.clip (np.dot (v1_u, v2_u), - 1,1))' и пропустить бизнес if-else. – letmaik

0

Использование 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).

5

Другая возможность использования только 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