基于Python的AFDM波形仿真:从chirp基理论到6G候选波形实现
2026/9/20 12:29:59 网站建设 项目流程

简介:面向无线通信、信号处理与6G技术研究者的AFDM波形理论解析源码包,系统展示仿射域多载波波形的数学建模与仿真思路,重点解决高多普勒频移和大时延扩展下OFDM性能下降的问题,适合高校研究生、通信算法工程师快速建立对候选波形的整体认知。zip压缩包共8个文件,以Python仿真脚本为主体,配合依赖说明与仿真结果txt,以及时域波形图、收发端星座图png和轻量配置文件,整体仅306KB,结构精简,便于直接运行与二次修改,适合课程设计或科研复现参考。已有245人学习下载。通过源码可复现AFDM连续时间信号模型、离散化矩阵表示、子载波干扰特性及时延-多普勒域分析,还能对照发射与接收星座图检验调制解调流程,并结合高铁、无人机、卫星通信等场景理解其应用价值;对于后续参数优化、低复杂度算法探索或6G融合研究,是一份可扩展的代码起点。 AFDM这个缩写,最近在通信波形研究圈子里被提到的频率越来越高。全称是Affine Frequency Division Multiplexing,仿射频率分复用,属于面向6G候选波形讨论时绕不开的一类新方案。我最近把AFDM从理论推导到可运行的Python仿真工程完整过了一遍,顺手整理了一套项目源码,这篇文章就围绕这段经历来写:不做论文式复述,而是把AFDM波形理论解析中的关键点、我在写源码时如何把公式落成代码,以及复现过程中踩过的坑,一次性讲清楚。适合正打算看6G波形方向、或者想拿AFDM练手做物理层仿真的朋友参考。

先说结论:AFDM不是玄学,它本质上是把OFDM那组复数指数基换成了chirp基,再用两个额外参数去主动适配信道在时延-多普勒维度上的扩展特性。好处是高速移动场景下,不依赖外部级联编码也能拿到分集增益。这套思路和OTFS有相似之处,但AFDM保留了OFDM的单载波块结构,复杂度可控,实际工程友好度更高。下面按理论、源码、踩坑三个角度来聊,尽量把每个“为什么”都交代清楚。

1. 为什么还要啃一种新波形

1.1 OFDM在高速场景下的真实瓶颈

OFDM统治了4G和5G的大部分物理层设计,在LTE、Wi-Fi里面几乎是绝对主力。它的思路很成熟:把宽带信道拆成一堆窄带平坦子信道,用循环前缀吸收时延扩展,接收端做一次FFT就能均衡。问题在于,这套设计默认信道在一个OFDM符号块内基本不变。

一旦终端或反射体高速运动,多普勒频移会把信道在单个符号块内“搅动”起来,子载波之间的正交性就会崩坏,产生载波间干扰。实际体验就像高铁上视频会议断断续续,不是网速不够,而是信道变化太快,OFDM在时间维度上收到的符号已经被打乱了。要硬扛这个问题,要么缩短符号长度牺牲频谱效率,要么加密导频做插值均衡,但代价都很大。

1.2 AFDM的差异化定位:用chirp基替代复指数基

AFDM属于多载波调制家族,但它在数据块上做的不是传统傅里叶变换,而是离散仿射傅里叶变换。名字里这个“仿射”体现在变换核上:除了常规的线性相位项,还引入了两个二次相位项。二次相位在物理上就等价于频率随时间线性扫描,也就是chirp信号。

用chirp基承载数据的直接结果是,发送信号在时域上有一种“扫频”特性,它能主动匹配信道在时延-多普勒平面上的能量散布。和OTFS一样,AFDM也在时延-多普勒域上获得分集;但和OTFS的二维网格变换不同,AFDM只在一维序列上做变换,接收机结构更接近OFDM:去CP、做DAFT、单抽头均衡,复杂度没有跳到二维匹配滤波的量级。这一点是我后来写代码时体会最深的。

1.3 这套仿真工程能拿来做什么

源码工程不是论文空壳,它解决几件实际事:一是验证AFDM在高斯信道和双选择性信道下的理论误码率;二是和OFDM在相同频谱效率、相同信噪比定义下做直观对比;三是给后续扩展留接口,比如接信道估计、做MIMO扩展、看PAPR优化。既可以用它复现论文曲线,也能当课程设计的参考实现。

