2010-05-13 18 views

Respuesta

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))) 

Nota: esto va a fallar cuando los vectores tienen la misma o la dirección opuesta. La aplicación correcta está aquí: https://stackoverflow.com/a/13849249/71522

+2

Además, si solo necesita cos, sin, tan de ángulo, y no el ángulo en sí mismo, entonces puede omitir math.acos para obtener coseno, y usar producto cruzado para obtener seno. – mbeckish

+0

Esto es exactamente lo que estaba buscando, ¡gracias! – Peter

+7

Dado que 'math.sqrt (x)' es equivalente a 'x ** 0.5' y' math.pow (x, y) 'es equivalente a' x ** y', me sorprende que hayan sobrevivido al hacha de redundancia manejado durante la transición de Python 2.x-> 3.0. En la práctica, usualmente hago este tipo de cosas numéricas como parte de un proceso de cómputo más intensivo, y el soporte del intérprete para '**' ir directamente al bytecode BINARY_POWER, frente a la búsqueda de 'matemática', el acceso a su atributo 'sqrt', y luego el código de byte dolorosamente lento CALL_FUNCTION, puede hacer una mejora mensurable en la velocidad sin costo de codificación o legibilidad. – PaulMcG

22

Usando numpy (muy recomendable), que haría:

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

La última línea puede resultar en un error que he encontrado fuera debido a errores de redondeo. Por lo tanto, si puntea (u, u)/norma (u) ** 2 resulta en 1.0000000002 y luego falla el arco (también 'funciona' para vectores antiparalelos) – BandGap

+0

He probado con u = [1,1,1 ] u = [1,1,1,1] funciona bien pero cada dimensión añadida devuelve valores ligeramente más pequeños o más pequeños que 1 ... – BandGap

+1

Nota: ** esto fallará ** (rendimiento 'nan') cuando la dirección de los dos vectores es idéntico o opuesto. Vea mi respuesta para una versión más correcta. –

68

Nota: todas las otras respuestas aquí se producirá un error si los dos vectores tienen ya sea la misma dirección (por ejemplo, (1, 0, 0), (1, 0, 0)) o direcciones opuestas (ex, (-1, 0, 0), (1, 0, 0)).

Aquí es una función que manejar correctamente estos casos:

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

¿No sería mejor usar 'np.isnan' en lugar de uno de la biblioteca matemática? En teoría, deberían ser idénticos, pero no estoy seguro en la práctica. De cualquier manera, me imagino que sería más seguro. – Hooked

+0

La única diferencia es que 'np.isnan' hará algo sensato si la entrada es una matriz, que nunca será el caso aquí. Sin embargo, usar 'np.isnan' definitivamente sería más limpio (no estoy seguro de por qué usé' math.isnan' ...), así que lo cambiaré. –

+9

También puede usar 'angle = np.arccos (np.clip (np.dot (v1_u, v2_u), - 1,1))' y omitir el negocio if-else. – letmaik

0

Usando numpy y el cuidado de los errores de redondeo de banda prohibida:

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) 

Nota, esta función será una excepción si uno de los vectores tienen una magnitud cero (se dividen por 0).

5

La otra posibilidad es usar simplemente numpy y le da el ángulo interior

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) 

y aquí está la salida:

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 
Cuestiones relacionadas