简介:面向正交时频空间调制(OTFS)通信体制的研究人员与高年级研究生,这份代码资源聚焦高移动性场景下的接收机均衡难题。由于OTFS符号在时频双选择性信道中会遭受二维干扰,传统线性均衡的矩阵求逆开销极大,该资源以线性最小均方误差并行干扰消除(LMMSE-PIC)为核心,通过并行干扰消除逐轮抑制二维符号间干扰,并利用一阶诺伊曼级数逼近矩阵求逆,使复杂度随传输符号总数准线性增长,适合编码OTFS系统的链路级仿真与算法对比。压缩包为ZIP格式,大小约15.84MB,包含论文配套的核心实现与仿真链路,支持复现外信息转移(EXIT)图、编码误码率(BER)曲线,以及同正交频分复用(OFDM)和已有PIC方法的性能对比。目前已有797人学习下载,对于研究OTFS信号检测、双选择信道均衡或非平稳无线通信的开发者,是一份可运行的参考实现,也可帮助理解低复杂度迭代均衡器的设计思路与性能折中。 在做OTFS接收机相关的算法验证项目时,最让人头疼的就是均衡器部分。OTFS虽然把信号搬到了时延-多普勒域,抗多普勒能力比OFDM强很多,但等效信道矩阵不再是对角的,符号之间会互相干扰。直接用最优MMSE均衡,矩阵维度是 MN×MN(M为多普勒域符号数,N为时延域子载波数),一次求逆就能让仿真平台直接卡死;用消息传递(MP)类算法又过于依赖信道稀疏模型,信道估计稍微偏一点性能就掉得厉害。这次项目选定了 LMMSE-PIC(线性最小均方误差-并行干扰消除)这条路,目标是写一套低复杂度的迭代均衡器代码,在性能和运算量中间找一个工程上真正能落地的平衡点。这篇笔记把从原理梳理、代码结构到调参踩坑的过程都整理出来,给正在做OTFS检测方向的同行一个可参考的范例。
1. 项目概述与整体设计思路
1.1 OTFS均衡器要解决的到底是什么问题
先把系统模型说清楚。OTFS发送端会把调制符号放在时延-多普勒(DD)域的网格上,再通过辛傅里叶变换(SFFT)映射到时频域,最后经过OFDM调制发出去。收端做逆过程,把接收信号映射回DD域。这么做的核心收益是:在高速移动场景下,多普勒频偏在DD域里表现为近似稀疏的冲激响应,信道矩阵比时频域稀疏得多,这是OTFS对抗高速移动的理论基础。
但信道稀疏不代表没有干扰。实际上,DD域等效信道矩阵 H 是一个块循环矩阵,时延维和多普勒维都会产生符号间干扰。特别是多普勒扩展较大时,相邻多普勒索引之间的泄漏效应明显,如果接收端只做简单的匹配滤波或单抽头均衡,误码率根本压不下来。均衡器的任务就是在已知信道估计的前提下,从收到的 y = Hx + w 中尽可能恢复发送符号向量 x。
我记得第一次在仿真里看到未均衡的DD域星座图时,星点已经完全糊成一团,那时就意识到均衡这一步是OTFS接收机性能的绝对瓶颈。
1.2 为什么选LMMSE-PIC而不是其他方案
选型之前我对比了几类主流方案。完整MMSE滤波器的理论性能最好,但等效信道矩阵是 MN 维的,以 M=128、N=32 为例,矩阵规模是4096×4096,直接求逆的复杂度接近 O((MN)^3),这个量级在链路级仿真里完全跑不动。MP算法复杂度低,但需要精确的稀疏信道先验,工程实现时对信道估计噪声很敏感,迭代不收敛的情况时有发生。
LMMSE-PIC走的是另一条路:它把全局均衡拆成“并行干扰消除 + 逐符号LMMSE滤波”两步。每次迭代时,先用上一次迭代得到的软符号估计,把当前目标符号以外的所有符号干扰从接收信号中减掉,再对残差信号做一个低维度的LMMSE滤波更新当前符号的估计值。因为干扰消除是并行进行,而且每个符号的滤波系数是独立计算的,不需要直接求解完整矩阵的逆,复杂度显著下降。
为了理解,可以把它类比成教室里改试卷:老师(均衡器)不是一次性评判所有学生(符号),而是每次只关注一个学生,先把其他同学的答案盖住(干扰消除),再专心看这一份卷子哪里对哪里错(MMSE滤波),反复几轮下来,整体准确率就上去了。
1.3 低复杂度实现的三个切入点
这个项目把“低复杂度”具体落实在三个方面:第一,避免全局矩阵求逆。LMMSE滤波更新时用 Sherman-Morrison 方式推导出基于当前软符号方差和信道列向量的标量表达式,滤波系数的计算被压缩到每次内积加除法;第二,利用信道结构预计算。每个符号的等效信道能量和干扰协方差可以在进入迭代循环之前一次性算好,迭代过程中只做查表和索引,不重复生成矩阵;第三,迭代次数控制在2-4次。实测下来,在典型城区信道模型下,第3次迭代之后BER曲线基本进入平台期,继续迭代边际收益很低,但耗时线性增长。
2. 核心算法原理与关键公式推导
2.1 信号模型与LMMSE滤波基础
把接收信号写简洁一点:
[ \mathbf{y} = \mathbf{H}\mathbf{x} + \mathbf{w} ]
其中 (\mathbf{x}) 是DD域的发送符号向量,长度 MN,(\mathbf{H}) 是等效信道矩阵,(\mathbf{w}) 是复高斯白噪声。标准LMMSE的估计结果是:
[ \hat{\mathbf{x}} = (\mathbf{H}^H\mathbf{H} + \sigma^2\mathbf{I})^{-1}\mathbf{H}^H\mathbf{y} ]
这个公式里 (\sigma^2\mathbf{I}) 就是噪声正则项,很多人推导时容易漏。它在低信噪比下能有效抑制噪声放大,在高信噪比下防止矩阵接近奇异导致数值爆炸。实际代码中我会在求逆之前故意给对角线加一个 (1e^{-6}) 级别的数值稳定项,哪怕理论上不需要,也比直接求逆稳得多。
但直接套用这个公式的问题很明显:((\mathbf{H}^H\mathbf{H} + \sigma^2\mathbf{I})^{-1}) 的计算量太大。因为 H 是 MN 维矩阵,这个逆矩阵的求解复杂度太高。
2.2 PIC如何把全局求逆变成逐符号标量更新
PIC的关键思想是把“一次处理所有符号”改为“迭代处理所有符号”。假设第 (t) 次迭代时,符号向量有软估计 (\tilde{\mathbf{x}}^{(t)}),第 (q) 个符号之外的符号构成的干扰项为:
[ \mathbf{z}q^{(t)} = \mathbf{y} - \sum{k \neq q} \mathbf{h}_k \tilde{x}_k^{(t)} ]
其中 (\mathbf{h}_k) 是 H 的第 k 列。对这个残差信号,当前符号的LMMSE滤波系数是一个标量倍数:
[ g_q = \frac{\mathbf{h}_q^H \mathbf{z}_q^{(t)}}{\mathbf{h}_q^H \mathbf{h}_q + \sigma^2} ]
这里分子是匹配滤波输出,分母是能量归一化加噪声项。整个计算变成一次向量点积和一次除法,完全不涉及全局矩阵求逆。每次迭代所有符号重复这个过程,因为每个符号的 (\mathbf{h}_q^H \mathbf{h}_q) 和 (\sigma^2) 都不变,可以提前算好存成向量,迭代时只取对应索引。
这个推导过程我反复验证过很多次,关键点在于分母不能把干扰符号的残余方差漏掉。严格来说,干扰消除不是完美的,软符号估计本身有误差,这种残余干扰的方差应该加到分母里。如果忽略,高信噪比条件下的BER会有明显的地板效应。代码里我用一个变量residual_var来存这个残余方差,每次迭代根据软符号方差和信道列能量重新计算一次。
2.3 软符号与方差更新流程
每次迭代除了更新软符号均值 (\tilde{x}_q),还要更新符号方差 (v_q)。均值用于下一次干扰消除,方差用于计算残余干扰功率。以QPSK为例,软符号可以直接通过 tanh 形式计算,高阶QAM则需要按星座点概率加权求和。我在代码里用标准LLR到符号概率的映射:
[ p_m = \frac{\exp(LLR_m)}{\sum_{j}\exp(LLR_j)} ]
[ \tilde{x}_q = \sum_m p_m c_m ]
[ v_q = \sum_m p_m |c_m - \tilde{x}_q|^2 ]
这一步是整个迭代算法收敛性的核心,如果软符号方差偏大,残余干扰就大,后续迭代几乎不进步;如果方差偏小,相当于置信度虚高,算法会朝着错误的方向“自信地”收敛。我自己调试时吃过这个亏,后面在4.3节会细说。
2.4 迭代伪代码与复杂度分析
整个均衡器主循环的伪代码如下:
# 输入: y(接收DD域信号), H(等效信道矩阵), sigma2(噪声方差), max_iter # 输出: llr(比特级软信息) M, N = H.shape[0], H.shape[1] h_energy = diag(H^H @ H).real # 预计算符号能量 x_hat = zeros(M * N, dtype=complex) var_symbol = ones(M * N) # 初始方差设1,对应全不确定 for t in range(max_iter): # 并行干扰消除 for q in range(M * N): if t == 0: z_q = y[q] # 第一次没有先验,退化为匹配滤波 else: # 从接收信号中减去除q以外的所有软符号贡献 z_q = y[q] - (H @ x_hat)[q] + H[q, q] * x_hat[q] g = h_energy[q] / (h_energy[q] + sigma2 + residual_var(q)) x_tilde_q = g * z_q # 更新软符号和方差(这里根据调制方式映射) x_hat[q], var_symbol[q] = symbol_mapping(x_tilde_q, modulation)复杂度方面,每次迭代主要开销是计算 (\mathbf{H}\tilde{\mathbf{x}}),这需要遍历 H 的每个非零元素。好的实现里 H 用稀疏矩阵存储,非零元素个数约为 (ML) 量级,L是信道抽头数,因此一次迭代复杂度约 (O(MNL))。全局求逆的开销则被彻底消除。以 M=32、N=64、L=8 为例,一次迭代大约只需要16万次复乘,这在实时仿真里几乎不构成压力。
3. 代码实现框架与核心模块详解
3.1 代码结构与模块划分
整个工程我按功能拆成了五个模块,文件结构如下:
otfs_lmmse_pic/ ├── config.py # 系统参数与仿真参数配置 ├── channel.py # 时变信道生成与DD域等效信道矩阵构建 ├── equalizer.py # LMMSE-PIC均衡器核心实现 ├── modulation.py # 调制解映射、符号概率与LLR计算 ├── simulation.py # 收发链路主仿真脚本 └── utils.py # 辅助工具(矩阵预处理、性能统计)模块划分的核心原则是“信道矩阵构建一次,均衡循环内只读”。调试时可以单独验证 channel.py 生成的矩阵是否满足能量守恒,也可以单独跑 equalizer.py 看不同迭代次数下的软符号分布,不需要每次都从头跑完整仿真。这种拆分对排查问题非常有效。
3.2 参数配置与接口设计
系统参数放在 config.py 里统一管理:
# config.py class OTFSConfig: # 时延-多普勒域网格 M = 128 # 多普勒域符号数 N = 32 # 时延域子载波数 cp_len = 64 # 循环前缀长度 # 调制参数 modulation = '16QAM' num_bits_per_symbol = 4 # 信道参数 delay_taps = [0, 2, 5] # 抽头时延 doppler_shifts = [0, 60, 120] # 抽头多普勒频偏(Hz) path_gains = [1.0, 0.7, 0.5] # 各抽头增益 # 均衡器参数 max_iter = 3 noise_var = 0.01 # 归一化噪声方差算法在迭代中会频繁访问调制方式对应的星座映射表,所以我建议把所有星座点坐标和归一化因子提前算好。
3.3 等效信道矩阵构建的预处理
等效信道矩阵 H 是整个项目最微妙的部分。直接从时域信道脉冲响应出发,通过时频响应矩阵再经SFFT变换到DD域,代码量大且容易出错。我在实现中发现一个更直接的方式:对每条时延抽头,其在DD域的响应可以表示为一个循环移位后的对角矩阵乘上一个由多普勒频率决定的相位旋转矩阵,这样 H 可以表达为:
[ \mathbf{H} = \sum_{l=0}^{L-1} g_l \cdot \mathbf{\Pi}^{d_l} \odot \mathbf{F}_D^H \operatorname{diag}(f_l) \mathbf{F}_D ]
代码中避免显式构造 (\mathbf{F}_D) 矩阵,而是直接用 FFT 和循环移位向量操作。核心思路是:每条径的贡献就是“时延维上的置换 + 多普勒维上的频移”,在DD域里表现为块循环结构,对H的存储使用稀疏矩阵,内存占用从几GB降到几MB。这一步优化是整个仿真能跑起来的关键。
3.4 均衡主循环与内存复用
均衡主循环里最容易犯的低级错误是每次迭代重新分配大数组。我一开始写的时候在循环体内反复构造临时矩阵,跑一次仿真内存反复波动,又慢又容易触发交换分区。后来改成预分配所有临时向量,循环内只做索引更新和原地写入,速度大概提高了三倍。
主循环里还需注意数值类型,建议所有复数数据统一用 double 复数,IMC实际运行中如果混用 single 和 double,迭代后期误差会被放大,尤其是高信噪比场景下误差可能比信号本身还大。
3.5 LLR输出与信道解码的对接
均衡器最终输出的是软比特信息,用于输给LDPC译码器。如何从软符号 (\tilde{x}_q) 和方差 (v_q) 得到LLR,这个问题一开始困扰我很久。最稳妥的做法是假设残余干扰服从高斯分布,对每个比特位置 m,把对应的两组星座点集合 (S_0)、(S_1) 的似然分别累加,最大对数近似就可以:
[ LLR(b_m) \approx \min_{c \in S_1} \frac{|\tilde{x}q - c|^2}{v_q} - \min{c \in S_0} \frac{|\tilde{x}_q - c|^2}{v_q} ]
这个近似在16QAM下误差很小,而且由于 v_q 在迭代后期会明显变小,等效于自动给高置信度符号更高权重,比硬判决输出解码性能好很多。我建议先实现这个简化版,确认整条链路无误后再升级到精确概率计算。
4. 仿真验证、参数影响与调试经验
4.1 仿真性能验证
仿真链路采用M=128、N=32、16QAM调制,信道设置为3条径、最大多普勒频偏120Hz,载频4GHz,对应移动速度约90km/h。在这个参数下,经典OFDM接收机的BER在高信噪比区域出现明显平台,而OTFS+LMMSE-PIC则没有明显的错误地板。
从迭代次数来看:第一轮迭代后的BER大约在1e-1量级,星座图边界能看出来但错误仍然密集;第二轮迭代之后BER下降趋势明显;第三轮迭代在高信噪比下和第四轮几乎重合。这说明在大多数信道实现下,3次迭代已经足够。这里要强调一点:这是归一化信噪比在8到20dB范围内的结论,如果信道多普勒进一步增大,可能需要5到6次迭代,具体要看残余干扰功率的变化。
4.2 关键参数的影响与选择
我把影响性能的几个参数列成一张表:
| 参数 | 影响方向 | 经验设置 |
|---|---|---|
| 迭代次数 | 越多收敛越好,但边际收益递减 | 3~4次足够 |
| 噪声方差估计值 | 偏大导致滤波过度平滑,偏小导致数值不稳定 | 用真实信道噪声的1.0~1.1倍 |
| 正则化系数 | 太小下矩阵近奇异,太大导致精度下降 | 设为噪声方差十分之一 |
| 信道矩阵存储方式 | 稠密存储导致内存爆炸,稀疏存储不影响性能 | 必须以稀疏矩阵存储 |
有一个容易忽略的细节是噪声方差的输入方式。在MATLAB仿真里直接用awgn函数加噪,再估算噪声方差,往往和实际设定值有偏差。LMMSE滤波对噪声方差非常敏感,如果给的值偏大,滤波结果会过度平滑,把符号细节抹掉;如果偏小,高信噪比下又会出现数值振荡。我的做法是动态计算接收端噪声方差并自动传给均衡器,避免手动硬编码。
4.3 常见问题与排查记录
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| BER高信噪比下出现平台 | 残余干扰方差未计入分母 | 每次迭代根据符号方差更新残余干扰功率 |
| 第一次迭代星座图完全发散 | 信道矩阵构建时未进行归一化 | 检查H的能量是否等于信道抽头能量和 |
| 迭代2到3次后BER反而恶化 | 软符号方差更新错误 | 打印每轮迭代的方差均值,正常情况下应该递减 |
| 内存溢出 | H被构建为稠密矩阵 | 改用稀疏矩阵,并用循环移位操作代替矩阵乘法 |
| 高多普勒信道下性能下降 | 迭代次数不足 | 将迭代上限扩展,观察残余干扰是否继续下降 |
印象最深的一次调试:BER性能在12dB后就再也降不下去,查了三天数据流才发现是信道矩阵的时延维索引从0开始还是从1开始的问题,导致抽头位置整体偏移了一个单位。这种问题仿真里表现很隐蔽,但会直接影响矩阵的置换方向,建议在channel.py里加一个单元测试,用已知信道抽头验证H矩阵的列循环结构。
4.4 提速与工程化的几个心得
代码实现层面,提速效果最明显的是三件事:第一是矩阵向量乘法 (\mathbf{H}\tilde{\mathbf{x}}) 改成稀疏矩阵左乘向量,这是每次迭代最大的性能热点;第二是把这个乘法里的共轭部分提前算好,因为 H 在循环中不变,只改变 (\tilde{\mathbf{x}}),可以复用预处理的索引向量;第三是用NumPy或MATLAB的向量化索引替换for循环遍历每个符号,特别是PIC更新部分,如果逐符号写循环,速度会慢两个数量级。
另外,建议仿真时保存每轮迭代的软符号方差均值和星座图,方便定位算法是否在正常收敛,而不是等到最终BER统计时才发现问题。
这个项目做完之后,我个人的一个深刻体会是:OTFS接收机算法难的不是理论推导,而是把矩阵级的运算映射到有限的存储和运算预算里。LMMSE-PIC方案在性能与复杂度之间给出的折中非常适合工程验证,但如果后续要做到实时原型,还需要进一步考虑定点化和流水线设计。最后分享一个小技巧:在开发调试阶段,可以先固定信道系数,不每次重新生成信道,这样能排除信道随机性的干扰,集中精力验证均衡器本身;等功能全部跑通后,再切回随机信道做整链路统计。先用这种“降维”方式定位问题,能省下大量时间。
本文还有配套的精品资源,点击获取