
简介面向卫星通信与随机几何建模方向的研究人员和工程师这份资源围绕低轨星座下行链路仿真提供完整实现。文档基于二项点过程BPP构建星座模型覆盖参数设置、星座与地面站生成、路径损耗与接收功率计算、SINR推导以及单星/多星场景下干扰期望分析与可视化对应论文复现需求可支撑巨型星座性能评估和星地链路设计。压缩包共1个docx文件体积51KB含详细可运行Python代码、数学推导与图表说明便于边读边实践。目前已有105人学习适合具备一定编程基础并希望掌握低轨卫星网络仿真方法的科研人员与工程师。1. 低轨卫星通信的随机几何建模从BPP模型到可复现的链路仿真做低轨卫星通信的人都会遇到同样的问题星座规模动辄几百上千颗卫星链路预算和干扰分析如果拿传统星座仿真软件一星一星去建模脚本绕地球转一圈就得等大半天。这篇基于随机几何的BPP二项点过程低轨星座下行链路仿真项目核心思路是用一个固定数量的随机点在球面上均匀分布来模拟星座的空间状态。它最大的价值不是精度多高而是把星座建模、路径损耗、SINR计算和干扰期望推导做成了一套可以直接跑通的Python代码拿到手改参数就能出图。和传统PPP泊松点过程模型不同BPP严格限制卫星数量在有限球面上更贴合真实星座的物理约束。适合正在做星地链路预算、低轨星座干扰分析、以及想快速验证星座配置对通信性能影响的科研人员和系统工程师。2. BPP模型的核心原理与参数体系为什么它比PPP更适合低轨星座2.1 从PPP到BPP有限点过程的选型逻辑传统随机几何分析卫星网络时通常使用PPP即泊松点过程。PPP假设点在无限平面上以一定强度随机分布每个区域的点数是随机的。这个假设在分析地面蜂窝网络时非常好用因为基站的位置确实是近似随机分布的而且一个城市的基站数量可能在下个月就变了。但换到低轨星座场景这个假设就和物理事实有冲突了低轨星座的卫星数量是确定的而且部署前就已经定死比如一箭一星的星链某一阶段就是某几个轨道面、每个轨道面多少颗总数在工程上是一个常数。BPP则正好对应这个场景。BPP的定义是在一个有界区域内独立地均匀撒N个点点的总数固定为N。放到低轨星座里就是在一个半径等于地球半径加轨道高度的球面上均匀撒N_sat颗卫星。每颗卫星的三维坐标被视为独立同分布随机变量这个特性让后续干扰分析中的距离分布描述变得直接不需要像PPP那样引入复杂的强度密度只需要知道卫星总数和球面面积就能推出干扰卫星的分布特性。这段代码是BPP星座生成的核心也是整个项目的基础def generate_bpp_constellation(N, R): 生成BPP模型卫星星座 参数: N: 卫星数量 R: 轨道半径 返回: sat_positions: 卫星笛卡尔坐标(km) theta np.random.uniform(0, 2 * np.pi, N) # 经度 phi np.arccos(2 * np.random.uniform(0, 1, N) - 1) # 纬度 x R * np.sin(phi) * np.cos(theta) y R * np.sin(phi) * np.sin(theta) z R * np.cos(phi) return np.column_stack((x, y, z))这段代码的核心技术点在于经度和纬度的采样方式。经度theta直接在[0, 2π)上均匀采样这没有问题。真正需要留意的是纬度phi的生成它没有直接在[0, π]上均匀采样而是先对[0, 1]上的均匀随机数做了一次arccos映射。原因很简单球面上的面积元是R²sin(φ)dφdθ纬度方向的弧长权重是sin(φ)。如果直接在[0, π]上均匀生成φ卫星会在赤道附近稀疏、在两极附近密集这就是经典的球面均匀采样的坑。用arccos反变换之后每个单位立体角内恰好分布相同数量的点这才是真正意义上的球面均匀。地面站位置的生成方式完全一致只是半径参数换成R_earth。这里有个小细节——地面站不是分布在球心地面而是分布在地球表面即半径6371km的球面上几何上和卫星星座是同一个坐标系统一处理的这让后面的距离计算可以直接复用cdist不需要做坐标系转换。2.2 链路损耗模型与系统参数表这套仿真里的路径损耗模型是自由空间路径损耗叠加一个简单的大气损耗近似。自由空间部分用的是标准公式FSPL 20 * np.log10(d) 20 * np.log10(f) - 147.55公式里的d单位是米f单位是Hz常数147.55对应4π/c的换算系数其中c是光速。经常有人把d和f的单位搞混比如用km和GHz代入公式出来的损耗会差好几个数量级。为避免这个问题代码里主仿真函数已经对距离做了统一处理cdist算出来的距离以km为单位在调用path_loss之前先乘以1000转换成米。大气损耗用的是最简单的线性模型大气损耗0.2dB/km×距离。这个参数其实是一个经验常数实际工程上精确的大气损耗要查ITU-R建议书里的雨衰模型ITU-R P.618和大气气体衰减模型ITU-R P.676Ka频段尤其明显。代码里用线性模型是论文复现常见的简化处理做链路预算初估够用但如果要精确评估雨衰地区链路可用度需要替换成ITU-R模型或实测气象数据。项目的系统参数设置值得整理成一张表方便后续调整参数时对照参数名符号取值单位说明地球半径R_earth6371km地面站所在基准面轨道高度h_leo1200km典型LEO高度兼顾时延和覆盖轨道半径R_orbit7571kmR_earth h_leo卫星数量N_sat100颗BPP总点数地面站数量N_gateway10个全球均匀撒点载波频率fc20GHzKa频段边缘信道带宽B100MHz带宽对应噪声功率计算噪声温度T290K接收机等效噪声温度发射功率Pt10dBW卫星发射功率卫星天线增益Gt30dBi星载天线增益地面站增益Gr40dBi地面站抛物面天线这套参数对应一个典型的中轨道高度、Ka频段点波束场景。N_sat100是一个合理的分析规模既有足够多的干扰源让干扰分析有统计意义又不会让单次仿真的距离矩阵计算大到内存吃紧。2.3 接收功率、噪声功率与SINR的计算链路接收功率是发射功率、天线增益和路径损耗的简单加减。注意这里用的是分贝值相加所以received_power返回的单位是dBWdef received_power(Pt, Gt, Gr, PL): return Pt Gt Gr - PL噪声功率用的是热噪声公式PnkTB其中k是玻尔兹曼常数1.38e-23J/KT是等效噪声温度290KB是带宽100MHz。计算出来之后要转成dBW方便后续统一单位Pn k * T * B Pn_dBW 10 * np.log10(Pn)把10dBW的发射功率经30dBi天线发出经过1200km自由空间路径损耗约174dB地面站40dBi增益接收接收功率大约是103040-174-94dBW。噪声功率大约是-174dBm/Hz80dBHz-94dBm换算成dBW约是-124dBW。信噪比大约在30dB量级这是单颗卫星、无干扰情况下的典型结果。实际仿真里SINR会因为干扰的存在显著低于这个值。SINR计算是先算线性功率占比再转dB这段代码在数值处理上有一处值得注意干扰功率I_total是在线性域累加的如果卫星数量特别大或者卫星距离特别近线性累加可能非常大超过浮点安全范围后结果是Inf。稍后在第5章避坑部分会展开讲这个问题。3. 单星与多星场景仿真主流程实现与结果解读3.1 主仿真函数simulate_leo_downlink全流程拆解主仿真函数是按每个地面站逐站独立仿真的逻辑组织的。对每一个地面站先计算它到所有卫星的距离再按距离最近原则挑服务卫星其余99颗卫星全部视为干扰源def simulate_leo_downlink(): # 生成星座和地面站 sat_positions generate_bpp_constellation(N_sat, R_orbit) gateway_positions generate_gateway_positions(N_gateway, R_earth) # 通信参数 Pt 10 # 发射功率10dBW Gt 30 # 卫星天线增益30dBi Gr 40 # 地面站天线增益40dBi Pn noise_power(B, T) sinr_results [] distance_results [] for gw in gateway_positions: # 计算到所有卫星的距离cdist返回km distances distance.cdist([gw], sat_positions)[0] * 1000 # 转换为米 # 找到最近的卫星作为服务卫星 serving_idx np.argmin(distances) serving_dist distances[serving_idx] # 服务链路计算路径损耗和接收功率 PL_serving path_loss(serving_dist, fc) Pr_serving 10 ** (received_power(Pt, Gt, Gr, PL_serving) / 10) # 干扰链路遍历除了服务卫星以外的所有卫星 I_total 0 for i, dist in enumerate(distances): if i ! serving_idx: PL_interferer path_loss(dist, fc) Pr_interferer 10 ** (received_power(Pt, Gt, Gr, PL_interferer) / 10) I_total Pr_interferer Pn_linear 10 ** (Pn / 10) sinr calculate_sinr(Pr_serving, I_total, Pn_linear) sinr_results.append(sinr) distance_results.append(serving_dist / 1000) return sinr_results, distance_results这段代码有两个工程上的细节值得展开。第一个细节是distance.cdist([gw], sat_positions)[0]这里对gw包了一层方括号变成二维数组是因为cdist要求输入是二维矩阵[0]取出后得到的是该地面站到全部卫星的一维距离数组。第二个细节是距离单位转换的位置cdist基于笛卡尔坐标算出的是km距离乘以1000转成米之后才进入path_loss确保和FSPL公式里d的单位米对得上。这是一个反复容易出现单位混滑的位置不少复现翻车就翻在这里。服务卫星的选取用的是argmin也就是找最近距离。这个启发式规则在低轨场景下是合理的距离越近自由空间损耗越小接收功率越高。实际星座系统还会考虑仰角约束和波束指向但作为基础仿真版本的假设最近卫星即服务卫星的做法在分析中是标准开端。3.2 多星干扰累加的逻辑与复杂度干扰累加是这套代码的计算瓶颈。每个地面站都要对全部N_sat-1颗非服务卫星计算路径损耗并累加线性功率复杂度是O(N_gateway×N_sat)。当N_sat100、N_gateway10时一共只需要计算1000次路径损耗完全在毫秒级别。但如果扩展到大星座场景N_sat达到数千颗、地面站达到几十个这个双层循环就会明显变慢。我通常会做两件事来优化。第一把干扰计算向量化# 向量化干扰累加 def compute_interference_vectorized(distances, serving_idx, fc): # 排除服务卫星 mask np.ones_like(distances, dtypebool) mask[serving_idx] False interferer_dists distances[mask] pls path_loss(interferer_dists, fc) powers 10 ** ((Pt Gt Gr - pls) / 10) return np.sum(powers)用numpy的布尔掩码一次性选出所有干扰距离向量化计算路径损耗和功率最后用np.sum完成累加。这段代码和双层循环的数值结果完全一致但执行效率有一个数量级的提升。第二如果星座上万颗卫星连向量化计算都会吃紧常见的做法是按距离截断只把距离小于某个阈值比如3000km的卫星算作干扰源更远的卫星对SINR的贡献基本可以忽略。3.3 结果图怎么读SINR与服务距离的散点关系仿真结果包含两幅图SINR对服务链路距离散点图和SINR直方图。散点图会显示一个明显的下行趋势——服务距离越远SINR越低这个趋势本身不意外。真正需要关注的是散点的离散程度。离散程度大说明同一距离附近SINR的波动范围较大这种波动来源于干扰项的随机性。即使两颗服务卫星离地面站的距离完全相同周边干扰卫星的分布差异可能导致干扰总功率相差好几个量级进而让SINR相差10dB以上。这个现象在BPP模型中体现得非常自然因为它本质上是把每颗卫星的位置都视作随机变量。解读这个结果时有一个判断角度如果直方图呈现近似双峰形状大概率是因为部分地面站恰好落在低轨星座较稀疏的区域在BPP的随机部署下可能附近只有12颗卫星干扰极低SINR偏高而另一些地面站附近卫星密集干扰大SINR被压得较低。这种空间非均匀性是随机几何建模相对于规则星座Walker星座构型的重要差异——后者每个区域都近似均匀覆盖前者则天然带有随机部署的运气成分。4. 干扰期望的理论计算与BPP验证把仿真和论文对上4.1 干扰期望公式的实现与简化假设论文中提出的干扰期望分析方法本质上是用一个平均距离近似来估计所有干扰卫星的贡献总和避免对每颗卫星逐颗计算。代码里expected_interference函数把干扰卫星的平均距离直接近似为轨道高度h_leo这样就能用一个统一的平均路径损耗估算单颗干扰卫星的平均接收功率再乘上干扰卫星总数(N-1)得到总干扰期望。def expected_interference(N, R_orbit, R_earth, h_leo, Pt, Gt, Gr, fc): theta_max np.arcsin(R_earth / R_orbit) avg_interferer_dist h_leo * 1000 # 平均距离近似为轨道高度 avg_PL path_loss(avg_interferer_dist, fc) avg_Pr_interferer received_power(Pt, Gt, Gr, avg_PL) E_I_linear (N - 1) * 10 ** (avg_Pr_interferer / 10) E_I 10 * np.log10(E_I_linear) return E_I这个函数里有两个值得一提的近似。theta_max是用地球半径和轨道半径算出的地心半张角它本意是描述卫星可见范围。但在干扰期望的计算里它只做了定义并没有真正被用来约束干扰卫星的分布。更大的近似在于把所有干扰卫星的距离一刀切地平为轨道高度这在低轨场景下有一定合理性对于从卫星视角看地球边缘方向的干扰卫星距离大约是轨道高度加上地球切线相关的量整体距离波动确实围绕轨道高度量级。但对于地面站正上方的干扰卫星真实距离可能远小于轨道高度路径损耗差异可以到10dB以上。这决定了理论值和仿真值之间会存在系统性偏差通常理论值会偏低干扰被低估。我在实际验证时通常不只算一个理论值还会给出一个区间用轨道高度算一个平均距离的下界情景用轨道高度加上地球半径量级的距离算一个上界情景这样仿真结果落在这个区间内理论模型就被认为是可用的。4.2 极角分布CDF验证蒙特卡洛与理论的对比BPP模型有一个可以严格推导的理论结果在球面上均匀撒N个点时一个固定参考点地面站位置到任意一个点的极角φ地心夹角的累积分布函数。当N1时CDF是(1-cosφ)/2。这个公式的推导基础是球面面积比例球面上极角小于φ的球冠面积占总面积的(1-cosφ)/2。验证代码的思路是多次重复生成随机星座经验统计极角的分布再和理论CDF曲线画在一起def verify_bpp_theory(N_sim1000): empirical_phi [] for _ in range(N_sim): sat_pos generate_bpp_constellation(1, R_orbit) gw_pos generate_gateway_positions(1, R_earth) dot_product np.dot(sat_pos[0], gw_pos[0]) / (R_orbit * R_earth) phi np.arccos(np.clip(dot_product, -1, 1)) empirical_phi.append(phi) x np.linspace(0, np.pi, 100) theory_cdf (1 - np.cos(x)) / 2 plt.figure() plt.hist(empirical_phi, bins30, densityTrue, cumulativeTrue, histtypestep, labelEmpirical) plt.plot(x, theory_cdf, r--, labelTheory) plt.xlabel(Polar Angle (rad)) plt.ylabel(CDF) plt.legend() plt.title(BPP Polar Angle Distribution Verification)这段代码里用np.dot(sat_pos[0], gw_pos[0])除以(R_orbit*R_earth)本质上是计算两个单位向量的余弦值再用arccos还原成极角。注意这里一定要加np.clip(..., -1, 1)的保护因为浮点误差可能导致余弦值略微超出[-1,1]的范围arccos对超界输入会返回NaN。0到π的极角对应的是卫星相对地面站的地心张角π表示卫星在地球的另一侧此时卫星对该地面站不可见。跑完蒙特卡洛后你会看到经验CDF曲线和理论曲线基本重合。这个验证结果是整个项目的理论基石——它证明了代码里的BPP生成方式确实在球面上实现了均匀随机分布而非因为采样方式错误导致集中效应。4.3 仿真SINR与理论干扰期望的对比逻辑有了理论干扰期望E_I对比的逻辑是这样的仿真的平均干扰功率把每个地面站仿真得到的I_total取平均应该和E_I在同一数量级。如果两者差距在3dB以内说明BPP的干扰建模和理论分析是自洽的如果差距很大优先检查单位转换和大气损耗参数有没有出问题。需要注意仿真的I_total是逐站累加的线性功率平均值而E_I是用(N-1)乘单星平均干扰功率。两者的数学结构一致唯一的偏差来自平均干扰距离的近似误差。我一般会在验证时把仿真干扰扩展到多组随机星座种子计算干扰均值方差再看理论值是否落在均值±一个标准差的范围内。5. 避坑与常见问题五个反复翻车的细节5.1 球面均匀采样直接生成phi导致两极密集现象星座三维图显示卫星明显聚集在地轴两端赤道附近稀疏。原因直接在[0,π]上均匀采样纬度忽略了球面面积元中sin(φ)权重。纬度越接近两极同样的Δφ对应的球面面积越小导致点密度变高。解决必须使用arccos反变换采样即phi np.arccos(2*np.random.uniform(0, 1, N)-1)。这个映射实质上是对球面面积做了归一化确保每个立体角内的点数期望相同。验证方法是做极角CDF对比测试理论曲线和经验曲线重合即采样正确。5.2 距离单位混滑km与m不分导致损耗整体偏移现象SINR结果普遍异常偏低服务距离在1000km附近时SINR已经低于0dB和常识不符。原因cdist基于笛卡尔坐标km计算距离但FSPL公式要求d以米为单位。如果直接把km值代入20log10(d)一项就比正确值小60dB相当于少算了60dB的路径损耗接收功率偏高结果看起来是SINR偏低其实是干扰也同步被高估整体呈现奇怪的数值。解决在主流程里强制做一次单位转换distances distance.cdist([gw], sat_positions)[0] * 1000把km转成m。更稳妥的做法是在path_loss函数内部注释里写明参数单位和返回单位并在调试时打印几个中间值做常识校验1200km在20GHz下的自由空间损耗应该在170dB左右如果算出110dB左右就一定有问题。5.3 线性累加干扰功率溢出为Inf现象卫星数量加到几千颗时I_total变成infSINR变成-log(inf)即-Inf散点图出现一条异常的水平线。原因每颗干扰卫星的线性功率约在10^(-14)W量级几千颗累加也在10^(-11)W量级本身不会溢出。真正的溢出风险来自接收功率计算时的中间量received_power先算dB值再取10^(dB/10)如果发射功率、增益和距离的组合让dB值偏正线性转换后的数值可能超过float64上限。解决在干扰累加前先排序过滤只累加距离小于阈值的卫星或者改用对数域累加用np.logaddexp逐个叠加干扰功率的dB值最后一次性转线性。对数域累加在数值上是稳定的代价是稍微增加一点计算量。5.4 不加随机种子导致结果不可复现现象同一个人跑两次代码SINR均值完全对不上不同人拿到代码跑出来的结果无法做交叉验证。原因np.random.uniform在每次运行时都会基于系统时间生成不同的随机序列星座和地面站位置每次都不一样。对科研复现来说这是硬伤——审稿人或同事需要能精确复现你的数据。解决在仿真入口加一行np.random.seed(42)或者在函数内部用np.random.default_rng(seed)创建独立随机数生成器。做批次实验时建议把种子放入参数列表按0-99编号跑100组取统计平均这样既保证可复现又能做蒙特卡洛置信区间分析。5.5 大气损耗线性模型在低频段误差放大现象把fc改成2GHzL频段后仿真结果和实测链路预算差距明显。原因0.2dB/km的大气损耗近似主要针对Ka频段。在L频段大气损耗远小于这个值在EHF频段30GHz以上这个值又偏小且没有考虑雨衰。同时线性模型假设损耗与距离成正比但实际上大气的吸收主要集中在低层大气卫星在仰角高时穿过的路径短、损耗小仰角低时路径长且经过低层厚度大损耗急剧上升。解决保留原模型作为基准同时实现一个按仰角分段的大气损耗查找表仰角大于10°时等效大气损耗约在0.30.5dB仰角小于10°时增加到25dB。这样既简单又能接近ITU-R模型的趋势。精确计算留给系统级工具仿真阶段重点是趋势和量级正确。6. 进阶扩展三维可视化、动态链路分析与蒙特卡洛验证拿到这个项目代码之后除了按部就班跑通还有三个扩展方向能让代码真正变成一个可用的分析工具。每个方向核心代码不超过二三十行但能把静态的随机几何分析扩展到工程上更关心的动态场景。第一个方向是三维星座可视化。原始代码只输出SINR和距离的统计图看不到星座和地面站的相对几何关系。把卫星画成红色点、地面站画成绿色三角、地球画成半透明球体就能直观看出BPP部署的随机性。代码里用球面参数方程生成地球表面网格用plot_surface绘制半透明球体这个做法的关键是alpha参数控制透明度避免地球遮住卫星轨迹。第二个方向是动态链路分析。真实低轨卫星是运动的单次仿真的静态结果只代表某一瞬间的星座状态。动态分析常见做法是让星座整体绕极轴以轨道角速度旋转在每个时间步上重新计算每个地面站到最近卫星的距离、仰角和SINR然后画出随时间变化的曲线。这个近似忽略了轨道倾角和升交点分布但作为初步的动态性能评估足够def rotate_points(points, delta_theta): 绕z轴旋转所有点 x, y, z points[:, 0], points[:, 1], points[:, 2] x_new x * np.cos(delta_theta) - y * np.sin(delta_theta) y_new x * np.sin(delta_theta) y * np.cos(delta_theta) return np.column_stack((x_new, y_new, z))第三个方向是高密度蒙特卡洛验证。BPP的精髓在于随机性单次布点的结果偶然性太强。我通常把外层仿真循环改成这样固定所有物理参数不变只改变随机种子跑200500次输出SINR的经验累积分布函数CDF曲线。我自己的习惯是拿到这类项目后先跑一遍原始脚本确认路径通然后立刻做三件事固定随机种子、用向量化替换掉双层循环、把干扰距离截断阈值加进去。从那以后我每次跑低轨星座仿真都强制走一遍这三步——先确认单次结果的数值合理性再做小规模种子测试确认稳定性最后才放心拿去和论文结论对比。希望这个流程也能帮你在复现类似项目时少走弯路。本文还有配套的精品资源点击获取