पायथन में दो एन-आयामी वैक्टर के बीच कोण


82

मुझे पायथन में दो एन-डायमेंशनल वैक्टर के बीच कोण (एस) निर्धारित करने की आवश्यकता है। उदाहरण के लिए, इनपुट निम्न की तरह दो सूचियां हो सकती हैं: [1,2,3,4]और [6,7,8,9]


1
यह सबसे अच्छा उत्तर है @ MK83 जैसा कि यह गणितीय अभिव्यक्ति थीटा = atan2 (u ^ v, uv) है। यहां तक ​​कि जहां u = [0 0] या v = [0 0] को कवर किया गया है क्योंकि यह केवल समय है atan2 अन्य उत्तरों में NaN का उत्पादन करेगा NaN का उत्पादन / मानदंड (u) या / standard (v) द्वारा किया जाएगा
पिलौपिली

जवाबों:


66
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


2
इसके अलावा, यदि आपको केवल कॉस, पाप, एंगल के टैन की आवश्यकता है, न कि एंगल की, तो आप cosine प्राप्त करने के लिए math.acos को छोड़ सकते हैं, और साइन प्राप्त करने के लिए क्रॉस उत्पाद का उपयोग कर सकते हैं।
मबेकिश

10
यह देखते हुए कि math.sqrt(x)के बराबर है x**0.5और math.pow(x,y)इसके बराबर है x**y, मुझे आश्चर्य है कि पायथन 2.x-> 3.0 संक्रमण के दौरान इन अतिरेक कुल्हाड़ियों से बच गए। व्यवहार में, मैं आमतौर पर इस प्रकार की संख्यात्मक चीजों को एक बड़ी कम्प्यूट-गहन प्रक्रिया के हिस्से के रूप में कर रहा हूं, और '**' के लिए दुभाषिया का समर्थन सीधे बायटेकोड BINARY_POWER के लिए जा रहा है, बनाम 'गणित' की खोज, पहुंच इसकी विशेषता 'sqrt', और फिर दर्द से धीमी गति से bytecode CALL_FUNCTION, बिना किसी कोडिंग या पठनीयता के गति में एक औसत दर्जे का सुधार कर सकता है।
पॉलमैक्स

5
जैसा कि सुन्न के जवाब में: यह विफल हो सकता है यदि राउंडिंग त्रुटि खेलने में आती है! यह समानांतर और विरोधी समानांतर वैक्टर के लिए हो सकता है!
बैंडगैप

2
नोट: यह विफल हो जाएगा यदि वैक्टर समान (पूर्व angle((1., 1., 1.), (1., 1., 1.))) हैं। थोड़ा और सही संस्करण के लिए मेरा जवाब देखें।
डेविड वोलेवर

2
यदि आप ऊपर कार्यान्वयन के बारे में बात कर रहे हैं, तो यह गोल त्रुटियों के कारण विफल हो जाता है, इसलिए नहीं कि वैक्टर समानांतर हैं।
पेस

153

