D2D信道仿真MATLAB工程:从信道建模到资源分配 简介面向无线通信领域研究人员与MATLAB开发者这套D2D信道仿真与资源分配代码包聚焦设备直连通信场景覆盖信道建模、路径损耗计算、干扰协调及频谱资源分配等关键环节并引入贪婪、遗传、粒子群等启发式算法对比适合学习D2D系统仿真与优化算法实现。包体共22个文件以20个m脚本为主、辅以2个asv自动备份文件整体压缩包仅16KB代码轻量、易于阅读和二次开发。核心模块包含主程序、用户与D2D对生成、干扰测量、Edmonds匹配、随机算法及启发式贪心算法等便于按功能拆解运行。目前已有1214人学习该资源。通过运行这些代码可直观理解D2D通信中资源分配与干扰抑制的权衡过程掌握基于MATLAB的系统级仿真流程为课程设计、毕业课题或论文复现提供可直接修改的参考实现。1. D2D信道仿真MATLAB从一段能跑的代码到一整套可复现的工程我之前在某项目组做终端直通物理层预研时最头疼的并不是算法推导而是网上能找到的D2D信道仿真MATLAB工程大多只丢一个孤零零的函数拿回来之后要自己拼配置、补随机种子、处理距离矩阵维度经常因为一个行列方向的问题就绕进去一整天。模拟项目X不一样它把信道建模和资源分配完整地串成了工程从config参数、路径损耗、瑞利/莱斯小尺度衰落、干扰计算到RB资源分配每个环节都是独立函数主循环跑完直接画出SINR累计分布曲线。这个工程特别适合两类人一是刚接触D2D物理层仿真、需要一个基线工程当作起点的研究生二是在宏蜂窝场景下对比D2D资源分配算法效果的工程研究者。整个代码相当克制没有依赖冷门工具箱装好基础MATLAB就能跑。2. 工程解剖目录结构、参数入口与技术选型2.1 文件布局仿真工程应该怎么拆拿到工程压缩包之后我习惯先把目录看一遍确认它是能跑起来的完整工程而不是一堆散落脚本。模拟项目X的目录结构大致是这样D2D_channel_sim/ ├── config.m ├── main.m ├── src/ │ ├── channel/ │ │ ├── path_loss.m │ │ ├── fast_fading.m │ │ └── shadow_fading.m │ ├── resource/ │ │ ├── compute_sinr.m │ │ └── allocate_rb.m │ └── utils/ │ ├── distance_matrix.m │ └── plot_cdf.m └── results/这个划分很直接channel目录只负责无线信道的物理过程resource目录只处理干扰和资源块分配utils目录放跨模块复用的工具函数。config.m是全局参数脚本main.m是唯一入口results目录用来落图和数据文件。我一般会坚持“入口只有一个”的原则不要在多个脚本里各自定义参数否则改起来会顾此失彼。channel和resource分离还有一个额外好处如果你想把信道模型换成别的标准只需要动channel下的文件资源分配算法完全不用碰反过来如果你只想调资源分配策略信道相关的文件可以原封不动。这种模块边界在工程代码里看不见摸不着但在做算法对比实验时非常重要——它保证了你换了分配算法之后信道条件完全相同公平性才有保障。2.2 参数入口config.m是改参数的第一站config.m本质上是一个MATLAB脚本不是函数所以它不返回任何东西而是通过直接执行给当前工作区注入一堆变量。main.m第一行会调用它。这样做有一个很实际的好处你在命令行里跑完main.m后所有参数还留在工作区里可以随时手工查看某个中间变量这对于调试距离矩阵或者信道增益特别方便。工程里比较关键的参数集中在下面这组我按照实际使用频率挑了主要的几个参数名默认值作用area_size1000仿真区域边长单位米决定用户密度bs_pos[0 0]基站坐标处理蜂窝链路时使用num_d2d10D2D用户对数量num_cellular5蜂窝用户数量也是上行RB数量carrier_freq2e9载频单位Hz用于路径损耗模型p_d2d23D2D发射功率单位dBmp_cellular23蜂窝用户发射功率单位dBmnoise_dbm-104等效噪声功率单位dBm含带宽折算path_loss_modelUMa蜂窝链路的路径损耗模型fading_typeRayleigh小尺度衰落类型可选Ricianmc_trials500蒙特卡洛仿真次数sinr_threshold_dB0资源分配的最低SINR门限注意噪声功率noise_dbm这里直接写成了等效值而不是让每个人再乘带宽算一次这样能少踩一个单位换算的坑。如果你有特别想要的噪声系数或带宽可以改成noise_dbm -174 10*log10(bandwidth) noise_figure但默认值已经包含了20MHz带宽和适中的噪声系数。改参数操作本身没什么玄学但有一点我吃过亏不要在main.m里再写一遍参数覆盖config.m。原工程里除了config.m之外没有任何地方重新定义噪声功率或衰落类型这是刻意保持的单一事实来源。如果你发现某次实验曲线不变化先检查是不是自己手动在工作区里覆盖了某个变量导致后续循环读的是旧值。2.3 为什么这个工程坚持用基础MATLAB函数很多D2D信道的例程会直接调用pdist2计算距离或者用comm.RayleighChannel这种通信工具箱对象。模拟项目X刻意避开了这两招数值上并没有区别但换来了更高的可移植性。比如距离计算用的是两层循环虽然代码看起来朴素一点可是在几百个用户对场景里计算速度完全够用而且不用依赖Statistics Toolbox瑞利衰落是手动用randn构造复数序列也不依赖额外工具箱。这意味着你在网上找一台只装了基础MATLAB的机器把工程放进去就能直接跑不会出现一打开就报“缺少工具箱”的尴尬。从二次开发角度看基础函数还有一个容易被忽略的好处你可以直接控制每一处实现的细节。用工具箱里的信道对象固然方便但它是一个黑匣子里面的归一化方式、滤波器尾长、相位更新逻辑都不透明出了问题你只能改参数碰运气。手写公式虽然要多写几行但每行都能解释调试起来不用猜。模拟项目X这种“能看穿一切”的写法其实更适合拿来做教学和算法对比因为任何一个变量都能打印出来人工验证。3. 核心模块拆解一个信号从发射机到SINR的完整链路3.1 距离矩阵坐标索引是第一条坑D2D信道仿真的第一步永远是算距离。工程里没有直接用MATLAB自带的pdist2而是写了一个distance_matrix函数放在utils目录下代码如下function distm distance_matrix(tx_pos, rx_pos) % distance_matrix: 计算发射机到接收机的欧氏距离矩阵 % tx_pos: Nt x 2 矩阵每一行是一个发射机坐标 % rx_pos: Nr x 2 矩阵每一行是一个接收机坐标 % 输出 distm: Nt x Nr 矩阵distm(i,j) 表示第 i 个发射机到第 j 个接收机的距离 Nt size(tx_pos, 1); Nr size(rx_pos, 1); distm zeros(Nt, Nr); for i 1:Nt for j 1:Nr distm(i, j) norm(tx_pos(i, :) - rx_pos(j, :)); end end end这个函数看着简单但它的参数顺序非常容易写反。在D2D直通链路里tx_pos传的是D2D发射机坐标集合rx_pos传的是D2D接收机坐标集合在蜂窝干扰链路里tx_pos传的是蜂窝用户坐标rx_pos传的是D2D接收机坐标。如果你把tx_pos和rx_pos传反了矩阵形状并不一定会报错因为只要两个维度恰好都是Nt和NrMATLAB照样能算出一个转置形状的结果但是物理意义上“信号链路”和“干扰链路”就串位了后面的SINR会变得完全无法解释。所以我拿到这个函数后做的第一件事不是在主循环里闷头往下写而是先构造两个极小的坐标矩阵做单元验证。比如让tx_pos只有两个点rx_pos只有两个点打印输出矩阵的对角元素确认第一个发射机到第一个接收机的距离是符合直觉的值再放大规模。这种验证方法虽然老土但能保证后面整个链路不因为一个索引问题崩掉。距离矩阵的单位也值得注意工程里默认全部用米path_loss内部会自己对极小距离做保护避免距离为零导致的log10除零。3.2 路径损耗3GPP模型与D2D直通模型的差异距离算完之后接下来就是把距离折成路径损耗。模拟项目X把路径损耗单独封装在path_loss.m中支持两种模式一种给宏蜂窝链路用一种给D2D直通链路用function pl path_loss(dist_m, fc, model) % path_loss: 路径损耗计算 % dist_m: 距离单位米可以是矩阵 % fc: 载频单位Hz当前版本暂时只在UMa公式中使用 % model: 选用的模型字符串UMa 或 D2D % 输出 pl: 路径损耗单位dB与输入dist_m同维度 if strcmp(model, UMa) pl 128.1 37.6 * log10(max(dist_m, 1e-3)); else pl 148 40 * log10(max(dist_m, 1e-3)); end endUMa代表的是城郊宏基站场景公式里的常数128.1包含了载频等于2GHz时的参考路径损耗37.6是路径损耗指数它描述的是非视距宏蜂窝链路。D2D直通链路的默认写法把指数拉高到40相当于用更快的损耗速度来模拟终端直连时周围物体遮挡更严重的情况。这里我在读代码时专门看了一眼它对dist_m为零的保护max(dist_m, 1e-3)这一步很有必要因为D2D接收机位置可能刚好和发射机坐标碰上如果不过滤log10(0)会直接给负无穷后面信道增益就全是零。工程里没有把fc真正代入D2D分支这是刻意的简化。实际项目中如果你想支持5G频段的3.5GHz或者毫米波可以把fc加进公式比如在UMa分支里加入20*log10(fc/2e9)之类的频率修正项。我建议在二次开发时把这个参数留好因为换频段是D2D参数研究中很常见的调整方向。路径损耗计算完之后一般会转成线性增益再参与信道增益叠加也就是后面代码里会出现sqrt(10.^(-pl/10))这种写法注意单位是dB运算顺序是先取负再除10最后乘方这个换算我在调试时经常看到有人写反。3.3 小尺度衰落瑞利和莱斯到底差在哪路径损耗描述的是大尺度趋势真实无线环境还需要加入小尺度衰落。工程里的fast_fading.m主要做了两种模式瑞利和莱斯。核心代码如下function h fast_fading(num_samples, fading_type, K_factor) % fast_fading: 生成小尺度复数信道系数 % num_samples: 需要生成的样本数量 % fading_type: Rayleigh 或 Rician % K_factor: 莱斯K因子单位是线性值仅在Rician时使用 % 输出 h: 1 x num_samples 复数序列平均功率归一化为1 if strcmp(fading_type, Rayleigh) h sqrt(0.5) * (randn(1, num_samples) 1i * randn(1, num_samples)); elseif strcmp(fading_type, Rician) mu sqrt(K_factor / (K_factor 1)); sigma sqrt(1 / (2 * (K_factor 1))); h mu sigma * (randn(1, num_samples) 1i * randn(1, num_samples)); else error(未知衰落类型请使用 Rayleigh 或 Rician); end end瑞利衰落看起来只是两个高斯随机变量的组合但里面那个sqrt(0.5)非常关键。一个标准的复高斯变量如果实部和虚部都是标准正态分布那么它的模平方期望值是2显然不符合信道系数功率归一化到1的约定。加上sqrt(0.5)之后实部虚部功率各为0.5合成功率正好是1。很多初学者在仿真时忘记这一步导致最后SINR整体偏高还以为是系统设计得好实际上是衰落功率被放大了3dB。这是我见过最多的一次性翻车点。莱斯模型则是在瑞利基础上加了一条固定视距路径mu是视距分量的幅度sigma是散射分量的标准差。从公式推导看K因子越大视距分量占比越高衰落波动越小。工程里默认只用瑞利但如果你把config.m里的fading_type改成Rician需要在main.m调用时多传一个K_factor参数这个参数在config.m中没有默认值我一般会把它设成10对应10dB的莱斯K因子。注意这里的K因子是线性值不是dB如果你从论文里读到K10dB需要先转成10再传进来。3.4 干扰计算与资源分配启发式算法的工程实现D2D用户复用蜂窝用户的上行RB时干扰主要来自使用同一RB的蜂窝用户。模拟项目X用compute_sinr.m把信号和干扰折算成SINR矩阵然后交给allocate_rb.m做分配。先看SINR怎么算function sinr_mat compute_sinr(H_d2d, H_interf, p_d2d, p_cellular, noise_power) % compute_sinr: 计算D2D链路的SINR线性值 % H_d2d: K x M 矩阵D2D链路信道增益 % H_interf: K x M 矩阵蜂窝用户到D2D接收机的干扰链路增益 % p_d2d: D2D发射功率单位瓦特 % p_cellular: 蜂窝用户发射功率单位瓦特 % noise_power: 噪声功率单位瓦特 % 输出 sinr_mat: K x M 矩阵线性SINR值 sinr_mat p_d2d * H_d2d ./ (p_cellular * H_interf noise_power); % 防止极端情况下的除零 sinr_mat(isinf(sinr_mat)) 1e6; end这里H_d2d的行对应D2D用户列对应蜂窝用户占用的RB。H_d2d(k,m)代表第k个D2D对在第m个RB上的等效信道增益H_interf(k,m)代表第m个蜂窝用户对第k个D2D接收机造成的干扰链路增益。分母里蜂窝用户发射功率乘以干扰链路增益再加上噪声功率这是上行链路最标准的SINR表达式。需要注意的是功率单位config.m里给的是dBm在main.m中调用时需要用10^((p_d2d-30)/10)转成瓦特如果偷懒直接用dBm数值参与计算SINR会被放大1e3倍曲线直接飞上天。接下来是资源分配。这个工程没有走复杂的迭代注水算法而是用一个带SINR门限的贪心策略我在阅读时把它单独抽出来看function rb_alloc allocate_rb(sinr_mat, sinr_threshold_lin) % allocate_rb: 给每个D2D用户分配一个RB使用保守的两用户复用策略 % sinr_mat: K x M 矩阵线性SINR值 % sinr_threshold_lin: 线性SINR门限低于该值的RB不能被分配 % 输出 rb_alloc: K x 1 向量取值0..M0表示未分配 K size(sinr_mat, 1); M size(sinr_mat, 2); rb_alloc zeros(K, 1); n_assign zeros(1, M); [~, order] sort(max(sinr_mat, [], 2), descend); for idx 1:K u order(idx); feasible true(1, M); for m 1:M % 一个RB最多服务2个D2D用户超过则不可行 if n_assign(m) 2 feasible(m) false; end % SINR低于门限的RB不可行 if sinr_mat(u, m) sinr_threshold_lin feasible(m) false; end end if any(feasible) [~, best] max(sinr_mat(u, :) .* feasible); rb_alloc(u) best; n_assign(best) n_assign(best) 1; else rb_alloc(u) 0; end end end这个分配策略的核心思想是“先来后到加门限控制”。sort那一行先按每个用户在所有RB上的最大SINR降序排序让信道条件好的用户优先生成分配决策。比如第k个D2D用户在某个RB上的SINR很高就先让它选走避免它后来被迫去跟强干扰用户挤同一个RB。feasible数组用来记录每个RB是否允许分给当前用户一个RB最多复用两个D2D用户这是在仿真自由度上是刻意的保守选择——如果允许无限制复用需要考虑同RB用户之间的互干扰代码要加一层迭代循环会让初版工程复杂不少。门限sinr_threshold_lin的作用是实现“宁可不分配也不要强干扰”如果一个RB的SINR低于门限就算它是唯一空闲的RB也直接标记为不可用。这种做法在很多论文里叫“门限调度”实际系统里也是为了避免边缘用户拖垮整体性能。如果你把门限设得太高比如调成10dB以上会发现D2D用户被强制未分配的比例明显上升甚至出现某个RB始终空置的情况这不是bug是策略正确工作。用户等级order计算也隐含了一个公平性问题SINR高的用户占便宜SINR低的用户只能在剩下的RB里选这种“机会主义”调度在对比算法时是常见的参照系。4. 跑通仿真从main.m到SINR累计分布图4.1 主流程一次仿真发生了什么工程把整个蒙特卡洛循环放在main.m里第一次打开这个文件时我原以为会有几百行实际上主干很短。核心循环大概可以浓缩成下面这个结构% main.m 核心流程 rng(42); % 固定随机流保证结果可复现 config; % 加载所有配置参数 sinr_all []; for trial 1:mc_trials % 生成随机拓扑 d2d_tx area_size * rand(num_d2d, 2) - area_size/2; d2d_rx area_size * rand(num_d2d, 2) - area_size/2; cellular_pos area_size * rand(num_cellular, 2) - area_size/2; % 信号链路信道增益 dist_d2d distance_matrix(d2d_tx, d2d_rx); pl_d2d path_loss(dist_d2d, carrier_freq, D2D); h_fast fast_fading(num_d2d, fading_type, 10); H_d2d sqrt(10.^(-pl_d2d/10)) .* h_fast; % 干扰链路信道增益 dist_interf distance_matrix(cellular_pos, d2d_rx); pl_interf path_loss(dist_interf, carrier_freq, path_loss_model); H_interf sqrt(10.^(-pl_interf/10)); % 功率与噪声换算 p_d2d_w 10^((p_d2d - 30) / 10); p_cell_w 10^((p_cellular - 30) / 10); noise_w 10^((noise_dbm - 30) / 10); % 计算SINR并分配RB sinr_lin compute_sinr(H_d2d, H_interf, p_d2d_w, p_cell_w, noise_w); ...... % 这里调用 allocate_rb 获得 rb_alloc然后只保留已分配用户的SINR sinr_all [sinr_all; sinr_dB_allocated(:)]; end plot_cdf(sinr_all, results/sinr_cdf.png);我故意在中间用省略号跳过了一个细节因为在阅读原工程时我发现它还有一步分配完RB后需要对sinr_lin做一次“按rb_alloc过滤”把未被分配的用户从统计样本里删掉。否则CDF曲线会混入大量低于门限的值掩盖真实调度的性能。这一步逻辑虽然简单但它直接决定了曲线的物理意义——是“所有D2D用户的SINR分布”还是“成功调度D2D用户的SINR分布”论文图里的曲线必须说清楚。我在自己的实验里一般保留后者并会在图例或者表格里注明样本数。整个运行过程在MC次数等于500时普通笔记本电脑大概要跑十几秒瓶颈主要不在距离循环而在SINR矩阵的逐次拼接。如果你嫌慢可以在循环开始前预先分配一个大数组每次往固定索引填充循环结束后再截断这是MATLAB性能优化最见效的一种方式。我改了之后500次仿真时间缩短了大概一半但代价是代码可读性稍微下降了一点。4.2 结果验证CDF曲线怎么看才可信跑通之后别急着截图先问自己三个问题。第一曲线取值范围是不是合理的物理范围如果sinr_dB的中位数在-5dB以下多半是噪声功率设置过高或者路径损耗指数取得太大如果中位数超过了20dB极其有可能是功率单位没换算或者衰落归一化出了问题。第二曲线形状是否符合预期瑞利衰落下的SINR CDF一般来说会比较平滑地穿过中值附近如果曲线出现明显台阶或者跳变可能是因为RB分配后样本数太少或者门限边缘条件把一堆用户切在了同一个整数值上。第三重复运行结果是否稳定由于main.m第一行固定了rng(42)在同一个环境下多次运行应该得到几乎完全一样的曲线只有随机流没有被重置时才应该有波动。我一般会连跑三次看一下三条曲线是否重叠如果三条线完全分离就说明有变量没有被重置。CDF还有一种快速验证方法把fading_type改成Rician并且把K因子调大比如设成100曲线会明显变得更集中在中高SINR区域。如果你看不到这个变化趋势那么大概率是fast_fading里的K因子没传对或者功率归一化被覆盖。这条验证并不严谨但它能快速暴露出代码里“衰落类型切换”是否真的生效。4.3 参数与结果的对应关系做资源分配方向的人最关心的就是参数变化如何影响曲线。我把常用调整和预期效果整理成下面这个简要对应表调参动作预期曲线变化关键原因调大p_d2dSINR曲线整体向右移动信号功率增大干扰项不变调大p_cellular干扰增大曲线向左移动D2D接收机收到的蜂窝干扰变强调低sinr_threshold_dB更多用户被调度低SINR样本变多门限放宽边缘用户也获得RB增加num_d2d单RB复用压力增大部分用户未分配可用RB数不变竞争加剧改area_size为200用户间距缩小路径损耗降低距离项直接影响信道增益这个表基本上覆盖了最常用的几个调节方向。如果你发现调了某个参数但曲线完全不变不用怀疑是自己操作不对大概率是参数在config.m中的名称没对上或者在main.m里有一处硬编码覆盖了它。我就在调p_cellular时遇到过这个问题因为main.m里还有一行临时变量p_cell_w 10^((p_cellular - 30)/10)看起来没问题但我在另一个函数里又自己定义了一遍结果改了半天没效果。这种同名覆盖问题在不写全局参数的工程里特别容易发生所以我在二次开发时会主动搜索所有出现某个参数的地方确认只有config.m一处定义。5. 避坑记录D2D仿真里最隐蔽的4个坑5.1 距离矩阵行列写反SINR数据一塌糊涂现象SINR曲线出现很多负20dB以下的样本甚至混入NaN检查路径损耗公式也找不到明显错误。原因调用distance_matrix(cellular_pos, d2d_rx)时把蜂窝用户坐标和D2D接收端坐标传反了导致干扰链路增益被写进信号链路信号链路增益被当作干扰两个角色互换。解决在distance_matrix函数头加一个参数维度断言如果输入矩阵的行数不符合当前链路的定义就报错另外在主脚本中每个trial开始时用size()打印关键矩阵维度人工看一眼是否与预期一致。从那以后我每次写带链路区分的仿真都在函数名后缀上注明信号链路还是干扰链路比如distance_matrix_signal和distance_matrix_interference避免语义模糊。5.2 瑞利衰落少乘sqrt(0.5)信道功率无故翻倍现象所有SINR曲线比同配置的理论值高约3dB且用户间相对差异不大整体看着“过于乐观”。原因randn生成的标准正态变量实部虚部功率各为1直接组合成复数后平均功率为2没有归一化到1。很多人只用randn(1,N) 1i*randn(1,N)忘记了系数。解决在fast_fading.m的瑞利分支中乘以sqrt(0.5)并在函数说明里明确标注“平均功率归一化为1”。如果你拿到的信道数据来自工具箱对象也要先看它的输出功率约定很多通信工具箱默认输出的是单位功率不需要你再手动缩放但有些模型需要额外配置。5.3 资源分配出现死循环CPU直接拉满现象运行allocate_rb时程序长时间不退出任务管理器里MATLAB CPU占用100%只能强制终止。原因我把sinr_threshold_dB设成了10dB在稀疏场景下大量用户找不到任何可行RB而原工程某个初版实现用了while循环等待用户找到可用RB找不到就永远空转。解决将RB搜索改成for循环并且增加最大尝试次数上限同时在进入分配之前判断max(sinr_mat(u,:))是否小于门限如果连最大SINR都低于门限就直接标记为未分配不再做内部扫描。我在二次开发后把分配函数改成“即使无可行RB也必须返回0”的约定主循环就永远不会卡死。5.4 随机种子不固定曲线复现成为玄学现象同一份代码隔天跑出来的CDF曲线形状有差异甚至两次运行中某个点的概率值能差5个百分点。原因rand和randn使用的全局随机流在每次MATLAB启动时都不同脚本没有重置不同机器上表现更明显。解决main.m第一行写死rng(42)如果做批量实验则使用for seed 1:100循环并将seed作为变量传入配置最后把每个seed对应的结果文件单独保存。固定随机种子还能辅助调试当你发现一条曲线很奇怪时可以回头用同一个seed重新运行复现同一组用户位置逐步打印中间变量定位问题。6. 二次开发把静态拓扑改成动态移动模型这个工程目前的拓扑是每轮蒙特卡洛重新随机生成相当于每个时刻的快照之间没有任何相关性。如果你想研究D2D用户在移动过程中的切换或者长时平均性能最简单的一步改造是加入随机游走模型。核心思路是在每个时间片t里更新坐标位置同时给快衰落加时间相关性。坐标更新可以写成一个很小的函数% 随机游走位置更新 function pos random_walk_update(pos, velocity, dt, area_size) % pos: N x 2 矩阵当前时刻坐标 % velocity: 平均移动速度单位m/s % dt: 仿真步长单位s step velocity * dt; pos pos step * randn(size(pos)); pos max(min(pos, area_size/2), -area_size/2); end调用时只需要把原来的rand生成位置改成先初始化位置再循环调用random_walk_update每个时刻重新计算距离矩阵和SINR。这里有个关键技术点快衰落不能每时刻重新生成独立随机数否则时间相关性会彻底丢失移动模型就变成了“位置漂移但信道快速随机闪变”的混合体物理上不自洽。我一般会给每个D2D链路预先存一个固定相位偏移量在每个时刻用相位旋转来模拟时间相关性比如h_t h_t_prev * exp(1i * delta_phase)其中delta_phase由移动速度和载频折算。改造完成后的验证方法很简单把velocity分别设成1m/s和5m/s运行两次仿真画出SINR CDF或者平均吞吐量随时间的变化曲线。如果两条曲线几乎完全一致说明移动模型并没有影响信道的时间相关性——这时候要回头检查是不是每个时刻都重新调用了fast_fading生成独立序列而不是做相位连续演化。如果你研究的是资源分配策略在移动场景下的切换开销还可以在分配函数里记录RB切换次数输出一个额外指标。最后我想说一个我自己的习惯拿到任何别人的仿真代码都先固定随机种子打印一遍关键矩阵的维度确认能复现再动算法。这个习惯挽救过我好几个下午的时间每次改动前也能有一个可信的baseline。希望帮到你。本文还有配套的精品资源点击获取