记一次fft重构(二): 矫正算法重构篇

1.前言 在上一节中,我们为fft模块简单的添加了抛物线插值算法,并且衡量了插值算法对不同频率差的幅度与频率的估计情况。然而,在与AI的多轮对线之下,我了解到: 不同窗函数适用的插值算法不同。这就意味着,原来设计的架构(基于算法进行的分类)并不适用。我们需要基于窗函数,对幅值矫正算法重新分类。同时,为矫正算法模块加入相位估计算法,以应对2025-G题(电路模型探究装置)这种需要相位谱(复数谱)的情形。 2.整体架构 2.1输出规范 得益于写抛物线插值时就考虑到了将来需要加入其他频率估计算法的情形。所以之前已经有了统一接口: @dataclass class EstimateData: freq_estimated: float amplitude_estimated: float 加入相位数据只需要在下方加入 phase_estimated: float 即可。 2.2 整体架构 如同《Head First 设计模式》中提到的, 程序的设计模式,应该面向修改关闭,面向拓展开放。面向不变关闭,面向变化打开。这有点类似与《矛盾论》 中提到的"抓主要矛盾"的哲学思想。在本程序中,我们注意到,其变化量有两个: 窗函数(Window function), 插值算法。因此,我们应该面向这两个变化量留出拓展的余地,将窗函数和插值算法解耦,而面向其他无关紧要的细节,我们可以写成紧耦合的算法。 但现在有以下几个问题: 原有的抛物线算法适用于所有窗函数,只不过估计精度不同。 所有窗函数适用的相位矫正算法都相同,但是频差与幅值估计算法均不同 一个窗函数可能有好几种不同的频差估计算法(如矩形窗就有rife和cadan两种频差估计算法),但不是所有算法都适用与某个窗函数。 原有的no_interp 和 parabolic_interp函数是紧耦合的,输入复数谱,频率bin,采样率,采样点数,相干增益后直接输出EstimateData,和新算法(将频率,幅值,相位估计算法拆分成不同函数)的写法有很大不同。 首先,对于问题4,我们采取了简单直接的做法: 构造一个抽象的(通用的)函数estimate_freq_amplitude_phase(),并将其他频率估计函数设置为私有函数,以免产生错误调用。 对于问题4,我在抽象函数estimate_freq_amplitude_phase()中,选择了使用选择分支来解决这个问题,因为旧的模式不会再增加,之后的代码架构一定是新的算法模式,因此我们直接采用了这种紧耦合的方法,不过这对我们后续的拓展毫无影响。 其次,对于问题2-3,我们采用了较为复杂的策略模式来实现。选择策略模式是因为其能较为简单的在嵌入式C代码中实现(使用结构体或swich-case方式达到类似的效果) 我的办法是使用了四个字典来满足我的调用要求:每种窗函数只能调用对应的算法,不能调用适用于其他窗函数的算法。以下为字典列表 由于幅值估计算法每一个窗只有一个,所以只用维护一个算法列表即可。 _AMP_METHODS = { "rect": _amp_rect, "hann": _amp_hann, "hamming": _amp_hamming, "blackman": _amp_blackman, "blackmanharris": _amp_blackmanharris, "flattop": _amp_flattop } 频差估计算法,这个用了三个算法字典,每个字典的作用分别为auto模式下调用的插值算法,不同窗函数能调用的插值算法表,所有算法对应的函数指针(python字典的值能设置为函数,这里姑且不严谨的叫它指针) _DELTA_METHODS = { "rife": _delta_rect_rife, "cadan": _delta_rect_candan, "granke": _delta_hann_grandke, "offelli": _delta_hamming_offelli, "andria_blackman": _delta_blackman_andria, "agrez": _delta_blackmanharris_agrez, "andria_flattop": _delta_flattop_andria } 所有的插值算法列表 _AUTO_DELTA_METHODS = { #自动窗函数选择表 "rect": "cadan", "hann": "granke", "hamming": "offelli", "blackman": "andria_blackman", "blackmanharris": "agrez", "flattop": "andria_flattop" } auto模式下,不同窗函数选择的频率差估计算法 _WINDOW_CHECK_LIST = { #查看用户是否选择错了窗函数算法 "rect": ["rife", "cadan"], "hann": ["granke"], "hamming": ["offelki"], "blackman": ["andria_blackman"], "blackmanharris": ["agrez"], "flattop": ["andria_flattop"] } 用于检测用户是否选择错了窗函数算法,如果选择错了,就报错 接着是这些算法的实现,我说实话大部分都没有什么意思,基本上都是对着公式写代码,没有半点数据结构。其中δ为频率差,A为幅值。 为了简便,我们做以下符号约定。 X为FFT后得到的复数谱,$\omega$ 为窗函数 ...

2026-06-24 · 4 min · 665 words · Saki酱的通信学习之路