नोट : यहां अन्य सभी उत्तर विफल हो जाएंगे यदि दोनों वैक्टर में एक ही दिशा (पूर्व (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गणित की लाइब्रेरी से एक के बजाय इसका उपयोग करना बेहतर नहीं होगा ? सिद्धांत रूप में वे समान होना चाहिए, लेकिन मैं अभ्यास में निश्चित नहीं हूं। किसी भी तरह से मुझे लगता है कि यह सुरक्षित होगा।
आदी

2
मेरा सुन्न (संस्करण == 1.12.1) arccosसीधे और सुरक्षित रूप से उपयोग कर सकता है । : [140] में: np.arccos (np.dot (np.array ([1,0,0]), np.array ([- 1,0,0])) आउट [140]: 3.1415926535898931 In [ 141]: np.arccos (np.dot (np.array ([1,0,0]), np.array ([1,0,0])) आउट [141]: 0.0
ene

2
विशेष मामला जहां कम से कम एक इनपुट वेक्टर शून्य वेक्टर छोड़ा गया है, जो कि विभाजन के लिए समस्याग्रस्त है unit_vector। एक संभावना यह है कि इस फ़ंक्शन में इनपुट वेक्टर को वापस कर दिया जाए जब यह मामला हो।
काफमैन

1
ang_between ((0, 0, 0), (0, 1, 0)) परिणाम के रूप में नान देगा, और 90 नहीं
FabioSpaghetti

2
@kafman 0-वैक्टर का कोण अपरिभाषित है (गणित में)। तो यह एक त्रुटि उठाता तथ्य अच्छा है।
उपयोगकर्ता

45

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

3
अंतिम पंक्ति में एक त्रुटि हो सकती है क्योंकि मुझे राउंडिंग त्रुटियों के कारण पता चला है। इस प्रकार यदि आप डॉट (यू, यू) / मानदंड (यू) ** 2 में यह 1.0000000002 में परिणाम देता है और आर्कॉस तब विफल हो जाता है (
एंटीपैरल

मैंने u = [1,1,1] के साथ परीक्षण किया है। u = [1,1,1,1] ठीक काम करता है, लेकिन हर आयाम ने 1 से थोड़ा बड़ा या
स्मालेर

3
नोट: यह विफल हो जाएगा (उपज nan) जब दो वैक्टर की दिशा समान या विपरीत होती है। अधिक सही संस्करण के लिए मेरा उत्तर देखें।
डेविड वोलेवर

2
इस पर नव की टिप्पणी को जोड़ते हुए, अंतिम पंक्ति को angle = arccos(clip(c, -1, 1))गोल मुद्दों से बचना चाहिए । यह @DavidWolever का मुद्दा हल करता है।
टिम टिसडल

4
ऊपर कोड स्निपेट का उपयोग करने वाले लोगों के लिए: clipसंख्यात्मक आयातों की सूची में जोड़ा जाना चाहिए।
लियाम डिकॉन

27

दूसरी संभावना बस उपयोग कर रही है 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

6
यह सबसे अच्छा उत्तर है क्योंकि यह गणितीय अभिव्यक्ति थीटा = atan2 (u ^ v, uv) है। और यह कभी विफल नहीं होता है!
पिलौपिली

1
यह 2-डी के लिए है। ओपी एनडी के लिए पूछ रहा था
23

3

यदि आप 3D वैक्टर के साथ काम कर रहे हैं, तो आप टूलबेल्ट वीजी का उपयोग करके इसे संक्षिप्त रूप से कर सकते हैं । यह सुन्न के ऊपर एक हल्की परत है।

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 में वर्बोज़ या अपारदर्शी हैं।


3

डेविड वोलेवर का समाधान अच्छा है, लेकिन

यदि आप हस्ताक्षरित कोण चाहते हैं तो आपको यह निर्धारित करना होगा कि क्या दी गई जोड़ी सही है या बाएं हाथ की है ( आगे की जानकारी के लिए विकि देखें )।

इसके लिए मेरा समाधान है:

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लेकिन मेरे मामले में यह अच्छी तरह से काम करता है। इस व्यवहार को ठीक किया जा सकता है (क्योंकि किसी भी जोड़ी के लिए हाथ का निर्धारण किया जाता है) लेकिन यह अधिक कोड लेता है जो मुझे चाहिए और लिखना होगा।


2

दो वैक्टर (एन-डायमेंशनल वेक्टर के लिए काम करता है) के बीच कोण खोजने का आसान तरीका,

पायथन कोड:

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

1

Sgt काली मिर्च के शानदार उत्तर पर निर्माण करना और संरेखित वैक्टर के लिए समर्थन जोड़ना और Numba का उपयोग करके 2x से अधिक स्पीडअप जोड़ना

@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 परिणाम बिना नंबा

  • 359 μs ± 2.86 μs प्रति लूप (मतलब ± एसटीडी। देव। 7 रनों की, 1000 छोरों प्रत्येक)

और साथ

  • 151 151s n 820 ns प्रति लूप (मतलब। Std। 7 रन का देव, 10000 लूप प्रत्येक)

0

सुन्न का उपयोग करना और 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 से विभाजित करें)।


0

जो कुछ (एसईओ जटिलताओं के कारण) हो सकता है, उसके लिए यहां दो लाइनों के बीच के कोण की गणना करने की कोशिश कर रहे हैं , जैसे कि (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
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.