2. AFDM理论必须吃透的几个点

2.1 DAFFT变换到底在做什么

AFDM的调制核心是离散仿射傅里叶变换。发射端对频域符号序列做逆变换,生成时域发送序列,简化后的表达式为:

s[n] = (1 / sqrt(N)) * sum_m x[m] * exp(j * 2 * pi * (c1 * n^2 + n * m / N + c2 * m^2))

这里N是子载波数,也就是一个块内的符号数。对比OFDM的IDFT,多出了两项:c1乘n的平方,c2乘m的平方。n是时域采样序号,m是数据序号。c1项让每个时域采样点携带一个随时间变化的二次相位,这是chirp特性的来源;c2项则是在数据域上做二次相位旋转。接收端解调就是反过来,先做DAFT正变换,再对每个子载波做单抽头均衡。

把这个变换写成矩阵形式就是s = A^H * x,其中A是DAFT矩阵。由于核函数是正交的,A^H * A等于单位阵,理论上只要参数选择正确,调制解调就是无损可逆的。这也是我最早验证对错的一个入口:先不看信道,直接让信号过调制解调链路,如果输出和输入不一致,说明c1、c2或者矩阵构造有问题。

2.2 chirp参数c1、c2与信道之间的对应关系

两个chirp参数不是拍脑袋取的。通过推导输入输出关系,可以得出等效信道矩阵的循环结构;c1和c2分别影响矩阵在“多普勒方向”和“时延方向”上的采样位置。直观理解,可以认为这对参数相当于在时延-多普勒平面上给信号打了一个旋转角,选对了角度,信号的能量才能铺满整个分集维度。

实际操作上,c2一般取有理数,常用形式是c2 = (N - 1) / N,或者按论文推导取特定分数;c1则和信道多普勒扩展的整数部分相关,需要根据最大多普勒频移估计来配置。更严格的条件是让2N倍的c1与N互质,或满足论文里给出的全分集约束。这里我不展开全部推导,但给一个结论:如果你的BER曲线不见改善,先查c1有没有跟着信道多普勒走,这是最常见的翻车原因。

2.3 全分集增益为什么会存在

全分集这句话听起来玄,实际含义是“一个符号经历的所有独立衰落路径,在接收端都能被收集起来并有效合并”。OFDM把符号摊在多条子载波上,每条子载波看到的信道可能很差;AFDM因为chirp基的存在,让同一个符号在时频平面上扩散开,多条路径的副本会在接收端被加权合并,等效信道变成一个多维叠加,深度衰落的概率显著下降。

打个比方,你在演唱会现场听主唱唱歌,直达声和四周墙壁的反射声到达耳朵的时间不一样,但大脑会自动把这些声音综合起来,就算某个方向被柱子挡住,你还是能听清。AFDM就相当于给接收机装了一副“定向收集”的耳朵,把多径从干扰变成了可用资源,这是它在高速场景下比OFDM能打的核心原因。

3. 项目源码:从公式到可跑的仿真

3.1 工程结构与运行方式

这套源码用Python加NumPy实现,核心逻辑依赖库很少,便于阅读和二次修改。目录结构很简单:

afdm_sim/ ├── modem.py # AFDM调制解调、DAFT矩阵生成 ├── channel.py # 双选择性信道生成 ├── ber_sim.py # BER仿真主流程 ├── compare_ofdm.py # OFDM对比脚本 └── run_sim.py # 一键运行入口

建议第一次跑的时候直接把N设成64或128,信噪比从0dB扫到20dB。不要一上来就用1024个子载波,因为后面的实现默认用矩阵乘,N大了内存会很尴尬。小N先把流程跑通,再切换到快速算法,这是最省时间的路线。

3.2 调制解调核心代码实现

调制端最直观的实现就是根据DAFT逆变换公式逐项累加。我早期写的版本是这样的:

import numpy as np def afdm_modulate(x, N, c1, c2): """ AFDM调制:频域符号序列 -> 时域发送序列 x: 长度为N的复数符号序列 """ n = np.arange(N) s = np.zeros(N, dtype=complex) for m_idx in range(N): phase = np.exp(2j * np.pi * ( c1 * n**2 + n * m_idx / N + c2 * m_idx**2 )) s += x[m_idx] * phase return s / np.sqrt(N)

