1. 做D2D通信模式选择仿真,先想清楚“模式选择”到底在选什么
我最早把D2D通信模式选择仿真的结果贴到组会上的时候,被问得最多的一个问题不是“图怎么画得这么好”,而是:“你仿真的这个模式选择,到底在选什么?”这个问题很基础,但很多人一上来写MATLAB代码,跑完蒙特卡洛,反而回答不上来。先把概念理清楚,后面的Simulation才不会变成自嗨。
D2D通信,也就是Device-to-Device Communication,允许两个距离较近的用户设备在蜂窝网络的控制下直接通信,数据不经过基站中转。这里的核心问题是:两个用户究竟该走蜂窝链路(先发到基站,再由基站转发),还是走直连链路(发送端直接发给接收端)?这就是“模式选择”的第一层含义。
第二层含义出在D2D直连模式内部。直连链路用的频谱从哪里来?两种典型做法:一种是D2D链路使用一个独立正交的资源块,完全避开蜂窝用户的频带,我习惯叫它D2D正交模式;另一种是D2D链路直接复用某个蜂窝用户的资源块,两边同时发送,我习惯叫它D2D复用模式。复用模式频谱效率高,但会带来互干扰。这两层选择加在一起,才是一个完整的“D2D通信模式选择”问题。
那模式选择要在仿真里做什么呢?站在网络侧看,它是一个典型的在线决策问题:给定当前的用户位置分布、信道衰落状态、可用RB资源和蜂窝用户服务质量要求,为每一对D2D候选用户选出一条合适的数据通路。在真实系统里,这个决策要考虑的因素比教科书里写的多得多。比如D2D发射机离基站很近时,如果走蜂窝链路,上行信号质量会非常好;但同一个小区的蜂窝用户也在用同样的频段,直连复用可能把基站接收端的SINR拖垮。又比如两个用户虽然物理距离只有50米,但中间有遮挡,直连信道很差,这时强行D2D反而不如走基站。
为什么我用MATLAB来做这个仿真,而不是直接用成熟的系统级仿真器?系统级仿真器能统一标准、提供完整协议栈,但对一个研究模式选择算法的人而言,干涉的东西太多了,跑一次批量实验的时间长,参数也难控制。MATLAB的好处在于矩阵运算快、随机数生成方便、画图交互性强,而且从信道生成到判决算法再到指标统计,所有细节都摊在眼前。我可以在半小时内改完判决准则,再跑1000次蒙特卡洛看趋势变化,这对算法前期的方案验证来说,效率非常高。
不过也要说清楚边界:这种脚本级别的仿真,比较适合回答“某种模式选择规则在给定信道假设下是否有效”“门限参数应该怎么取舍”这类问题,不适合作为最终部署前的性能评估工具。后面我还会提到,干扰模型简化过头会带来什么后果。
2. 仿真场景里的干扰关系:蜂窝上行、D2D复用和干扰矩阵
2.1 场景参数与用户分布怎么定
我通常习惯把仿真场景做成单小区、一个基站、若干蜂窝用户、若干D2D候选对。小区半径R取500米,基站放在圆心。蜂窝用户数量设为N_C,每一个蜂窝用户占一个上行RB,负责一条上行数据流;D2D候选对数量设为N_D,每一对由一个D2D发送端和一个D2D接收端组成。具体参数可以按项目需求改,但核心是先把“谁和谁可能产生干扰”的关系画出来。
我常用的一组初始参数如下:
| 参数 | 示例取值 | 说明 |
|---|---|---|
| 载波频率 | 2 GHz | 路径损耗模型参考频段,也可以换1.8GHz |
| 小区半径 | 500 m | 单基站覆盖半径 |
| RB带宽 | 180 kHz | LTE系统典型资源块带宽 |
| 蜂窝用户数 | 8 | 每个用户占用一个RB |
| D2D候选对数 | 12 | 有直通需求且满足最小距离的用户对 |
| D2D距离范围 | 20 ~ 100 m | 超过100m默认直连优势不大 |
| 设备最大发射功率 | 23 dBm | 典型终端功率上限 |
| 噪声功率 | -114 dBm(1MHz折算) | 实际按RB带宽折算,再加噪声系数 |
用户位置生成看起来很基础,但很容易做错。蜂窝用户要均匀分布在圆内,直接rand * R生成半径再随机角度,得到的分布其实是靠近圆心的用户密度偏高,因为面积元是r dr dθ,不是dr dθ。正确做法是半径乘上sqrt(rand):
% 生成N个均匀分布在半径为R的圆内的点 rho = R * sqrt(rand(N, 1)); theta = 2 * pi * rand(N, 1); pos = [rho .* cos(theta), rho .* sin(theta)];至于D2D对,我先在小区内随机放D2D发送端,再在发送端周围按照给定距离范围随机生成接收端,方向和距离都随机。接收端可能落到小区外,这在仿真里不用过于纠结,只要你在结果统计时正确判断链路的得与失即可。如果项目要求D2D对必须在小区覆盖范围内,再做一个拒绝抽样循环,把落到圆外的接收端重新生成一次。
2.2 信道模型与链路增益的计算口径
D2D仿真里最常见的信道假设是:大尺度路径损耗 + 小尺度瑞利衰落。我的做法是先用对数距离模型计算路径损耗,再叠加快衰落增益。公式用的比较典型的是:
PL_dB = 128.1 + 37.6 * log10(d_km)
这个模型在1km以内还算常用,d_km是发射机和接收机之间的距离。注意d不能取0,脚本里我一律设置最小保护距离,比如10米;超过这个保护距离,路径损耗才按公式计算。
小尺度衰落我直接用复高斯随机变量。一次性生成一个服从圆对称复高斯分布的系数h,幅度服从瑞利分布。最终链路的线性增益等于10^(-PL_dB/10) * abs(h)^2。写成一个函数放在MATLAB里,每次都生成一个新的快照:
function [gain_dB, PL_dB] = genChannel(d_m) d_km = max(d_m / 1000, 0.01); PL_dB = 128.1 + 37.6 * log10(d_km); h = (randn(1, 1) + 1j * randn(1, 1)) / sqrt(2); gain_linear = 10^(-PL_dB / 10) * abs(h)^2; gain_dB = 10 * log10(gain_linear + eps); end有一点要提醒:在实际频分双工系统中,上行和下行信道不一定满足互易性。初版仿真里我往往假定正反方向用同一个信道增益,这样能大幅简化干扰关系的计算;但到了更精细的阶段,我会给两个方向分别生成衰落系数,尤其是做TDD系统仿真时,互易性假设反而不能乱用。
2.3 复用单个上行RB至少涉及三条链路
模式选择的核心难点不是单条链路自身的信号质量,而是干扰耦合。以D2D复用某个蜂窝用户的上行RB为例,至少要同时看三条链路的关系:
| 链路符号 | 物理含义 | 在SINR表达式中的角色 |
|---|---|---|
| g_CB | 蜂窝用户到基站的信道增益 | 蜂窝用户的有用上行信号 |
| g_DD | D2D发送端到D2D接收端的信道增益 | D2D链路的有用信号 |
| g_DB | D2D发送端到基站的信道增益 | 对基站干扰,影响蜂窝用户 |
| g_CD | 蜂窝用户到D2D接收端的信道增益 | 对D2D接收端的干扰 |
蜂窝用户正交占用自己的RB时是没有外来干扰的。一旦某个D2D对复用了这个RB,D2D发送端的信号会一路打到基站,叠加在蜂窝用户的上行信号上;反过来,蜂窝用户的发射信号也会泄放到D2D接收端。这两条交叉干扰链路就是模式选择仿真里最需要算清楚的部分。
如果多个D2D对同时复用一个RB,还会出现D2D发射机之间的互干扰。我的处理方法是先把每个RB对应的发射机集合找出来,再统一叠加干扰。MATLAB里可以用cell数组存每个RB上的活跃发送端索引,然后逐个累加干扰功率。这个细节在初版仿真里很容易被漏掉,因为只放一对D2D时看起来一切都合理。
3. 模式选择判决器:从SINR矩阵到资源分配逻辑
3.1 三种候选模式各自的SINR表达式
我在这个项目里设计了三路候选模式,分别是蜂窝模式、D2D正交模式和D2D复用模式。每次都把三路SINR都算出来,再交给判决器选择,而不是一上来就根据距离拍板。
第一路,蜂窝模式。D2D发送端把数据发给基站,基站再下发给D2D接收端。在这个模式下,D2D对占用一个独立RB,不和其他用户共享,所以SINR计算里只有热噪声:
SINR_CM = P_d + g_DB - (N0 * B)
这里的P_d是D2D发送端的发射功率,g_DB是发送端到基站的链路增益,后面那一项是该RB带宽上的噪声功率。蜂窝模式的短板很明显,发送端到基站的距离往往不近,路径损耗大,所以在小区边缘时SINR往往不高;同时一次通信走两跳,容量还要打对折。
第二路,D2D正交模式。D2D发送端直接发给接收端,也使用独立RB,不产生额外干扰:
SINR_orth = P_d + g_DD - (N0 * B)
因为D2D对通常距离很近,这个SINR往往远高于蜂窝模式,代价是多占用了一个本可以给蜂窝用户使用的RB。
第三路,D2D复用模式。D2D发送端复用某个蜂窝用户的上行RB,双方同频同时发送。这时候SINR公式里要叠加上来自蜂窝用户的干扰:
SINR_reuse = P_d + g_DD - 10 * log10(10^((P_c + g_CD)/10) + 10^((N0*B)/10))
对应地,被复用蜂窝用户的上行SINR也会下降:
SINR_CU_after = P_c + g_CB - 10 * log10(10^((P_d + g_DB)/10) + 10^((N0*B)/10))
算SINR时最容易踩的坑就是dB值直接相减。比如 P_d + g_DD 算出的是有用信号功率的dB值,干扰和噪声也分别是dB值,直接拿这三者做减法在数学上完全不对。必须先分别转成线性值,在线性域相加,最后再转回dB。我一开始也犯过这个错,结果画出来的吞吐量曲线在低干扰区是合理,在高干扰区突然跳出一个离谱的正增益。
3.2 判决准则:约束优先,效用次之
模式选择不是一个单纯“挑SINR最大”的问题。如果仅按SINR最大选,复用模式因为占用了蜂窝用户的频谱,即使自身SINR略高于正交模式,也会让蜂窝用户业务受损。所以我设计的判决器分两步。
第一步,QoS约束筛选。每一个候选模式的SINR必须满足该模式自身的门槛,同时复用模式必须额外满足蜂窝用户干扰后的SINR不低于设定门限:
- 蜂窝模式:要求 SINR_CM >= q_d,q_d是D2D链路可以接受的最低SINR;
- D2D正交模式:要求 SINR_orth >= q_d;
- D2D复用模式:要求 SINR_reuse >= q_d,且 SINR_CU_after >= q_cu。
第二步,在通过筛选的候选模式里选效用最高的。如果我只想追求最大吞吐量,效用函数可以简单写成:
U = B * log2(1 + SINR)
如果想权衡功耗,可以写成容量除以发射功率,单位变成 bit/(s·W)。有的算法想保护蜂窝用户更多一点,就在效用函数中给SINR_CU_after加一个权重惩罚,蜂窝用户损失越大,效用越低。在我的项目里,我用的是“先筛选,再让SINR最大化”的方案,实现简单,调试也直观。
3.3 MATLAB里的判决器实现
计算SINR我封装成一个函数,入参是有用信号发射功率、有用链路增益、干扰功率数组和噪声功率:
function sinr_dB = calSinr(p_dBm, gain_dB, interf_dBmList, noise_dBm) signal_lin = 10^((p_dBm + gain_dB) / 10); interf_lin = 0; if ~isempty(interf_dBmList) interf_lin = sum(10.^(interf_dBmList(:) / 10)); end noise_lin = 10^(noise_dBm / 10); sinr_lin = signal_lin / (interf_lin + noise_lin); sinr_dB = 10 * log10(sinr_lin); end这个函数的好处是干扰功率数组可以是任意长度,多个D2D发送端造成的累积干扰可以很方便地叠加。哪怕以后扩展出更复杂的干扰图,也不需要改这个基本结构。
判决器我写成下面这样,输入三种候选SINR和蜂窝用户干扰后的SINR,输出最终模式和选中的SINR:
function [mode, sinrSel] = decideMode(sinrCM, sinrOrth, sinrReuse, sinrCUafter, qD, qCU) valid = false(1, 3); valid(1) = (sinrCM >= qD); valid(2) = (sinrOrth >= qD); valid(3) = (sinrReuse >= qD) && (sinrCUafter >= qCU); candidates = [sinrCM, sinrOrth, sinrReuse]; candidates(~valid) = -Inf; if any(valid) [sinrSel, mode] = max(candidates); else mode = 0; % 当前快照下三种模式都不达标 sinrSel = max(candidates); end end这里的mode可以定义为1、2、3,分别对应蜂窝模式、D2D正交模式和D2D复用模式;mode为0表示这一轮判决失败,D2D对暂时不传输。如果你后续要对着判决结果画饼图,这个mode值就是直接的分类依据。
我特别强调“候选SINR都算一遍再选”的做法。实际项目中很多模式下SINR之间是有耦合的,比如复用的蜂窝用户不同,P_c和g_CD就不同,复用SINR自然也不一样。都算一遍的代价高一点,但胜在排查逻辑时非常清晰。若宰了流程,直接用一个固定门限做距离判决,你会完全看不到干扰对模式选择的影响。
4. 蒙特卡洛主循环:用MATLAB把仿真跑起来
4.1 主循环的整体骨架
模式选择仿真最常用的实验方法是蒙特卡洛,也就是在随机信道上反复生成拓扑和信道快照,然后在每个快照里运行判决算法,最后对所有快照做统计平均。MATLAB里主循环的结构非常固定,我直接写成下面这个框架:
% 主仿真参数 R = 500; % 小区半径,单位m N_C = 8; % 蜂窝用户数 N_D = 12; % D2D候选对数 dMin = 20; % D2D最小直通距离 dMax = 100; % D2D最大直通距离 pD = 20; % D2D发射功率,dBm pC = 20; % 蜂窝用户发射功率,dBm noiseDbm = -114; % 按1MHz带宽折算,后续按实际RB带宽调整 qD = 0; % D2D QoS阈值,dB qCU = 3; % 蜂窝用户SINR保护阈值,dB N_iter = 1000; % 蒙特卡洛次数 rng(42); stats = zeros(N_iter, 5); % 分别记录模式、D2D吞吐量、功耗、CU干扰损失、中断标志 for iter = 1:N_iter % 1. 生成拓扑 [posCU, posDtx, posDrx] = genTopology(R, dMin, dMax, N_C, N_D); % 2. 每对D2D独立决策 for k = 1:N_D dD = norm(posDtx(k, :) - posDrx(k, :)); gDD = genChannel(dD) ... % 继续生成gDB/gCD/gCB ... % 3. 调用判决器 % 4. 记录统计量 end end % 最后取平均并绘图每次迭代里的随机信道不同,判决结果也不同。这种频率快照抽样能反映网络在不同信道状态下的平均表现。如果你只跑几十次,曲线会抖得很厉害,后面加参数扫描时完全看不清趋势。
4.2 我统计的五个核心指标
指标设计要能回应“模式选择到底好不好”这个问题,而不是只看一条曲线。我固定统计五个量,放在每次迭代里累计:
| 指标 | 计算方式 | 说明 |
|---|---|---|
| 模式占比 | 三种模式被选中的次数占总有效判决次数比例 | 直观反映算法偏向哪种通路 |
| 平均D2D吞吐量 | mean(B * log2(1 + sinrSel)) | 中断链路按0计算 |
| 平均发射功率 | D2D发送端实际发射功率均值 | 蜂窝模式通常需要更大功率 |
| 蜂窝用户SINR损失 | 复用干扰前后CU SINR差值均值 | 衡量对蜂窝用户的保护程度 |
| 中断概率 | 判决器返回mode==0的次数比例 | 三模式均不可用的场景占比 |
这五个指标不是等价的。比如模式占比高不代表性能好,如果正交模式占比很高,说明D2D和蜂窝用户基本做到了频域隔离,但同时也意味着占用了额外RB,频谱效率可能没那么高。我会把中断概率和蜂窝用户SINR损失放在一起看:中断概率低、SINR损失又小,这个判决器才算是真的平衡。
4.3 计算吞吐量时不要忘记资源代价
在这个项目里我比较过两种统计方式。第一种是用每条D2D链路实际获得的吞吐量,直接对B * log2(1 + SINR)取平均。第二种是再把正交模式额外占用RB的代价考虑进去,用“单位RB的频谱效率”来比较。区别在正交模式上:复用模式的D2D链路虽然SINR低一些,但它没有额外消耗频谱资源;正交模式的SINR高,但等于又多拿了一个RB。只看第一条链路吞吐量,你会得出正交模式永远更好的结论,这不符合频谱效率的整体逻辑。
我这里推荐在统计时增加一个“系统总频谱效率”维度:
系统总频谱效率 = 所有蜂窝用户吞吐量之和 / N_RB_total + 所有D2D吞吐量之和 / N_RB_total
当正交模式新增一个RB时,分母变大;当复用模式频谱被重复利用时,分母不变。这样两种资源策略才有可比性。很多做D2D仿真的人在这里吃亏,画出来的图看上去复用模式很弱,其实是把资源占用的代价忘算了。
5. 结果解读与参数敏感度:为什么仿真图会“长得特别怪”
5.1 最大D2D距离对模式占比影响很大
我最早把D2D最大距离从50米一路扫到200米,画出模式占比图后发现一个现象:距离上限超过150米后,D2D正交模式占比就开始明显下降,蜂窝模式占比升高。原因倒不复杂,距离增大后直连路径损耗上升,SINR_orth跌到qD以下;同时蜂窝模式走基站反而稳定,毕竟基站到设备的路径损耗比两个在地面相距很远的用户之间的损耗更可预测。
但这里有个反直觉的结果:即便直连SINR已经很低,模式选择器还是会在一些快照里选直连模式,因为qD设成0 dB,也就是只要信号不低于噪声就能传。现实中,0 dB SINR对采用高阶调制编码的数据业务来说几乎不可用。所以我后来把qD调到了3到5 dB,模式占比曲线才更符合实际观感。这个经验值得记住:模式选择仿真的门限参数,最好参考实际传输方案的可达SINR工作点,而不是随便拍一个0。
5.2 蜂窝用户保护门限qCU的敏感性
我还专门扫过qCU的取值,从0、3、6到10 dB。结论是复用比例对qCU高度敏感。qCU从3上升到10 dB,复用模式占比通常会从百分之四十多跌到百分之十几,而蜂窝用户的SINR损失确实被压住了。换句话说,qCU是一颗“旋钮”,旋得越紧,D2D复用越难触发。
这个反馈对算法设计很重要。如果你的项目目标是最大化D2D接入率,那qCU就不能定太高,否则大多数D2D对都会被逼到正交模式,小区里RB也不够用。如果目标是保证蜂窝业务不受影响,qCU定低一点,比如3 dB,基本能把平均SINR损失控制在1 dB以内。最终的取值要由业务优先级决定,仿真能做的就是给你这张“旋钮曲线”。
5.3 干扰模型的完整度比算法复杂程度更影响结果
项目里我做过一次对比:把蜂窝用户到D2D接收端的干扰g_CD按固定30 dB衰减近似处理,还是每次根据实际距离和随机衰落完整生成。结果非常明显,固定衰减时常把SINR_reuse高估了2~3 dB,导致模式选择器过度偏向复用模式,系统总频谱效率反而下降。也就是说,判决策略本身没有变化,只是干扰模型粗了一点,最终结论却会反转。
这类仿真有一个共性:算法部分可能只占代码量三分之一,剩下的都是信道生成、干扰计算和统计口径。算法再精巧,如果干扰关系没吃透,结果就是纸面繁荣。我更倾向把精力先花在干扰矩阵的完整性上,再回头微调判决函数。
6. 仿真代码里最容易被忽略的五个细节
6.1 dB值转换顺序
这是很多新手第一个翻车的地方。计算SINR之前,先把dB形式的功率、增益全部转成线性值,在线性域里叠加干扰和噪声,最后再取对数。尤其是接收端同时接收多路干扰时,10.^和sum必须放在同一个表达式里,不要拆开。我在代码里的calSinr函数就是按这个逻辑写的,算出来的SINR无论干扰多大都不会出现负无穷或NaN。
6.2 距离上的上下限保护
路径损耗模型在d极小时会给出非常低的路径损耗,甚至可能出现负的路径损耗,这是在现实中不会发生的事。我建议所有距离在进入信道生成函数之前先做max(d, d_min)处理,d_min可以取10米。D2D接收端和发送端是同一个位置的可能性不高,但在随机拓扑里偶尔会产生距离接近0的快照,不做保护就很容易让某个SINR变成无穷大,后面统计平均时就出现一个异常尖峰。
6.3 随机种子要多设几组交叉验证
仿真里固定随机种子能保证结果可复现,但只固定一棵种子会骗人。我通常先用rng(42)跑一遍,再换成另外两个种子各跑一遍,把模式占比和中断概率的波动范围记录下来。如果三组种子下的结论方向一致,我才敢说这个趋势是参数变化引起的,而不是某个幸运拓扑带来的偶然而已。在MATLAB里,只要把种子作为参数传入仿真函数,这个验证做起来非常快。
6.4 判决器的“兜底分支”一定要写
当三种模式都不满足QoS约束时,判决器不能什么都不做,也不应该抛异常退出。项目里我让mode返回0,同时把这条D2D对的吞吐量计为0,但中断概率统计要单独记录。否则后续画平均吞吐量曲线时,断裂的链路会下拉平均值,如果你意识不到这部分是中断而不是慢速传输,很容易误判算法性能。我还会在代码里加入一个计数器,统计mode==0的次数占比,这样中断行为一目了然。
6.5 做参数扫描时批次脚本比交互画图更高效
一个人调参数的时候喜欢开着MATLAB一张一张图看,但一旦参数组超过10组,交互式画图就成了瓶颈。我会把所有实验参数放进一个表格,循环调用仿真函数,最后统一把结果写入结构数组。这样既能用boxchart对比多组模式的分布,也能快速生成灵敏度热力图。这个过程看似枯燥,却是模式选择仿真里最能发现规律的一步。
如果你现在正准备写类似的D2D通信模式选择仿真,我的建议是先从一个D2D对、一个蜂窝用户的最小场景开始,把单个RB上的SINR链路关系彻底算清楚,再往多对、多用户方向扩展。不要第一天就试图把多小区干扰协调和完整协议栈都塞进去,那样跑出来的结果只会让你更困惑。先让判决器在最小场景上做出符合直觉的判断,再一点点把复杂度加回去,这样的仿真曲线才敢拿去支撑设计决策。