Python'da iki n boyutlu vektör arasındaki açılar


82

Python'da iki n boyutlu vektör arasındaki açıları belirlemem gerekiyor. Örneğin, giriş aşağıdaki gibi iki liste olabilir: [1,2,3,4]ve [6,7,8,9].


1
Bu en iyi cevap @ MK83'dür çünkü matematiksel ifade teta = atan2 (u ^ v, uv) 'dir. u = [0] veya v = [0] kapsansa bile, bu sadece atan2'nin diğer cevaplarda NaN'yi üreteceği zamandır NaN / norm (u) veya / norm (v) tarafından üretilecektir.
PilouPili

Yanıtlar:


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)))

Not : Vektörler aynı veya zıt yöne sahip olduğunda bu başarısız olur. Doğru uygulama burada: https://stackoverflow.com/a/13849249/71522


2
Ayrıca, yalnızca cos, sin, tan of angle'e ihtiyacınız varsa ve açının kendisine ihtiyacınız yoksa, kosinüsü elde etmek için math.acos'u atlayabilir ve sinüs elde etmek için çapraz çarpımı kullanabilirsiniz.
mbeckish

10
Olduğu göz önüne alındığında math.sqrt(x)eşdeğerdir x**0.5ve math.pow(x,y)eşdeğerdir x**y, ben bu fazlalık balta Python 2.x-> 3.0 geçişi sırasında wielded hayatta şaşırttı. Pratikte, genellikle bu tür sayısal şeyleri daha büyük bir işlem yoğun sürecin parçası olarak yapıyorum ve yorumlayıcının '**' desteği doğrudan BINARY_POWER bayt koduna, 'matematik' araması yerine erişim 'sqrt' özniteliği ve ardından son derece yavaş olan CALL_FUNCTION bayt kodu, hiçbir kodlama veya okunabilirlik maliyeti olmaksızın hızda ölçülebilir bir gelişme sağlayabilir.
PaulMcG

5
Numpy ile verilen cevapta olduğu gibi: Yuvarlama hatası devreye girerse bu başarısız olabilir! Bu, paralel ve anti-paralel vektörler için olabilir!
BandGap

2
Not: Vektörler aynı ise (örn .) Bu başarısız olacaktırangle((1., 1., 1.), (1., 1., 1.)) . Biraz daha doğru bir versiyon için cevabıma bakın.
David Wolever

2
Yukarıdaki uygulamadan bahsediyorsanız, vektörlerin paralel olması nedeniyle değil, yuvarlama hataları nedeniyle başarısız olur.
Tempo

153

Not : İki vektör aynı yöne (ör. (1, 0, 0), (1, 0, 0)) Veya zıt yönlere (ör . (-1, 0, 0), (1, 0, 0)) Sahipse buradaki diğer yanıtların tümü başarısız olacaktır .

İşte bu durumları doğru bir şekilde ele alacak bir işlev:

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.isnanMatematik kütüphanesindeki yerine kullanmak daha iyi olmaz mıydı ? Teoride aynı olmalılar, ancak pratikte pek emin değilim. Her iki durumda da daha güvenli olacağını düşünürdüm.
Hooked

2
Benim numpy (version == 1.12.1) arccosdoğrudan ve güvenle kullanabilir . : Giriş [140]: np.arccos (np.dot (np.array ([1,0,0]), np.array ([- 1,0,0]))) Çıkış [140]: 3,1415926535897931 Giriş [ 141]: np.arccos (np.dot (np.array ([1,0,0]), np.array ([1,0,0]))) Çıkış [141]: 0,0
ene

2
En az bir giriş vektörünün sıfır vektörü olduğu özel durum atlanır, bu da bölme için sorunludur unit_vector. Bir olasılık, bu durumda, bu fonksiyonda sadece giriş vektörünü döndürmektir.
kafman

1
angle_between ((0, 0, 0), (0, 1, 0)) sonucu nan vermek olup 90 olacaktır
FabioSpaghetti

2
@kafman 0-vektörlerin açısı tanımsızdır (matematikte). Yani bir hata yaratması gerçeği iyidir.
kullanıcı

45

