Мені потрібно визначити кут (и) між двома n-мірними векторами в Python. Наприклад, вхідними даними можуть бути два списки, наприклад, такі: [1,2,3,4]
та [6,7,8,9]
.
Мені потрібно визначити кут (и) між двома n-мірними векторами в Python. Наприклад, вхідними даними можуть бути два списки, наприклад, такі: [1,2,3,4]
та [6,7,8,9]
.
Відповіді:
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
math.sqrt(x)
це еквівалентно x**0.5
та math.pow(x,y)
еквівалентно x**y
, я здивований, що вони пережили надмірність сокири, яка використовувалася під час переходу Python 2.x-> 3.0. На практиці я зазвичай роблю подібні числові речі як частину більшого обчислювального процесу, а також підтримка інтерпретатора для **, що прямує до байт-коду BINARY_POWER, проти пошуку `` математики '', доступу до його атрибуту 'sqrt', а потім до болісно повільного байт-коду CALL_FUNCTION, можна помітно поліпшити швидкість без витрат на кодування та читабельність.
angle((1., 1., 1.), (1., 1., 1.))
). Дивіться мою відповідь на дещо правильнішу версію.
Примітка : всі інші відповіді тут не вийде, якщо два вектора мають або в одному напрямку (наприклад, (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
замість того, що з математичної бібліотеки? Теоретично вони повинні бути однаковими, але я не зовсім впевнений на практиці. У будь-якому випадку я б уявив, що це було б безпечніше.
arccos
безпосередньо та безпечно. : У [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
. Одна з можливостей - просто повернути вхідний вектор у цій функції, коли це так.
Використовуючи 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
nan
), коли напрямок двох векторів буде однаковим або протилежним. Дивіться мою відповідь для більш правильної версії.
angle = arccos(clip(c, -1, 1))
щоб уникнути проблем із округленням. Це вирішує проблему @DavidWolever.
clip
слід додати до списку numpy-імпорту.
Інша можливість - використання просто, 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
Якщо ви працюєте з 3D-векторами, ви можете зробити це стисло, використовуючи інструментальну стрічку vg . Це легкий шар поверх numpy.
import numpy as np
import vg
vec1 = np.array([1, 2, 3])
vec2 = np.array([7, 8, 9])
vg.angle(vec1, vec2)
Ви також можете вказати кут огляду для обчислення кута за допомогою проекції:
vg.angle(vec1, vec2, look=vg.basis.z)
Або обчисліть підписаний кут за допомогою проекції:
vg.signed_angle(vec1, vec2, look=vg.basis.z)
Я створив бібліотеку під час свого останнього запуску, де вона була мотивована таким використанням: простими ідеями, які є багатослівними або непрозорими в NumPy.
Рішення Девіда Волевера - це добре, але
Якщо ви хочете мати підписані кути, вам слід визначити, чи є дана пара правшею чи лівшею (див. Wiki для отримання додаткової інформації).
Моє рішення для цього:
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))
Це не ідеально через це, NotImplementedError
але в моєму випадку це працює добре. Цю поведінку можна виправити (тому що придатність визначається для будь-якої пари), але потрібно більше коду, який я хочу і повинен написати.
Простий спосіб знайти кут між двома векторами (працює для n-мірного вектора),
Код Python:
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
Спираючись на чудову відповідь sgt pepper додавши підтримку вирівняних векторів, а також додавши прискорення понад 2x за допомогою 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
результати без Numba
І с
Використовуючи numpy та піклуючись про помилки округлення 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)
Зверніть увагу, ця функція видасть виняток, якщо один із векторів має нульову величину (ділимо на 0).
Для тих небагатьох, хто, можливо, (через ускладнення SEO) закінчив тут, намагаючись обчислити кут між двома лініями в python, як і в (x0, y0), (x1, y1)
геометричних лініях, є нижченаведене мінімальне рішення (використовує shapely
модуль, але його можна легко змінити, щоб не):
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)
І використання було б
>>> 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