我有一个稍微不寻常的问题,但我试图避免重新编码 FFT。
一般来说,我想知道这一点:如果我有一个为 type 实现的算法float
,但它可以在任何定义了一组操作的地方工作(例如,复数,也定义了+
,*
...),什么是在支持这些操作的另一种类型上使用该算法的最佳方法是什么?在实践中,这很棘手,因为通常数字算法是为了速度而不是通用性而编写的。
具体来说:
我正在处理具有非常高动态范围的值,因此我想将它们存储在日志空间中(主要是为了避免下溢)。
我想要的是一些系列的 FFT 的日志:
x = [1,2,3,4,5]
fft_x = [ log( x_val ) for x_val in fft(x) ]
即使这样也会导致严重的下溢。我想要的是存储日志值并使用+
代替*
和logaddexp
代替+
等。
我对如何做到这一点的想法是实现一个简单的 LogFloat 类,它定义了这些原始操作(但在日志空间中操作)。然后我可以通过让它使用我记录的值来简单地运行 FFT 代码。
class LogFloat:
def __init__(self, sign, log_val):
assert(float(sign) in (-1, 1))
self.sign = int(sign)
self.log_val = log_val
@staticmethod
def from_float(fval):
return LogFloat(sign(fval), log(abs(fval)))
def __imul__(self, lf):
self.sign *= lf.sign
self.log_val += lf.log_val
return self
def __idiv__(self, lf):
self.sign *= lf.sign
self.log_val -= lf.log_val
return self
def __iadd__(self, lf):
if self.sign == lf.sign:
self.log_val = logaddexp(self.log_val, lf.log_val)
else:
# subtract the smaller magnitude from the larger
if self.log_val > lf.log_val:
self.log_val = log_sub(self.log_val, lf.log_val)
else:
self.log_val = log_sub(lf.log_val, self.log_val)
self.sign *= -1
return self
def __isub__(self, lf):
self.__iadd__(LogFloat(-1 * lf.sign, lf.log_val))
return self
def __pow__(self, lf):
# note: there may be a way to do this without exponentiating
# if the exponent is 0, always return 1
# print self, '**', lf
if lf.log_val == -float('inf'):
return LogFloat.from_float(1.0)
lf_value = lf.sign * math.exp(lf.log_val)
if self.sign == -1:
# note: in this case, lf_value must be an integer
return LogFloat(self.sign**int(lf_value), self.log_val * lf_value)
return LogFloat(self.sign, self.log_val * lf_value)
def __mul__(self, lf):
temp = LogFloat(self.sign, self.log_val)
temp *= lf
return temp
def __div__(self, lf):
temp = LogFloat(self.sign, self.log_val)
temp /= lf
return temp
def __add__(self, lf):
temp = LogFloat(self.sign, self.log_val)
temp += lf
return temp
def __sub__(self, lf):
temp = LogFloat(self.sign, self.log_val)
temp -= lf
return temp
def __str__(self):
result = str(self.sign * math.exp(self.log_val)) + '('
if self.sign == -1:
result += '-'
result += 'e^' + str(self.log_val) + ')'
return result
def __neg__(self):
return LogFloat(-self.sign, self.log_val)
def __radd__(self, val):
# for sum
if val == 0:
return self
return self + val
然后,想法是构造一个LogFloat
s 列表,然后在 FFT 中使用它:
x_log_float = [ LogFloat.from_float(x_val) for x_val in x ]
fft_x_log_float = fft(x_log_float)
如果我重新实现 FFT 绝对可以做到这一点(并且只需使用我以前使用的LogFloat
任何地方float
,但我想我会征求意见。这是一个相当反复出现的问题:我有一个我想在日志空间中操作的股票算法(而且它只使用了一些操作,如“+”、“-”、“ ”、“/”等)。
这让我想起了用模板编写泛型函数,这样返回参数、参数等都是由同一类型构造的。例如,如果您可以对浮点数进行 FFT,那么您应该能够轻松地对复杂值执行一次(只需使用为复杂值提供必要操作的类)。
就目前而言,看起来所有 FFT 实现都是为前沿速度而编写的,因此不会很普遍。所以到目前为止,看起来我必须为泛型类型重新实现 FFT ......
我这样做的原因是因为我想要非常高精度的卷积(并且N^2运行时间非常慢)。任何建议将不胜感激。
*注意,我可能需要为 实现三角函数LogFloat
,这很好。
编辑:
这确实有效,因为LogFloat
它是一个交换环(并且它不需要实现三角函数LogFloat
)。最简单的方法是重新实现 FFT,但@JFSebastian 还指出了一种使用Python
通用卷积的方法,它可以避免对 FFT 进行编码(同样,使用 DSP 教科书或Wikipedia 伪代码非常容易)。