就像@MattL。和@aconcernedcitizen 说,这个问题是数字的。
Python 在scipy.signal.firls
内部使用求解器scipy.linalg.solve
。对于您的输入,求解器会引发“矩阵奇异”错误,但firls
会抑制错误并回退到另一个scipy.linalg.lstsq
不会引发错误但也无法正确解决问题的求解器。
增加数值分辨率会有所帮助。
任意精度firls
实现
这是我修改后的版本scipy.signal.firls
。它mpmath
用于任意精度浮点数据类型和任意精度求解器。为简洁起见,我删除了所有评论。该代码在原始 Scipy 1.7 源的BSD 3-Clause 许可下获得许可,版权所有 2001-2002 Enthought, Inc. 和 2003-2019, SciPy Developers;版权所有。
import numpy as np
from scipy.linalg import (hankel, toeplitz)
import mpmath as mp
def _get_fs(fs, nyq):
if nyq is None and fs is None:
fs = 2
elif nyq is not None:
if fs is not None:
raise ValueError("Values cannot be given for both 'nyq' and 'fs'.")
fs = 2*nyq
return fs
def firls(numtaps, bands, desired, weight=None, nyq=None, fs=None):
nyq = 0.5 * _get_fs(fs, nyq)
numtaps = int(numtaps)
if numtaps % 2 == 0 or numtaps < 1:
raise ValueError("numtaps must be odd and >= 1")
M = (numtaps-1) // 2
nyq = mp.mpf(nyq)
if nyq <= 0:
raise ValueError('nyq must be positive, got %s <= 0.' % nyq)
bands = np.asarray(bands, dtype=mp.mpf).flatten() / nyq
if len(bands) % 2 != 0:
raise ValueError("bands must contain frequency pairs.")
if (bands < 0).any() or (bands > 1).any():
raise ValueError("bands must be between 0 and 1 relative to Nyquist")
bands.shape = (-1, 2)
desired = np.asarray(desired, dtype=mp.mpf).flatten()
if bands.size != desired.size:
raise ValueError("desired must have one entry per frequency, got %s "
"gains for %s frequencies."
% (desired.size, bands.size))
desired.shape = (-1, 2)
if (np.diff(bands) <= 0).any() or (np.diff(bands[:, 0]) < 0).any():
raise ValueError("bands must be monotonically nondecreasing and have "
"width > 0.")
if (bands[:-1, 1] > bands[1:, 0]).any():
raise ValueError("bands must not overlap.")
if (desired < 0).any():
raise ValueError("desired must be non-negative.")
if weight is None:
weight = np.ones(len(desired), dtype=mp.mpf)
weight = np.asarray(weight, dtype=mp.mpf).flatten()
if len(weight) != len(desired):
raise ValueError("weight must be the same size as the number of "
"band pairs (%s)." % (len(bands),))
if (weight < 0).any():
raise ValueError("weight must be non-negative.")
n = np.arange(numtaps)[:, np.newaxis, np.newaxis]
q = np.dot(np.diff(np.vectorize(mp.sincpi)(bands * n) * bands, axis=2)[:, :, 0], weight)
Q1 = toeplitz(q[:M+1])
Q2 = hankel(q[:M+1], q[M:])
Q = Q1 + Q2
n = n[:M + 1]
m = (np.diff(desired, axis=1) / np.diff(bands, axis=1))
c = desired[:, [0]] - bands[:, [0]] * m
b = bands * (m*bands + c) * np.vectorize(mp.sincpi)(bands * n)
b[0] -= m * bands * bands / 2.
b[1:] += m * np.vectorize(mp.cospi)(n[1:] * bands) / (mp.pi * n[1:]) ** 2
b = np.dot(np.diff(b, axis=2)[:, :, 0], weight)
a = mp.lu_solve(Q, b)
coeffs = np.hstack((a[:0:-1], 2 * a[0], a[1:]))
return coeffs
具有足够的精度(通过变量设置mp.mp.prec
)这个版本firls
在你的输入上工作得很好。在这种情况下,为了正确显示低于 -400 dB 的幅度值,幅度频率响应计算必须以任意精度进行,如果没有任意精度的快速傅里叶变换 (FFT) 实现,这将非常缓慢。
import mpmath as mp
import matplotlib.pyplot as plt
mp.mp.prec = 150
N = 287
coeff = firls(N, [0, .4, .6, 1], [1, 1, 0, 0])
num_freqs = 256 + 1
f = np.arange(num_freqs, dtype=mp.mpf)/(num_freqs - 1)
phases = np.arange(N)*f.reshape((num_freqs, 1))
phasors = np.vectorize(mp.cospi)(phases) - 1j*np.vectorize(mp.sinpi)(phases)
response = np.dot(phasors, coeff)
plt.plot(0.5*f, 20*np.vectorize(mp.log10)(abs(response)))
plt.grid(True)
plt.ylabel("dB")
plt.xlabel("Frequency (cycles/sample)")
plt.show()
输出:
所以,是的,只要有足够的精度,最小二乘滤波器设计就可以正常工作!
通过以下方式将任意精度系数四舍五入为双精度浮点:
coeff = coeff.astype(np.float64)
没有给出很好的结果:
任意精度 Kaiser 加窗 FIR 滤波器设计
这也是 Kaiser 加窗低通有限脉冲响应 (FIR) 滤波器设计的任意精度实现(未经过广泛测试)。在测试中,我调整了 Kaiser 参数 beta,使第一个频率响应零与最小二乘滤波器的频率响应与您的输入参数相匹配:
def mpf_kaiser_lowpass_fir(N, cutoff, beta):
n = np.arange(N, dtype=mp.mpf)
alpha = mp.mpf((N-1)/2)
win = np.vectorize(lambda x: mp.besseli(0, x))(beta*np.vectorize(mp.sqrt)(1 - ((n-alpha)/alpha)**2))/mp.besseli(0, beta)
coeffs = cutoff*win*np.vectorize(mp.sincpi)((n-(N-1)/2)*cutoff)
return coeffs
coeff2 = mpf_kaiser_lowpass_fir(N, 0.5, 44.9535)
看起来最小二乘设计(蓝色)正在战胜 Kaiser 窗口设计(橙色):
放大:
调整 beta 以使 0.3 个周期/样本的两个频率响应相等,给出了质量相似的比较结果(未显示)。