首页 / 资讯中心 / 文章详情

SLM降低PAPR仿真代码:OFDM峰均比抑制与CCDF对比实践

SLM降低PAPR仿真代码:OFDM峰均比抑制与CCDF对比实践 ★ FEATURED ARTICLE
简介这份资源是面向无线通信与OFDM系统学习者的SLM降低PAPR仿真代码聚焦峰均功率比抑制这一经典问题适合已掌握MATLAB基础、正在做课程设计或论文复现的中高级读者。压缩包共1个文件为单个m脚本体积约726B轻量易读可直接在MATLAB中运行并修改参数。已有703人学习下载具备一定的参考基础。代码围绕子载波数128、仿真次数10000次等参数展开完整呈现OFDM符号生成、相位扰动、功率计算与最小功率版本选择流程并计算原始信号与SLM处理后的CCDF曲线进行可视化对比便于直观观察PAPR降低效果。读者可据此理解SLM的相位矢量设计思路、编码开销与计算复杂度之间的权衡并在此基础上调整参数开展误码率与功耗效率的延伸分析是入门PAPR抑制仿真的实用脚本。1. 从一条“波形尖峰”说起SLM 降低 PAPR 的仿真代码能解决什么做 OFDM 基带仿真的人大概都遇到过这种场面时域波形里偶尔冒出一个特别高的尖峰功率放大器一进去就非线性失真误码率曲线在某个信噪比之后突然不往下掉了。翻来覆去查链路最后发现不是信道估计的问题也不是同步的问题而是峰均功率比 PAPR 太高。这份 SLM 降低 PAPR 的仿真代码就是围绕这个具体问题展开的用选择性映射Selected MappingSLM的思路在发送端生成多组相位旋转后的候选信号挑出 PAPR 最低的那一组发出去接收端再靠边带信息把相位还原回来。它适合两类人一类是通信、信号处理方向的学生和研究者想跑通一条完整的 PAPR 对比链路看清 CCDF 曲线怎么随候选数变化另一类是工程上要做基带算法验证的开发者想拿一份能改参数、能换调制方式、能接自己信道模型的仿真骨架。代码本身不依赖大型工具箱核心就是 OFDM 调制、相位序列生成、PAPR 计算和 CCDF 统计这几块读起来门槛不高但要把结果跑对、把曲线跑平里面有不少细节值得掰开讲。2. 项目定位与技术选型为什么是 SLM为什么用这套仿真骨架2.1 SLM 在 PAPR 抑制里的位置PAPR 抑制的方法大致分三类信号预畸变限幅、压扩、编码类、以及概率类。SLM 属于概率类思路不是把尖峰削掉而是通过相位旋转让同一组数据产生多个统计独立的候选时域信号从中挑 PAPR 最小的那个发送。它的好处是不引入带内失真代价是需要传输边带信息哪一组相位被选中并且发送端要做 U 次 IFFT。这份仿真代码正是按这个逻辑组织的候选数 U 是一个可调参数U 越大CCDF 曲线往左下压得越明显但计算量和边带开销也随之上来。理解这一点后面看代码里的循环结构就不会迷路。2.2 技术栈与运行环境代码是纯数值仿真常见做法是用 Python 配 NumPy 和 Matplotlib少数人会用 MATLAB。这里按 Python 路线讲因为依赖轻、跨平台、画图方便。核心依赖只有三个依赖作用备注numpy矩阵运算、FFT/IFFT、随机相位生成主力几乎所有计算都靠它matplotlib画 CCDF 曲线、时域波形对比只用于结果可视化scipy可选用于 QAM 调制映射或统计拟合若手写映射可省运行环境建议 Python 3.8 以上NumPy 1.20 以上。不需要 GPU单机 CPU 跑几千个 OFDM 符号、几百次蒙特卡洛就够出平滑曲线。我一般会把随机种子固定住否则每次跑出来的 CCDF 曲线在低概率尾部会抖看起来像代码有 bug其实是样本数不够。2.3 仿真链路的整体结构一条完整的 SLM-OFDM 仿真链路包含这些环节比特生成 → 星座映射QPSK/16QAM→ 串并转换 → 复制成 U 路 → 每路乘不同相位序列 → IFFT 得到时域候选 → 计算每路 PAPR → 选最小者 → 可选加边带信息 → 过信道 → 接收端 FFT、相位还原、解映射 → 统计误码率和 CCDF。这份代码的重点在前半段也就是 PAPR 抑制效果本身信道和接收端可以简化但结构要留出来方便二次开发时接进去。下面这张目录结构是我按这类仿真项目的常见组织方式整理的实际拿到源码时对照着看即可slm_papr_sim/ ├── main.py # 入口参数配置、调用仿真、画图 ├── ofdm.py # OFDM 调制/解调、PAPR 计算 ├── slm.py # 相位序列生成、候选选择、边带信息 ├── mapping.py # QPSK/16QAM 星座映射与解映射 ├── stats.py # CCDF 统计、蒙特卡洛循环 └── config.py # 全局参数子载波数、U、符号数、调制阶数这种拆法的好处是每个文件职责单一改调制方式只动 mapping.py改候选数只动 config.py不会牵一发动全身。新手最容易犯的错是把所有逻辑塞进一个脚本跑到后面想换个子载波数都得全局搜索替换血泪经验。3. 核心模块拆解从相位序列到 CCDF 曲线的完整链路3.1 参数配置与 OFDM 符号生成先看配置。子载波数 N、候选数 U、仿真符号数、调制阶数这几个参数决定了整个仿真的规模和精度。N 一般取 64、128、256取大了 IFFT 点数多、PAPR 分布更接近理论值但单次仿真变慢U 取 2、4、8、16是观察 SLM 增益的主要旋钮。下面是一段典型的配置与 OFDM 调制代码import numpy as np # 全局参数 N 128 # 子载波数即 IFFT 点数 U 8 # SLM 候选数越大 PAPR 抑制越好但计算量越高 N_SYM 2000 # 蒙特卡洛符号数决定 CCDF 曲线尾部平滑度 M 4 # 调制阶数4 表示 QPSK CP_LEN 16 # 循环前缀长度PAPR 计算时通常不计入 def ofdm_modulate(freq_symbols): 频域符号 - 时域 OFDM 符号不含 CP return np.fft.ifft(freq_symbols, nN) * np.sqrt(N) def compute_papr(time_signal): 按 dB 计算单个 OFDM 符号的 PAPR peak np.max(np.abs(time_signal) ** 2) avg np.mean(np.abs(time_signal) ** 2) return 10 * np.log10(peak / avg)这里有两个容易翻车的点。第一ifft之后乘sqrt(N)是为了保持功率归一化不乘的话平均功率会随 N 变化PAPR 数值看着对但和理论曲线对不上。第二PAPR 计算用的是未加 CP 的时域信号因为 CP 只是复制尾部不改变峰值与平均功率之比如果你把 CP 加进去再算数值会略低和文献对比时会有系统性偏差。参数上N_SYM建议至少 1000否则 CCDF 在 10^-3 量级就开始抖U从 2 开始试逐步加到 16观察曲线左移的边际收益递减。3.2 相位序列生成与候选信号构造SLM 的核心是那组相位旋转因子。常见做法是从 {1, -1, j, -j} 里随机取或者用单位圆上的均匀相位。代码里要为每个候选生成独立的相位向量且第一路通常保持全 1 作为参考这样接收端可以以它为基准做差分还原。下面这段是候选构造的关键def generate_phase_sequences(U, N, seed42): 生成 U 组长度为 N 的相位序列第一组全 1 作参考 rng np.random.default_rng(seed) phases np.ones((U, N), dtypecomplex) # 从 {1, -1, j, -j} 中随机选取保证恒模 candidates np.array([1, -1, 1j, -1j]) for u in range(1, U): phases[u] rng.choice(candidates, sizeN) return phases def slm_candidates(freq_symbols, phases): 对同一组频域符号生成 U 个时域候选 cands [] for u in range(phases.shape[0]): rotated freq_symbols * phases[u] cands.append(ofdm_modulate(rotated)) return np.array(cands)generate_phase_sequences里第一组固定为全 1是为了让接收端知道“原始相位”长什么样边带信息只需要传被选中那组的索引不用传整个相位向量。slm_candidates对每个候选做一次逐点相乘再 IFFT注意这里是频域乘相位、时域看效果等价于时域循环移位但 SLM 关心的是 PAPR 统计特性不是移位本身。参数上相位从四相集合取是工程上最省边带信息的做法2 bit 就能表示一个相位如果改成连续均匀相位抑制效果略好但边带开销变大二次开发时可以在这里做权衡。3.3 候选选择、边带信息与 PAPR 统计选出 PAPR 最小的候选之后要记录它的索引这就是边带信息。仿真里可以假设边带理想传输也可以加一点误码看鲁棒性。统计部分用蒙特卡洛循环累积每个符号的最小 PAPR最后算 CCDFdef run_simulation(N_SYM, U, N, phases): papr_slm np.zeros(N_SYM) papr_original np.zeros(N_SYM) for s in range(N_SYM): bits np.random.randint(0, 2, N * 2) # QPSK 每符号 2 bit freq qpsk_map(bits) # 频域符号 cands slm_candidates(freq, phases) paprs np.array([compute_papr(c) for c in cands]) idx np.argmin(paprs) # 选 PAPR 最小 papr_slm[s] paprs[idx] papr_original[s] paprs[0] # 参考不选直接用第一路 return papr_original, papr_slm def ccdf(papr_db, thresholds): CCDFP(PAPR threshold) return np.array([np.mean(papr_db t) for t in thresholds])run_simulation里papr_original取的是第一路候选的 PAPR作为未做选择的基线这样两条曲线画在一起对比才公平。ccdf用而不是在连续分布下差别可忽略但和文献公式对齐时要注意。阈值一般取 0 到 12 dB步长 0.5 dB。跑完之后用 Matplotlib 画半对数图横轴 PAPR(dB)纵轴 CCDF就能看到 U2、4、8、16 时曲线依次左移。这里有个细节如果N_SYM只有几百10^-3 以下的点基本是噪声别拿它下结论。3.4 结果可视化与曲线对比画图部分不复杂但坐标轴和标注要规范否则自己回头看都认不出哪条线是哪条import matplotlib.pyplot as plt thresholds np.arange(0, 12.5, 0.5) ccdf_orig ccdf(papr_original, thresholds) ccdf_slm ccdf(papr_slm, thresholds) plt.semilogy(thresholds, ccdf_orig, k-, labelOriginal OFDM) plt.semilogy(thresholds, ccdf_slm, r--, labelfSLM (U{U})) plt.xlabel(PAPR (dB)) plt.ylabel(CCDF) plt.grid(True, whichboth) plt.legend() plt.savefig(ccdf_slm.png, dpi150)semilogy是对数纵轴CCDF 尾部才看得清。grid(whichboth)把次网格也打开读图时方便估读。保存图片用dpi150够用投稿再调高。如果要把多个 U 值画在一张图上把run_simulation包一层循环每次换 U 重跑注意相位序列的种子要一致否则不同 U 之间的对比会混入随机性看起来像玄学。4. 跑起来与改起来环境搭建、参数调优与二次开发入口4.1 环境搭建与首次运行拿到代码后第一步是把依赖装齐。建议用虚拟环境避免和系统里的 NumPy 版本打架python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install numpy matplotlib scipy python main.py首次运行建议先把N_SYM调到 200、U调到 4几十秒就能出图确认链路通了再往上加。如果main.py里没有现成的qpsk_map按标准 QPSK 映射补一个即可每两个比特映射到(±1±1j)/sqrt(2)注意归一化否则平均功率不是 1PAPR 数值会整体偏移。跑通之后检查两件事原始 OFDM 的 CCDF 在 10 dB 附近是否落在 10^-1 到 10^-2 之间N128 时的典型值SLM 曲线是否明显左移。如果原始曲线就不对先别怀疑 SLM回头查映射归一化和 IFFT 缩放。4.2 参数调优U、N 与符号数怎么配这三个参数互相牵制配不好要么跑得慢要么曲线抖。经验上的搭配是N 取 128 或 256U 取 4 到 16N_SYM 至少 2000。U 从 4 加到 8PAPR 在 10^-2 点大约能再降 0.5 到 1 dB从 8 加到 16 收益更小边际递减很明显。N 增大时 PAPR 分布整体右移这是 OFDM 的固有特性不是代码错。N_SYM 不够时CCDF 尾部会出现“断崖”或“平台”那是样本不足不是算法失效。我一般会先固定 N 和 N_SYM只扫 U把曲线族画出来再换 N 看趋势这样每次只动一个变量出问题好定位。4.3 二次开发的几个入口这份代码的扩展性主要留在这几个地方。第一换调制方式在mapping.py里加 16QAM 映射注意 PAPR 分布会随星座变化QPSK 的 PAPR 通常比 16QAM 略低。第二换相位集合把generate_phase_sequences里的四相集合换成单位圆均匀相位观察抑制增益和边带开销的权衡。第三接信道在选完候选之后加 AWGN 或衰落信道接收端做 FFT、除以相位、解映射统计 BER这样就能同时看 PAPR 和误码率两条线。第四换 PAPR 计算方法现在用的是离散时间过采样前的峰值如果要做更精确的估计可以在 IFFT 前补零做 4 倍过采样峰值会更接近真实连续信号但计算量翻几倍。这些改动都不需要动主循环结构属于“插拔式”扩展。5. 避坑与排查SLM 仿真里最容易翻车的几件事5.1 曲线不左移甚至比原始还高现象是跑完发现 SLM 的 CCDF 和原始曲线几乎重合个别点还更高。原因通常有两个一是候选选择时用了argmax而不是argmin或者把 PAPR 算成了负值二是相位序列没有真正作用到频域符号上比如乘在了 IFFT 之后。解决方法是打印单个符号的 U 个 PAPR 值确认最小值确实小于第一路再检查freq_symbols * phases[u]这一行是否在ofdm_modulate之前执行。5.2 CCDF 尾部抖动得像噪声现象是 10^-3 以下的曲线锯齿严重换一次随机种子形状就变。原因是N_SYM太小尾部事件本来就稀少样本不够自然抖。解决办法是把N_SYM加到 5000 以上或者固定种子后多次运行取平均。注意这不是算法问题别去改 SLM 逻辑越改越乱。5.3 PAPR 数值整体偏高或偏低现象是和文献对比时原始 OFDM 的 CCDF 整体平移了 1 到 2 dB。原因多半是功率归一化没做对IFFT 后没乘sqrt(N)或者星座映射没除以sqrt(2)。检查compute_papr里平均功率是否接近 1如果不是回头补归一化。另一个可能是把 CP 算进了 PAPR导致数值偏低统一口径即可。5.4 边带信息假设过于理想导致结论偏乐观现象是仿真里 SLM 效果很好但一加信道误码率就崩。原因是代码默认边带信息无误传输实际中边带出错会导致整组符号相位还原错误误码扩散。二次开发时要在接收端给索引加一点误码概率或者用差分相位降低对边带准确性的依赖否则结论只能停留在“理想边带”前提下。5.5 多组 U 对比时随机性混入现象是 U4 的曲线偶尔比 U8 还好看起来反直觉。原因是每次换 U 都重新生成了随机数据和相位样本不同不可比。解决办法是固定数据比特和相位种子只改 U让对比在相同输入下进行。这一点在写论文或做报告时尤其重要否则审稿人一眼就能看出对比不公平。6. 二次开发技巧把仿真代码变成能验证想法的工具真要把这份代码用起来关键不是跑出一张图而是让它能快速验证你的新想法。我的习惯是先把config.py里的参数全部外置成命令行参数或字典这样换配置不用改代码然后在stats.py里把蒙特卡洛循环和统计逻辑分开循环只负责产生 PAPR 样本统计单独成函数方便换不同的 CCDF 估计方法。下面这个小改造可以把“扫 U”变成一行调用def sweep_u(u_list, N_SYM2000, N128): results {} for u in u_list: phases generate_phase_sequences(u, N, seed42) _, papr_slm run_simulation(N_SYM, u, N, phases) results[u] papr_slm return resultssweep_u里种子固定为 42保证不同 U 之间用的是同一批数据和同一组基础相位对比才干净。返回的字典直接喂给画图函数一张图叠多条曲线。再进一步可以把run_simulation里的qpsk_map换成可传入的函数对象这样换调制方式不用改循环体符合开闭原则。验证方法上我一般会做两件事一是用 U1 跑一遍此时 SLM 退化为原始 OFDM曲线应该和基线完全重合这是链路正确性的自检二是把 N 从 128 换到 256看原始 CCDF 是否按理论右移如果趋势对说明 IFFT 和 PAPR 计算没问题。这两步做完再去试新的相位集合或过采样心里才有底。还有一个容易被忽略的技巧把每次仿真的关键参数和结果哈希存下来比如N_U_Nsym_seed组合对应一条曲线下次复现直接查表不用重跑。仿真代码最怕的就是“上次那个图是怎么跑出来的”想不起来有了这层记录后悔药就省了。从那以后我每次改 SLM 的相位生成逻辑都强制先跑 U1 自检和 N 翻倍趋势检查确认基线没动再去看新算法的增益。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站