天天看點

python三維向量運算,在Python中實作3D向量:numpy vs x,y,z字段

這個問題有不同的方面,我可以給你一些提示,告訴你如何解決這些問題。請注意,這些都是建議,你肯定需要看看你最喜歡哪一個。

支援線性代數

您提到要支援線性代數,例如向量加法(元素加法)、叉積和内積。這些可用于numpy.ndarray,是以您可以選擇不同的方法來支援它們:隻需使用一個numpy.ndarray,而不必為自己的類操心:import numpy as np

vector1, vector2 = np.array([1, 2, 3]), np.array([3, 2, 1])

np.add(vector1, vector2) # vector addition

np.cross(vector1, vector2) # cross product

np.inner(vector1, vector2) # inner product

在numpy中沒有定義内建向量旋轉,但是有幾個可用的源,例如"Rotation of 3D vector"。是以你需要自己實作它。

您可以建立一個類,獨立于存儲屬性的方式,并提供一個__array__方法。這樣,您就可以支援(所有)numpy函數,就像您的執行個體本身是numpy.ndarray一樣:class VectorArrayInterface(object):

def __init__(self, x, y, z):

self.x, self.y, self.z = x, y, z

def __array__(self, dtype=None):

if dtype:

return np.array([self.x, self.y, self.z], dtype=dtype)

else:

return np.array([self.x, self.y, self.z])

vector1, vector2 = VectorArrayInterface(1, 2, 3), VectorArrayInterface(3, 2, 1)

np.add(vector1, vector2) # vector addition

np.cross(vector1, vector2) # cross product

np.inner(vector1, vector2) # inner product

這将傳回與第一種情況相同的結果,是以您可以為numpy函數提供接口,而無需使用numpy數組。如果您的類中存儲了一個numpy數組,那麼__array__方法可以簡單地傳回它,是以這可能是一個參數,用于在内部将x、y和z存儲為numpy.ndarray(因為這基本上是“免費的”)。

您可以子類化np.ndarray。我不想在這裡詳細讨論,因為這是一個進階的話題,可以很容易地證明一個完整的答案是正确的。如果您真的考慮到這一點,那麼您應該看看"Subclassing ndarray"的官方文檔。我不推薦這樣做,我在幾個類上做了子類np.ndarray并且在這條路上有幾個“粗糙的egde”。

你可以自己實作你需要的操作。這是重新發明輪子,但它的教育和樂趣-如果隻有少數。我不建議在嚴肅的生産中使用這個,因為這裡還有幾個已經在numpy函數中處理過的“粗糙邊”。例如溢出或下溢問題,函數的正确性。。。

可能的實作(不包括旋轉)可能如下所示(這次是使用内部存儲的清單):class VectorList(object):

def __init__(self, x, y, z):

self.vec = [x, y, z]

def __repr__(self):

return '{self.__class__.__name__}(x={self.vec[0]}, y={self.vec[1]}, z={self.vec[2]})'.format(self=self)

def __add__(self, other):

x1, y1, z1 = self.vec

x2, y2, z2 = other.vec

return VectorList(x1+x2, y1+y2, z1+z2)

def crossproduct(self, other):

x1, y1, z1 = self.vec

x2, y2, z2 = other.vec

return VectorList(y1*z2 - z1*y2,

z1*x2 - x1*z2,

x1*y2 - y1*x1)

def scalarproduct(self, other):

x1, y1, z1 = self.vec

x2, y2, z2 = other.vec

return x1*x2 + y1*y2 + z1*z2

注意:您可以實作這些can編碼方法,并實作前面提到的__array__方法。這樣,您就可以支援任何期望值為numpy.ndarray的函數,并且還可以擁有自己的方法。這些方法不是獨占的,但是您将得到不同的結果,上面的方法傳回标量或Vector,但是如果您通過__array__,您将得到numpy.ndarray。

使用包含三維矢量的庫。從某種意義上說,這是其他方面最簡單的方法,可能非常複雜。另一方面,現有的類可能會開箱即用,而且可能在性能方面得到了優化。另一方面,您需要找到一個支援您的用例的實作,您需要閱讀文檔(或通過其他方式了解它是如何工作的),并且您可能會遇到錯誤或限制,這些錯誤或限制最終會對您的項目造成影響。啊,還有一個附加的依賴項,您需要檢查許可證是否與您的項目相容。另外,如果您複制了實作(請檢查許可證是否允許這樣做!)你需要維護(即使隻是同步)外來代碼。