这段代码里最容易写错的是相位项的符号和归一化系数。DAFT矩阵本身是幺正的,所以调制端要除以根号N,接收端也要除以根号N,两边对称才能保证能量守恒。如果少除一个根号N,发射功率会比预期高一倍,后面所有信噪比都会对不上,这是我在仿真里查出过的问题之一。

解调就是DAFT正变换,本质上只需要把指数项的符号取反:

def afdm_demodulate(r, N, c1, c2): """ AFDM解调:时域接收序列 -> 变换域符号序列 """ n = np.arange(N) X_hat = np.zeros(N, dtype=complex) for m_idx in range(N): phase = np.exp(-2j * np.pi * ( c1 * n**2 + n * m_idx / N + c2 * m_idx**2 )) X_hat[m_idx] = np.sum(r * phase) return X_hat / np.sqrt(N)

注意,这里假设接收端的c1、c2和发射端完全一致。实际工程里这是换算出来的信道参数,需要考虑估计误差;但仿真阶段先定死参数,便于专注理解信道的信噪比影响。后面接信道估计时再把c1、c2变成动态配置即可。

3.3 双选择性信道的仿真设计

双选择性信道要同时体现频率选择性和时间选择性,工程上常用多径抽头模型来实现。第p条径有独立的复增益、时延和多普勒频移,离散化后对发送符号逐点叠加:

def apply_channel(s, params, fs): """ params: 每条径的 (幅度, 时延采样点数, 多普勒频移Hz) fs: 采样率 """ N = len(s) r = np.zeros(N, dtype=complex) for amp, delay, doppler in params: idx = np.arange(N) - delay valid = (idx >= 0) & (idx < N) r[valid] += amp * s[idx[valid]] * np.exp( 2j * np.pi * doppler * np.arange(N)[valid] / fs ) return r

这里有两个注意点。一是时延不一定是整数个采样点,理想情况下要做分数时延滤波,但仿真入门阶段先取整数时延即可,差别不大;二是多普勒频移会让信道在单个符号块内变化,所以必须在逐点层面乘上旋转因子,不能只在块级别乘一个常数,否则就退化成纯频率选择性信道了,测不出AFDM的优势。

在打噪声时,要按信号平均功率来折算噪声功率,比较稳妥的做法是:

snr_linear = 10 ** (snr_db / 10) signal_power = np.mean(np.abs(rx_signal) ** 2) noise_var = signal_power / snr_linear noise = np.sqrt(noise_var / 2) * ( np.random.randn(N) + 1j * np.random.randn(N) )

复噪声在实部和虚部的方差各是噪声功率的一半,很多刚开始做通信仿真的人会在这里漏掉1/2,导致最终BER曲线和理论值整体偏移好几个dB。

3.4 快速实现:把DAFT拆成FFT

上面这段双重循环只适合验证小规模链路。N等于256时肉眼可见地卡,N到了1024基本没法接受。DAFT虽然多两个二次相位项,但可以拆成“三个对角相位矩阵夹一个FFT”的结构,复杂度从O(N^2)降到O(N logN)。拆法思路是这样:把指数项里的nm/N单独拿出来做FFT,c1n^2和c2m^2作为前后两端的相位旋转。用代码表示就是:

def afdm_modulate_fast(x, N, c1, c2): m = np.arange(N) n = np.arange(N) phase1 = np.exp(2j * np.pi * c2 * m**2) # 数据域相位 phase2 = np.exp(2j * np.pi * c1 * n**2) # 时域相位 X = x * phase1 S = np.fft.fft(X, norm="ortho") return S * phase2 def afdm_demodulate_fast(r, N, c1, c2): m = np.arange(N) n = np.arange(N) phase1 = np.exp(-2j * np.pi * c2 * m**2) phase2 = np.exp(-2j * np.pi * c1 * n**2) Y = r * np.conj(phase2) X = np.fft.ifft(Y, norm="ortho") return X * np.conj(phase1)

注意相位符号和配合。用这个实现跑N等于1024、几千个符号也不会有压力。我在实际工程里默认用快速版本,只在调试时用矩阵版本核对结果。

3.5 OFDM对比实验设计

