Tôi cần xác định (các) góc giữa hai vectơ n-chiều trong Python. Ví dụ: đầu vào có thể là hai danh sách như sau: [1,2,3,4]
và [6,7,8,9]
.
Tôi cần xác định (các) góc giữa hai vectơ n-chiều trong Python. Ví dụ: đầu vào có thể là hai danh sách như sau: [1,2,3,4]
và [6,7,8,9]
.
Câu trả lời:
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)))
Lưu ý : điều này sẽ không thành công khi các vectơ có cùng hướng hoặc ngược hướng. Cách triển khai chính xác ở đây: https://stackoverflow.com/a/13849249/71522
math.sqrt(x)
tương đương x**0.5
và math.pow(x,y)
tương đương với x**y
, tôi ngạc nhiên vì chúng sống sót sau chiếc rìu dư thừa được sử dụng trong quá trình chuyển đổi Python 2.x-> 3.0. Trong thực tế, tôi thường làm những việc kiểu số này như một phần của quy trình máy tính chuyên sâu hơn và sự hỗ trợ của trình thông dịch cho '**' sẽ chuyển trực tiếp đến mã bytecode BINARY_POWER, so với tra cứu 'toán học', quyền truy cập đối với thuộc tính 'sqrt' của nó, và sau đó là mã bytecode CALL_FUNCTION chậm một cách đáng kinh ngạc, có thể tạo ra một sự cải thiện có thể đo lường được về tốc độ mà không cần mã hóa hoặc khả năng đọc.
angle((1., 1., 1.), (1., 1., 1.))
:). Xem câu trả lời của tôi cho một phiên bản đúng hơn một chút.
Lưu ý : tất cả các câu trả lời khác ở đây sẽ không thành công nếu hai vectơ có cùng hướng (ví dụ (1, 0, 0)
, (1, 0, 0)
) hoặc ngược hướng (ví dụ (-1, 0, 0)
, (1, 0, 0)
).
Đây là một chức năng sẽ xử lý chính xác các trường hợp này:
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
thay vì một từ thư viện toán học? Về lý thuyết, chúng phải giống hệt nhau, nhưng tôi không chắc trong thực tế. Dù thế nào thì tôi cũng tưởng tượng sẽ an toàn hơn.
arccos
trực tiếp và an toàn. : Trong [140]: np.arccos (np.dot (np.array ([1,0,0]), np.array ([- 1,0,0]))) Hết [140]: 3,1415926535897931 Trong [ 141]: np.arccos (np.dot (np.array ([1,0,0]), np.array ([1,0,0]))) Hết [141]: 0,0
unit_vector
. Một khả năng là chỉ trả về vectơ đầu vào trong hàm này khi trường hợp này xảy ra.
Sử dụng numpy (rất khuyến khích), bạn sẽ làm được:
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
) khi hướng của hai vectơ giống hệt nhau hoặc ngược chiều. Xem câu trả lời của tôi để có phiên bản chính xác hơn.
angle = arccos(clip(c, -1, 1))
để tránh các vấn đề làm tròn. Điều này giải quyết vấn đề của @DavidWolever.
clip
nên được thêm vào danh sách các nhập khẩu không cần thiết.
Khả năng khác là sử dụng chỉ numpy
và nó cung cấp cho bạn góc nội thất
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)
và đây là đầu ra:
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
Nếu bạn đang làm việc với vectơ 3D, bạn có thể thực hiện điều này một cách chính xác bằng cách sử dụng vg đai công cụ . Đó là một lớp nhẹ trên đầu trang của numpy.
import numpy as np
import vg
vec1 = np.array([1, 2, 3])
vec2 = np.array([7, 8, 9])
vg.angle(vec1, vec2)
Bạn cũng có thể chỉ định góc xem để tính toán góc thông qua phép chiếu:
vg.angle(vec1, vec2, look=vg.basis.z)
Hoặc tính toán góc đã ký thông qua phép chiếu:
vg.signed_angle(vec1, vec2, look=vg.basis.z)
Tôi đã tạo thư viện ở lần khởi động cuối cùng của mình, nơi nó được thúc đẩy bởi những cách sử dụng như thế này: những ý tưởng đơn giản nhưng dài dòng hoặc không rõ ràng trong NumPy.
Giải pháp của David Wolever là tốt, nhưng
Nếu bạn muốn có các góc có chữ ký, bạn phải xác định xem một cặp đã cho là thuận tay phải hay trái (xem wiki để biết thêm thông tin).
Giải pháp của tôi cho điều này là:
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))
Nó không hoàn hảo vì điều này NotImplementedError
nhưng đối với trường hợp của tôi, nó hoạt động tốt. Hành vi này có thể được khắc phục (vì độ thuận tay được xác định cho bất kỳ cặp nào nhất định) nhưng tôi cần nhiều mã hơn mà tôi muốn và phải viết.
Xây dựng dựa trên câu trả lời tuyệt vời của sgt Pepper và thêm hỗ trợ cho các vectơ được căn chỉnh cộng với việc tăng tốc hơn 2 lần bằng 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
kết quả không có Numba
Và với
Cách dễ dàng để tìm góc giữa hai vectơ (hoạt động đối với vectơ n chiều),
Mã 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
Sử dụng numpy và xử lý các lỗi làm tròn của 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)
Lưu ý, hàm này sẽ ném ra một ngoại lệ nếu một trong các vectơ có độ lớn bằng 0 (chia cho 0).
Đối với một số người có thể có (do các biến chứng SEO) đã kết thúc ở đây khi cố gắng tính toán góc giữa hai đường trong python, như trong (x0, y0), (x1, y1)
các đường hình học, có giải pháp tối thiểu dưới đây (sử dụng shapely
mô-đun, nhưng có thể dễ dàng sửa đổi không):
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)
Và việc sử dụng sẽ là
>>> 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