[英]Implementing 3D vectors in Python: numpy vs x,y,z fields
我正在用Python實現3D Vector類。 我的向量有坐標x,y和z(所有浮點數),我需要決定如何存儲這些信息。 我在這里至少可以看到三個選項:
1)制作三個獨立的浮點字段:self.x,self.y,self.z
class Vector:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
2)制作一個列表,比如說self.data,有三個元素。 如果對象可以是常量,我也可以使用元組。
class Vector:
def __init__(self, x, y, z):
self.data = [x,y,z]
3)制作一個numpy數組,比如self.data,有三個元素。
import numpy as np
class Vector:
def __init__(self, x, y, z):
self.data = np.array([x,y,z])
對於選項(2)和(3),我可以實現屬性和設置器來訪問單個坐標
@property
def x(self):
return self.data[0]
4)為什么不進行冗余? 我可以同時擁有一個列表(或元組或numpy數組)和單獨的字段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
沒有定義內置向量旋轉,但有幾個可用的源,例如“3D向量的旋轉” 。 所以你需要自己實現它。
您可以創建_how您存儲屬性的一類,獨立的,並提供__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-array。 如果你的類中存儲了一個numpy-array,那么__array__
方法可以簡單地返回它,這樣就可以將你的x
, y
和z
存儲為內部的numpy.ndarray
(因為那基本上是“免費的”)。
你可以np.ndarray
。 我不會在這里詳細介紹,因為這是一個高級主題,可以很容易地證明整個答案本身。 如果您真的考慮到這一點,那么您應該查看“Subclassing ndarray”的官方文檔。 我不推薦它,我參與了幾個子類np.ndarray
類,並且在np.ndarray
有幾個“粗糙的egdes”。
您可以自己實施所需的操作。 這是重新發明的輪子,但它具有教育性和趣味性 - 如果它們只有少數幾個。 我不推薦這個用於嚴肅的制作,因為這里也有幾個已經在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
注意:您可以實現這些可編碼的方法並實現我之前提到的__array__
方法。 這樣你就可以支持任何期望numpy.ndarray
函數,也可以使用你自己開發的方法。 這些方法並不相互排斥,但你會有不同的結果,上述回報方法的標量或Vector
,但如果你通過__array__
你會得到numpy.ndarray
的背部。
使用包含3D矢量的庫。 從某種意義上說,這是其他方面最簡單的方法,它可能非常復雜。 從好的方面來說,現有的類可能是開箱即用的,它可能在性能方面進行了優化。 另一方面,您需要找到一個支持您的用例的實現,您需要閱讀文檔(或通過其他方式弄清楚它是如何工作的),並且您可能會遇到對您的項目來說非常糟糕的錯誤或限制。 啊,你得到一個額外的依賴,你需要檢查許可證是否與您的項目兼容。 另外,如果您復制實現(檢查許可證是否允許!),您需要維護(即使它只是同步)外部代碼。
在這種情況下,性能很棘手,所提到的用例非常簡單,每個任務應該是微秒級 - 所以你應該能夠每秒執行幾千到幾百萬次操作。 假設你沒有引入不必要的瓶頸! 但是,您可以微觀優化操作。
讓我從一些一般的tipps開始:
避免使用numpy.ndarray
< - > list
/ float
操作。 這些都很貴! 如果大多數操作使用numpy.ndarray
,則不希望將值存儲在列表中或作為單獨的屬性存儲。 同樣,如果要訪問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向量化運算。 其中大多數與您描述的類不兼容,並且完全不同的方法可能是合適的:例如,以與numpys函數正確接口的方式存儲數組向量數組(多維數組)的類! 但我認為這個答案超出了范圍,並且不會真正回答你的問題,這個問題僅限於只存儲3個值的類。
我用不同的方法使用相同的方法做了一些基准測試,但這有點作弊。 通常,您不應該為一個函數調用計時, 您應該測量程序的執行時間 。 在程序中,被稱為數百萬次的函數中的微小速度差異可以比僅僅被稱為幾次的方法中的大速度差異產生更大的整體差異....或者不是! 我只能為函數提供時間,因為您沒有共享程序或用例,因此您需要找出哪種方法最適合您(正確性和性能)。
還有其他幾個因素需要考慮哪種方法最好,但這些因素更多是“元”因素,與您的計划沒有直接關系。
重新發明輪子(自己實現功能)是一個學習的機會。 你需要確保它正常工作,你可以計時,如果它太慢,你可以嘗試不同的方法來優化它。 你開始考慮算法復雜性,常數因素,正確性......而不是考慮“哪個函數將解決我的問題”或“我如何使numpy函數正確解決我的問題”。
使用NumPy進行長度為3的陣列可能就像“在蒼蠅中用大炮射擊”,但這是一個很熟悉numpy功能的好機會,將來你會更多地了解NumPy如何工作(矢量化,索引,廣播, ...)即使NumPy不適合這個問題和答案。
嘗試不同的方法,看看你有多遠。 我在回答這個問題時學到了很多東西,嘗試這些方法很有趣 - 比較差異的結果,調整方法調用的時間並評估它們的局限性!
考慮到使用Vector
類,我更喜歡選項-3。 由於它產生numpy數組,因此通過使用numpy,向量操作相對簡單,直觀且快速。
In [81]: v1 = Vector(1.0, 2.0, 3.0)
In [82]: v2 = Vector(0.0, 1.0, 2.0)
In [83]: v1.data + v2.data
Out[83]: array([1.0, 3.0, 5.0])
In [85]: np.inner(v1.data, v2.data)
Out[85]: 8.0
這些操作已經在numpy性能方面得到了很好的優化。
如果一個簡單的矢量類型行為是你的目標,絕對堅持純粹的numpy解決方案。 這件事情是由很多原因導致的:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.