性能

在這種情況下,性能是很棘手的,上面提到的用例非常簡單,每個任務的順序應該是微秒——是以您應該已經能夠每秒執行幾千到一百萬個操作。假設你沒有引入不必要的瓶頸!但是,您可以對操作進行微觀優化。

讓我從一些一般的技巧開始:避免numpy.ndarray<->list/float操作。這些東西很貴!如果大多數操作使用numpy.ndarrays,則不希望存儲va清單中的值或作為單獨的屬性。同樣,如果您想通路Vector的各個值,或者對這些值進行疊代,或者對它們執行list操作,那麼将它們存儲為清單或單獨的屬性。

使用numpy對三個值進行操作相對來說效率較低。numpy.ndarray對于大型數組來說非常好,因為它可以更有效地存儲值(空間)并比純python操作具有更好的伸縮性。然而,這些優點有一些開銷,這對于小數組來說是非常重要的(比如length << 100,這是一個有根據的猜測,而不是一個固定的數字!)。對于這樣小的數組,python解決方案(我使用上面已經介紹過的解決方案)可能比numpy解決方案快得多:class VectorArray:

def __init__(self, x, y, z):

self.data = np.array([x,y,z])

# addition: python solution 3 times faster

%timeit VectorList(1, 2, 3) + VectorList(3, 2, 1)

# 100000 loops, best of 3: 9.48 µs per loop

%timeit VectorArray(1, 2, 3).data + VectorArray(3, 2, 1).data

# 10000 loops, best of 3: 35.6 µs per loop

# cross product: python solution 16 times faster

v = Vector(1, 2, 3)

a = np.array([1,2,3]) # using a plain array to avoid the class-overhead

%timeit v.crossproduct(v)

# 100000 loops, best of 3: 5.27 µs per loop

%timeit np.cross(a, a)

# 10000 loops, best of 3: 84.9 µs per loop

# inner product: python solution 4 times faster

%timeit v.scalarproduct(v)

# 1000000 loops, best of 3: 1.3 µs per loop

%timeit np.inner(a, a)

# 100000 loops, best of 3: 5.11 µs per loop

不過,正如我所說,這些計時是微秒級的,是以這實際上是微觀優化。但是,如果您的重點是類的最佳性能,那麼使用純python和自實作函數可以更快。

一旦你嘗試做很多線性代數運算,你就應該利用numpys向量化運算。其中大多數與您描述的類不相容,完全不同的方法可能是合适的:例如,一個類以與numpy函數正确接口的方式存儲數組向量數組(多元數組)!但我認為這超出了這個答案的範圍,也不會真正回答您的問題,因為這個問題僅限于一個隻存儲3個值的類。

我用同樣的方法用不同的方法做了一些基準測試,但是這有點作弊。一般來說,您不應該為一個函數調用計時,您應該測量程式的執行時間。在程式中,一個被稱為數百萬次的函數中的一個微小的速度差可以比一個隻被稱為幾次的方法中的一個巨大的速度差産生更大的整體差異。。。。或者不!我隻能提供函數的計時,因為您沒有共享程式或用例,是以您需要找到最适合您的方法(正确性和性能)。

結論

還有其他幾個因素可以考慮哪種方法是最好的,但這些都是“元”原因,與您的程式沒有直接關系。重新發明輪子(自己實作功能)是一個學習的機會。你需要確定它工作正常,你可以計時,如果它太慢,你可以嘗試不同的方法來優化它。你開始考慮算法的複雜性,常數因子,正确性。。。而不是考慮“哪個函數将解決我的問題”或“我如何使那個numpy函數正确地解決我的問題”。

對length-3數組使用NumPy可能就像“用大炮向蒼蠅射擊”一樣,但這是一個很好的機會,可以讓您更加熟悉NumPy的功能,将來您将更加了解NumPy的工作原理(矢量化、索引、廣播等),即使NumPy不适合這個問題和答案。

嘗試不同的方法,看看你能走多遠。我在回答這個問題時學到了很多,嘗試這些方法很有趣——比較結果是否有差異,計時方法調用并評估它們的局限性!