ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

贪婪算法在OFDM资源分配中的Matlab实现与调参

贪婪算法在OFDM资源分配中的Matlab实现与调参 简介面向OFDM系统的资源分配优化场景这里提供一套基于贪婪算法的Matlab实现与说明文件。资源定位于无线通信方向的学生与算法研究者适合已经掌握OFDM基础、希望借助实际代码理解贪婪算法在子载波分配和功率控制中局部最优决策策略的读者。压缩包共2个文件核心是一个m脚本完成初始化、按信噪比排序的贪心选择、功率迭代分配以及误码率或吞吐量评估等流程另一个txt文档补充理论背景与代码使用说明可辅助快速上手。整个包体仅896B轻量精简便于下载后直接运行和二次修改。该资源已有180人学习具有一定实践参考价值。通过研读代码读者可以直观看到贪婪算法如何在多子载波环境下权衡资源并在此基础上结合其他优化策略进一步改善系统性能。1. 在OFDM里用贪婪算法先要弄清楚资源分配在解决什么问题很多人看到贪婪算法第一个反应是它有一个固定现成的函数但放到OFDM场景里它并不是某个工具箱里可以直接调用的小工具而是一种逐比特、逐子载波的资源分配策略。OFDM系统把整个频带切成几十到上千个正交子载波不同子载波上的信道增益和噪声方差都不一样基带处理侧就需要决定每个子载波用几比特调制分配多少发射功率才能在目标误码率下拿到尽量高的吞吐量。贪婪算法处理的正是这个离散化的资源分配问题。它从0比特开始每一轮只挑增加1比特所需额外功率最小的那个子载波把它的调制阶数提高一级重复直到总速率达标或总功率用完。这种做法的复杂度远低于穷举和动态规划结果又接近最优所以常被拿来做链路级仿真、吞吐量基准和预研阶段的快速验证。这篇文章从OFDM的IFFT/FFT原理讲起给一个能在Matlab里直接跑通的最小实现再讲三个关键参数怎么调、两个常见坑在哪里最后用两个低成本方法验证算法没有写错。适合正在做物理层仿真、通信算法毕设或5G预研的人参考。2. OFDM资源分配为什么要用贪婪算法从注水定理到离散比特表2.1 先看OFDM的基带模型子载波、信道增益和噪声OFDM调制的基本做法是发送端把频域符号序列通过IFFT变成时域波形接收端用FFT再解回来配合循环前缀抵消符号间干扰。只要保护间隔足够长、子载波间正交性保持得好这N个子载波就可以拆成N个并行的窄带子信道来独立处理。所谓ofdm原理里的并行传输本质是让每个子载波自己在频域上衰落而不是整个宽带信号一起深衰落。接收端在第k个子载波上拿到的信号可以写成y_k H_k x_k n_k这里H_k是第k个子载波上的复信道增益包含了路径损耗、阴影和频率选择性衰落n_k是零均值复高斯噪声实部和虚部方差各为sigma2/2。因为子载波之间已经解耦资源分配就变成在每个子载波上根据|H_k|^2和噪声方差决定用几比特调制以及给多大功率。OFDM的IFFT/FFT这一层解决的是怎么把数据放到并行子载波上真正决定每个子载波能承载多少比特的是信道估计给出来的H_k和噪声方差。因此在做贪婪算法之前先要把这部分输入准备好。同步、循环前缀、频偏估计都正常工作时剩下的问题才是纯资源分配。2.2 注水定理是理论下限离散比特却让问题变成组合优化如果允许每个子载波使用任意连续速率并且目标是在总功率约束下最大化总速率经典结论是注水定理。最优功率分配是p_k max(mu - sigma2 / |H_k|^2, 0)mu由总功率约束决定。信道好的子载波分到更多功率信道差的子载波可能不分功率。注水定理给出的是连续容量域的理论解但在实际OFDM系统里不能直接用因为调制阶数是离散的BPSK是1比特QPSK是2比特16QAM是4比特64QAM是6比特不可能给某个子载波分配3.7比特。一旦限制每个子载波只能从有限集合里选调制阶数这个问题就从凸优化变成了组合优化。做一个最简单的估计64个子载波每个子载波有7种可能0到6比特组合数是7^64穷举完全不可行。更麻烦的是不同调制阶数达到相同目标误码率所需的SNR并不相同存在一个门限分配时不能只看信道增益还要看每一级调制对应的SNR增量。这时候如果硬套连续注水再四舍五入到离散调制阶数会带来两个问题一是舍入方向不好控制二是总功率约束容易在边界上被破坏。比较稳妥的做法是把问题重新表述为在总速率和总功率约束下选择一组离散比特数和对应发射功率使总发射功率最小或使总速率最大。2.3 贪婪算法的选型理由为什么不是穷举或动态规划贪婪算法的思路非常直接从所有子载波都是0比特开始每次扫描所有还能再增加1比特的子载波计算给它加这1比特需要额外付出多少发射功率然后选出额外功率最小的那个子载波加1比特。这个过程重复到达到目标速率或者总功率预算耗尽。这个过程本质上是一种以边际成本递增为原则的调度。因为每次加的都是当前全局最小的代价前几步的分配质量非常高。随着比特数增多每个子载波的边际代价也在上升系统会自动避开信道较差的子载波把比特集中到信道好的子载波上。这跟注水定理刻画的方向是一致的。几种典型方法对比如下方法复杂度最优性适用场景穷举搜索O(M^N)绝对最优子载波数小于8、调制阶数很少时动态规划O(N * B * P)量化条件下最优比特和功率量化粒度很粗实现复杂贪婪算法O(N * B)近似最优误差通常小于5%大规模子载波、链路仿真、实时调度在实际工程里N经常是64、128甚至更大动态规划的内存和时间开销都很难接受而贪婪算法每一轮只需要扫描一次N总共扫描target_bits轮复杂度在毫秒级。再加上实现简单调试时可以直接观察每个子载波的比特分配过程所以它成了OFDM资源分配里最常见的一套基准方案。这里还有一个很容易被忽略的点贪婪算法给出的是比特分配不是直接给出发射功率。发射功率在分配完成后需要按各子载波所需SNR来反推或者做整体功率归一化。这部分在下一章的Matlab实现里会一起处理。3. 用Matlab把贪婪比特和功率分配写成可运行的最小实现3.1 构造OFDM子载波信道和噪声的仿真输入写Matlab实现时第一步不是堆代码而是把输入参数定义清楚。我在工程里一般先确定N为子载波数H为复数信道增益向量sigma2为每子载波上的噪声功率。信道可以是频率选择性衰落的仿真结果也可以是实测估计值噪声功率则取决于带宽、接收机噪声系数和子载波间隔。下面这段代码生成一个最简单但能说明问题的输入N 64; % 子载波数 H (randn(1,N) 1j*randn(1,N)) / sqrt(2); % 复高斯信道平均功率为1 sigma2 0.1; % 每个子载波上的噪声功率 snr abs(H).^2 / sigma2; % 无发射功率时的等效SNR这里的H是频域信道估计结果每个元素是复数abs(H).^2才是功率增益。randn生成的实部和虚部各自除以sqrt(2)是为了让E[abs(H)^2] 1方便后续门限参数和发射功率保持同数量级。sigma2取0.1意味着在平均信道增益为1时一个单位发射功率能得到10dB左右的SNR。在实际仿真里H来自信道估计模块而不是随机生成但输入格式是一样的。snr这个变量在调试时很有用可以一眼看出哪些子载波本来就差哪些子载波有潜力被贪婪算法选中。3.2 贪婪比特加载主循环按边际信噪比逐步加比特接下来是核心循环。我采用一种非常常见的离散比特加载模型达到某误码率所需SNR门限近似为snr_req(b) gamma * (2^b - 1)gamma是SNR gap反映编码调制和误码率距离香农极限的差距。从b-1比特提升到b比特所需额外SNR增量为inc_snr(b) gamma * 2^(b-1)这个增量是线性的意味着越往上加比特边际代价越贵正好符合贪婪算法想要的递增边际成本。主循环代码如下% 参数定义 target_bits 128; % 目标总比特数可以改成你想仿真的负载 max_bits 6; % 单个子载波最大比特数对应64QAM gamma 2.0; % SNR gap未编码QAM常用1.5~2.5 b zeros(1,N); % 每个子载波当前分配的比特数 % 预计算从0到max_bits每一步所需SNR增量线性值 inc_snr (b_new) gamma * 2.^(b_new - 1); % 先确认目标速率不可能超过上限 if target_bits N * max_bits error(目标比特数超过全部子载波最大承载能力); end % 贪婪主循环 while sum(b) target_bits extra_power inf(1,N); % 存放每个子载波加1比特的额外功率 for k 1:N if b(k) max_bits % 额外功率 额外SNR / (信道增益 / 噪声功率) extra_power(k) inc_snr(b(k)1) / (abs(H(k)).^2 / sigma2); end end [~, idx] min(extra_power); % 找最小边际功率的子载波 b(idx) b(idx) 1; % 给它加1比特 end这段代码的逻辑是每次循环先扫描所有子载波计算如果给某个子载波加1比特发射功率要额外增加多少。信道好、当前比特数低的子载波extra_power就小更容易被选中。min(extra_power)取了全局最小值所以这个算法严格遵循每次做代价最小的提升。inc_snr(b_new)这个函数要特别注意当b_new1时它表示从0比特跳到1比特需要的SNR是gamma当b_new6时它表示从5比特跳到6比特需要的SNR是32*gamma。这个指数增长关系是自然的因为64QAM比QPSK需要高得多的SNR才能维持相同误码率。如果目标速率定得过高比如target_bits N*max_bits那么每个子载波都会被填满贪婪算法实际上没有选择空间结果就是平均分配。这种情况不会报错但说明负载已经逼近系统容量边界继续提高速率需要更大的调制阶数或者更低的gamma。3.3 比特分配完成后的功率归一化与BER验证贪婪算法输出的b只是比特分配还需要计算每个子载波需要多少发射功率。功率的计算根据SNR门限反推p_k snr_req(b_k) * sigma2 / |H_k|^2如果总功率超过预算就把所有子载波功率按相同比例缩放。缩放后会有一部分子载波达不到门限实际误码率会升高所以工程上一般会预留功率余量不要让分配结果总是顶在功率上限上。snr_req (bb) gamma * (2.^bb - 1); % 所需SNR线性值 P_alloc snr_req(b) .* sigma2 ./ (abs(H).^2); % 实际所需发射功率 P_budget 128; % 总功率预算 if sum(P_alloc) P_budget scale P_budget / sum(P_alloc); P_alloc P_alloc * scale; fprintf([警告] 总功率超限已等比缩放为原来的 %.2f%%\n, scale*100); end % 显示前8个子载波的分配结果用于核对 for k 1:8 fprintf(子载波%2d: 比特%d, 功率%.4f\n, k, b(k), P_alloc(k)); end这套功率计算方式有一个隐含假设每个子载波独立解调子载波之间没有互干扰。只要OFDM的循环前缀设置合理这个假设成立。如果后面要做的不是单用户点对点链路而是多用户OFDMA调度那么还需要在贪婪循环之前先做子载波分配不能直接用这套代码处理多用户竞争。功率归一化之后可以做一次完整的调制加噪解调来验证BER。具体做法是对每个子载波根据b(k)选择调制阶数发射功率乘以信道加上复高斯噪声再除以信道增益完成均衡最后判决。这里需要Communication Toolbox的qammod和qamdemod也可以用自定义映射函数代替。第一次跑通时不要急着看平均BER先把每个子载波的调制阶数、发射功率和接收SNR打印出来和分配结果互相印证。4. 把贪婪算法调稳的3个参数和2个易踩的坑4.1 参数1单次步进的SNR门限贪婪循环里的inc_snr函数决定了每加1比特需要多高的SNR它由gamma放大。gamma取得越大比特增长需要的功率就越贵同样总功率下分配的总比特数就越少误码率也更低gamma取得过小看起来吞吐上去了但实际调制阶数根本无法支撑目标误码率BER直接飘红。未编码QAM在误比特率10^-4左右时gamma一般在1.5到2.5之间。如果系统里加了LDPC或Turbo编码gamma可以适当下调因为编码增益会降低对SNR门限的要求。我一般先把gamma设为2.0跑一轮再用实际调制解调循环测BER如果BER低于目标则每轮降低0.1重新仿真直到BER接近目标这样得到的gamma就是当前链路条件下的工程值。不要试图从一本书里抄一个固定的gamma在所有场景里用。gamma会随目标BER、编码方式、调制阶数轻微变化但在大多数方案设计阶段取一个固定近似值做资源分配相对排名是够用的。4.2 参数2目标速率与子载波最大比特数max_bits决定了调制阶数的上限。WiFi、LTE这类系统通常支持到64QAM也就是6比特新的标准里也有256QAM对应8比特。max_bits设得越高每个子载波能堆的比特越多但高调制阶数对SNR和相位噪声更敏感还需要更高的功率来支撑同时峰值平均功率比也会变大功放的线性回退要求更高。target_bits的选择更直接。它应该落在系统能支持的速率区间内也就是0到N*max_bits之间。如果太接近上界贪婪算法会发现所有子载波都已经加满比特最后几轮只能往已经很差的子载波上继续堆边际功率高得离谱。此时观察P_alloc会发现个别子载波功率异常大这不是算法bug而是负载率设置过高。调试时可以加一条保护判断如果所有子载波都到max_bits但sum(b)仍然小于target_bits说明目标速率超出能力直接报错并提醒降低target_bits或增大max_bits。这里有一个值得说的细节贪婪算法并不保证每个子载波的功率都低于某个单载波功率限制。如果系统对单载波有功率上限需要在循环里增加约束条件把超过上限的子载波排除掉。加了这种约束后算法复杂度不变但某些边缘子载波会被迫放弃。4.3 参数3信道估计误差的裕度真实系统里H是由导频信号估计出来的信道估计本身有误差而且信道在时变。贪婪算法特别依赖信道排序的准确性因为它会把比特集中到看起来SNR最高的几个子载波上。如果估计值偏高实际分配功率就不够那些子载波的实际BER就会恶化如果估计值偏低又会让信道好的子载波闲置。处理办法是在计算extra_power时给信道增益乘一个惩罚系数% 对信道估计误差做保守修正 H_eff H * (1 - est_error); % est_error0.1 表示认为估计值偏高10% extra_power(k) inc_snr(b(k)1) / (abs(H_eff(k)).^2 / sigma2);est_error这个裕度参数一般取5%到15%。它不改变贪婪算法的结构只是让排序略微偏向信道更稳的子载波。加入这个修正后性能波动会变小尤其在做衰落信道下的蒙特卡洛仿真时特别明显。有时也可以在噪声功率上做文章把sigma2增大10%再分配效果类似。两者结合容易过度保守导致吞吐下降选一个就行。4.4 易踩的坑实虚部功率不匹配Matlab没有强制区分复数功率和实虚部功率写代码时很容易把复数信道的功率算错。比如有人想取实部虚部分别计算会写成real(H).^2 real(H).^2忘了第二个应该是imag(H)。这种错误在单路径仿真里可能不明显因为复高斯信道的实部虚部统计相同但换成确定性信道模型或实测信道后就完全对不上。我的习惯是统一用abs(H).^2计算功率增益。如果你确实需要手工检查可以直接对比power1 abs(H).^2; power2 real(H).^2 imag(H).^2; max_abs_err max(abs(power1 - power2)); % 应接近0这个检查在写完信道生成代码后跑一次能省下很多后面排查的时间。4.5 易踩的坑把OFDM符号数当成迭代次数贪婪算法处理的是相干时间内的信道快照。只要信道在连续几个OFDM符号内基本不变资源分配结果就可以被重复使用不需要每个符号都重新跑一遍贪婪循环。有些初学者会把主信道矩阵构造成三维第一维是OFDM符号数然后在每个符号索引上重新分配实际上这是在低移动性场景里做了大量无用计算。正确的做法是先判断信道变化速度。慢衰落下每帧更新一次资源分配把b和P_alloc当成帧级别的公共变量快衰落到每个符号信道都剧烈变化时才需要每符号更新但那时也意味着信道估计本身可能已经不准了。如果你用的是Simulink做链路级验证可以对照Simulink中ofdm调制解调模块的使用示例看它把资源分配放在哪个处理阶段通常是在每帧的导频估计之后、数据符号调制之前。5. 怎么验证贪婪算法没写错用零载荷反推和边缘速率对照写完贪婪算法后最容易让人心里没底的是我选的这个子载波到底对不对代码里min那一行是不是挑错了方向这里给两个不依赖第三方库的验证方法只需要提前知道结果用来反推算法内部有没有系统性错误。第一个方法是平坦信道零载荷反推。把信道设置成所有子载波增益完全一致H ones(1,N); sigma2 1; target_bits 128;在这个条件下所有子载波初始状态完全相同那么贪婪算法应该把比特尽可能均匀地分配到64个子载波上。因为每个子载波增加1比特的代价完全一致循环会均匀打点128个比特分配到64个子载波上结果应该是50个子载波2比特14个子载波3比特比特数差值最多为1。如果你运行完发现有些子载波是0比特有些是5比特说明循环里对SNR或者功率的计算存在不对称误差去检查abs(H).^2和sigma2能不能对上号。第二个方法是边缘速率对照。把贪婪算法得到的比特分配和连续注水公式对比不需要精确相等但趋势要一致。用Matlab优化工具箱求一下连续条件下的最优功率分布再换算成等效比特log2(1 snr)把结果和贪婪分配画在同一个子载波索引坐标里。信道好的子载波两边都应该有更高的比特数和功率信道差的子载波两边都应该趋向0。如果发现贪婪算法在一个明显深衰点还是给了4比特那通常不是算法问题而是target_bits设置太高导致无路可退检查一下负载率是不是接近满负荷。最后可以做一个更严苛的自洽检查把贪婪算法分出来的P_alloc回代到snr_req公式里计算出每个子载波的理论SNR再从Matlab自带调制函数里找对应BER曲线。算出来的BER和目标BER相差一个数量级以内就说明资源分配和调制参数匹配如果差三个数量级以上说明gamma和实际调制阶数严重不匹配别急着怪算法。把gamma往低调一档再跑一遍同时观察总功率归一化的缩放比例如果缩放比例小于50%说明初始分配结果大概率偏乐观需要保留更多功率余量。本文还有配套的精品资源点击获取
返回列表