Ich muss den Winkel zwischen zwei n-dimensionalen Vektoren in Python bestimmen. Die Eingabe kann beispielsweise zwei Listen wie die folgenden sein: [1,2,3,4]
und [6,7,8,9]
.
Ich muss den Winkel zwischen zwei n-dimensionalen Vektoren in Python bestimmen. Die Eingabe kann beispielsweise zwei Listen wie die folgenden sein: [1,2,3,4]
und [6,7,8,9]
.
Antworten:
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)))
Hinweis : Dies schlägt fehl, wenn die Vektoren entweder die gleiche oder die entgegengesetzte Richtung haben. Die korrekte Implementierung finden Sie hier: https://stackoverflow.com/a/13849249/71522
math.sqrt(x)
äquivalent zu x**0.5
und math.pow(x,y)
äquivalent zu ist x**y
, bin ich überrascht, dass diese die Redundanz-Axt überlebt haben, die während des Python 2.x-> 3.0-Übergangs angewendet wurde. In der Praxis mache ich diese Art von numerischen Dingen normalerweise als Teil eines größeren rechenintensiven Prozesses, und die Unterstützung des Interpreters für '**' geht direkt zum Bytecode BINARY_POWER, im Gegensatz zur Suche nach 'math', dem Zugriff Aufgrund seines Attributs 'sqrt' und des schmerzhaft langsamen Bytecodes CALL_FUNCTION kann die Geschwindigkeit ohne Kosten für Codierung oder Lesbarkeit messbar verbessert werden.
angle((1., 1., 1.), (1., 1., 1.))
. B. ). Siehe meine Antwort für eine etwas korrektere Version.
Hinweis : alle anderen Antworten hier wird scheitern , wenn die beiden Vektoren entweder in die gleiche Richtung (ex, (1, 0, 0)
, (1, 0, 0)
) oder entgegengesetzte Richtungen (ex, (-1, 0, 0)
, (1, 0, 0)
).
Hier ist eine Funktion, die diese Fälle korrekt behandelt:
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
anstelle des aus der Mathematikbibliothek zu verwenden? Theoretisch sollten sie identisch sein, aber in der Praxis bin ich mir nicht ganz sicher. So oder so würde ich mir vorstellen, dass es sicherer wäre.
arccos
direkt und sicher verwendet werden. : 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
. Eine Möglichkeit besteht darin, in dieser Funktion nur den Eingabevektor zurückzugeben, wenn dies der Fall ist.
nan
), wenn die Richtung der beiden Vektoren entweder identisch oder entgegengesetzt ist. Siehe meine Antwort für eine korrektere Version.
angle = arccos(clip(c, -1, 1))
, Rundungsprobleme zu vermeiden. Dies löst das Problem von @DavidWolever.
clip
sollte zur Liste der Numpy-Importe hinzugefügt werden.
Die andere Möglichkeit ist nur zu verwenden numpy
und es gibt Ihnen den Innenwinkel
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)
und hier ist die Ausgabe:
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
Wenn Sie mit 3D-Vektoren arbeiten, können Sie dies mit dem Toolbelt vg präzise tun . Es ist eine leichte Schicht auf Numpy.
import numpy as np
import vg
vec1 = np.array([1, 2, 3])
vec2 = np.array([7, 8, 9])
vg.angle(vec1, vec2)
Sie können auch einen Betrachtungswinkel angeben, um den Winkel über die Projektion zu berechnen:
vg.angle(vec1, vec2, look=vg.basis.z)
Oder berechnen Sie den vorzeichenbehafteten Winkel per Projektion:
vg.signed_angle(vec1, vec2, look=vg.basis.z)
Ich habe die Bibliothek bei meinem letzten Start erstellt, wo sie durch Verwendungen wie diese motiviert war: einfache Ideen, die in NumPy ausführlich oder undurchsichtig sind.
Die Lösung von David Wolever ist gut, aber
Wenn Sie signierte Winkel haben möchten, müssen Sie feststellen, ob ein bestimmtes Paar Rechts- oder Linkshänder ist ( weitere Informationen finden Sie im Wiki ).
Meine Lösung dafür ist:
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))
Aus diesem Grund ist es nicht perfekt, NotImplementedError
aber für meinen Fall funktioniert es gut. Dieses Verhalten könnte behoben werden (weil die Handlichkeit für jedes gegebene Paar bestimmt wird), aber es braucht mehr Code, als ich schreiben möchte und muss.
Aufbauend auf der großartigen Antwort von sgt pepper und der Unterstützung für ausgerichtete Vektoren sowie einer Beschleunigung von über 2x mit 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
Ergebnisse ohne Numba
Und mit
Einfache Möglichkeit, den Winkel zwischen zwei Vektoren zu finden (funktioniert für n-dimensionale Vektoren),
Python-Code:
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
Verwenden Sie numpy und kümmern Sie sich um die Rundungsfehler von 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)
Beachten Sie, dass diese Funktion eine Ausnahme auslöst, wenn einer der Vektoren eine Größe von Null hat (durch 0 teilen).
Für die wenigen, die (aufgrund von SEO-Komplikationen) hier versucht haben, den Winkel zwischen zwei Linien in Python zu berechnen , wie in (x0, y0), (x1, y1)
geometrischen Linien, gibt es die folgende minimale Lösung (verwendet das shapely
Modul, kann aber leicht geändert werden, um dies nicht zu tun):
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)
Und die Verwendung wäre
>>> 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