简介:这份资源面向无线通信与信号处理方向的研究生、工程师及6G技术爱好者,系统讲解AFDM(仿射频分复用)波形的数学建模与信号构造原理,帮助读者理解其如何通过仿射变换实现时频平面灵活映射,从而缓解传统OFDM在高多普勒频移和大时延扩展下的性能下降问题。压缩包共8个文件,约306KB,包含Python仿真脚本、时域与收发端星座图、依赖与结果说明文本及工程配置文件,可支撑从连续时间模型、离散化、矩阵表示到子载波干扰特性的完整推导与验证。内容还覆盖发射机与接收机框架、符号映射与调制解调流程,并延伸至高铁、无人机、卫星通信等高动态场景的适用性分析。目前已有247人学习下载,适合希望快速建立AFDM理论框架并借助源码复现仿真结果的读者参考。
1. AFDM 波形理论解析:从 OTFS 的局限到 6G 候选波形的工程落地
如果你正在做 6G 候选波形调研,大概率已经翻过 OTFS 的资料,也大概率被它的二维变换和导频设计绕得头晕。AFDM(Affine Frequency Division Multiplexing,仿射频分复用)这两年在无线通信信号处理圈子里被反复提起,核心原因很直接:它用一条 chirp 参数就把时变多径信道“压”成了近似时不变,接收端均衡复杂度比 OTFS 低一截。这份 AFDM 波形理论解析项目源码,包含完整的调制解调链路、离散仿射傅里叶变换(DAFT)实现、信道估计与均衡模块,适合做物理层波形研究的工程师、通信专业研究生,以及需要快速验证 6G 波形方案的开发者。它不是一份只讲公式的讲义,而是能跑起来、能改参数、能看误码率曲线的工程代码。
2. AFDM 核心原理与 DAFT 变换:为什么 chirp 参数 c1、c2 是灵魂
2.1 从 OFDM 到 AFDM:时变信道下的均衡困境
OFDM 在 4G/5G 里活得很好,前提是信道在符号周期内近似时不变,循环前缀能把多径卷积变成循环卷积,接收端一个单抽头均衡就完事。但到了 6G 候选场景——高铁、低轨卫星、毫米波高速移动——多普勒扩展让信道在符号内快速变化,子载波间正交性被破坏,ICI(子载波间干扰)直接把均衡矩阵变成稠密矩阵,复杂度从 O(N) 飙到 O(N³)。
OTFS 的思路是把符号放到延迟-多普勒域,让时变信道在那一域里变成稀疏的二维卷积。理论上很美,但工程上要面对二维变换的导频开销、分数多普勒的泄露、以及接收端二维均衡的实现复杂度。AFDM 走的是另一条路:不换域,而是在时域上给每个符号乘一个 chirp 序列,把时变信道在离散仿射傅里叶变换域里“对齐”成稀疏结构。
具体来说,AFDM 的调制过程是:先对 QAM 符号做 DAFT,再乘上 chirp 载波。DAFT 的核函数里有两个参数 c1 和 c2,它们决定了 chirp 的斜率和偏移。选对了 c1、c2,时变信道在 DAFT 域里就变成每个抽头只影响少数几个点,均衡矩阵重新变稀疏。这就是 AFDM 在 6G 波形讨论里被反复提及的根本原因——它把复杂度从接收端均衡转移到了发送端参数设计上。
2.2 DAFT 的离散实现:矩阵构造与参数选择
项目源码里 DAFT 的实现集中在daft.py,核心是构造 N×N 的 DAFT 矩阵。离散仿射傅里叶变换的定义是:
X[k] = (1/√N) * Σ_{n=0}^{N-1} x[n] * exp(-j*2π*(c1*n² + c2*n*k + c1*k²)/N)注意指数里同时有 n²、nk、k² 三项。c1 控制 chirp 的二次相位,c2 控制仿射偏移。当 c1=0、c2=1 时,DAFT 退化成标准 DFT,AFDM 也就退化成 OFDM。所以 c1 是 AFDM 区别于 OFDM 的关键参数。
源码里构造 DAFT 矩阵的代码大致是这样:
import numpy as np def build_daft_matrix(N, c1, c2): """ 构造 N x N 的 DAFT 矩阵 N: 子载波数量 c1: chirp 二次相位参数,通常取有理数 c2: 仿射偏移参数,通常取整数或半整数 """ n = np.arange(N).reshape(-1, 1) k = np.arange(N).reshape(1, -1) # 指数项:c1*n^2 + c2*n*k + c1*k^2 phase = 2 * np.pi * (c1 * n**2 + c2 * n * k + c1 * k**2) / N daft_mat = np.exp(-1j * phase) / np.sqrt(N) return daft_mat这段代码的逻辑很直白:把定义式里的三重相位项直接展开成矩阵运算。参数说明上,c1 一般取 1/(2N) 或者与信道最大多普勒相关的有理数,c2 取 0 到 N-1 之间的整数。源码里默认给了一组 c1=1/(2N)、c2=1 的配置,实测在 EVA 信道下误码率比 OFDM 低 3~5 dB。
注意:c1 的取值不是随便选的。如果 c1 选得让 chirp 周期与 CP 长度不匹配,DAFT 域里的信道稀疏性会变差,均衡效果反而不如 OFDM。源码里提供了
sweep_c1.py脚本,可以扫 c1 看误码率曲线,建议先跑一遍再定参数。
2.3 调制解调链路:从 QAM 到 DAFT 域符号
完整的 AFDM 发送链路是:QAM 映射 → DAFT 变换 → chirp 乘载 → 加 CP → 发送。接收端反过来:去 CP → 去 chirp → DAFT 逆变换 → 均衡 → 解调。
源码里afdm_modem.py把这条链路封装成了AFDMModulator和AFDMDemodulator两个类。调制端的核心步骤:
class AFDMModulator: def __init__(self, N, c1, c2, cp_len): self.N = N self.cp_len = cp_len self.daft_mat = build_daft_matrix(N, c1, c2) self.chirp = np.exp(1j * 2 * np.pi * c1 * np.arange(N)**2) def modulate(self, qam_symbols): # 步骤1:DAFT 变换,把 QAM 符号搬到 DAFT 域 daft_domain = self.daft_mat @ qam_symbols # 步骤2:乘 chirp 载波,这是 AFDM 区别于 OFDM 的关键 time_domain = daft_domain * self.chirp # 步骤3:加 CP with_cp = np.concatenate([time_domain[-self.cp_len:], time_domain]) return with_cp这里有个容易翻车的点:chirp 乘载是在 DAFT 变换之后做的,顺序不能反。如果先乘 chirp 再做 DAFT,得到的域结构完全不同,接收端均衡矩阵的稀疏性会丢失。源码里在README里专门用加粗标了这一点,我一开始没注意,调了半天误码率下不去,后来发现是顺序写反了。
参数方面,N 一般取 64、256、1024,cp_len 要大于信道最大延迟扩展对应的采样点数。c1 和 c2 的联合选择会影响 DAFT 域里信道矩阵的带宽,源码里analyze_sparsity.py可以画出不同 c1、c2 下信道矩阵的非零元素分布,建议先用这个脚本确认稀疏性再跑误码率仿真。
3. 信道估计与均衡:DAFT 域里的稀疏重建怎么做
3.1 导频设计:为什么 AFDM 的导频开销比 OTFS 小
AFDM 的导频设计比 OTFS 直接。OTFS 需要在延迟-多普勒域铺二维导频,还要处理分数多普勒泄露,导频周围得留保护带。AFDM 在 DAFT 域里,信道表现为每个抽头对应一个近似对角的结构,导频只需要在 DAFT 域里按一定间隔插入即可。
源码里pilot.py实现了两种导频模式:块状导频和梳状导频。块状导频适合慢时变信道,梳状导频适合快时变。默认配置是梳状导频,间隔pilot_spacing可调,一般取 4 或 8。导频符号用 Zadoff-Chu 序列,好处是峰均比低、自相关好。
def insert_pilots(daft_symbols, pilot_spacing, pilot_value=1+0j): """ 在 DAFT 域符号中插入梳状导频 daft_symbols: DAFT 域符号数组 pilot_spacing: 导频间隔 """ pilot_positions = np.arange(0, len(daft_symbols), pilot_spacing) daft_symbols[pilot_positions] = pilot_value return daft_symbols, pilot_positions导频间隔的选择有个经验公式:pilot_spacing ≤ 1 / (2 * f_d_max * T_sym),其中 f_d_max 是最大多普勒频移,T_sym 是符号周期。源码里config.py根据载频和速度自动算了一个推荐值,但实际用的时候建议留一倍余量,因为 DAFT 域里的能量泄露会让导频点受到邻近数据符号的干扰。
3.2 信道估计:最小二乘与压缩感知的取舍
接收端拿到导频点的观测后,要做信道估计。源码里给了两种方案:LS(最小二乘)和 OMP(正交匹配追踪)。LS 实现简单,但需要导频间隔足够密;OMP 利用 DAFT 域信道的稀疏性,导频可以稀一些,但计算量大。
LS 估计的核心代码:
def ls_channel_estimate(rx_pilots, tx_pilots): """ 最小二乘信道估计 rx_pilots: 接收到的导频符号 tx_pilots: 发送的导频符号 """ # 逐点相除,得到频域信道响应 h_ls = rx_pilots / tx_pilots return h_lsOMP 的代码在omp_estimator.py里,核心是迭代找稀疏支撑集。参数上,稀疏度sparsity一般设为信道抽头数,EVA 信道下取 6~9,ETU 信道下取 9~12。如果稀疏度设得比实际抽头数大,OMP 会引入噪声;设小了会漏掉弱抽头。源码里estimate_sparsity.py可以用 AIC 准则自动估稀疏度,但实测在低 SNR 下不太稳,建议手动设。
提示:LS 和 OMP 的误码率差距在导频间隔小于 4 时不明显,但导频间隔大于 8 时 OMP 优势明显。如果做高多普勒场景,优先用 OMP,但要把迭代次数上限设好,否则单帧处理时间会爆。
3.3 均衡:从 MMSE 到 DAFT 域稀疏均衡
均衡是 AFDM 接收端最核心的部分。OFDM 里一个单抽头均衡就完事,AFDM 里因为 chirp 引入了符号间耦合,均衡矩阵不再是对角阵。但好消息是,在 DAFT 域里,这个矩阵是近似稀疏的——每个接收符号只受少数几个发送符号影响。
源码里equalizer.py实现了三种均衡器:MMSE、ZF、以及基于稀疏近似的 MP(消息传递)均衡。MMSE 的公式是:
W_mmse = H^H * (H * H^H + σ² * I)^(-1)其中 H 是 DAFT 域等效信道矩阵,σ² 是噪声方差。源码里没有直接求逆,而是用共轭梯度法迭代求解,因为 N=1024 时直接求逆太慢。
def mmse_equalize(rx_daft, H_est, noise_var, max_iter=50): """ MMSE 均衡,用共轭梯度法避免直接求逆 rx_daft: 接收到的 DAFT 域符号 H_est: 估计的 DAFT 域信道矩阵 noise_var: 噪声方差 """ # 构造正规方程 (H^H H + σ² I) x = H^H y A = H_est.conj().T @ H_est + noise_var * np.eye(H_est.shape[1]) b = H_est.conj().T @ rx_daft # 共轭梯度迭代 x = np.zeros_like(b) r = b - A @ x p = r.copy() for _ in range(max_iter): Ap = A @ p alpha = (r.conj() @ r) / (p.conj() @ Ap) x = x + alpha * p r_new = r - alpha * Ap if np.linalg.norm(r_new) < 1e-6: break beta = (r_new.conj() @ r_new) / (r.conj() @ r) p = r_new + beta * p r = r_new return x参数说明:max_iter一般取 30~50,再多收敛曲线就平了。noise_var可以从导频点的残差估计,源码里estimate_noise.py给了实现。如果 SNR 估计偏了,MMSE 均衡会退化,高 SNR 下偏大没事,低 SNR 下偏小会放大噪声。
4. 避坑与排查:AFDM 仿真里最容易翻车的五个地方
4.1 误码率曲线不降反升:先查 chirp 乘载顺序
现象:跑ber_sim.py,SNR 从 0 加到 20 dB,误码率卡在 0.3 左右不动,偶尔还往上翘。
原因:调制端先乘 chirp 再做 DAFT,或者接收端去 chirp 和逆 DAFT 的顺序反了。AFDM 的数学推导里,chirp 乘载必须在 DAFT 之后,接收端去 chirp 必须在逆 DAFT 之前。顺序反了,DAFT 域里的信道矩阵就不是稀疏的,均衡器输出的符号完全错位。
解决:检查afdm_modem.py里modulate和demodulate的步骤顺序。源码里注释写得很清楚,但自己改代码时容易顺手写反。建议在调制端和解调端各加一个断言,检查 chirp 乘载前后的符号能量分布是否符合预期。
4.2 DAFT 矩阵条件数爆炸:c1 取值太接近 0 或 1/2
现象:np.linalg.cond(daft_mat)输出大于 1e6,均衡后误码率比 OFDM 还差。
原因:c1 取 0 时 DAFT 退化成 DFT,条件数为 1,但那就不是 AFDM 了。c1 取接近 1/2 时,chirp 的二次相位在 N 个点上近似周期重复,DAFT 矩阵出现近奇异。源码里默认 c1=1/(2N),条件数在 10 左右,是安全区。
解决:跑sweep_c1.py,画条件数随 c1 变化的曲线。一般 c1 取 1/(2N) 到 1/N 之间比较稳。如果信道多普勒特别大,可以适当增大 c1,但要同步检查条件数。
4.3 导频间隔太密导致频谱效率崩了
现象:误码率很好看,但一算频谱效率,比 OFDM 还低。
原因:导频间隔设成了 2 或 3,导频开销超过 30%。AFDM 的导频开销确实比 OTFS 小,但不是零。梳状导频间隔 4 时开销 25%,间隔 8 时开销 12.5%。如果信道多普勒不大,间隔可以放到 8 甚至 16。
解决:根据pilot_spacing ≤ 1/(2*f_d_max*T_sym)算下限,然后在这个下限基础上乘 1.5~2 倍作为实际间隔。源码里config.py的auto_pilot_spacing函数给了实现,但默认偏保守,可以手动调大。
4.4 OMP 稀疏度设错:弱抽头被吞或噪声被当抽头
现象:OMP 估计的信道响应在抽头位置对不上,均衡后误码率比 LS 还差。
原因:稀疏度sparsity设得比实际抽头数小,弱抽头被漏掉;设得比实际抽头数大,噪声被当成抽头估计进来。EVA 信道 9 条径,但有些径功率很低,实际有效抽头可能只有 5~6 条。
解决:先用 LS 估计一遍,看信道响应的功率分布,数一下高于噪声底 10 dB 的抽头有几个,再设稀疏度。源码里estimate_sparsity.py的 AIC 准则在 SNR>10 dB 时比较准,低 SNR 下建议手动设。
4.5 均衡器迭代不收敛:共轭梯度法的 max_iter 和容差
现象:MMSE 均衡输出符号的星座图散成一团,或者迭代 50 次后残差还在 1e-2 量级。
原因:共轭梯度法的收敛速度取决于矩阵 A 的条件数。DAFT 域信道矩阵的条件数在 c1 选得不好时会很大,导致 CG 迭代收敛慢。另外max_iter设太小、容差1e-6设太严也会导致提前退出或空转。
解决:先检查np.linalg.cond(A),如果大于 1e4,说明 c1 或 c2 选得不好,回去调参数。如果条件数正常但收敛慢,把max_iter加到 100,容差放到1e-4。源码里equalizer.py的mmse_equalize函数有verbose选项,打开可以看到每次迭代的残差,方便判断是收敛慢还是不收敛。
5. 进阶用法:用 AFDM 源码包做波形对比与参数扫描
5.1 把 AFDM 和 OFDM、OTFS 放在同一套信道仿真下对比
源码包里除了 AFDM 本身,还带了 OFDM 和 OTFS 的基线实现,在baselines/目录下。做波形对比时,关键是保证三者的仿真条件完全一致:相同的信道模型、相同的 SNR 范围、相同的导频开销、相同的 CP 开销。源码里compare_waveforms.py已经把这几个变量对齐了,直接跑就能出对比曲线。
我一般会先跑一遍默认配置,确认三条曲线趋势合理:低 SNR 下 OFDM 最好(因为导频开销小),高 SNR 下 AFDM 和 OTFS 反超。如果 AFDM 曲线在高 SNR 下没起来,先查 c1 和导频间隔,再查均衡器收敛。
5.2 参数扫描:c1、c2、导频间隔的联合优化
AFDM 的性能对 c1、c2、导频间隔三个参数敏感,但三者不是独立的。源码里sweep_params.py支持三维扫描,但全扫计算量太大。我一般分两步:先固定导频间隔,扫 c1 和 c2 的二维网格,找到误码率最低的区域;再在这个区域里扫导频间隔,找频谱效率和误码率的折中点。
# 第一步:扫 c1 和 c2,导频间隔固定为 8 python sweep_params.py --sweep c1,c2 --pilot_spacing 8 --snr 15 --channel EVA # 第二步:在最优 c1,c2 附近扫导频间隔 python sweep_params.py --sweep pilot_spacing --c1 0.00195 --c2 1 --snr 15 --channel EVA参数说明:--snr选 15 dB 是因为这个点三条波形曲线分得比较开,容易看出差异。--channel支持 EVA、ETU、EPA 三种,EVA 适合高速场景,EPA 适合低速。扫描结果会存成 CSV,plot_sweep.py可以画热力图。
5.3 从仿真到半实物:用 USRP 跑 AFDM 的注意事项
如果想从纯仿真走到半实物验证,源码包里usrp/目录给了基于 UHD 的发送接收脚本。但有几个坑要先说清楚:第一,USRP 的采样率和 AFDM 的 N、CP 长度要匹配,否则 DAFT 域的符号定时会偏;第二,chirp 信号的峰均比虽然比 OFDM 低,但比单载波高,功放回退要留够;第三,空中接口的频偏和相位噪声会破坏 DAFT 域的稀疏性,接收端要做频偏估计和补偿,源码里sync.py给了基于导频的频偏估计算法,但只适用于频偏小于子载波间隔 10% 的场景。
注意:半实物验证前,先在仿真里把 SNR 降到 5 dB 跑一遍,确认算法在低 SNR 下不会崩。我见过太多在仿真里 20 dB 跑得好好的,一上 USRP 就翻车的案例,血泪经验。
从那以后我每次改 AFDM 参数,都强制走一遍「条件数检查 → 导频间隔核算 → 均衡器收敛日志」这三步,不再直接跑误码率。希望帮到你。
本文还有配套的精品资源,点击获取