Winkel zwischen zwei n-dimensionalen Vektoren in Python
82
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].
Dies ist die beste Antwort für @ MK83, da es sich genau um den mathematischen Ausdruck theta = atan2 (u ^ v, uv) handelt. Selbst der Fall, in dem u = [0 0] oder v = [0 0] behandelt wird, weil dies nur die Zeit ist, in der atan2 das NaN in den anderen Antworten erzeugt. NaN wird durch die / norm (u) oder / norm (v) erzeugt.
PilouPili
Antworten:
66
import math
defdotproduct(v1, v2):return sum((a*b) for a, b in zip(v1, v2))
deflength(v):return math.sqrt(dotproduct(v, v))
defangle(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
Wenn Sie nur cos, sin, tan of angle und nicht den Winkel selbst benötigen, können Sie die math.acos überspringen, um Cosinus zu erhalten, und cross product verwenden, um Sinus zu erhalten.
mbeckish
9
Angesichts der Tatsache, dass dies math.sqrt(x)äquivalent zu x**0.5und 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.
PaulMcG
5
Wie in der Antwort mit numpy: Dies kann fehlschlagen, wenn der Rundungsfehler ins Spiel kommt! Dies kann für parallele und antiparallele Vektoren passieren!
BandGap
2
Hinweis: Dies schlägt fehl, wenn die Vektoren identisch sind (z angle((1., 1., 1.), (1., 1., 1.)). B. ). Siehe meine Antwort für eine etwas korrektere Version.
David Wolever
2
Wenn Sie über die obige Implementierung sprechen, schlägt dies aufgrund von Rundungsfehlern fehl, nicht weil die Vektoren parallel sind.
Tempo
150
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
defunit_vector(vector):""" Returns the unit vector of the vector. """return vector / np.linalg.norm(vector)
defangle_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))
Wäre es nicht besser, np.isnananstelle 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.
Hooked
2
Mein numpy (Version == 1.12.1) kann arccosdirekt 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
ene
2
Der Sonderfall, bei dem mindestens ein Eingabevektor der Nullvektor ist, wird weggelassen, was für die Division in problematisch ist unit_vector. Eine Möglichkeit besteht darin, in dieser Funktion nur den Eingabevektor zurückzugeben, wenn dies der Fall ist.
Kafman
1
angle_between ((0, 0, 0), (0, 1, 0)) ergibt nan als Ergebnis und nicht 90
FabioSpaghetti
2
Der Winkel von @kafman 0-Vektoren ist undefiniert (in Mathematik). Die Tatsache, dass ein Fehler auftritt, ist also gut.
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
Die letzte Zeile kann zu einem Fehler führen, wie ich aufgrund von Rundungsfehlern herausgefunden habe. Wenn Sie also (u, u) / norm (u) ** 2 punktieren, ergibt dies 1.0000000002 und der Arccos schlägt fehl (funktioniert auch für antiparallele Vektoren)
BandGap
Ich habe mit u = [1,1,1] getestet. u = [1,1,1,1] funktioniert gut, aber jede hinzugefügte Dimension gibt etwas größere oder kleinere Werte als 1 zurück ...
BandGap
3
Hinweis: Dies schlägt fehl (Ausbeute nan), wenn die Richtung der beiden Vektoren entweder identisch oder entgegengesetzt ist. Siehe meine Antwort für eine korrektere Version.
David Wolever
2
Wenn Sie den Kommentar von neo hinzufügen, sollte die letzte Zeile darin bestehen angle = arccos(clip(c, -1, 1)), Rundungsprobleme zu vermeiden. Dies löst das Problem von @DavidWolever.
Tim Tisdall
4
Für die Leute, die das obige Code-Snippet verwenden: clipsollte zur Liste der Numpy-Importe hinzugefügt werden.
Liam Deacon
26
Die andere Möglichkeit ist nur zu verwenden numpyund 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
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.
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:
defunit_vector(vector):""" Returns the unit vector of the vector"""return vector / np.linalg.norm(vector)
defangle(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, NotImplementedErroraber 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.
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 shapelyModul, 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.0defangle_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° * π/180if line1_vertical and line2_vertical:
# Perpendicular vertical linesreturn0.0if 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))
defslope(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)
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
quelle
math.sqrt(x)
äquivalent zux**0.5
undmath.pow(x,y)
äquivalent zu istx**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))
quelle
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.0unit_vector
. Eine Möglichkeit besteht darin, in dieser Funktion nur den Eingabevektor zurückzugeben, wenn dies der Fall ist.Mit numpy (sehr zu empfehlen) würden Sie Folgendes tun:
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
quelle
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 Innenwinkelimport 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
quelle
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:
Oder berechnen Sie den vorzeichenbehafteten Winkel per Projektion:
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.
quelle
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.quelle
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 NumbaUnd mit
quelle
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
quelle
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).
quelle
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 dasshapely
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
quelle