提供不同线性代数后端的架构
我正在用Python开发一个新系统,这个系统主要是处理数字计算。
一个重要的要求是能够使用不同的线性代数后端:从用户自己实现的代码到一些通用的库,比如Numpy。线性代数的实现(也就是后端)必须和接口是独立的。
我最初的架构尝试是这样的:
(1) 定义系统接口
>>> v1 = Vector([1,2,3])
>>> v2 = Vector([4,5,6])
>>> print v1 * v2
>>> # prints "Vector([4, 10, 18])"
(2) 实现代码,使得可以独立于后端使用这个接口
# this example uses numpy as the back-end, but I mean
# to do this for a general back-end
import numpy
def numpy_array(*args): # creates a numpy array from the arguments
return numpy.array(*args)
class VectorBase(type):
def __init__(cls, name, bases, attrs):
engine = attrs.pop("engine", None)
if not engine:
raise RuntimeError("you need to specify an engine")
# this implementation would change depending on `engine`
def new(cls, *args):
return numpy_array(*args)
setattr(cls, "new", classmethod(new))
class Vector(object):
__metaclass__ = VectorBase
# I could change this at run time
# and offer alternative back-ends
engine = "numpy"
@classmethod
def create(cls, v):
nv = cls()
nv._v = v
return nv
def __init__(self, *args):
self._v = None
if args:
self._v = self.new(*args)
def __repr__(self):
l = [item for item in self._v]
return "Vector(%s)" % repr(l)
def __mul__(self, other):
try:
return Vector.create(self._v * other._v)
except AttributeError:
return Vector.create(self._v * other)
def __rmul__(self, other):
return self.__mul__(other)
这个简单的例子是这样工作的:Vector
类保存了一个由后端创建的向量实例的引用(在这个例子中是numpy.ndarray
);所有的数学运算都是由接口来实现的,但具体的计算是交给后端去处理。
实际上,接口重载了所有合适的运算符,并将计算交给后端(这个例子只展示了__mul__
和__rmul__
,但你可以想象其他操作也是这样处理的)。
我愿意在性能上做一些牺牲,以换取可定制性。虽然我的例子可以运行,但感觉不太对——我觉得这样会让后端因为太多的构造函数调用而变得很笨重!这就需要一种不同的metaclass
实现,以便更好地处理调用的延迟。
那么,你会建议我怎么实现这个功能呢?我想强调的是,保持系统中所有的Vector
实例在功能上是一致的,并且与线性代数后端是独立的,这一点非常重要。
3 个回答
顺便说一下,你可以很简单地配置和构建NumPy,让它使用Intel的数学核心库或者AMD的核心数学库,而不是通常的ATLAS + LAPACK。这其实很简单,只需要创建一个site.cfg文件,里面设置好blas_libs
、lapack_libs
、library_dirs
和include_dirs
这些变量就行了。(关于如何为MKL和ACML设置这些选项,网上有很多资料可以查。)把这个文件放在setup.py
脚本旁边,然后像往常一样进行构建。
如果你想在这些标准的线性代数库之间切换,可以为每个库构建一个不同的NumPy实例,并使用virtualenvs来管理它们。
我知道这样做可能没有你想要的灵活性,无法使用你自己的自定义数学库,但我只是想提一下这个方法。而且虽然我没有深入研究过,但我想你可能也能让NumPy与自定义库一起构建,这样做的工作量可能比自己从头开始构建一个前端要少,尤其是如果你想保留NumPy/SciPy的丰富功能的话。
为什么不直接创建一个“虚拟”类(AbstractVector
),这个类就像你例子中的Vector,然后为每种实现创建不同的子类呢?
你可以通过像 Vector = NumPyVector
这样的方式来选择引擎。