Numpy kullanarak (şiddetle tavsiye edilir) şunları yaparsınız:

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
Son satır, yuvarlama hataları nedeniyle bulduğum gibi bir hatayla sonuçlanabilir. Dolayısıyla, (u, u) / norm (u) ** 2'ye nokta koyarsanız, 1.0000000002 ile sonuçlanır ve arccos başarısız olur (antiparalel vektörler için de 'çalışır')
BandGap

U = [1,1,1] ile test ettim. u = [1,1,1,1] iyi çalışıyor ancak eklenen her boyut 1'den biraz daha büyük veya daha küçük değerler döndürüyor ...
BandGap

3
Not: İki vektörün yönü aynı veya zıt olduğunda bu başarısız olur (verim nan). Daha doğru bir versiyon için cevabıma bakın.
David Wolever

2
Buna neo'nun yorumunu ekleyerek, son satırda angle = arccos(clip(c, -1, 1))yuvarlama sorunlarından kaçınmak gerekir . Bu, @DavidWolever'ın sorununu çözer.
Tim Tisdall

4
Yukarıdaki kod parçacığını kullanan kişiler için: clipnumpy içe aktarmalar listesine eklenmelidir.
Liam Deacon

27

Diğer olasılık sadece kullanmaktır numpyve size iç açıyı verir

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)

ve işte çıktı:

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
Bu tam olarak matematiksel ifade teta = atan2 (u ^ v, uv) olduğu için en iyi cevaptır. Ve bu asla başarısız olmaz!
PilouPili

1
Bu 2-D içindir. OP
nD'yi

3

3B vektörlerle çalışıyorsanız, bunu araç kemeri vg kullanarak kısaca yapabilirsiniz . Uyuşukluğun üstünde hafif bir tabakadır.

import numpy as np
import vg

vec1 = np.array([1, 2, 3])
vec2 = np.array([7, 8, 9])

vg.angle(vec1, vec2)

Açıyı yansıtma yoluyla hesaplamak için bir görüntüleme açısı da belirleyebilirsiniz:

vg.angle(vec1, vec2, look=vg.basis.z)

Veya işaret açısını projeksiyon yoluyla hesaplayın:

vg.signed_angle(vec1, vec2, look=vg.basis.z)

Kütüphaneyi son girişimde oluşturdum, bu tür kullanımlarla motive edildi: NumPy'de ayrıntılı veya opak olan basit fikirler.


3

David Wolever'ın çözümü iyidir, ancak

Eğer sahip olmak istiyorsanız imzalanan açıları verilen bir çifti haklı ya da (bkz teslim sol eğer belirlemek zorunda wiki ileri bilgi için).

Bunun için benim çözümüm:

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))

Bu NotImplementedErroryüzden mükemmel değil ama benim durumum için iyi çalışıyor. Bu davranış düzeltilebilir (çünkü herhangi bir çift için ellilik belirlenir) ancak istediğim ve yazmak zorunda olduğum daha fazla kod gerektirir.


2

İki vektör arasındaki açıyı bulmanın kolay yolu (n boyutlu vektör için çalışır),

Python kodu:

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 Biber'in harika cevabını temel almak ve hizalanmış vektörler için destek eklemek, ayrıca Numba kullanarak 2 kattan fazla hız artışı eklemek

@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 içermeyen sonuçlar

  • Döngü başına 359 µs ± 2.86 µs (ortalama ± std. Sapma 7 çalıştırma, her biri 1000 döngü)

Ve birlikte

  • 151 µs ± 820 ns döngü başına (ortalama ± std. Sapma 7 çalıştırma, her biri 10000 döngü)

0

Numpy kullanmak ve BandGap'in yuvarlama hatalarını dikkate almak:

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)

Bu fonksiyon, vektörlerden biri sıfır büyüklüğe sahipse (0'a bölün) bir istisna atacaktır.


0

Geometrik çizgilerde olduğu gibi python'daki iki çizgi arasındaki açıyı hesaplamaya çalışırken burada sona eren (SEO komplikasyonları nedeniyle) birkaç kişi (x0, y0), (x1, y1)için, aşağıdaki minimum çözüm vardır ( shapelymodülü kullanır , ancak kolayca değiştirilemez):

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)

Ve kullanım olurdu

>>> 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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.