对比实验最容易出问题的地方,是没有保证两个系统消耗同样多的频谱和时间资源。我做的对比设计是:AFDM和OFDM都用64个子载波,符号块长度一致,循环前缀长度一致,调制阶数同为QPSK或16QAM,信道完全复用同一条多普勒多径信道。这样两者的频谱效率基本一致,BER差异才能公平比较。

在这个设定下,低速场景比如多普勒频移10Hz时,AFDM和OFDM的BER曲线基本重合,这很正常,不要怀疑实现错了。等把多普勒拉到200Hz甚至500Hz时,差值才会拉开,OFDM会出现明显的误差平台,AFDM的曲线会继续下降,这就是chirp基带来的分集增益在起作用。

4. 复现过程中的高频问题与排查技巧

4.1 chirp参数乱选,BER曲线完全没有下降趋势

见过很多次的现象:c1、c2随便取数,仿真跑完发现BER在10dB以上就开始平了,怎么提信噪比都没用。这基本就是参数和信道不匹配。c1要对应多普勒扩展,c2要符合时延扩展下的全分集条件,不能当固定值用。我调参的经验是先固定c2为论文推荐值,只扫描c1,看BER随多普勒变化是否单调改善,找到拐点后再联合调c2。

建议在代码里加一段自检:先生成DAFT矩阵,检查A^H @ A和单位阵的误差,如果误差大于1e-8,说明参数组合或矩阵实现有误,后面的仿真结果都不用看。

4.2 归一化和信噪比定义不一致

前面提到过少除根号N和复噪声方差漏半的问题,这两类错误都比较隐蔽,因为单看时域波形好像没毛病。排查技巧是:先用纯高斯白噪声信道跑一组BER作为基线,和QPSK理论BER曲线对比,如果差了3dB或5dB这种固定偏移,多半是噪声方差或功率归一化写错了。对不上就一步步打印信号功率、噪声功率、实际SNR,别凭感觉改系数。

4.3 矩阵实现太慢,仿真跑不起来

这是一个很实际的工程问题。如果是验证算法逻辑,N取64或128,矩阵版完全够用;但要做BER扫描,每个信噪比点要跑上万帧,矩阵版就会非常浪费时间。我后来把所有关键链路换成FFT拆分版,单帧时间从毫秒级降到微秒级,整个扫描才变得可操作。建议新上手的人直接写快速版本,但保留一个矩阵版本作为验证基准,两边输出逐一对比确保全部一致。

4.4 常见问题速查表

问题现象可能原因解决办法
BER曲线在高SNR下平掉c1/c2与信道不匹配用自检脚本验证DAFT矩阵,再扫描c1
BER比理论值差3dB或5dB噪声方差或功率归一化错误检查复噪声方差是否除以2,检查归一化系数
与OFDM对比无差异多普勒设置太低拉大多普勒频移到200Hz以上
N增大后仿真极慢使用了双重循环或矩阵乘切换到FFT分解实现
接收端解调后星座图旋转收发端c1/c2符号不一致统一采用exp(j...)正向、exp(-j...)反向的约定
过信道后总功率暴增没有对多径增益做归一化对信道抽头功率做总和归一化处理

5. 给想继续深挖的朋友留几条思路

我个人在把AFDM源码跑通之后,最大的感受是这套框架搭起来不难,真正的功夫在物理参数和数学参数之间的对应关系上。后面如果再往前推进,可以尝试在接收端插入基于导频的信道估计模块,看看c1、c2在不知道精确多普勒时还能不能稳住性能;也可以把单发单收的链路扩展到MIMO-AFDM,研究不同天线配置下的分集表现。还有一条线是PAPR优化,chirp信号天然会改变峰均比,但具体怎么利用这个特性扬长避短,目前论文里也还在探索。

最后再分享一个我自己常用的调试习惯:不要一上来就跑完整BER曲线,先把调制解调链路在无信道状态下测通,再逐步加入噪声、加多径、加多普勒,每一步都对可视化结果做一次确认。这样出了问题能很快定位在哪一层,而不是面对一堆乱糟糟的曲线无从下手。AFDM的源码实现本身并不神秘,熟悉了这组二次相位变换之后,你会发现它和OFDM之间的距离比想象中要近得多。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询