简介:面向无线通信方向的学生与研究人员,这份 MATLAB 仿真资料聚焦 D2D 通信的信道建模与资源分配问题,适合已具备通信原理基础、希望动手复现启发式算法求解频谱分配的学习者。资源包共 22 个文件,以 20 个 .m 脚本为主体,辅以 2 个 .asv 自动备份文件,压缩包约 16KB,体积轻量但模块划分清晰。脚本覆盖信道建模、路径损耗计算、用户与 D2D 对生成、干扰距离统计、随机算法与启发式算法实现、最优方案求解及性能对比等环节,可支撑从系统建模到结果分析的完整仿真流程。已有 1214 人学习,说明其在同类主题中具备一定参考价值。读者可据此理解 D2D 直连通信中多径衰落、干扰协调与功率控制等因素如何影响资源分配,并借助现成脚本快速搭建实验环境、对比不同算法在吞吐量与覆盖率上的表现,为课程设计或课题研究提供可复用的代码基础。
1. D2D 信道仿真:为什么“链路能通”和“系统能跑”是两回事
很多人第一次做 D2D 仿真,都会掉进同一个坑:单条链路的误码率曲线画得漂漂亮亮,一放进多用户场景,系统吞吐不升反降。问题不在代码写错了,而在于把“点对点信道”和“干扰受限网络”混为一谈。D2D 通信的本质,是让两个终端在不经过基站转发的情况下直接传输数据,同时复用蜂窝用户的频谱资源。复用带来频谱效率,也带来同频干扰——这两件事是同一枚硬币的两面。所以一份靠谱的 D2D 信道仿真,必须同时回答三个问题:直连链路本身的衰落怎么建模、复用资源后干扰怎么叠加、资源分配策略怎么在干扰和吞吐之间做取舍。搞懂这套机制,你才能明白为什么仿真里那些参数不是随便填的,也才能看懂别人代码里每一个for循环到底在算什么。
2. 信道模型:从大尺度衰落到小尺度衰落的叠加逻辑
2.1 为什么 D2D 链路不能直接套用蜂窝上行模型
蜂窝链路的收发两端是“基站—终端”,天线高度、仰角、传播环境都有明确的统计规律,3GPP 那套路径损耗模型就是为此标定的。D2D 不一样:收发两端都贴地,天线高度通常只有一米多,周围的行人、车辆、建筑拐角都会进入第一菲涅尔区,视距(LOS)和非视距(NLOS)的切换极其频繁。常见的实现思路是把 D2D 链路拆成两段概率加权:以一定概率判为 LOS,按 LOS 路径损耗指数计算;否则判为 NLOS,用更大的损耗指数和额外的穿透损耗。这个概率通常写成距离的分段函数,距离越近越可能是 LOS。
仿真里最容易被忽略的是“概率”这个词的落地方式。它不是让你对每条链路取平均,而是每次信道实现时用一次随机判决。也就是说,同一条链路在不同仿真帧里可能一会儿 LOS 一会儿 NLOS,这正是 D2D 信道抖动大的来源。如果你在代码里对概率做了期望处理,画出来的曲线会异常平滑,和实际场景对不上,这就是典型的“仿真太理想”翻车现场。
2.2 大尺度与小尺度参数的组合顺序
一个合格的 D2D 信道增益,通常是这么乘出来的:
% 单条 D2D 链路的复合信道增益 function h = d2d_channel_gain(d, params) % 1) 视距判决:按距离分段概率抽样 p_los = params.p_los_coef * exp(-d / params.p_los_scale); is_los = rand() < p_los; % 2) 大尺度路径损耗:LOS/NLOS 用不同指数 if is_los pl_db = params.pl0_los + 10 * params.alpha_los * log10(d); else pl_db = params.pl0_nlos + 10 * params.alpha_nlos * log10(d) ... + params.penetration_loss; end % 3) 阴影衰落:对数正态,dB 域叠加 shadow_db = params.sigma_shadow * randn(); % 4) 小尺度衰落:瑞利或莱斯,按 LOS 与否切换 if is_los h_small = sqrt(params.k_rician / (params.k_rician + 1)) + ... sqrt(1 / (params.k_rician + 1)) * ... (randn() + 1j * randn()) / sqrt(2); else h_small = (randn() + 1j * randn()) / sqrt(2); end % 5) 线性域合成 h = h_small * 10^(-(pl_db + shadow_db) / 10); end这段代码的关键不在语法,而在顺序和域。路径损耗和阴影衰落都在 dB 域相加,最后统一转到线性域再乘小尺度复增益,顺序错了量纲就乱了。p_los_coef和p_los_scale控制视距概率随距离衰减的快慢,alpha_los一般取 2 附近,alpha_nlos取 3 到 4,sigma_shadow常见取值 4 到 8 dB,k_rician是莱斯因子,LOS 下取 10 左右比较常见。这些数值不是拍脑袋,而是对应不同场景(室内、街区、开阔地)的经验标定,换场景就得换一组。
提示:小尺度衰落在仿真里要按“帧”更新,而不是按“链路”只生成一次。很多初学者在初始化时给每条链路固定一个
h,跑完整个仿真都不变,等于把快衰落当成了准静态,吞吐曲线会虚高。
2.3 阴影衰落的相关性为什么值得单独处理
严格来说,阴影衰落不是独立同分布的。相邻链路、相邻时刻的阴影分量存在空间和时间相关性,尤其在密集 D2D 场景里,多个终端挤在同一片街区,阴影会“抱团”。常见的简化做法是给每条链路独立生成,这在链路数少时问题不大;但当你研究资源分配算法时,相关性会直接影响干扰的方差,进而影响调度决策的稳健性。如果仿真结果和理论分析差得离谱,先回头看看是不是把相关性一刀切掉了。我一般会用一个相关矩阵做 Cholesky 分解再乘高斯向量,代价不大,但能让干扰统计更接近真实。
3. 干扰建模:复用资源后,谁在干扰谁
3.1 三类干扰的叠加关系
D2D 复用蜂窝上行资源时,干扰至少有三条路径:D2D 发射端干扰基站、D2D 发射端干扰同频的其他 D2D 接收端、蜂窝用户干扰 D2D 接收端。这三类干扰在仿真里必须分别累加,不能只算其中一条。很多简化模型只考虑“蜂窝对 D2D”的干扰,结果资源分配算法显得特别有效,一放到完整干扰环境里就崩。原因很简单:D2D 之间的相互干扰在密集场景下才是主导项。
干扰的计算本质是功率域求和再转信干噪比:
% 计算某个 D2D 接收端的 SINR function sinr = calc_d2d_sinr(rx_id, tx_id, P_d2d, H, cellular_interf, params) signal = P_d2d(tx_id) * abs(H(tx_id, rx_id))^2; % 同频 D2D 干扰:排除自身发射端 d2d_interf = 0; for k = 1:params.num_d2d if k ~= tx_id && params.rb_assignment(k) == params.rb_assignment(tx_id) d2d_interf = d2d_interf + P_d2d(k) * abs(H(k, rx_id))^2; end end % 蜂窝干扰 + 噪声 noise = params.noise_psd * params.bandwidth; sinr = signal / (d2d_interf + cellular_interf(rx_id) + noise); endrb_assignment是资源块分配向量,只有复用同一资源块的链路才产生同频干扰,这是整个干扰模型的核心开关。noise_psd乘带宽得到噪声功率,单位要和发射功率统一。注意cellular_interf是预先算好的向量,因为蜂窝用户的发射功率和位置在一帧内通常不变,没必要每次重算。
3.2 资源分配为什么是干扰管理的“总闸”
资源分配在 D2D 仿真里不是附属功能,而是决定干扰结构的顶层设计。常见的策略分三类:随机分配、基于距离的复用、基于图着色的冲突规避。随机分配实现最简单,但干扰方差最大;基于距离的策略是“离得够远才允许复用同一资源块”,本质是用空间隔离换干扰抑制;图着色把每条 D2D 链路看成顶点,冲突关系看成边,用贪心着色给资源块,目标是让同色顶点之间的干扰总和最小。
仿真里验证分配策略是否有效,不能只看平均吞吐,要看吞吐的累积分布。一个策略如果提升了平均值但把 5% 边缘用户的吞吐压到接近零,那它在实际系统里是不可接受的。这也是为什么很多论文里的“最优分配”在仿真中表现平平——它们优化的是总和,而真实系统关心的是公平性。你在跑仿真时,至少要把平均吞吐、5% 分位吞吐、以及中断概率三个指标一起看,否则很容易被平均值骗过去。
3.3 功率控制与资源分配的耦合
D2D 发射功率不是固定值。功率控制的目标是在保证自身 SINR 的前提下尽量降低对别人的干扰,这天然和资源分配耦合在一起:功率降了,可复用的链路就多了;复用多了,干扰又上来了。仿真里常见的做法是先定功率再分配资源,或者交替迭代。前者简单但次优,后者更接近真实但计算量大。如果你的仿真规模不大(几十条链路),交替迭代跑得动,结果也更有说服力;规模上去之后,就得在迭代次数和精度之间做取舍,这个取舍本身就是设计的一部分。
4. 仿真主循环:帧、调度、统计三件事怎么串
4.1 时间粒度的选择决定了仿真的可信度
D2D 仿真里有两个时间尺度:信道相干时间和调度周期。小尺度衰落按相干时间更新,调度按传输时间间隔(TTI)更新,两者通常不是一回事。如果你把信道更新和调度放在同一个循环里,等于假设每个 TTI 信道都完全独立,这会高估分集增益。常见的实现思路是让信道在若干个 TTI 内保持不变,到相干时间再重生成,这样快衰落的时间相关性才被保留。
主循环的骨架大致是这样:
% D2D 系统级仿真主循环 for frame = 1:params.num_frames % 终端位置:慢移动,按帧更新 [pos_d2d, pos_cell] = update_positions(pos_d2d, pos_cell, params); % 大尺度 + 阴影:每帧重算 H_large = build_large_scale(pos_d2d, pos_cell, params); for tti = 1:params.tti_per_frame % 小尺度:按相干时间决定是否重生成 if mod(tti, params.coherence_tti) == 1 H_small = build_small_scale(params); end H = H_large .* H_small; % 资源分配 + 功率控制 [rb, P] = resource_allocation(H, params); % 逐链路算 SINR 与吞吐 for i = 1:params.num_d2d sinr(i) = calc_d2d_sinr(i, tx_of(i), P, H, cell_interf, params); rate(i) = params.bandwidth * log2(1 + sinr(i)); end % 统计累加 stats = accumulate(stats, rate, sinr, params); end endcoherence_tti控制小尺度衰落的更新频率,取值太小会让信道变化过快、调度来不及响应,太大则低估了信道动态。num_frames决定统计样本量,太少则 5% 分位吞吐的估计方差很大,一般至少几百帧才稳。accumulate里要同时记录瞬时速率和中断事件,后面出图才有料。
4.2 统计口径不统一,结果就没法比
同一个仿真平台,不同人跑出来的吞吐可能差一倍,很多时候不是模型问题,是统计口径问题。速率是按香农公式算的理论值还是查表得到的链路级值?中断是按 SINR 门限判还是按速率门限判?边缘用户是按位置分位还是按速率分位?这些定义不写清楚,结果就没法横向比较。我一般会在仿真配置里把这几项显式写成字段,出图时一并标注,避免自己过几个月都忘了当时怎么算的。
5. 避坑与排查:仿真结果不对劲时先看这几处
5.1 吞吐随链路数增加反而下降
现象:D2D 链路数从 10 加到 50,系统总吞吐先升后降,降得还很陡。原因通常是干扰项漏了或者资源块数量没随链路数调整,导致大量链路挤在少数资源块上,同频干扰爆炸。解决:先打印每个资源块上的复用链路数,确认分配策略是否真的在分散负载;再检查干扰求和里有没有把自身链路算进去,自身干扰会让 SINR 系统性偏低。
5.2 SINR 分布出现异常长尾
现象:大部分链路 SINR 正常,少数链路 SINR 低到负几十 dB。原因多半是路径损耗里距离取了零或极小值,log10(d)直接发散。解决:给距离加一个最小下限(比如 1 米),并在生成位置时排除收发端重合的情况。这个坑很隐蔽,因为只有极少数样本触发,平均值看不出来,但会污染中断概率统计。
5.3 换一组随机种子结果就大变
现象:同一套参数,换个种子吞吐波动超过 20%。原因通常是帧数太少,或者阴影衰落方差设得过大而样本不足。解决:先把num_frames加到上千帧看波动是否收敛;如果仍不收敛,检查是不是每条链路只生成了一次阴影,导致空间相关性缺失、方差被放大。收敛性验证是仿真可信度的底线,别跳过。
5.4 资源分配算法“看起来很美”
现象:某分配算法在仿真里比随机分配好一大截,但换成密集场景优势消失。原因往往是算法里隐含了“干扰可忽略”的假设,或者只在稀疏场景下调过参。解决:把链路密度作为扫描变量,画出吞吐随密度变化的曲线,看算法在哪个区间失效。一个稳健的算法应该在中等密度下仍有增益,而不是只在特定点好看。
6. 设计取舍:仿真精度和计算代价之间的那条线
做 D2D 信道仿真,绕不开一个根本矛盾:模型越细,越接近真实,但计算量也越大。小尺度衰落按每条链路每个 TTI 独立生成,统计上最干净,但链路数上百、帧数上千时,光是生成复高斯就够呛;阴影相关性用完整相关矩阵最准,但矩阵维度是链路数的平方,规模一上去内存先扛不住。所以真正要做的不是“要不要简化”,而是“在哪一层简化、简化后哪些结论还站得住”。
我的经验是把简化分成两类。一类是“统计上可替代”的简化,比如用分段概率代替完整的射线追踪来判断 LOS,只要概率函数标定得合理,大尺度统计量不会偏太多,这类简化可以放心用。另一类是“结构性”的简化,比如忽略阴影相关性、假设干扰独立,这类简化会改变干扰的方差结构,进而影响资源分配算法的排序,属于会动摇结论的简化,能不做就不做。判断标准很简单:换一种简化方式,如果你的算法排名变了,那这个简化就不能省。
验证方法上,我习惯做两件事。第一是极限校验:把链路数设成 1,仿真结果应该退化成单链路误码率或容量的理论值,对不上就说明信道模型本身有问题。第二是单调性校验:发射功率增加,SINR 和吞吐应该单调不降(在干扰可忽略区间),如果出现非单调,多半是干扰项或功率归一化写错了。这两个校验不花什么时间,但能挡掉大部分低级错误。
| 简化项 | 对结论的影响 | 建议 |
|---|---|---|
| LOS 概率分段近似 | 小,大尺度统计稳定 | 可放心用 |
| 阴影独立假设 | 中,影响干扰方差 | 密集场景慎用 |
| 小尺度准静态 | 大,高估分集增益 | 按相干时间更新 |
| 干扰只算蜂窝 | 大,低估 D2D 互干扰 | 必须补全 |
那张表不是让你照抄,而是提醒你每做一次简化,都问一句“这会不会改变我要验证的那个结论”。仿真不是越真越好,是越“对问题敏感”越好。从那以后我每次搭 D2D 仿真,都强制先跑一遍单链路极限校验和功率单调性校验,再去看系统级曲线,省下来的返工时间远比写校验代码多。希望帮到你。
本文还有配套的精品资源,点击获取