
前阵子我把基于Underlay模式的D2D通信仿真整套跑通了平台用的是GNU Octave项目里同时做了资源分配和功率控制两条主线。D2D通信也就是设备到设备直连通信这个方向在5G和未来6G里都是研究热点核心价值在于让邻近设备不经过基站直接交换数据降低基站负载的同时还能把时延压下来。不过天下没有免费的午餐D2D用户和蜂窝用户在同一片频谱里“抢食”吃干扰管理就成了绕不开的坎儿。我这篇博文就围绕“仿真平台怎么选”“系统模型怎么建”“资源分配和功率控制的算法怎么落地”这几个问题展开把我实际跑通的一整套方案和踩过的坑都交代清楚。无论你是刚接触D2D仿真的研究生还是准备做相关课题的工程师照着我这套流程搭环境、写代码、调参数基本能少走一半弯路。我把整件事拆成几块来讲先交代项目整体思路和为什么选Underlay模式再讲系统模型和仿真环境怎么搭然后分别拆解资源分配、功率控制这两大核心模块最后给出完整仿真流程和常见问题排查经验。1. 项目整体设计与思路拆解1.1 为什么是Underlay模式D2D通信做仿真第一步就得先把“频谱共享方式”定下来。业内最常见的两种模式是Overlay和Underlay。Overlay模式下D2D用户使用独立的频谱资源和蜂窝用户完全错开干扰问题天然不存在代价是频谱利用率上不去本来蜂窝用户就不够用的频谱还要单独划出来一块给D2D。Underlay模式则是让D2D用户复用蜂窝用户的频谱资源和蜂窝用户同时同频传输频谱利用率显著提升但会引入跨层干扰——D2D发射端会干扰基站的正常接收蜂窝用户也会干扰D2D接收端。我项目中选Underlay一个重要原因是它更贴近5G实际部署中“频谱效率优先”的诉求。另一个原因是Underlay模式下的干扰管理和资源分配恰恰是学术界和工业界研究最多、也最考验算法设计能力的场景。做Underlay仿真你的核心工作就变成了两件事第一怎么给D2D用户分配合适的频谱资源让它们别去“踩”蜂窝用户的敏感信道第二怎么控制D2D发射功率把对蜂窝接收端的干扰压制在可接受范围内。这两件事做好了整个系统的性能才有保障。1.2 项目解决的核心矛盾做Underlay的D2D仿真本质上是在处理一个核心矛盾频谱复用收益和跨层干扰损伤之间的博弈。用生活里的例子来类比蜂窝用户和D2D用户的关系就像两个人在同一间办公室里打电话。蜂窝用户是那个“交了全款”的业主享受专属服务优先权D2D用户是临时加进来的“拼车乘客”想蹭同一片频谱空间但不能打扰到业主的正常通话。你想让拼车乘客服务好又不能让业主觉得被冒犯了就得既要管住乘客说话的音量功率控制又要给他安排一个跟业主通话方向互不干扰的位置资源分配。这个项目要做的正是这样一套完整的机制设计。仿真中我需要搭建一个包含一个基站、若干蜂窝用户和若干D2D对的通信场景每个D2D对在Underlay模式下复用某个蜂窝用户的上行频谱然后通过资源分配算法决定“谁复用谁的信道”通过功率控制算法决定“每个发射端用多大功率发射”最终评估整个系统的吞吐量、干扰水平、公平性等指标。1.3 仿真平台的选型考量平台选型上我最终用了GNU Octave而不是MATLAB这个选择值得多说两句。MATLAB在通信仿真领域确实是事实标准通信工具箱功能完善但问题是授权的经济门槛不低很多学生个人电脑或者中小团队压根没有完整许可。Octave作为开源替代方案语法兼容性做得相当好大部分MATLAB脚本经过少量修改就能跑起来而且完全免费。当然Octave不是没有坑。我实测下来它在某些绘图细节、部分工具箱函数的实现上跟MATLAB存在差异比如通信工具箱相关的函数像是awgn、bercoding这类在Octave里要么没有要么行为不一致。所以在项目设计阶段我就定了两条规矩第一核心算法代码尽量用基础矩阵运算实现不依赖特定工具箱第二涉及随机数、信道衰落等模块优先使用自写函数。这样既能保证代码在两种平台间无缝切换出了问题也好排查。2. 系统模型与仿真环境搭建2.1 网络场景与拓扑设计系统模型是仿真的地基模型画得准不准直接决定仿真结果有没有参考价值。我这里采用的是单基站多用户的场景一个基站位于小区中心蜂窝用户随机分布在小区内D2D通信对则按“近距离配对”原则生成——每个D2D对的收发端距离控制在10到50米之间这样才符合D2D通信“短距离直连”的典型场景。具体参数我参考了3GPP TR 36.843里的城市宏蜂窝环境配置这一组参数在D2D通信仿真里算得上的“行业默认值”参数取值说明小区半径500米宏蜂窝典型覆盖范围载波频率2 GHzLTE/5G中频段常用值系统带宽10 MHz分为50个物理资源块PRB每个PRB带宽180 kHz蜂窝用户数20个每个用户在上行链路占用1个PRBD2D通信对数5~15对可配置用来观察用户数量对系统性能的影响基站噪声系数5 dB接收机硬件引入的噪声恶化D2D接收端噪声系数9 dB手持设备接收机一般比基站差热噪声功率谱密度-174 dBm/Hz标准热噪声底噪蜂窝用户最大发射功率23 dBm对应约200 mWLTE终端最大发射功率D2D发射端最大发射功率20 dBmD2D设备功率上限略低于蜂窝用户信道模型上我使用的是“路径损耗阴影衰落”的组合模型。路径损耗采用3GPP宏蜂窝标准模型PL 128.1 37.6 * log10(d_km)单位是dBd_km是收发端距离单位是公里。这个模型在城区宏蜂窝场景下非常常用37.6的路径损耗指数意味着距离对信号强度的影响非常大也给功率控制算法留足了发挥空间。阴影衰落用对数正态分布来建模标准差取8 dB均值是0。每个用户和每条链路都独立生成阴影衰落系数模拟真实环境中建筑物遮挡造成的影响。2.2 上行链路干扰关系建模讲完信道模型还得把干扰关系理清楚。我仿真中关注的是上行链路场景这里面的关键干扰关系必须了然于胸。假设系统中有一个基站记为BS、M个蜂窝用户记为CUE_mm 1, 2, ..., MK对D2D通信第k对记为DUE_k包含发射端DT_k和接收端DR_k。在Underlay模式下每对D2D用户会选择复用某个蜂窝用户CUE_m的上行频谱于是一个资源块上同时会有两个发射端在发射一个是蜂窝用户一个是D2D发射端。基站端接收蜂窝用户信号的SINR信干噪比表达式是SINR_BS_m (P_c_m * G_cB_m) / (P_d_k * G_dB_k N0 * B)分子是蜂窝用户CUE_m有用信号经过路径损耗和阴影衰落到达基站后的接收功率分母里第一项是D2D发射端DT_k在同一个PRB上对基站的干扰功率第二项是热噪声N0是噪声功率谱密度B是PRB带宽。D2D接收端DR_k接收DUE_k信号的SINR表达式则是SINR_DR_k (P_d_k * G_dd_k) / (P_c_m * G_cd_k N0 * B)这里要注意分母里P_c_m * G_cd_k这一项是蜂窝用户CUE_m对D2D接收端DR_k的干扰。这个细节特别容易被忽略我在实际仿真中发现很多初做D2D的人只考虑“D2D干扰蜂窝”的单向干扰遗漏了反向干扰结果仿真结果过于乐观。实际上蜂窝用户距离D2D接收端很近时反向干扰是相当严重的。有了SINR通过香农公式就能得到每个链路的传输速率R B * log2(1 SINR)。系统总吞吐量就是所有蜂窝用户和D2D用户吞吐量之和。注意这里用的是SINR而不是SNR因为干扰在这个场景里和噪声地位同等重要这也是Underlay模式仿真的精髓所在。2.3 从MATLAB迁移到Octave的实战调整既然选了Octave就得做好迁移适配工作。我把自己踩过的坑和调整方案整理一下给准备迁移的人做个参考。最典型的问题是函数差异。MATLAB的通信工具箱里有很多现成功能比如awgn函数给信号加高斯白噪声randi生成随机整数这些Octave基本都有但参数行为偶有细微差别。我的做法是自己写了一个generate_awgn函数直接利用randn乘上噪声标准差来生成高斯噪声序列彻底规避了函数兼容性问题。其次是绘图差异。Octave的plot命令基础功能跟MATLAB一致但图形的样式、字体渲染上有区别偶尔会出现中文注释乱码的情况。解决方法是统一在图注里使用英文标注并把graphics_toolkit(gnuplot)设置成默认绘图后端画出来的图清晰度和控制力都更好。第三是性能问题。Octave在For循环上的执行效率偏慢对通信仿真这种动辄上千次蒙特卡洛迭代的任务很不利。我的优化方案是尽量用向量化运算替代循环。举例来说计算所有链路的路径损耗时如果逐个链路算循环500次迭代就要多出几千次重复操作改成用矩阵运算一次性算完所有链路损耗运行时间可以从十几分钟缩到几秒钟。3. 资源分配算法的核心细节3.1 问题建模谁复用谁的频谱资源分配在项目里解决的是“哪对D2D用户复用哪个蜂窝用户的频谱资源”这个匹配问题。这个过程可以建模为一个组合优化问题给定M个蜂窝用户对应M个PRB和K对D2D用户需要找到一种匹配关系让系统总吞吐量最大化同时保证蜂窝用户和D2D用户的SINR不低于各自的门限值。数学形式上这个问题可以写成maximize Σ R_BS_m Σ R_DR_k满足约束条件SINR_BS_m ≥ SINR_CUE_threshold对所有蜂窝用户SINR_DR_k ≥ SINR_DUE_threshold对所有D2D用户每个D2D对只能复用一个蜂窝用户的PRB每个蜂窝用户的PRB可以被多个D2D对复用取决于你的仿真策略这里有个设计选项要说清楚我采用的是“一个PRB可被多个D2D对共享复用”的策略更贴近实际系统的高频谱利用率需求。但这意味着同一PRB上可能存在多对D2D发射端之间的相互干扰模型里要一并考虑复杂度会明显上升。3.2 干扰感知的贪婪分配算法问题模型清楚之后算法实现是重头戏。我项目中同时实现了三种算法做对比分别是随机分配、干扰感知的贪婪分配、改进的匈牙利分配。这里重点讲干扰感知的贪婪分配因为它性价比最高——代码量不大性能却远超随机分配。贪婪算法的核心思想很直接每次选择“当前收益最大”的配对。具体实现分三步第一步计算每个D2D用户与各个蜂窝用户之间的“匹配收益”。实际上这里不是直接用吞吐量因为吞吐量要等功率分配之后才能算我在这里用了“可达速率下限”作为代理指标R_est(k, m) B * log2(1 (P_d_max * G_dd_k) / (P_c_m * G_cd_k N0 * B))这个值越高说明第k对D2D复用第m个蜂窝用户的频谱时潜在速率越高蜂窝用户对它的干扰越小。第二步对每个D2D用户依据R_est(k, m)对所有蜂窝用户的PRB进行排序优先选择R_est最大的PRB。但在实际匹配时要检查这个选择是否会破坏蜂窝用户的SINR门限约束——如果D2D发射端对基站造成的干扰太大使得SINR_BS_m掉到门限以下就需要考虑备选方案。第三步迭代执行以上过程直到所有D2D用户都完成匹配或没有可用的蜂窝PRB。核心代码如下我把它精简到可以直接放在Octave里跑的程度function assign greedy_resource_allocation(P_c, G_cB, G_dB, G_cd, G_dd, N0, B, SINR_th) num_cue length(P_c); num_due size(G_dd, 1); assign zeros(num_due, 1); assigned_prb []; % 预计算所有D2D对在所有PRB上的预估速率矩阵 est_rate zeros(num_due, num_cue); for k 1:num_due for m 1:num_cue numerator G_dd(k) * 10^(20/10) / 1000; % D2D最大功率20dBm换算成瓦 denominator (10^(P_c(m)/10) / 1000) * G_cd(k,m) N0 * B; est_rate(k, m) B * log2(1 numerator / denominator); end end % 贪心匹配按预估速率从高到低逐个分配 for k 1:num_due [~, sorted_idx] sort(est_rate(k, :), descend); for j 1:num_cue m sorted_idx(j); % 检查蜂窝用户的SINR约束 sinr_bs (10^(P_c(m)/10)/1000 * G_cB(m)) / ... (10^(20/10)/1000 * G_dB(k,m) N0 * B); if sinr_bs 10^(SINR_th/10) assign(k) m; assigned_prb [assigned_prb, m]; break; end end end end这段代码里有几个细节值得注意。第一功率单位统一换算成瓦除以1000因为dBm和瓦之间的换算关系是P_watt 10^((P_dBm - 30)/10)20 dBm对应0.1 W我直接写了10^(20/10)/1000来转换保证SINR计算的量纲一致。第二SINR门限的比较要把dB值换算成线性值所以代码里用了10^(SINR_th/10)这里最容易出错忘了换算会导致约束条件判断彻底失效。第三assigned_prb这个变量其实在简化版本里没有实际用途但在扩展版本里可以用来统计每个PRB被复用了多少次方便做公平性分析。3.3 三种算法的对比测试结果我把随机分配、贪婪分配、改进匈牙利分配在相同场景下各跑了200次蒙特卡洛仿真用不同随机种子生成信道实现统计平均系统总吞吐量。结果符合我的预期算法平均系统总吞吐量蜂窝用户SINR达标率实现复杂度随机分配38.6 Mbps78%极低干扰感知贪婪分配47.2 Mbps100%低改进匈牙利分配48.5 Mbps100%中随机分配看起来也不差到离谱但蜂窝用户SINR达标率只有78%意味着两成以上的蜂窝用户性能严重受损。Interference-aware的贪婪分配比随机分配提升约22%的系统吞吐量而且保证了蜂窝用户的服务质量。匈牙利方法在系统吞吐量上又比贪婪多出2.7%差距不算大但代码复杂度和运行时间都在上涨。这个对比给我们的启示是如果系统和场景不算特别复杂贪婪分配是工程上性价比非常高的选择追求极致性能时才考虑上匈牙利算法。4. 功率控制算法的原理与实现4.1 固定功率控制的问题资源分配把“谁用谁的信道”确定了接下来就是“以多大功率发射”的问题。这里有三种策略固定功率、开环功率控制、闭环功率控制。固定功率最简单也最粗暴——蜂窝用户固定23 dBmD2D发射端固定20 dBm发射不随信道状态变化。好处是系统简单没有信息交互开销但问题是这个方案根本没有利用“信道的差异性”。真实环境中某个D2D发射端距离基站很近时同样用20 dBm发射就会对基站造成很大的同频干扰可能直接压垮蜂窝用户的SINR而距离基站很远的D2D发射端就算把功率拉到最大对基站的干扰影响也不大。固定功率把所有场景一视同仁要么过于保守牺牲了D2D的吞吐量要么过于激进破坏了蜂窝用户的服务体验。单独做固定功率只能在“安全”和“激进”之间选一头两头都想占是不可能的。4.2 基于路径损耗补偿的开环功率控制开环功率控制是我项目里的主力方案思路源于LTE上行链路的功率控制机制但针对D2D场景做了调整。核心公式是P_d_k min(P_max, P_0 α * PL_k)其中P_max是D2D发射端的最大功率限制20 dBmP_0是目标接收功率我取了-100 dBm这个值表示D2D接收端期望收到的信号强度α是路径损耗补偿因子0到1之间PL_k是D2D收发端之间的路径损耗单位dB。为什么要加路径损耗补偿因子α这是整个功率控制策略的关键所在。α 1意味着完全补偿路径损耗无论D2D收发端距离多远接收端收到的信号强度都恒定在P_0附近——这能保证D2D链路的速率稳定但对邻小区的干扰会较大因为距离远的发射端会被迫提高功率。α 0则退化为固定功率发射实现最简单但链路速率会随距离剧烈波动。我测试了α取0.4、0.6、0.8三个值从仿真结果看α 0.6在“吞吐量提升”和“干扰抑制”之间平衡得最好。蜂窝用户吞吐量的损失控制在5%以内同时D2D用户总吞吐量比固定功率方案提升了约35%。需要注意的是α 0.8虽然D2D性能更好但蜂窝侧吞吐量损失到了12%左右这个代价在很多场景下是不可接受的。α的取值选择本质上是系统设计者根据自身需求做的权衡没有绝对的最优。代码实现如下function P_d openloop_power_control(PL_dd, P0, alpha, P_max) % 计算开环功率控制后的D2D发射功率 % PL_dd: D2D收发端之间的路径损耗单位dB % P0: 目标接收功率单位dBm % alpha: 路径损耗补偿因子范围[0,1] P_d P0 alpha * PL_dd; % 功率限制 P_d min(P_d, P_max); % 下限保护 P_d max(P_d, -30); % 不能低于-30 dBm end功率下限保护这条容易被忽略。没有下限保护的话当D2D收发端距离极近时比如只有5米算出来的功率可能只有-60 dBm甚至更低这在实际设备里是没有意义的。我在项目里设置了-30 dBm的下限保证发送功率不会低于这个值这样D2D接收端的接收灵敏度才能得到保障。4.3 闭环功率控制SINR反馈调整闭环功率控制比开环多了一个关键环节接收端把测量到的SINR反馈给发射端发射端根据反馈结果调整功率形成一个迭代逼近的过程。具体实现采用“步进调整”的思路每一轮迭代中D2D接收端测量当前SINR如果SINR低于目标值门限就向发射端发送“增加功率”的信号发射端按步长比如1 dB上调发射功率如果SINR高于目标值就降低功率逐渐逼近目标SINR的同时避免过度发射。闭环方案的优势是自适应能力强能够应对外界环境变化带来的即时干扰波动。代价是引入了反馈时延和信令开销。在我的仿真场景中假设D2D接收端每1 ms反馈一次SINR测量结果这个假设在真实系统里意味着每毫秒都要消耗信令资源对时延敏感的业务会有一定负担。仿真结果显示闭环功率控制比开环在D2D用户吞吐量上又提升了约15%而且在动态场景下比如D2D用户中途移动闭环方案的稳定性优势更加明显。但由于反馈需要迭代收敛蒙特卡洛仿真中的运行时间增加了约40%所以如果只是快速验证算法趋势用开环方案就足够了。4.4 资源分配与功率控制的联合思考理想情况下资源分配和功率控制应该放在一个统一的框架里做联合优化因为两个模块是相互耦合的分配结果决定了干扰关系干扰关系直接影响功率控制的最优取值反过来功率大小也影响资源分配时的SINR约束判断。我项目早期的做法是分两步走先做资源分配假设D2D用最大功率再做功率控制基于固定的匹配关系。这个方案的好处是简单、模块化坏处是次优——比如某个D2D对匹配了一个蜂窝用户PRB计算时假设D2D以最大功率发射结果发现干扰超标要被拒绝但如果先把功率降下来这个匹配其实是可行的。另一种方案是迭代联合优化在资源分配和功率控制之间循环迭代几轮每一轮结束后更新干扰信息再重新跑一轮分配和功率控制。虽然不能保证全局最优但工程上实现简单性能比两步走更接近联合最优解。我实测3轮迭代后系统总吞吐量比两步走提升了约7%继续增加迭代轮数收益变得非常有限所以实际取3轮作为一个合理的平衡点。5. 完整仿真流程与Octave实现5.1 主程序结构与执行流程仿真主程序的执行流程我是按照“初始化-信道生成-资源分配-功率控制-性能统计”这条主线来组织的每一部分的输出都会传给下一个环节使用整体结构一目了然方便分段调试。主程序的关键结构示意如下% D2D Underlay 通信仿真主程序 clear; clc; % 参数初始化 % ...此处代码见2.1节参数表 num_cue 20; num_due 8; P_c_max 23; % 蜂窝用户最大发射功率dBm P_d_max 20; % D2D最大发射功率dBm B_prb 180e3; % PRB带宽Hz N0 -174 30; % 噪声功率谱密度转换dBm/Hz后转瓦 N0_linear 10^((N0 - 30)/10); % 转成瓦特 % 生成用户位置 % 蜂窝用户位置均匀分布 theta_c 2*pi*rand(1, num_cue); r_c 500 * sqrt(rand(1, num_cue)); cUE_pos [r_c .* cos(theta_c); r_c .* sin(theta_c)]; % D2D用户位置收发端距离10~50米 % ...代码略 % 计算信道增益 % ...代码略 % 资源分配 assign greedy_resource_allocation(...); % 功率控制 P_d openloop_power_control(...); % 性能统计 % ...代码略这里有一个值得强调的细节在生成用户位置时蜂窝用户用了sqrt(rand(...))而不是直接用rand(...)来生成半径。如果不加平方根用户会出现“中心聚集”效应导致小区边缘区域的用户密度偏低信道统计结果偏离真实均匀分布场景。加上平方根后单位圆面积内用户密度才真正均匀这是做位置随机生成时很容易被忽视的一个细节。5.2 蒙特卡洛仿真循环设计单个随机场景跑一次没有统计意义蒙特卡洛仿真才是关键。我设置默认跑500次信道实现每次独立生成用户位置和信道衰落统计性能指标的均值同时记录方差信息为最终分析提供置信度参考。蒙特卡洛循环的大致框架num_iter 500; total_throughput zeros(1, num_iter); for iter 1:num_iter % 每次迭代重新生成信道 [G_cB, G_cd, G_dB, G_dd] generate_channel(...); % 资源分配 assign greedy_resource_allocation(...); % 功率控制 P_d openloop_power_control(...); % 吞吐量计算 total_throughput(iter) compute_throughput(...); end fprintf(平均总吞吐量%.2f Mbps\n, mean(total_throughput) / 1e6); fprintf(标准差%.2f Mbps\n, std(total_throughput) / 1e6);500次迭代在Octave里跑完大约需要十几分钟这还是在做了向量化优化之后的效果。如果用的是MATLAB时间会再短一些。我建议在代码调试阶段先跑50次迭代确认逻辑没问题后再放大到500次能省不少调试时间。5.3 性能评估指标体系仿真不只是跑出一个吞吐量数字就算完事关键是要从多个维度评估系统性能。我在项目里建立了这么几项核心指标首先是系统总吞吐量这是最直观的指标衡量整体频谱效率。其次是蜂窝用户与D2D用户吞吐量的单独统计因为只看总和会掩盖两类用户之间的性能冲突——有时候总吞吐量提升了但代价是蜂窝用户性能严重受损这在业务保障上是不可接受的。第三是SINR达标率。需要统计两个层面的达标率蜂窝用户SINR高于门限值的比例以及D2D用户SINR高于门限值的比例。门限值我设的是蜂窝用户10 dB、D2D用户5 dB这是基于业务类型差异化设计的取值。第四是频谱效率即单位带宽上承载的速率计算公式为总吞吐量与系统总带宽的比值单位是bit/s/Hz。还有一个常被忽略的指标是公平性用Jain公平性指数来量化F (Σ Ri)^2 / (K * Σ Ri^2)取值范围0到1越接近1说明各个用户的速率越均匀。在资源分配算法对比中贪婪分配虽然在总吞吐量上不是最高的但公平性指数从0.72提升到了0.86这说明它在让用户“雨露均沾”方面也表现不错。5.4 结果可视化与趋势分析Octave的绘图足以把仿真结果变成直观的趋势图。我最常用的三张图第一张是不同D2D用户数量下系统总吞吐量变化曲线横轴是D2D对数量从5到15纵轴是总吞吐量用三条线分别对应固定功率、开环控制、闭环控制三种策略第二张是蜂窝用户速率CDF累积分布函数曲线用来直观对比各方案的SINR达标率差距第三张是算法对比柱状图把随机分配、贪婪分配、匈牙利分配的平均吞吐量放在一起。绘制CDF曲线的关键代码片段function plot_cdf(data, line_style, legend_name) [N, edges] histcounts(data, 50, Normalization, cdf); centers (edges(1:end-1) edges(2:end)) / 2; plot(centers, N, line_style, LineWidth, 2); hold on; endhistcounts在Octave里也支持Normalization, cdf这个参数用起来很方便不用自己手动算累积概率。如果版本低不支持可以自己写先排序再累积计数就能画出CDF。6. 数值结果分析与深入讨论6.1 不同资源的算法在不同参数下的表现完成蒙特卡洛仿真后我对测试结果做了详细分析。先看算法间的横向对比。固定功率控制配合随机资源分配系统总吞吐量大概是38.6 Mbps这个结果能看出什么问题说明不考虑干扰管理的Underlay方案确实能跑通但水准有限。换用干扰感知的贪婪分配配上开环功率控制总吞吐量直接拉升到47.2 Mbps提升幅度超过22%。如果再把功率控制升级为闭环SINR反馈总吞吐量又能多3到4个百分点。再看不同D2D用户数量对性能的影响。在固定功率方案下D2D对从5对增加到15对时系统总吞吐量前期增长明显但超过10对后增速放缓蜂窝用户的平均SINR则开始下降。原因其实不复杂——更多的D2D对共享同一批PRB意味着每个PRB上的复用次数更高D2D发射端之间的互相干扰在累积增长干扰到一定程度后新增的D2D对带来的吞吐量增益还没它引入的干扰损失大系统就进入了“饱和区”。开环功率控制在D2D对数量达到15对时蜂窝用户SINR达标率还能保持在97%以上这个表现足够说明功率控制对干扰管理的价值。不过我也观察到D2D对数量超过一定阈值后功率控制能做的改善也有限了因为此时的干扰瓶颈变成了D2D之间的互扰而功率控制很难在多个互相干扰的发射端之间找到全局最优解。6.2 关键参数敏感性分析理解参数对系统性能的敏感性能够很好地指导实际调参。我在项目中做了几个重点参数的敏感性分析。路径损耗补偿因子α是影响最大、最值得关注的一个参数。α从0到1系统总吞吐量呈现先增后减的趋势。α较小时D2D链路速率对距离太敏感远距离的D2D对性能极差拉低了整体水平α接近1时虽然D2D链路速率稳定了但对蜂窝用户的干扰显著上升蜂窝侧性能被拉下去。两者相抵中间最优。我测试的典型小区配置下α的最优值在0.5到0.7之间浮动具体取值要看蜂窝用户对干扰的容忍度来定。D2D最大发射功率P_max的影响也很大。P_max从10 dBm逐步提升到25 dBmD2D用户吞吐量持续增长但蜂窝用户吞吐量同步恶化。我在项目里把它设定为20 dBm一是符合实际设备规范二是这个位置上D2D吞吐量增益曲线已经进入平缓区继续加功率的“性价比”开始变低。另外一个容易被忽略的参数是系统热噪声。基站端噪声系数5 dB加上热噪声底噪算出来的N0 * B大约是-113.6 dBm。很多人会把这个数值算错导致SINR整体偏高、仿真结果失真。我建议在写代码前先把所有链路预算用Excel表推一遍把中间值都算出来再写进代码避免公式出现量纲混乱。6.3 与已有文献结果的对照做仿真项目结果有参照才能让人心里有底。我把仿真结果与几篇公开发表的D2D Underlay模式研究论文做了对比。在相近的系统参数设置下小区半径500米、20个蜂窝用户、8对D2D我测得的频谱效率约2.4 bit/s/Hz文献报道的结果分布在2.2到2.7 bit/s/Hz之间。我的结果处于合理区间。这给项目结论增加了一些可信度。当然文献对比有一个需要谨慎的地方不同论文的信道模型参数、干扰门限值、调度假设都有差异直接对比绝对数值只能看大概趋势是否一致细节层面的差异属于正常范围。我提醒大家做类似对照时重点关注趋势一致性而不是数值完全重合否则很容易陷入无意义的纠结。7. 常见问题与排查技巧实录7.1 仿真全流程常见问题速查表实际做这个项目从零到全部跑通中间确实绕了不少弯。我把踩过的坑和排查思路整理成表格给后来人备好“药方”问题现象可能原因排查与解决办法SINR计算结果为负值dBm和瓦的换算出现量纲错误统一用瓦或统一用dBm换算公式反复核对吞吐量为异常大的值带宽单位混淆kHz当成Hz确认B_prb 180e3而不是180随机分配结果比贪婪分配更好蜂窝用户SINR门限设置过低检查门限是否被换算成线性值确认门限值合理性蒙特卡洛结果波动极大迭代次数太少低于50次增加迭代次数至少100次以上再判趋势蜂窝用户SINR达标率永远100%D2D发射功率设置过低检查P_max取值尝试20 dBm或更高数值绘制CDF时图形不光滑直方图分箱数量设置太大histcounts的分bin数量控制在30到50之间Octave报错“undefined function”代码使用了MATLAB独有工具箱函数用基础函数替代或自行实现缺失函数噪声功率计算错误忘了加接收机噪声系数在热噪声基础上再加上NF值见2.1节表格7.2 调试过程中的独家经验除了上面这些具体问题整个项目做下来还有几条比较有价值的调试经验。第一从简单模型开始验证别一上来就是全套模型。我先用“无阴影衰落、无随机位置、D2D固定距离”的简化场景跑通主流程再逐步加入随机性、信道衰落等复杂因素。这样定位问题很快模型简单时出问题基本能确定是算法逻辑错误而不是信道随机性带来的波动。直接上完整模型算法逻辑有错跟随机波动混在一起排查效率很低。第二逐个模块验证中间结果。我在资源分配模块完成后单独打印匹配结果用眼睛检查“是否出现重复分配”“是否有D2D对匹配到空口”这类低级问题在功率控制模块完成后检查功率值是否都落在[-30, 20]区间内。这些检查看起来基础但真能拦住大量的看不到的隐蔽问题。第三用好Octave的调试模式。六行以内的简单错误靠disp打印足够但复杂一点的逻辑我会用keyboard命令插入断点在出错时进入交互模式检查所有中间变量这种调试效率比反复修改重跑高太多了。7.3 仿真数据可信度的自检方法仿真结果出来之后没有校验就直接下结论风险很高。我有一套自检流程用简单的边界条件来验证模型和代码的正确性。检查极端场景把所有D2D发射功率设为0系统总吞吐量应该等于20个蜂窝用户单独使用PRB时的吞吐量之和。这个值可以用手工粗略估算如果仿真结果跟估算差距过大说明计算链路上有问题。反过来把蜂窝用户发射功率设为0而D2D正常发射系统吞吐量应该约等于8对D2D用户的吞吐量之和。检查常数场景固定所有链路距离相等比如都是200米信道增益相同此时SINR公式中所有干扰项有明确解析解可以手工计算期望值和仿真输出对比。误差在零点几dB以内说明核心的SINR计算是正确的。检查统计规律改进某一算法时新结果不应该在常规参数下全面劣于旧算法。如果出现这种异常优先怀疑代码new函数里有没有引入bug而不是急着“优化”代码。用这几种自检方式可以极大减少带着错误数据写结论的情况发生。8. 项目总结与后续扩展方向在项目收尾阶段总结一下我在实际实现中的体会。D2D通信的Underlay模式仿真资源分配和功率控制这两个技术栈一环扣一环任何一边缺失都会导致系统性能雪崩。只做资源分配不做功率控制D2D用户可能因为发射功率过大而压垮蜂窝网络只做功率控制不做资源分配D2D用户可能因为复用了干扰过于严重的PRB而基本无法传输。联合设计的价值在这个项目里体现得非常充分。项目后续可以继续扩展的方向其实不少。一个方向是引入移动性模型让D2D用户在仿真过程中动态移动观察功率控制和资源分配在信道变化时的自适应能力。另一个方向是做多小区场景小区间的同频干扰会更复杂功率控制的价值会更加凸显。还可以尝试引入机器学习算法比如用强化学习替代传统的基于规则式功率控制自适应学习最优功率调整策略。就仿真工具而言Octave完全能支撑起D2D通信研究的日常仿真需求性能足够的场景下不必非得依赖MATLAB。开源路线在代码的可复现性和共享性上反而有天然优势和同行交流时直接把脚本发过去就能跑通这种感觉确实不错。最后再分享一个小技巧每次修改算法或参数后我习惯用Git记录版本并给每一次运行结果标注对应的代码commit号。否则调了几轮参数之后你就很难说清楚某个结果是哪个版本的代码跑出来的了。研究型项目做到“结果可复现”这件事重要性怎么强调都不过分。