
简介面向通信与网络方向的研究者与工程师压缩包内含基于泊松点过程PPP随机几何的蜂窝网络蒙特卡洛仿真实现。资源围绕基站空间分布建模、干扰与覆盖分析等典型问题利用PPP模拟基站或用户设备的不规则分布并通过蒙特卡洛大量随机试验考察覆盖概率、吞吐量与干扰水平等关键指标可帮助分析不同基站密度下的网络性能。压缩包共33个文件以28个MATLAB脚本为主涵盖PPP分布生成、单小区与多小区依赖仿真、性能指标计算、结果绘图等功能另含3个可编辑的FIG图形文件和2张JPG预览图便于直观理解中间结果。整包仅135KB轻量易用已有347人学习下载。适合需要掌握PPP随机几何建模或开展蜂窝仿真实验的高年级本科生、研究生及科研人员尤其适用于蜂窝网络规划与优化场景。1. 蜂窝仿真里为什么绕不开 PPP 与蒙特卡洛做蜂窝网络仿真的工程师多半都有过这种经历明明把基站坐标摆得很整齐仿真出来的覆盖率曲线却跟实测对不上。原因不复杂——现实里的基站选址从来不是均匀网格受地形、物业、工程条件影响站址天然带有随机性。用 PPPPoisson Point Process泊松点过程给基站位置建模再用蒙特卡洛方法跑大量随机快照统计性能这套组合已经是蜂窝仿真、随机几何分析里最常见的落地路径。它解决的核心问题只有一个在站址不确定的前提下怎么估计覆盖率、信干噪比分布这类关键指标。适合刚接触无线网络仿真的研究生、算法工程师也适合需要快速验证网络方案的规划人员。2. 用 PPP 给基站位置建模密度参数与坐标生成2.1 为什么六边形网格不够用随机几何里的 PPP 又是什么传统蜂窝网仿真喜欢用六边形网格划分小区每个基站占据一个正六边形边界清晰、计算方便。但这个模型的硬伤在于真实站址不会按几何图形生长。城市里基站挂在楼顶、路灯杆、信号塔上位置分布更接近随机撒点。如果仿真里坚持用理想网格算出来的覆盖概率跟实际路测数据之间的误差通常能到两位数百分比。PPP 是随机几何里最基础的点过程模型在给定区域内点的数量服从泊松分布每个点的位置在区域内独立均匀分布。它的核心参数只有一个——密度 λ表示单位面积内的平均点数。对蜂窝仿真来说λ 就是单位面积内的基站数。这个模型的吸引力在于它既保留了随机性数学上又有闭合解可以参考方便后面验证蒙特卡洛结果对不对。但要注意PPP 假设点的位置完全独立真实场景里基站会为了避免同频干扰而保持一定站间距这时候 PPP 偏悲观更精细的模型有 Ginibre 点过程、泊松洞过程等。工程落地时我通常先用 PPP 跑通链路再根据实测数据决定要不要换模型。2.2 密度 λ 怎么定从站间距反推仿真第一步是定 λ。实际做网络规划时我们拿到的通常不是“每平方公里多少个基站”而是站间距Inter-Site DistanceISD。从 ISD 反推 λ 有一个经验换算公式[ \lambda \frac{2}{\sqrt{3} \cdot ISD^2} ]这个公式的假设是每个基站覆盖一个正六边形区域六边形面积取倒数得到密度。例如 ISD 500 米时λ ≈ 4.6 个/平方公里这里的“平方公里”对应公式里 ISD 以公里为单位。仿真区域如果取 4km × 4km那基站总数期望就是 4.6 × 16 ≈ 74 个。实际仿真时不一定非要这么精确。如果你研究的是宏基站密集部署场景λ 取 30~50 个/km² 都是常见的如果是微基站补盲λ 可能到 100 以上。关键是先确定你仿真区域代表的真实地理尺度再选 λ不要让单位错位了。2.3 生成 PPP 基站坐标一个可以直接跑的 Python 实现生成 PPP 点过程坐标的核心思路分两步先按泊松分布抽取点数再在区域内均匀撒点。代码如下import numpy as np def generate_ppp(lambda_val, area_size, seedNone): 在矩形区域内生成 PPP泊松点过程点集 lambda_val: 每平方公里的点数密度 area_size: (width, height)单位 km seed: 随机种子用于可复现实验 rng np.random.default_rng(seed) # 1. 泊松抽样点子数量 N ~ Poisson(lambda * area) n_points rng.poisson(lambda_val * area_size[0] * area_size[1]) # 2. 均匀撒点每个点的 x、y 在区域内均匀分布 xs rng.uniform(0, area_size[0], n_points) ys rng.uniform(0, area_size[1], n_points) return np.column_stack([xs, ys]) # 示例4km x 4km 区域密度 40 个/km²固定随机种子便于复现 bs_positions generate_ppp(lambda_val40, area_size(4, 4), seed20240501) print(f生成的基站数量: {len(bs_positions)})这段代码里有两个关键参数。第一个是rng.poisson(lambda_val * area_size[0] * area_size[1])这一步决定了本快照里基站总数注意它是随机的同一个 λ 不同种子生成的 N 可能不同如果希望固定基站数量、只随机位置就不要走泊松抽样直接np.random.choice固定 N 即可。第二个是rng.uniform撒点它要求所有位置等概率这是均匀 PPP 的定义属性。这里有一个我踩过的坑如果区域边长太短比如 1km × 1km 配 λ40期望点数只有 40一个快照里可能有 30 个也可能有 50 个这个波动会直接影响后面的覆盖率统计。解决方法是增大仿真区域或者增加快照数来摊平波动。3. 蒙特卡洛快照循环SINR 计算与覆盖概率统计3.1 快照法的核心思想PPP 只生成了基站位置要算覆盖概率还得给每个基站配用户、算链路损耗、算干扰。蒙特卡洛快照法的做法是把“生成基站 → 分配用户 → 计算 SINR → 判定是否覆盖”这整套流程重复 N 次每次都是一个独立的随机快照最后把 N 次结果平均得到统计意义上的性能指标。为什么要跑这么多遍因为单次快照里基站位置是随机的用户和基站的距离、干扰源的远近都在变。你可能碰到一次好运气用户刚好贴着基站SINR 很高也可能碰到一次差运气用户被两个强干扰源夹击。只有大量快照平均下来结果才接近真实分布。快照数量少了结果波动大数量够了结果收敛。这个收敛过程本身也是蒙特卡洛方法的基础性质。3.2 用户位置与服务基站分配用户位置一般也在区域内均匀撒点可以复用 PPP 生成函数但密度不用太大每平方公里几十个用户就够了因为覆盖概率只跟用户和基站的位置关系有关用户密度本身只是权重。服务基站分配最常见的规则是“最近基站”用户属于离它最近的基站这是蜂窝网络最基本的小区归属逻辑。需要注意的是PPP 基站位置完全不相关所以可能有两个基站靠得很近导致某个小区面积特别小。这是 PPP 模型的固有特性不是 bug。如果仿真结果里出现某块区域覆盖率异常低先确认是不是这种极端站距造成的。3.3 完整的快照循环代码把上面所有步骤串起来一个最小可用的蒙特卡洛快照循环如下import numpy as np def generate_user_positions(n_users, area_size, rng): 生成用户坐标均匀撒点 xs rng.uniform(0, area_size[0], n_users) ys rng.uniform(0, area_size[1], n_users) return np.column_stack([xs, ys]) def compute_sinr(bs_positions, user_positions, alpha3.5, noise_dbm-104, rngNone): 计算每个用户到最近基站的 SINR单位 dB alpha: 路径损耗指数 noise_dbm: 噪声功率dBm sinr_db_list [] for user in user_positions: # 1. 计算用户到所有基站的距离 dists np.linalg.norm(bs_positions - user, axis1) # 2. 最近基站作为服务基站其余都是干扰源 sorted_idx np.argsort(dists) serving_dist dists[sorted_idx[0]] # 3. 路径损耗模型L dist^(-alpha)转成 dB # 这里直接用线性域计算避免 dB 转换引入误差 serving_power serving_dist ** (-alpha) # 4. 所有非服务基站信号叠加为干扰 interference np.sum(dists[sorted_idx[1:]] ** (-alpha)) # 5. 加高斯噪声线性域计算 SINR noise_linear 10 ** (noise_dbm / 10) / 1000 # dBm - W 的近似换算 sinr_linear serving_power / (interference noise_linear) sinr_db 10 * np.log10(sinr_linear) sinr_db_list.append(sinr_db) return np.array(sinr_db_list) def monte_carlo_coverage(lambda_bs, area_size, n_users, n_snapshots, alpha3.5, threshold_db-5, seed42): 蒙特卡洛快照法统计覆盖率 threshold_db: 覆盖阈值SINR 大于该值判定为覆盖单位 dB rng np.random.default_rng(seed) coverage_flags [] sinr_all [] for snapshot in range(n_snapshots): # 重新生成基站位置这是蒙特卡洛快照法的关键 n_bs rng.poisson(lambda_bs * area_size[0] * area_size[1]) bs np.column_stack([ rng.uniform(0, area_size[0], n_bs), rng.uniform(0, area_size[1], n_bs) ]) users generate_user_positions(n_users, area_size, rng) sinr_db compute_sinr(bs, users, alpha, noise_dbm-104, rngrng) sinr_all.extend(sinr_db.tolist()) # 以 SINR 阈值判断覆盖 coverage_flags.extend((sinr_db threshold_db).tolist()) coverage_prob np.mean(coverage_flags) sinr_array np.array(sinr_all) return coverage_prob, sinr_array这段代码有几个容易踩的地方逐个说明第一10 ** (noise_dbm / 10) / 1000这句看着别扭其实是把 dBm 换算成瓦。dBm 的定义是相对于 1mW 的 dB 数所以10 ** (dBm / 10)得到的是 mW再除以 1000 才是 W。严格来说仿真经常用 mW 作单位直接算比值因为 SINR 是功率比分子分母单位一致就行噪声功率保不保留其实影响不大——干扰主导时噪声几乎可以忽略。第二干扰计算里dists[sorted_idx[1:]]把服务基站排除掉了这个顺序很重要。如果用sort()而不是argsort()索引就不对了。这两个函数的差别是血泪教训换来的sort返回排好序的数组本身argsort返回排序后的索引。第三快照之间的独立性。每次循环都必须重新生成基站和用户不能复用上一轮的坐标。如果偷懒在循环外生成一次基站就反复用那蒙特卡洛平均变成了空间平均的重复计算结果会严重偏离真实分布。3.4 参数怎么调路径损耗指数与噪声底路径损耗指数 α 在城区环境一般取 3.5~4视距场景可以取 2 左右。α 越大信号随距离衰减越快远处干扰源贡献越小SINR 会整体抬高。如果你仿真结果里的 SINR 整体偏低先检查 α 是不是设得太小比如 2而不是急着调基站密度。噪声底 -104 dBm 是 20MHz 带宽 LTE 系统常见的近似值。带宽不同噪声底不同计算公式是热噪声密度-174 dBm/Hz 10 × log10(带宽)。做 5G 宽带宽仿真时100MHz 带宽要取到 -94 dBm 左右直接用 -104 会高估 SINR。4. 覆盖率收敛判定与 λ 对结果的敏感性4.1 快照数不够时会发生什么蒙特卡洛结果要稳定快照数一般不能少于 2000。如果你运行上面的代码n_snapshots100和n_snapshots5000的结果可能有 5 个百分点以上的差异。这不是代码写错了是随机性还没被摊平。怎么判断收敛了没有一个笨办法是跑不同快照数看结果差多少更系统的方法是分块平均——把 10000 个快照分成十组每组算一个覆盖率看十组之间的波动范围。波动范围在 ±0.5 个百分点以内基本可以收手还差得远就继续加快照。4.2 密度 λ 怎么影响覆盖率PPP 密度对覆盖率的影响不是线性的这里有一个容易想当然的点基站越多覆盖率一定越高实际上在干扰受限场景里加基站的同时也加了干扰源。当服务基站变近带来的增益和干扰源变多带来的损失抵消时覆盖率会趋于一个平台期。以 λ 从 20 到 80 个/km² 为例仿真区域 4km×4kmα3.5阈值 -5dB噪声 -104dBm覆盖概率大概从 40% 出头缓慢爬到 60% 左右曲线明显变平。如果你做的方案评估里出现“加大密度但覆盖率不动”的现象这通常不是仿真 bug而是系统进入了干扰受限区。这时候要改的不是 λ而是干扰协调、波束赋形这些机制。密度 λ (个/km²)平均基站数(16km²)覆盖概率(示例)备注20320约 42%稀疏宏站覆盖率低40640约 55%中等密集城区801280约 60%干扰受限增益趋缓数值仅供参考不同传播参数下会有浮动。但趋势是稳定的密度翻倍覆盖率不会翻倍。4.3 仿真区域大小与边界效应还有一个被问过很多次的问题仿真区域设多大合适区域太小边界效应严重——边缘用户周围基站数量不足干扰损失被低估覆盖率虚高。区域太大每轮快照要算的距离矩阵太大仿真慢。常见做法是仿真区域至少 10 倍于基站平均覆盖半径。比如 λ40 时平均每个基站覆盖面积是 1/40 km²半径约 90 米那区域边长至少取几百米到 1 公里以上。另一个更稳妥的办法是“环带法”只统计区域中心部分用户的覆盖率边缘 20% 的用户不算进结果。5. 蜂窝仿真避坑5 个常见的翻车现场5.1 边界基站密度失衡现象仿真区域边缘的基站覆盖率比中心区域高出一截整体覆盖率偏高。原因矩形区域边缘外没有基站边缘用户到基站的平均距离其实更远但因为区域外没有干扰源边缘用户的 SINR 反而虚高。这是几何边界造成的系统性偏差。解决只统计中心区域覆盖结果。比如 4km×4km 的区域只统计距离边缘 0.5km 以内的用户不计入结果更规范的做法是引入周期性边界条件或环带采样。5.2 快照数太少导致结论不可复现现象同一个参数跑两次覆盖率差了 3 个百分点以上或者改一个无关参数比如用户随机数种子结果跳变。原因每个快照内基站数量本身就是泊松随机数方差大。快照数低于 500 时少数极端快照会对均值产生显著影响。解决先用 500、1000、2000、5000 四档快照数各跑一次看结果收敛趋势。2000 是个底线推荐 5000 以上。同时固定随机种子便于问题复现。5.3 路径损耗模型与距离的单位错乱现象SINR 结果全是负值或者高得离谱40dB。原因PPP 生成时区域边长以 km 为单位基站坐标是 km 量级但路径损耗公式dist ** (-alpha)假设距离单位是 m。当 dist0.3 时按 km 算的 0.3⁻³·⁵ ≈ 45按 m 算的 300⁻³·⁵ ≈ 5×10⁻⁹中间差了约 10 个数量级。解决统一单位。要么生成 PPP 时把区域边长换成 mλ 换成“每平方米点数”要么在路径损耗计算前把距离乘以 1000。我习惯第二种因为 λ 用 km² 更直观。5.4 干扰源列表里混进了服务基站现象SINR 普遍偏低尤其在用户紧贴基站时也不见高。原因代码里没有排除服务基站把最强信号也当干扰叠加了。这种情况最隐蔽因为结果看起来好像“有干扰所以合理”实际算的是服务信号被自己抵消的荒谬场景。解决用argsort拿到距离排序索引后务必从索引 1 开始切片。同时建议单步调试一个简化的两基站场景手算一遍确认逻辑。5.5 对数正态阴影衰落的正弦曲线陷阱现象加入阴影衰落shadowing后覆盖率曲线呈波浪形快照数量增加也不完全平滑。原因很多人用rng.lognormal(0, sigma)给每次链路的阴影衰落生成独立随机数但忘记每个快照都重新生成。快照之间如果复用同一衰落值等效于给场景加了一个固定偏移平均后仍然残留相关噪声。解决阴影衰落必须放在快照循环内部每个用户、每条链路独立抽样。另外为了模拟空间相关性需要按距离生成相关衰落矩阵这属于进阶内容初版仿真可以先用独立衰落。6. 让你的仿真结果经得起检查验证方法与加速技巧蒙特卡洛仿真最怕的不是结果错而是错了看不出来。我做完一套 PPP 仿真最先做的是降维验证把仿真区域缩成 1km×1kmλ 取 1这时干扰源数量很少可以手算几个典型位置的 SINR跟代码输出对照。其次是对照理论值——均匀 PPP 下信号与最近基站距离的分布有瑞利分布的近似关系覆盖概率可以粗略估算如果蒙特卡洛结果偏离理论预期超过 5 个百分点先别急着改参数回头检查代码逻辑。还有一个习惯把每次快照的基站坐标、SINR 值落盘画分布直方图。只看平均覆盖率会掩盖很多问题——比如双峰分布说明部分区域干扰严重单看均值根本发现不了。落盘之后还能做分位数分析报告 P10、P50、P90 的 SINR这比单一覆盖率更有参考价值。性能优化方面千万别一上来就优化代码。先跑小规模场景验证逻辑再逐步放大。放大后最耗时的部分是用户到基站的距离矩阵计算numpy的向量化已经做了大半剩下的瓶颈是 Python 循环本身。这时可以用numba给compute_sinr函数加jit装饰器通常能快 5~10 倍改动只有一行。更激进的做法是换用 Cython但收益边际递减一般不值得。最后说一个我自己的教训刚开始跑 PPP 仿真时为了图快把区域缩到 1km×1km、快照只跑 200 次结果覆盖率每周都变一个月后才发现是统计波动。后来学乖了仿真结果必须带“95% 置信区间”否则不汇报。做法是把 10000 个快照分成 20 批每批算一个覆盖率然后报告均值和标准差。这套做法帮我在评审会上少挨了很多骂。希望帮到你。本文还有配套的精品资源点击获取