一、实验概述#
1.1 实验目的#
本实验旨在巩固本学期所学的信息网络建模与仿真相关知识,具体包括:
- 离散事件系统仿真方法与蒙特卡洛仿真方法的基本原理与实现
- 随机变量与随机过程的生成方法
- 业务源模型的建模与仿真
- 信道模型的建模与仿真
- 拓扑与运动模型的建模与仿真
- 竞争式 MAC 协议的工作原理与性能对比
通过在已有仿真代码基础上修改信源模型、运动模型和信道模型三个模块,对比 baseline(基线组)与 enhanced(增强组)两组实验结果,培养信息网络仿真方面的动手实践能力。
1.2 实验内容简述#
本实验以无线网络中三种竞争式 MAC 协议的性能仿真为核心任务。在原始仿真代码的基础上,从三个维度对仿真场景进行增强,使其更贴近真实无线环境:
- 信源模型改进:将单一泊松业务源替换为混合突发业务模型,模拟部分终端短时连续发包的场景
- 运动模型引入:为终端添加随机路点移动模型,替代静态终端假设
- 信道模型增强:在路径损耗和阴影衰落的基础上叠加 Rayleigh 快衰落,并采用 SINR 捕获判决替代传统冲突模型
通过 baseline 与 enhanced 两组实验的对比,量化分析三个增强因素共同引入后对协议吞吐量、冲突次数和平均时延的综合影响。
二、实验原理#
2.1 离散事件系统仿真#
离散事件系统仿真是一种事件驱动的时间推进机制。与固定步长仿真按等间隔 检查事件不同,DES 的仿真时钟直接跳到下一个事件发生的时刻,事件之间没有中间时间步。当没有任何事件发生时,系统状态保持不变,直到下一个事件触发状态更新。
DES 的优势在于:固定步长需要选择合适的时间粒度——太粗会遗漏事件,太细会浪费计算资源;而 DES 直接跳到事件时刻,既不遗漏也不浪费。本仿真中,数据包到达、发送完成、冲突发生等均为离散事件,每次事件触发一次状态更新,事件之间的时间间隔不等。
代码中体现为主循环的时间推进:
while True:
next_time = protocol_func(now_time) # 推进到下一个事件
...
now_time = next_time # 时间跳转,无中间步长python2.2 蒙特卡洛仿真方法#
蒙特卡洛仿真方法是一种基于随机抽样和统计平均的数值计算方法。其理论基础是大数定律:设 为独立同分布的随机变量,期望为 ,则当 时,
即样本均值以概率收敛于真实期望。根据中心极限定理,样本均值的标准误差为 ,重复次数扩大 100 倍,精度提高 10 倍。
本实验采用参数扫描的方式应用蒙特卡洛方法:对归一化业务负载 进行分段扫描,0.1 至 1.0 以 0.1 为步长,1.2 至 2.0 以 0.2 为步长,共 15 个负载点。每个 值独立运行一次完整的离散事件仿真,每次仿真累计处理 个成功数据包后计算吞吐量 ,最终以 为横轴、 为纵轴绘制吞吐量-负载曲线。当前实现对每个 值运行一次较长仿真,通过累计大量成功分组降低随机波动。本实验系统涉及多个随机过程(泊松到达、随机退避、阴影衰落、Rayleigh 衰落),难以推导封闭形式的解析解,因此采用蒙特卡洛仿真方法。
2.3 随机变量生成:逆变换法#
逆变换法是一种从均匀分布随机变量生成其他分布随机变量的通用方法。其核心思想是利用目标分布累积分布函数(CDF)的反函数,将均匀随机数映射为目标分布的随机数。
设目标随机变量 的 CDF 为 ,若已有 ,则:
服从目标分布。对于指数分布,CDF 为 ,令 ,解出:
在本实验中,数据包的到达间隔服从指数分布,均值为 (即 )。代码中通过逆变换法生成到达间隔:
self.mgtime = -self.Tint * np.log(1 - np.random.rand(self.Mnum))python其中 np.random.rand() 生成 , 避免 的数值问题, 即为服从均值为 的指数分布随机变量。
2.4 业务源模型#
业务源模型是仿真信号处理链路的第一个环节,位于 MAC 层之前。它的功能是为每个终端生成数据包,并决定每个数据包的到达时间——即描述终端”何时发包”和”发多频繁”。在仿真中,信源模块不关心数据包的内容,只负责按特定的随机过程为每个终端安排到达时刻。
泊松过程#
泊松过程是描述随机事件发生的经典数学模型。设 表示时间区间 内事件发生的次数,若满足独立增量性、平稳增量性和普通性,则称 为强度为 的泊松过程。
泊松过程的一个重要性质是:相邻事件的到达间隔服从指数分布。设第 次事件与第 次事件之间的时间间隔为 ,则:
指数分布具有无记忆性:,即无论已经等待了多长时间,下一个事件在未来的到达概率与刚一开始等待时相同。
本实验中,参数关系为:
其中 为一个数据包的传输时间, 为归一化业务负载, 为终端数量。当 增大时, 减小,终端发包更频繁,信道负载更高。
突发业务模型#
纯泊松过程中,所有终端的发包强度相同,无法反映真实网络中不同终端业务特征的差异。突发业务模型将终端分为两类:
- 普通终端:保持原始泊松过程,到达间隔为
- 突发终端:到达间隔缩短为 ,其中
通过设定突发终端比例 (即 20%),系统平均到达率为:
即混合模式下系统整体到达率是纯泊松模式的 1.6 倍。突发终端的密集发包会提高局部竞争强度,通常会增加冲突风险;其对最终吞吐量的影响需要结合信道捕获、终端移动和协议机制共同分析。
2.5 信道模型#
信道模型是仿真信号处理链路的第四环节,位于发射机之后、接收机之前。它的功能是模拟信号从发射端到接收端的传播过程,计算每个终端的接收功率。如表一所示,本实验涉及三层信道衰落,从外到内依次为:
表一 信道衰落层次及作用
| 层次 | 模型 | 性质 | 作用 |
|---|---|---|---|
| 第一层 | 对数距离路径损耗 | 确定性 | 决定接收功率基本量级 |
| 第二层 | 阴影衰落 | 随机(慢变) | 模拟障碍物遮挡造成的功率波动 |
| 第三层 | Rayleigh 衰落 | 随机(快变) | 模拟多径传播造成的快速信号波动 |
对数距离路径损耗模型#
电磁波传播时信号功率随距离增大而衰减,对数距离路径损耗模型描述了这一基本规律:
其中 dB 为 1 米参考距离处的路径损耗, 为路径损耗指数(对应城市宏蜂窝场景), 为发射端到接收端的距离(米)。
接收功率的计算公式为:
其中 为发射功率,、 为天线增益, 为阴影衰落。当终端移动时,距离 随之变化,接收功率也随之动态更新。
阴影衰落#
对数距离路径损耗模型是确定性的,相同距离的终端接收功率完全相同。但现实中,传播路径上的建筑物、树木等障碍物会对信号产生遮挡,造成额外的功率损耗。阴影衰落(大尺度衰落)描述了这种由于障碍物遮挡导致的功率波动,其特点是变化缓慢,终端移动几十米甚至上百米才会发生明显变化。
阴影衰落在路径损耗基础上叠加一个随机衰落项:
其中 dB 为阴影衰落标准差。 的物理含义是障碍物遮挡造成的额外功率损耗(dB),正值表示信号被衰减,负值表示信号相对增强。阴影衰落使得相同距离的终端有不同的接收功率,离基站近的终端不一定信号最强。
噪声功率计算#
接收机在接收信号时不可避免地受到热噪声的影响。热噪声功率计算公式为:
其中 dBm/Hz 为热噪声功率谱密度(对应常温 290K), Hz 为信号带宽, dB 为接收机噪声系数。代入数值: dBm。
噪声功率在初始化时一次性计算,仿真过程中保持不变,作为 SINR 计算的分母组成部分。
Rayleigh 衰落#
路径损耗和阴影衰落描述的是信号在较长距离上的变化规律,但信号还会在很短的距离内发生快速波动。这是由于信号经过建筑物、地面等物体的多次反射、散射、衍射后,以多条路径叠加的形式到达接收端。这些多径信号的相位不同,叠加后可能增强也可能抵消,造成接收信号的快速波动。
当传播环境中没有直射路径(NLOS)时,接收幅度服从 Rayleigh 分布。Rayleigh 衰落的信道增益建模为复高斯随机变量:
其中 ,。功率增益 服从指数分布,均值为 1,即 Rayleigh 衰落的平均增益为 0 dB,不会改变接收功率的长期均值,只引入短期波动。
叠加 Rayleigh 衰落后,接收功率变为:
Rayleigh 衰落是时变的,每次计算接收功率时重新采样,模拟信道的快速波动。两组实验的信道配置差异如表二所示。
表二 baseline 与 enhanced 信道配置差异
| 配置 | baseline | enhanced |
|---|---|---|
| 信道模型 | 路径损耗 + 阴影衰落 | 路径损耗 + 阴影衰落 + Rayleigh 快衰落 |
| 阴影衰落标准差 | 1.0 dB | 6.0 dB |
2.6 SINR 与捕获效应#
为什么用 SINR 而不是 SNR#
SNR(信噪比)只考虑信号功率与噪声功率之比,适用于单用户点对点通信场景。本仿真的场景是多个终端竞争同一信道,当多个终端同时发送时,其他终端的信号对目标终端而言就是干扰。此时必须使用 SINR(信号干扰噪声比),同时考虑噪声和干扰:
SNR 与 SINR 的主要差异如表三所示。
表三 SNR 与 SINR 的适用场景对比
| 指标 | 分子 | 分母 | 适用场景 |
|---|---|---|---|
| SNR | 信号功率 | 噪声功率 | 单用户点对点通信 |
| SINR | 信号功率 | 噪声 + 干扰功率 | 多用户竞争信道(本仿真) |
如果在本仿真中使用 SNR 而忽略干扰项,等于假设同时发送的终端之间互不影响,这与现实不符,也无法体现冲突和捕获效应。
捕获效应#
当多个终端同时发送数据包时,传统观点认为所有信号互相干扰,接收端无法解调任何一个信号。但实际情况并非如此:如果多个同时发送的信号中,最强信号的 SINR 超过解调门限,接收端仍然可以从干扰中解调出这个最强信号,这就是捕获效应。
捕获判决的数学表达为:
其中 为最强信号的接收功率, 为热噪声功率, 为其他同时发送终端的干扰功率之和, dB 为解调门限。只要最强信号的 SINR 达到门限,该信号就能被成功解调,其余信号仍然冲突丢失。
捕获效应提升了系统的整体吞吐量:在传统冲突模型下完全丢失的数据包,在捕获效应下可能有一部分被成功接收。但这也引入了不公平性——离基站近、信号强的终端更容易被捕获成功。
2.7 拓扑与运动模型#
拓扑与运动模型不属于信号处理主链路,而是信道模块的前置依赖,为信道模块提供终端位置输入。它的功能是在覆盖区域内生成终端的初始位置,并在仿真过程中根据运动模型动态更新终端位置。终端位置决定了每个终端到基站的距离,进而影响路径损耗和接收功率。
终端位置生成#
在半径 m 的圆形覆盖区域内生成 个终端,AP 位于 。初始终端位置沿用原始参考代码的离散随机撒点方式:随机产生整数坐标的 、,通过圆域约束 和重复位置检查后写入坐标数组,避免多个终端完全重合。
随机路点模型中的目标点生成则使用面积均匀采样方法,对半径进行平方根变换以避免目标点集中在圆心附近:
角度 在 上均匀采样,转换为直角坐标:
终端与基站的三维距离为 ,由于 AP 高度为 5m,即使终端位于圆心,最小距离也为 5m。
随机路点模型#
静态终端模型假设终端位置永远不变,这在真实场景中几乎不存在。Random Waypoint 模型引入了”有目的地的移动”和”到达后暂停”两个特征,比简单的随机游走更贴近真实行为。
每个终端重复以下循环:
- 选择目标点:在半径 的圆形覆盖区域内随机选择目标点 ,采用均匀分布生成
- 选择移动速度:在 范围内随机选取速度 ,本实验 ,
- 向目标移动:计算当前朝目标方向的单位向量,位置按速度推进:
当剩余距离小于步长时,直接将终端定位到目标点。
- 到达后暂停:暂停时间 ,,本实验 秒。暂停结束后选择新目标点和速度,重复循环。
Random Waypoint 模型为信道模块提供随时间变化的终端位置,使得距离 和接收功率 随仿真推进动态更新。终端移动带来的信道条件变化直接影响 SINR 计算和捕获效应的判决结果。
2.8 MAC 协议#
MAC 协议是仿真信号处理链路的第二环节,位于信源之后、发射机之前。它的功能是决定终端何时可以接入信道,当终端有数据待发送时,由 MAC 协议控制发送时机、处理冲突、安排重传。不同的 MAC 协议在信道利用率和冲突概率之间有不同的权衡。
Pure ALOHA#
Pure ALOHA 由 Abramson 于 1970 年提出,最初用于解决卫星通信中的随机接入问题。其核心规则只有一条:终端有数据就立即发送,不做任何信道检测。
工作流程:终端按泊松过程产生数据包,到达后立即占用信道开始发送,发送持续时间为 。当多个终端同时占用信道时,通过 SINR 捕获判决判断冲突。冲突终端按指数退避策略等待一段随机时间后重新发送:。
Pure ALOHA 的关键缺陷在于冲突窗口:一个数据包在时刻 开始发送,其冲突窗口为 ,总长度为 。任何其他终端在这个时间窗口内开始发送都会发生冲突。
理论吞吐量公式为:
当 时吞吐量达到最大值 ,即信道最多只有约 18.4% 的时间被有效利用。
Slotted ALOHA#
Slotted ALOHA 由 Roberts 于 1972 年提出,通过引入时隙结构将冲突窗口缩小一半,使最大吞吐量翻倍。其核心改进是将时间划分为等长的时隙,终端只能在时隙边界开始发送。
时隙长度 。终端产生数据包后不立即发送,而是等待到下一个时隙边界才开始发送:
由于所有发送都在时隙边界开始,冲突只可能发生在同一时隙内,冲突窗口从 缩小为 。
理论吞吐量公式为:
当 时吞吐量达到最大值 ,即信道最多约 36.8% 的时间被有效利用。Pure ALOHA 与 Slotted ALOHA 的关键差异如表四所示。
表四 Pure ALOHA 与 Slotted ALOHA 特征对比
| 特征 | Pure ALOHA | Slotted ALOHA |
|---|---|---|
| 发送时机 | 有数据立即发送 | 仅在时隙边界发送 |
| 冲突窗口 | ||
| 理论最大吞吐量 | ||
| 最优工作点 | 0.5 | 1. |
np-CSMA#
ALOHA 协议的核心问题是终端在发送前完全不感知信道状态,导致冲突概率很高。CSMA(载波侦听多路访问)协议的核心改进是:发送前先侦听信道,只有信道空闲时才发送,信道忙则等待。
CSMA 有多种变体,按”信道忙时如何处理”分为:1-坚持 CSMA(持续侦听,信道一空闲就立即发送)、p-坚持 CSMA(以概率 在信道空闲时发送)、非坚持 CSMA(信道忙时随机退避一段时间再重新侦听)。非坚持 CSMA 的优势在于避免了多个终端同时等待信道空闲后立即发送导致的群冲突。
np-CSMA 的工作流程:终端按泊松过程产生数据包,到达后先调用载波侦听函数检查信道是否空闲。侦听机制通过检查当前时刻是否落在某次传输窗口内来判断信道状态,并引入传播延迟 模拟侦听延迟——刚开始的发送不会立即被侦听到。若信道空闲,立即发送;若信道忙,按指数退避等待一段随机时间后重新侦听。若多个终端同时侦听到信道空闲并同时发送,仍会发生冲突,冲突终端同样按指数退避后重新进入侦听流程。三种协议的机制差异如表五所示。
表五 三种 MAC 协议机制对比
| 特征 | Pure ALOHA | Slotted ALOHA | np-CSMA |
|---|---|---|---|
| 发送前侦听 | 无 | 无 | 有 |
| 发送时机 | 有数据立即发送 | 时隙边界发送 | 侦听空闲后发送 |
| 信道忙时处理 | 不关心 | 不关心 | 随机退避后再侦听 |
| 冲突概率 | 最高 | 中等 | 最低 |
2.9 性能指标#
性能指标是仿真信号处理链路的最后一个环节,位于接收机之后。它的功能是统计仿真过程中的各项数据,计算吞吐量、业务负载等系统级指标,用于评估不同 MAC 协议在不同条件下的性能表现。
吞吐量 #
吞吐量 衡量的是信道被有效利用的比例,即单位时间内有多少比例的时间被用于成功传输数据。它是评估 MAC 协议性能最核心的指标,直接反映了协议的效率。
是无量纲的归一化指标,取值范围为 。 需要与业务负载 配合使用:低负载时 (几乎无冲突),中等负载时 达到峰值(协议的最优工作点),高负载时 下降(冲突增多导致信道利用率降低)。
业务负载 #
业务负载 衡量的是终端向信道”请求”发送数据的强度,即单位时间内所有终端试图发送的总数据量与信道容量的比值。 是系统的输入, 是系统的输出,两者的关系反映了协议的效率。
同样是无量纲的归一化指标。 表示信道未饱和, 为信道饱和点, 表示信道过载,冲突不可避免。本实验通过扫描 从 0.1 到 2.0,逐步增大业务负载,观察协议在不同负载下的吞吐量表现。
吞吐量-负载曲线#
以 为横轴、 为纵轴绘制的曲线反映了协议在不同负载下的性能。低负载时 (几乎无冲突),高负载时 下降(冲突增多),峰值点为协议的最优工作点。
理论曲线( 和 )假设当多个终端同时发送时所有数据包全部丢失。但本仿真实现了 SINR 捕获判决,当最强信号的 SINR 超过解调门限时,该数据包仍可被成功解调。这意味着理论上的”冲突”在仿真中有一部分变成了”成功传输”,因此仿真得到的吞吐量 高于理论值。这种差异在高负载时尤为明显: 越大,同时发送的终端越多,冲突越频繁,但捕获效应”挽救”了一部分数据包,使得仿真曲线在理论曲线上方。
三、原始代码分析#
3.1 系统架构#
本章中的“原始代码分析”以课程给定参考代码的基本仿真框架为参照,同时结合当前 mac_protocols.py 中保留下来的 baseline 逻辑进行说明。也就是说,第三章主要解释参考程序原本具备的系统结构和协议仿真流程;第四章再说明本实验在该框架上新增的混合业务源、随机路点运动模型和 Rayleigh 快衰落信道。
本实验的仿真程序以 mac_protocols.py 为核心文件,整体采用离散事件仿真思想实现无线随机接入网络中三种竞争式 MAC 协议的性能比较。程序的主体是 WirelessNetworkSimulator 类,一个仿真实例对应一组实验配置,例如 baseline 或 enhanced。实例内部维护终端位置、业务到达时间、MAC 状态、信道参数、运动模型状态和统计量;外部调用 simulate_all_protocols() 后,程序会依次运行 Pure ALOHA、Slotted ALOHA 和 np-CSMA 三种协议,并输出吞吐量、冲突次数、平均等待时间和捕获成功次数等结果。
从信号处理流程看,程序可以分为六个主要环节:信源、MAC 层、发射机、信道、接收机和统计分析。信源模块负责生成终端的数据包到达时间;MAC 层根据协议规则决定立即发送、时隙发送、载波侦听或退避;发射机模块给出发射功率和天线增益;信道模块根据终端位置计算路径损耗、阴影衰落和快衰落后的接收功率;接收机模块根据 SINR 门限判断是否发生捕获成功;统计模块记录成功分组数、总发送分组数、冲突次数和等待时间,并形成吞吐量-业务负载曲线,如图一所示。

程序的主要状态变量包括 Mstate、mtime、mgtime、Mstime 和 mxy。其中 Mstate 记录终端状态,取值包括 STANDBY、TRANSMIT 和 COLLISION;mgtime 记录业务包产生时刻;mtime 记录终端下一次事件时刻;Mstime 记录发送开始时刻,用于 np-CSMA 的载波侦听;mxy 保存终端的三维坐标。仿真主循环位于 _run_single_simulation(),该函数每次推进到下一个离散事件,而不是按固定时间步扫描,因此仿真效率较高,也符合离散事件系统仿真的建模思想。
3.2 各模块功能说明#
信源模块对应 _init_traffic_state() 和 _next_packet_time()。在基础泊松业务下,每个终端的数据包到达间隔服从指数分布,代码中使用反函数法生成随机到达间隔:
其中 为 上的均匀随机数, 为平均到达间隔。业务负载 越大,平均到达间隔越短,终端产生数据包越频繁。
MAC 模块包含三种协议的事件处理函数。_pure_aloha_protocol() 实现 Pure ALOHA,终端有包到达后立即发送,不进行时隙对齐和载波侦听;_slotted_aloha_protocol() 实现 Slotted ALOHA,将到达时间和退避后的重发时间对齐到下一个时隙边界,时隙长度等于一个数据包传输时间;_np_csma_protocol() 实现非坚持 CSMA,终端发送前先调用 _carrier_sense() 判断信道是否忙,若忙则随机退避后再尝试。三种协议的工作原理对比如图二所示。

发射机和信道模块主要由 _calculate_distance() 和 _calculate_power() 实现。程序设定基站位于圆形覆盖区域中心上方,终端分布在半径 m 的圆形区域内。接收功率计算时先根据终端到 AP 的三维距离计算路径损耗,再叠加阴影衰落;在 enhanced 配置下,进一步叠加 Rayleigh 快衰落。
接收机模块由 _calculate_sinr() 和 _resolve_channel() 实现。当多个终端同时处于发送或冲突状态时,程序计算所有活动发送终端的接收功率,选择接收功率最大的终端作为捕获候选,然后计算:
若 不低于门限 dB,则最强终端保持 TRANSMIT 状态并计为捕获成功,其余终端进入 COLLISION 状态;若不满足门限,则所有同时发送终端均按冲突处理。
统计模块在 _run_single_simulation()、compute_statistics() 和 build_comparison() 中完成。单次仿真返回的字段包括 traffic、throughput、collision_count、avg_delay 和 capture_success_count。其中吞吐量和业务负载的计算公式分别为:
这里 是成功发送的数据包总符号数, 是所有尝试发送的数据包总符号数, 为符号速率, 为仿真总时间。
3.3 原始代码的局限性#
原始参考程序能够完成三种随机接入协议的基本仿真,但其无线环境假设较理想化。首先,终端位置在初始化后保持不变,信道距离项不会随时间变化,无法反映移动终端导致的接收功率动态变化。其次,所有终端采用相同泊松业务源,业务强度完全一致,无法体现实际网络中少数终端短时间连续发包的突发现象。第三,原始信道主要考虑路径损耗和阴影衰落,缺少小尺度快衰落,对 NLOS 多径环境下接收功率的快速波动描述不足。最后,传统冲突模型往往将重叠发送直接视为全部失败,而实际无线接收中存在捕获效应,强信号终端在 SINR 足够高时仍可能被成功解调。
因此,本实验的改进目标不是改变三种 MAC 协议本身,而是在保持 Pure ALOHA、Slotted ALOHA 和 np-CSMA 可比性的前提下,从业务源、运动模型和信道模型三个维度增强仿真场景,使 baseline 与 enhanced 的对比能够体现更真实无线环境对协议性能的影响。
四、改进方案设计#
本实验采用 baseline 与 enhanced 两组实验配置进行对比。baseline 保留较基础的泊松业务、静态终端和对数距离信道;enhanced 在此基础上引入混合突发业务、随机路点运动模型和 Rayleigh 快衰落信道。为了保证比较口径一致,两组实验都使用同一套 SINR 捕获判决,差异集中在业务源、终端运动和信道衰落层次上。
4.1 改进 1:混合突发业务源模型#
业务源改进的目的是模拟真实网络中终端业务强度不均匀的情况。基础泊松模型假设所有终端具有相同平均到达率,但实际网络中常见视频上传、文件传输、短时控制消息集中上报等突发业务,少数终端可能在短时间内产生更密集的数据包。为了体现这种异质性,enhanced 配置引入混合突发业务源。
代码中通过 traffic_mode 参数区分业务模式。baseline 使用 traffic_mode="poisson",所有终端均为普通泊松源;enhanced 使用 traffic_mode="mixed",在 _init_traffic_state() 中随机选择 20% 终端作为突发终端,并用 is_burst_node 数组记录。普通终端的到达间隔均值为 ,突发终端的到达间隔均值缩短为:
其中 。也就是说,突发终端的平均到达率是普通终端的 4 倍。若突发终端比例为 ,则系统整体平均到达率约为:
代入 、 得:
因此,在相同名义负载参数下,混合业务会使实际业务到达更集中、更不均匀,更容易触发同时发送和退避,从而检验不同 MAC 协议在非均匀业务场景下的稳定性。程序实现时没有改变三种 MAC 协议的处理函数,而是在业务到达时间生成前先初始化终端类型,再根据终端类型选择不同的指数分布均值。这样做的优点是业务模型与 MAC 协议解耦,Pure ALOHA、Slotted ALOHA 和 np-CSMA 可以共享同一套业务源逻辑。
关键实现片段如下。_init_traffic_state() 在每个业务负载开始前重新选择突发终端,_next_packet_time() 则在生成下一次到达时间时判断当前终端是否属于突发源。
def _init_traffic_state(self):
self.is_burst_node = np.zeros(self.Mnum, dtype=bool)
if self.traffic_mode == "mixed":
burst_num = max(1, int(self.Mnum * self.burst_ratio))
burst_idx = np.random.choice(self.Mnum, burst_num, replace=False)
self.is_burst_node[burst_idx] = True
def _next_packet_time(self, node_id: int, now_time: float) -> float:
interval = self.Tint
if self.traffic_mode == "mixed" and self.is_burst_node[node_id]:
interval = self.Tint / self.burst_factor
return now_time - interval * np.log(1 - random.random())python4.2 改进 2:随机路点运动模型#
运动模型改进的目的是使终端位置随时间变化,从而使路径损耗和接收功率不再是静态量。baseline 中终端在初始化后保持静止,终端到 AP 的距离固定;enhanced 引入随机路点模型(Random Waypoint),使终端在圆形覆盖区域内随机选择目标点、以随机速度移动、到达后暂停,再选择下一个目标点,其轨迹示意如图三所示。

随机路点模型的目标点在半径 的圆形区域内均匀生成。为避免节点集中在圆心附近,半径采用平方根变换:
目标点坐标为:
移动速度在 上均匀采样:
本实验中 ,,最大暂停时间为 1.0s。代码中 _init_mobility() 初始化每个终端的目标点和速度,_refresh_waypoint_target() 在终端完成暂停后重新选择目标点和速度,_update_positions() 在每个离散事件到来前根据当前仿真时间推进终端位置。为了降低事件密集时的计算开销,程序通过 mobility_update_interval=0.01 控制位置批量更新时间间隔。
随机路点模型与信道模块直接耦合。终端位置改变后,终端到 AP 的距离 随时间变化,路径损耗和接收功率也随之变化:
其中 为阴影衰落项, 为快衰落项。移动模型本身不直接改变 MAC 协议规则,但会通过接收功率影响 SINR 捕获判决。代码实现上,_init_mobility() 为每个终端设置初始目标点和速度;_refresh_waypoint_target() 在终端到达目标并完成暂停后刷新下一段移动;_update_positions() 在离散事件推进时按照经过的时间更新位置。该函数还使用 mobility_update_interval 做更新间隔控制,避免在高负载下每个 MAC 事件都全量更新所有终端位置。
为了突出模型流程,下面给出与源码一致的伪代码。它保留了“暂停、移动、到达、刷新目标”的核心状态变化。
def _init_mobility(self):
reset targets, speeds, pause timers
if mobility_model != "random_waypoint":
return
waypoint_targets = random_points_in_cell(Mnum)
node_speeds = uniform(v_min, v_max, size=Mnum)
def _update_positions(self, now_time):
if mobility_model != "random_waypoint":
return
dt = now_time - last_position_update_time
if dt < mobility_update_interval:
return
for each node:
remaining = dt
while remaining > 0:
if node is pausing:
consume pause time
if pause ends:
refresh target and speed
else:
move toward current target
if target can be reached in remaining time:
set position to target
enter random pause
else:
advance by speed * remaining
remaining = 0
last_position_update_time = now_timepython4.3 改进 3:Rayleigh 衰落信道与 SINR 捕获判决#
信道模型改进的目的是补充小尺度快衰落,使仿真能够描述 NLOS 多径传播下接收功率的快速随机波动。为避免阴影衰落标准差差异干扰信道对比,baseline 与 enhanced 都使用 6 dB 阴影衰落;两组在信道模型上的关键差异是 baseline 只采用对数距离路径损耗与阴影衰落,而 enhanced 在此基础上进一步叠加 Rayleigh 快衰落。信道衰落层次如图四所示。

对数距离路径损耗模型为:
其中 dB,路径损耗指数 。阴影衰落项服从高斯分布:
Rayleigh 快衰落建模为复高斯信道:
在 _calculate_power() 中,当 channel_model="rayleigh" 时,程序为每次功率计算重新采样复高斯随机变量,并将 加到接收功率中。这样 Rayleigh 衰落不会改变长期平均功率,但会引入短时功率波动,使某些终端在特定时刻因快衰落增强而成为最强接收信号。
接收机判决由 _calculate_sinr() 和 _resolve_channel() 共同完成。当同时发送终端集合为 时,程序先计算每个终端的接收功率,选择最强终端 :
再计算最强终端的 SINR:
若 ,其中 dB,则最强终端被捕获成功,其余终端判定为冲突;否则所有同时发送终端均冲突。该机制比“重叠发送全部失败”的简单冲突模型更贴近无线接收过程,也使吞吐量、冲突次数和捕获成功次数之间具有可解释的联系。
下面的代码片段体现了信道模型和接收判决的衔接。_calculate_power() 输出线性接收功率,_calculate_sinr() 选出最强信号并计算其 SINR,_resolve_channel() 根据门限恢复被捕获的最强终端。
def _calculate_power(self, idx):
dxy = self._calculate_distance(self.bxy, self.mxy[idx])
dxy = np.maximum(dxy, 1.0)
shadow = self.shadow_std_db * self.mrnd[idx]
power_dbm = (
self.tpowdBm + self.txGain + self.rxGain
- 38.5 - self.alfa * 10 * np.log10(dxy) - shadow
)
if self.channel_model == "rayleigh":
h = (np.random.randn(len(idx)) + 1j * np.random.randn(len(idx))) / np.sqrt(2)
rayleigh_gain = np.maximum(np.abs(h) ** 2, 1e-12)
power_dbm += 10 * np.log10(rayleigh_gain)
return 10 ** (power_dbm / 10)
def _calculate_sinr(self, idx):
pow_vals = self._calculate_power(idx)
max_pos = np.argmax(pow_vals)
signal = pow_vals[max_pos]
interference = np.sum(pow_vals) - signal
sinr_db = 10 * np.log10(signal / (self.NoisePower + interference))
return max_pos, sinr_db
def _resolve_channel(self, idx):
if len(idx) <= 1:
return
self.collision_count += 1
max_pos, sinr_db = self._calculate_sinr(idx)
self.Mstate[idx] = COLLISION
if sinr_db >= self.tcn:
self.Mstate[idx[max_pos]] = TRANSMIT
self.capture_success_count += 1python五、仿真实验与结果分析#
5.1 实验参数设置#
每组实验均对三种协议进行业务负载扫描,扫描范围为 ,每个负载点累计成功接收 10000 个数据包后停止。
表六给出了主要实验参数。
表六 主要实验参数
| 参数 | 取值 | 说明 |
|---|---|---|
| 100 | 终端数量 | |
| 10000 | 每个业务负载下累计成功包数 | |
| 100 m | 圆形覆盖半径 | |
| 20 dBm | 终端发射功率 | |
| 3 | 路径损耗指数 | |
| 128 symbols | 数据包长度 | |
| 256000 symbols/s | 符号速率 | |
| 0.0005 s | 单个数据包传输时间, | |
| -174 dBm/Hz | 热噪声功率谱密度 | |
| 5 dB | 接收机噪声系数 | |
| 10 dB | SINR 捕获门限 |
表七给出了 baseline 与 enhanced 的配置差异。
表七 baseline 与 enhanced 配置差异
| 配置项 | baseline | enhanced |
|---|---|---|
| 业务源 | 泊松业务,traffic_mode="poisson" | 混合突发业务,traffic_mode="mixed" |
| 运动模型 | 静态终端,mobility_model="static" | 随机路点,mobility_model="random_waypoint" |
| 信道模型 | 对数距离路径损耗 + 阴影衰落 | 对数距离路径损耗 + 阴影衰落 + Rayleigh 快衰落 |
| 阴影衰落标准差 | 6 dB | 6 dB |
| SINR 捕获判决 | 使用 | 使用 |
| 突发终端比例 | 参数保留,未启用混合业务 | 20% |
| 突发因子 | 参数保留,未启用混合业务 | 4.0 |
| 运动速度范围 | 不移动 | 0.5 到 2.0 |
| 最大暂停时间 | 不移动 | 1.0 s |
图五是最核心的结果图,将两组实验、三种协议的吞吐量-负载曲线画在同一张图中,便于直接观察协议差异和增强模型影响。

图五中的六条曲线对应 3 种协议与 2 组实验配置。整体上,np-CSMA 的吞吐量最高,Slotted ALOHA 次之,Pure ALOHA 最低,说明载波侦听和时隙同步都能减少无序竞争带来的吞吐损失。统一阴影衰落标准差后,enhanced 不再表现为所有协议都单向提升:Pure ALOHA 的最大吞吐量由 0.28685 降至 0.28211,而 Slotted ALOHA 和 np-CSMA 分别由 0.49739、0.65781 提升至 0.51805、0.66554。这说明 enhanced 综合模型同时引入了突发业务、移动性和 Rayleigh 快衰落,其结果是多因素共同作用,而不是单个模块带来的单向增益。
5.2 baseline 实验结果#
baseline 实验使用泊松业务、静态终端和对数距离信道。三种协议的统计结果如表八所示。
表八 baseline 实验统计结果
| 协议 | 平均吞吐量 | 最大吞吐量 | 最佳 | 最佳实际负载 | 冲突次数 | 平均等待时间/s | 捕获成功次数 |
|---|---|---|---|---|---|---|---|
| Pure ALOHA | 0.23904 | 0.28685 | 1.0 | 0.99888 | 532314 | 0.12122 | 143247 |
| Slotted ALOHA | 0.38185 | 0.49739 | 1.6 | 1.57996 | 76865 | 0.05471 | 28738 |
| np-CSMA | 0.41980 | 0.65781 | 2.0 | 2.02295 | 3186 | 0.05055 | 1322 |

图六展示了 baseline 下 Pure ALOHA 的吞吐量-负载曲线。仿真曲线先随 增大而上升,在 附近达到最大吞吐量 0.28685,之后随着负载继续升高而下降。下降的原因是 Pure ALOHA 不做时隙同步和载波侦听,终端有包即发,高负载下重叠发送迅速增多,冲突次数累计达到 532314 次。图中理论曲线为经典公式 ,它低于仿真曲线的主要原因是经典理论假设“只要重叠发送就全部失败”,而本实验即使在 baseline 中也使用 SINR 捕获判决。路径损耗和 6 dB 阴影衰落会造成不同终端接收功率不同,当最强信号的 SINR 超过 10 dB 时,该分组仍可成功接收,因此实际吞吐量高于无捕获效应的理论值。

图七展示了 baseline 下 Slotted ALOHA 的结果。与 Pure ALOHA 相比,Slotted ALOHA 将发送开始时间对齐到时隙边界,把冲突窗口从 缩短为 ,因此曲线整体更高,最大吞吐量达到 0.49739,最佳 为 1.6。图中的理论曲线为 ,同样低于实际仿真值。这不是理论公式错误,而是两者假设不同:经典 Slotted ALOHA 理论仍然采用“同一时隙中多个发送全部失败”的碰撞模型;本实验中同一时隙若出现多个终端发送,接收端仍会计算最强信号的 SINR,并在满足门限时保留一个成功包。baseline 下捕获成功次数为 28738 次,这部分成功传输会把实际吞吐量抬高到理论曲线之上。

图八展示了 baseline 下 np-CSMA 的结果。曲线在本次扫描范围内随业务负载持续上升,最大吞吐量出现在 ,达到 0.65781。该现象说明在 的范围内,np-CSMA 尚未表现出明显的过载下降。原因是 np-CSMA 在发送前进行载波侦听,信道忙时不会立即发送,而是随机退避后再尝试,因此总冲突次数只有 3186 次,远低于 Pure ALOHA 和 Slotted ALOHA。该图没有叠加 ALOHA 理论曲线,因为 np-CSMA 的载波侦听和退避机制不满足经典 ALOHA 理论公式的假设。
5.3 enhanced 实验结果#
enhanced 实验同时启用混合突发业务、随机路点运动模型和 Rayleigh 快衰落。阴影衰落标准差仍为 6 dB,与 baseline 保持一致。三种协议的统计结果如表九所示。
表九 enhanced 实验统计结果
| 协议 | 平均吞吐量 | 最大吞吐量 | 最佳 | 最佳实际负载 | 冲突次数 | 平均等待时间/s | 捕获成功次数 |
|---|---|---|---|---|---|---|---|
| Pure ALOHA | 0.23378 | 0.28211 | 0.9 | 0.94863 | 642279 | 0.14392 | 144881 |
| Slotted ALOHA | 0.40856 | 0.51805 | 1.4 | 1.50619 | 84383 | 0.06261 | 32997 |
| np-CSMA | 0.44903 | 0.66554 | 2.0 | 2.11629 | 3542 | 0.05761 | 1186 |

图九展示了 enhanced 下 Pure ALOHA 的曲线。由于混合突发业务使实际到达更集中,Pure ALOHA 的冲突压力显著增大,冲突次数从 baseline 的 532314 次增至 642279 次。虽然 Rayleigh 快衰落带来了更多瞬时功率差异,捕获成功次数也略增至 144881 次,但综合作用下最大吞吐量从 0.28685 降至 0.28211,平均等待时间升至 0.14392s。图中的经典理论曲线仍低于仿真曲线,原因同样在于理论模型没有 SINR 捕获效应;即使 enhanced 的总体最大吞吐量低于 baseline,它仍高于无捕获假设下的 Pure ALOHA 理论值。

图十展示了 enhanced 下 Slotted ALOHA 的曲线。曲线峰值达到 0.51805,最佳 为 1.4,对应实际业务负载为 1.50619。与 baseline 相比,Slotted ALOHA 的最大吞吐量提升 4.15%。虽然冲突次数增加到 84383 次,平均等待时间也升至 0.06261s,但捕获成功次数增加到 32997 次,说明 Rayleigh 快衰落带来的接收功率差异在同一时隙冲突中仍有一定收益。理论曲线 低于仿真曲线,是因为理论公式假设同一时隙只要有两个及以上终端发送就全部失败,而本实验中最强信号达到 10 dB SINR 门限即可成功。

图十一展示了 enhanced 下 np-CSMA 的曲线。曲线仍在扫描范围内随 上升,最大吞吐量为 0.66554,出现在 。与 baseline 相比,np-CSMA 的提升幅度较小,因为载波侦听机制已经在 MAC 层减少了大量重叠发送,物理层捕获能够发挥作用的场景有限。值得注意的是,enhanced 下 np-CSMA 的平均等待时间由 0.05055s 增至 0.05761s,冲突次数也从 3186 次增至 3542 次。这说明混合突发业务和信道波动提高了竞争复杂度,虽然吞吐量略有提升,但等待代价也随之增加。
5.4 baseline 与 enhanced 对比分析#
表十汇总了两组实验的核心对比指标。
表十 baseline 与 enhanced 核心指标对比
| 协议 | baseline 最大吞吐量 | enhanced 最大吞吐量 | 最大吞吐量变化 | 冲突次数变化 | 平均等待时间变化/s | 捕获成功次数变化 |
|---|---|---|---|---|---|---|
| Pure ALOHA | 0.28685 | 0.28211 | -1.65% | +109965 | +0.02271 | +1634 |
| Slotted ALOHA | 0.49739 | 0.51805 | +4.15% | +7518 | +0.00790 | +4259 |
| np-CSMA | 0.65781 | 0.66554 | +1.18% | +356 | +0.00707 | -136 |

图十二对比了 Pure ALOHA 在 baseline 和 enhanced 下的吞吐量变化。统一阴影衰落后,enhanced 并未提升 Pure ALOHA 的最大吞吐量,反而从 0.28685 小幅下降到 0.28211。Pure ALOHA 的协议规则最简单,所有终端到达即发送,因此它对突发业务最敏感。enhanced 中捕获成功次数仅增加 1634 次,但冲突次数增加 109965 次,说明混合突发业务和移动信道波动带来的竞争压力超过了 Rayleigh 捕获收益。该图提醒我们,对 Pure ALOHA 这类无侦听协议,综合增强模型不一定表现为吞吐提升。

图十三对比了 Slotted ALOHA 的两组曲线。Slotted ALOHA 是本次实验中 enhanced 相对 baseline 提升最明显的协议,最大吞吐量提升 4.15%。其原因可能是时隙结构限制了冲突发生的时间范围,而 Rayleigh 快衰落增加了同一时隙内最强信号被捕获的机会。结果上,捕获成功次数增加 4259 次,但冲突次数也增加 7518 次,平均等待时间增加 0.00790s。该图说明,Slotted ALOHA 的吞吐量收益伴随更高竞争开销,不能简单解释为“增强模型全面改善”。

图十四对比了 np-CSMA 的两组曲线。两条曲线非常接近,enhanced 的最大吞吐量只提升 1.18%。这是因为 np-CSMA 在发送前会进行载波侦听,baseline 中已经把冲突次数压低到 3186 次,因此 enhanced 的捕获机制可转化的冲突空间有限。另一方面,混合突发业务使待发送请求更集中,随机路点和快衰落使信道状态更波动,导致 enhanced 下冲突次数增加 356 次,平均等待时间增加 0.00707s。该图说明,对具备载波侦听的协议而言,增强模型更明显地体现为真实场景复杂度,而不是单纯的性能提升。

图十五将最大吞吐量、冲突次数和平均等待时间三个统计指标放在同一组柱状图中。最大吞吐量方面,Slotted ALOHA 和 np-CSMA 在 enhanced 中高于 baseline,而 Pure ALOHA 略低于 baseline。冲突次数方面,三种协议在 enhanced 中均有所增加,其中 Pure ALOHA 增幅最大,说明突发业务对无侦听协议影响最强。平均等待时间方面,三种协议也均上升,反映出 enhanced 的业务源和移动信道共同增加了竞争复杂度。该图的价值在于把吞吐、冲突和时延放在一起观察,避免只用吞吐量单一指标评价协议优劣。
5.5 结果小结#
综合结果表明,三种协议的性能排序在两组实验中保持一致:np-CSMA 最高,Slotted ALOHA 次之,Pure ALOHA 最低。这与协议机制相符:Pure ALOHA 完全随机发送,冲突窗口最大;Slotted ALOHA 通过时隙同步缩短冲突窗口;np-CSMA 通过载波侦听在发送前规避忙信道,因此冲突最少。
从 baseline 到 enhanced,Slotted ALOHA 和 np-CSMA 的最大吞吐量略有提升,Pure ALOHA 则略有下降。这说明综合增强模型并不是简单提升所有协议,而是同时带来两类影响:混合突发业务和移动性会增加竞争、冲突和等待;Rayleigh 快衰落和 SINR 捕获可能把部分冲突转化为成功传输。不同协议对这两类影响的敏感性不同。
需要注意的是,当前实验是综合增强实验,混合突发业务、随机路点运动和 Rayleigh 衰落同时启用,因此第五章的结论应表述为”enhanced 综合模型对性能的影响”,不能将吞吐量提升完全归因于某一个单独模块。若后续需要进一步量化每个模块的独立贡献,可以增加消融实验,例如只开启混合业务、只开启随机路点、只开启 Rayleigh 衰落,并与 baseline 逐项比较。
六、总结与展望#
6.1 实验结论#
本实验在课程参考代码的基础上,从业务源、运动模型和信道模型三个维度对无线网络仿真场景进行了增强,并通过 baseline 与 enhanced 两组实验对比了 Pure ALOHA、Slotted ALOHA 和 np-CSMA 三种竞争式 MAC 协议的性能变化。主要结论如下。
第一,三种协议的性能排序在两组实验中保持一致:np-CSMA 最高,Slotted ALOHA 次之,Pure ALOHA 最低。这一排序与协议机制一致:Pure ALOHA 不做任何信道检测,冲突窗口最大;Slotted ALOHA 通过时隙同步将冲突窗口缩小一半;np-CSMA 在发送前进行载波侦听,信道忙时退避,冲突次数远低于前两者。
第二,enhanced 综合模型同时引入了两类相互对立的影响。一方面,混合突发业务使部分终端发包更密集,随机路点运动使终端位置随时间变化,两者共同提高了竞争复杂度,增加了冲突次数和平均等待时间。另一方面,Rayleigh 快衰落叠加阴影衰落扩大了终端间的接收功率差异,使 SINR 捕获效应更容易发生,部分冲突可以转化为成功传输。不同协议对这两类影响的敏感性不同,最终的吞吐量变化取决于两者的净效果。
第三,Slotted ALOHA 对综合增强模型最为敏感,最大吞吐量提升 4.15%。时隙结构限制了冲突发生的时间范围,而 Rayleigh 快衰落增加了同一时隙内最强信号被捕获的机会,两者协同产生了正向收益。Pure ALOHA 的最大吞吐量下降 1.65%,冲突次数增加 109965 次,说明突发业务对无侦听协议的冲击超过了 Rayleigh 捕获带来的收益。np-CSMA 的最大吞吐量仅提升 1.18%,因为载波侦听已经在 MAC 层大幅减少了冲突,物理层捕获可转化的空间有限。
第四,当前实验为综合增强实验,三个模块同时启用,因此上述结论反映的是三个因素共同作用的结果,不能将吞吐量变化完全归因于某一个单独模块。
6.2 不足与改进方向#
本实验存在以下不足,可在后续工作中加以改进。
在运动模型方面,当前随机路点模型的移动速度和暂停时间均采用均匀分布,缺少对真实场景中用户行为特征的刻画。实际场景中,行人移动速度往往服从对数正态分布,暂停时间则可能呈现重尾特征。引入更贴近实际的速度和暂停时间分布,可以使终端移动行为更加真实,也有助于观察不同运动模式对协议性能的影响。
在信道模型方面,当前实验只考虑了 NLOS 场景下的 Rayleigh 衰落,假设所有终端与基站之间没有直射路径。但在郊区、农村或无人机通信等场景中,直射路径可能存在,此时接收信号幅度服从 Rician 分布而非 Rayleigh 分布。引入 Rician 衰落并调节 因子(直射分量与散射分量的功率比),可以覆盖从纯 NLOS 到强 LOS 的连续过渡,使信道模型更加完整。
在 MAC 协议方面,当前实验只对比了三种基本的竞争式协议。实际无线网络中还广泛使用 CSMA/CA(带冲突避免的载波侦听多路访问)和 TDMA(时分多址)等协议。将这些协议纳入仿真框架,可以在更丰富的协议维度上验证增强模型的适用性。
在实验设计方面,由于 enhanced 配置同时启用了三个模块,无法区分各模块的独立贡献。后续可以增加消融实验,分别只开启混合业务源、只开启随机路点运动、只开启 Rayleigh 衰落,并与 baseline 逐项对比,从而量化每个模块对吞吐量、冲突次数和平均时延的独立影响。
源码#
"""无线随机接入协议仿真脚本。
本文件同时完成三件事:
1. 对 Pure ALOHA、Slotted ALOHA、np-CSMA 三种 MAC 协议进行离散事件仿真;
2. 对比 baseline 与 enhanced 两组实验配置;
3. 将仿真结果输出到终端、JSON、Markdown 和 seaborn 图像。
baseline 与 enhanced 的核心区别只体现在业务源、运动模型和信道模型配置上。
两组实验统一使用 SINR 捕获判决,便于直接比较不同增强因素对吞吐量、冲突和时延的影响。
"""
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import random
import time
import json
import argparse
from pathlib import Path
from typing import List, Dict, Any, Optional
# 终端 MAC 状态常量。
# STANDBY:终端空闲或等待下一次分组到达;
# TRANSMIT:终端正在发送,若没有冲突或可被 SINR 捕获,则最终计为成功;
# COLLISION:终端当前发送已被判为冲突,后续会重新退避。
STANDBY = 0
TRANSMIT = 1
COLLISION = 2
def setup_chinese_font():
"""设置中文图表字体。
seaborn 会重置 matplotlib 的部分样式参数,因此这里同时设置
matplotlib rcParams 与 seaborn theme,保证保存图片时中文标题、
坐标轴标签和负号都能正常显示。字体列表按优先级排列,宋体不可用时
自动回退到系统常见中文字体。
"""
font_options = [
"SimSun",
"宋体",
"Microsoft YaHei",
"SimHei",
"Arial Unicode MS",
"DejaVu Sans",
]
plt.rcParams["font.sans-serif"] = font_options
plt.rcParams["axes.unicode_minus"] = False
sns.set_theme(
style="whitegrid",
rc={
"font.sans-serif": font_options,
"axes.unicode_minus": False,
},
)
class WirelessNetworkSimulator:
"""无线随机接入网络的离散事件仿真器。
一个实例代表一组实验配置,例如 baseline 或 enhanced。实例内部维护
终端位置、业务到达时间、MAC 状态、移动模型状态、信道参数和统计量。
外部调用 `simulate_all_protocols()` 后,会依次运行三种协议并返回曲线数据。
"""
def __init__(
self,
Mnum: int = 100,
spend: int = 10000,
traffic_mode: str = "poisson",
mobility_enabled: bool = False,
shadow_std_db: float = 6.0,
result_prefix: str = "baseline",
mobility_model: Optional[str] = None,
channel_model: str = "log_distance",
mobility_update_interval: float = 0.01,
):
"""初始化仿真器参数和静态拓扑。
Args:
Mnum: 终端数量。
spend: 每个业务负载 G 下需要累计的成功发送分组数。
traffic_mode: 业务源模型,`poisson` 为原始泊松业务,
`mixed` 为普通泊松业务与突发业务混合。
mobility_enabled: 兼容旧接口的开关。未显式指定 `mobility_model`
时,True 会映射为 random_waypoint,False 会映射为 static。
shadow_std_db: 阴影衰落标准差,单位 dB。
result_prefix: 实验组名称,用于标识 baseline/enhanced。
mobility_model: 运动模型,支持 `static` 和 `random_waypoint`。
channel_model: 信道模型,支持 `log_distance` 和 `rayleigh`。
mobility_update_interval: 随机路点位置更新间隔。大于 0 时按固定
时间间隔批量推进位置,避免每个 MAC 事件都更新所有终端。
"""
# 系统参数。bit_rate 当前只保留为系统信息,实际分组持续时间由
# packet_length / sym_rate 计算得到。
self.bit_rate = 512e3
self.sym_rate = 256e3
self.packet_length = 128
# Dtime 表示传播/侦听延迟相对于一个分组持续时间的比例。
self.Dtime = 0.01
# alfa 为路径损耗指数;sigma 保留原始参数,当前阴影衰落使用 shadow_std_db。
self.alfa = 3
self.sigma = 6
# AP 参数。基站位于圆心上方 5 m,终端在半径 r 的二维圆域内移动。
self.r = 100
self.bxy = np.array([0, 0, 5])
self.tcn = 10 # SINR 捕获门限,单位 dB。
# 终端参数。
self.Mnum = Mnum
# 改进1:业务源模式,poisson 表示原始泊松业务,mixed 表示普通业务和突发业务混合。
self.traffic_mode = traffic_mode
# 改进2:运动模型,static 表示静态终端,random_waypoint 表示随机路点模型。
if mobility_model is None:
mobility_model = "random_waypoint" if mobility_enabled else "static"
if mobility_model not in {"static", "random_waypoint"}:
raise ValueError("mobility_model 必须是 'static' 或 'random_waypoint'")
self.mobility_model = mobility_model
self.mobility_enabled = self.mobility_model != "static"
# 改进3:信道模型,log_distance 为路径损耗+阴影衰落,rayleigh 额外叠加快衰落。
if channel_model not in {"log_distance", "rayleigh"}:
raise ValueError("channel_model 必须是 'log_distance' 或 'rayleigh'")
self.channel_model = channel_model
# 阴影衰落标准差,baseline 保留原始 1 dB,enhanced 使用 sigma=6 dB。
self.shadow_std_db = shadow_std_db
# 为 baseline/enhanced 两组实验生成不同文件名前缀,避免结果互相覆盖。
self.result_prefix = result_prefix
# 突发业务终端比例,设置为 20% 便于体现不同业务强度。
self.burst_ratio = 0.2
# 突发终端平均发包间隔缩短为普通终端的 1/4。
self.burst_factor = 4.0
# 随机路点模型参数:速度单位按仿真时间尺度理解,暂停时间单位为秒。
self.waypoint_min_speed = 0.5
self.waypoint_max_speed = 2.0
self.waypoint_max_pause_time = 1.0
if mobility_update_interval < 0:
raise ValueError("mobility_update_interval 必须大于等于 0")
self.mobility_update_interval = mobility_update_interval
# 记录上一次位置更新时间,用于按事件间隔推进终端位置。
self.last_position_update_time = 0.0
# 仿真参数。三种协议使用同一套信道、业务源和统计逻辑。
self.protocols = ["Pure ALOHA", "Slotted ALOHA", "np-CSMA"]
self.spend = spend
# 计算参数。
# Ttime 是一个分组占用信道的时间,也就是事件表中发送开始到发送结束的间隔。
self.Ttime = self.packet_length / self.sym_rate
self.tpowdBm = 20 # dbm
self.txGain = 0
self.rxGain = 0
self.N0 = -174 # dbm/Hz
self.NF = 5 # dB
self.NoisePowerdBm = self.N0 + 10 * np.log10(self.sym_rate) + self.NF
self.NoisePower = 10 ** (self.NoisePowerdBm / 10)
# 初始化终端位置。
# mxy 为 Mnum x 3 数组,前三列分别表示 x、y、z 坐标。
self.mxy = self._generate_terminal_positions(self.r, self.Mnum, 0)
# 保存初始拓扑,保证每个业务负载从同一组初始位置开始仿真。
self.initial_mxy = self.mxy.copy()
# mrnd 是每个终端固定的阴影衰落随机量。一次仿真中同一终端的阴影项保持一致。
self.mrnd = np.random.randn(self.Mnum)
# 记录哪些终端属于突发业务源。
self.is_burst_node = np.zeros(self.Mnum, dtype=bool)
# 随机路点模型状态:目标位置、当前速度、剩余暂停时间。
self.waypoint_targets = np.zeros((self.Mnum, 2))
self.node_speeds = np.zeros(self.Mnum)
self.pause_remaining = np.zeros(self.Mnum)
def _generate_terminal_positions(self, r: float, n: int, h: int = 0) -> np.ndarray:
"""生成初始终端位置。
这里保留原始代码的离散随机撒点方式:随机产生整数 x/y 坐标,
通过圆域约束和重复位置检查后写入坐标数组。这样可以避免多个终端
完全重合,并保证初始终端都位于 AP 覆盖半径内。
Args:
r: 圆形覆盖半径。
n: 需要生成的终端数量。
h: 是否随机生成终端高度。当前实验默认 h=0,即终端位于 z=0 平面。
"""
posxy = np.zeros((n, 3))
occupied_positions = set()
for i in range(n):
while True:
xx = round(r * random.random()) * np.sign(
np.sin(2 * np.pi * random.random())
)
yy = round(r * random.random()) * np.sign(
np.cos(2 * np.pi * random.random())
)
if xx**2 + yy**2 <= r**2 and (xx != 0 or yy != 0):
if (xx, yy) not in occupied_positions:
occupied_positions.add((xx, yy))
posxy[i, 0] = xx
posxy[i, 1] = yy
if h == 1:
posxy[i, 2] = random.random() + 3
break
return posxy
def _generate_random_points_in_cell(self, n: int) -> np.ndarray:
"""在圆形覆盖范围内均匀生成二维目标点。
随机路点模型需要为终端生成新的移动目标。半径使用
`r * sqrt(U)` 而不是 `r * U`,目的是让圆面积上的点分布均匀,
避免目标点过度集中在圆心附近。
"""
radius = self.r * np.sqrt(np.random.rand(n))
theta = 2 * np.pi * np.random.rand(n)
return np.column_stack((radius * np.cos(theta), radius * np.sin(theta)))
def _reset_states(self):
"""重置单次仿真的动态状态和统计变量。
每个协议、每个业务负载 G 都会调用一次该函数。这里不重新生成
初始拓扑,只从 `initial_mxy` 恢复位置,保证不同 G 的比较不受上一次
终端移动轨迹影响。
"""
# Mstate 记录每个终端当前 MAC 状态。
self.Mstate = np.zeros(self.Mnum, dtype=int)
# Mplen 记录每个终端当前分组长度。当前模型所有分组等长。
self.Mplen = np.full(self.Mnum, self.packet_length)
# mgtime:分组生成时间;mtime:下一次事件时间;Mstime:最近一次发送开始时间。
self.mgtime = None
self.mtime = None
self.Mstime = None
self.slot = None
# 每次单独仿真前恢复初始拓扑,避免上一组移动结果影响下一组。
self.mxy = self.initial_mxy.copy()
# 重置统计变量
self.Spnum = 0
self.Splen = 0
self.Tplen = 0
self.Wtime = 0
# 新增统计:记录发生重叠传输的次数。
self.collision_count = 0
# 新增统计:记录 SINR 捕获成功次数。
self.capture_success_count = 0
def _init_traffic_state(self):
"""初始化普通业务终端和突发业务终端。
baseline 使用 `poisson`,所有终端的到达过程相同。enhanced 使用
`mixed`,随机选取一部分终端作为突发业务源,使其平均到达间隔更短。
"""
# 默认所有终端都是普通泊松业务源。
self.is_burst_node = np.zeros(self.Mnum, dtype=bool)
if self.traffic_mode == "mixed":
# 混合模式下随机选取一部分终端作为突发业务源。
burst_num = max(1, int(self.Mnum * self.burst_ratio))
burst_idx = np.random.choice(self.Mnum, burst_num, replace=False)
self.is_burst_node[burst_idx] = True
def _next_packet_time(self, node_id: int, now_time: float) -> float:
"""生成某个终端的下一次分组到达时间。
分组到达间隔服从指数分布,使用反函数法生成:
interval_sample = -mean_interval * log(1-U)。
对突发终端,mean_interval 会除以 burst_factor,从而提高短时间内的发包密度。
"""
interval = self.Tint
if self.traffic_mode == "mixed" and self.is_burst_node[node_id]:
# 突发终端缩短到达间隔,用来模拟短时间连续发包。
interval = self.Tint / self.burst_factor
return now_time - interval * np.log(1 - random.random())
def _init_mobility(self):
"""初始化运动模型状态。
`static` 模式只清空运动状态;`random_waypoint` 模式会为每个终端
生成初始目标点和移动速度。该函数在每个业务负载 G 开始前调用,
保证不同负载下的移动状态从同一时间起点重新开始。
"""
self.last_position_update_time = 0.0
self.waypoint_targets = np.zeros((self.Mnum, 2))
self.node_speeds = np.zeros(self.Mnum)
self.pause_remaining = np.zeros(self.Mnum)
if self.mobility_model != "random_waypoint":
return
self.waypoint_targets = self._generate_random_points_in_cell(self.Mnum)
speed_span = self.waypoint_max_speed - self.waypoint_min_speed
self.node_speeds = self.waypoint_min_speed + speed_span * np.random.rand(
self.Mnum
)
def _refresh_waypoint_target(self, node_id: int):
"""为一个终端选择新的随机路点目标和移动速度。
终端到达当前目标并完成暂停后,会调用该函数进入下一段移动。
速度在 [waypoint_min_speed, waypoint_max_speed] 内均匀抽样。
"""
self.waypoint_targets[node_id] = self._generate_random_points_in_cell(1)[0]
speed_span = self.waypoint_max_speed - self.waypoint_min_speed
self.node_speeds[node_id] = (
self.waypoint_min_speed + speed_span * random.random()
)
def _update_positions(self, now_time: float):
"""按事件时间推进随机路点运动状态。
仿真主循环由 MAC 事件驱动。若每个事件都更新所有终端位置,在大规模节点
和高业务负载下开销很大。因此这里使用 `mobility_update_interval` 做时间门限:
只有距离上次位置更新足够久时,才批量推进全部终端的位置。
"""
if self.mobility_model != "random_waypoint":
return
dt = now_time - self.last_position_update_time
if dt <= 0:
return
# 运动模型按移动时间尺度更新,避免每个 MAC 事件都触发全节点位置计算。
if self.mobility_update_interval > 0 and dt < self.mobility_update_interval:
return
for node_id in range(self.Mnum):
# remaining 表示本次位置更新时间片中还没有被暂停或移动消耗的时间。
remaining = dt
while remaining > 0:
if self.pause_remaining[node_id] > 0:
# 终端处于路点暂停阶段时,优先消耗暂停时间;暂停结束后再换新目标。
pause_step = min(remaining, self.pause_remaining[node_id])
self.pause_remaining[node_id] -= pause_step
remaining -= pause_step
if self.pause_remaining[node_id] <= 0:
self._refresh_waypoint_target(node_id)
continue
current_xy = self.mxy[node_id, 0:2]
target_xy = self.waypoint_targets[node_id]
direction = target_xy - current_xy
distance = float(np.linalg.norm(direction))
if distance <= 1e-9:
# 已经到达目标点时进入随机暂停;暂停时间可能为 0,此时立即刷新目标。
self.pause_remaining[node_id] = (
self.waypoint_max_pause_time * random.random()
)
if self.pause_remaining[node_id] <= 0:
self._refresh_waypoint_target(node_id)
continue
travel_time = distance / self.node_speeds[node_id]
if travel_time <= remaining:
# 本次时间片足够到达目标点:终端停在目标点并进入暂停阶段。
self.mxy[node_id, 0:2] = target_xy
remaining -= travel_time
self.pause_remaining[node_id] = (
self.waypoint_max_pause_time * random.random()
)
else:
# 本次时间片不足以到达目标点:沿目标方向前进 speed * remaining。
self.mxy[node_id, 0:2] += (
direction / distance * self.node_speeds[node_id] * remaining
)
remaining = 0
self.last_position_update_time = now_time
def _calculate_distance(self, bstn: np.ndarray, mstn: np.ndarray) -> np.ndarray:
"""计算 AP 到终端的三维欧氏距离。"""
return np.sqrt(np.sum((bstn - mstn) ** 2, axis=1))
def _calculate_power(self, idx: np.ndarray) -> np.ndarray:
"""计算指定终端集合在 AP 处的接收功率。
返回值为线性功率,单位与 NoisePower 保持一致。计算过程为:
1. 发射功率 + 天线增益;
2. 扣除 1 m 参考路径损耗;
3. 扣除对数距离路径损耗;
4. 扣除阴影衰落;
5. enhanced 的 Rayleigh 信道额外叠加快衰落增益。
"""
dxy = self._calculate_distance(self.bxy, self.mxy[idx])
# 距离下限设为 1 米,避免 log10(0) 或过小距离导致功率异常。
dxy = np.maximum(dxy, 1.0)
refPL_1m = 38.5
shadow = self.shadow_std_db * self.mrnd[idx]
# 前两层沿用原信道:对数距离路径损耗 + 阴影衰落。
power = (
self.tpowdBm
+ self.txGain
+ self.rxGain
- refPL_1m
- self.alfa * 10 * np.log10(dxy)
- shadow
)
if self.channel_model == "rayleigh":
# 第三层:Rayleigh 快衰落。h ~ CN(0, 1),|h|^2 服从均值为 1 的指数分布。
h = (np.random.randn(len(idx)) + 1j * np.random.randn(len(idx))) / np.sqrt(
2
)
rayleigh_gain = np.maximum(np.abs(h) ** 2, 1e-12)
power += 10 * np.log10(rayleigh_gain)
pow_val = 10 ** (power / 10)
return pow_val
def _calculate_sinr(self, idx: np.ndarray):
"""计算最强接收终端及其 SINR。
SINR 公式为 signal / (noise + interference)。其中 signal 取当前
同时发送终端中接收功率最大的一个,interference 为其余同时发送终端
接收功率之和。返回的 max_pos 是 idx 内部位置,不是全局终端编号。
"""
pow_vals = self._calculate_power(idx)
# 选择接收功率最大的终端作为捕获候选。
max_pos = np.argmax(pow_vals)
signal = pow_vals[max_pos]
# 其他同时发送终端的功率都作为干扰。
interference = np.sum(pow_vals) - signal
sinr = signal / (self.NoisePower + interference)
sinr_db = 10 * np.log10(max(sinr, 1e-30))
return max_pos, sinr_db
def _resolve_channel(self, idx: np.ndarray):
"""处理同时发送终端的冲突和 SINR 捕获。
若当前只有 0 或 1 个活动发送终端,无需信道判决。若多个终端重叠发送,
先记一次冲突,再计算最强终端的 SINR。只有 SINR 达到门限时,最强终端
被捕获为成功发送,其余终端保持冲突状态。
"""
if len(idx) <= 1:
return
# 同一时刻存在多个活动发送终端,记为一次冲突事件。
self.collision_count += 1
max_pos, sinr_db = self._calculate_sinr(idx)
if self.Mstate[idx[max_pos]] == TRANSMIT:
# 先默认全部冲突,再根据 SINR 门限恢复最强终端为成功发送状态。
self.Mstate[idx] = COLLISION
if sinr_db >= self.tcn:
self.Mstate[idx[max_pos]] = TRANSMIT
self.capture_success_count += 1
def _carrier_sense(self, now_time: float) -> int:
"""np-CSMA 的载波侦听功能。
根据各终端最近发送开始时间 Mstime 判断当前时刻是否落在某次传输窗口内。
Dtime * Ttime 用于模拟传播/侦听延迟,使刚开始的发送不会立即被侦听到。
返回 1 表示信道忙,返回 0 表示信道空闲。
"""
if self.Mstime is None or self.Mstime.size == 0:
return 0
delay = self.Dtime * self.Ttime
transmitting = np.where(
(self.Mstime + delay <= now_time)
& (now_time <= self.Mstime + delay + self.Ttime)
)[0]
return 1 if len(transmitting) > 0 else 0
def _pure_aloha_protocol(self, now_time: float) -> float:
"""纯 ALOHA 协议的单步事件处理。
该函数每次处理当前 now_time 对应的所有终端事件,并返回下一次事件时间。
Pure ALOHA 不做时隙对齐,也不做发送前侦听;分组一到达就尝试发送。
"""
if now_time < 0: # 初始化
self.mgtime = np.array(
[self._next_packet_time(i, 0.0) for i in range(self.Mnum)]
)
self.mtime = self.mgtime.copy()
self.Mstate = np.zeros(self.Mnum, dtype=int)
self.Mstime = np.full(self.Mnum, -np.inf)
return np.min(self.mtime)
# 处理成功传输:上一轮被判定为 TRANSMIT 的终端在发送结束时累计吞吐量。
idx_success = np.where((self.mtime == now_time) & (self.Mstate == TRANSMIT))[0]
if len(idx_success) > 0:
idx = idx_success[0]
self.Spnum += 1
self.Splen += self.Mplen[idx]
self.Wtime += now_time - self.mgtime[idx]
self.Mstate[idx] = STANDBY
self.mgtime[idx] = self._next_packet_time(idx, now_time)
self.mtime[idx] = self.mgtime[idx]
# 处理冲突:冲突终端不计成功,按指数退避重新安排发送时间。
idx_collision = np.where((self.mtime == now_time) & (self.Mstate == COLLISION))[
0
]
if len(idx_collision) > 0:
self.Mstate[idx_collision] = STANDBY
self.mtime[idx_collision] = now_time - self.Rint * np.log(
1 - np.random.rand(len(idx_collision))
)
# 处理发送:到达当前事件时间的终端立即占用信道直到分组发送完成。
idx_send = np.where(self.mtime == now_time)[0]
if len(idx_send) > 0:
self.Mstate[idx_send] = TRANSMIT
self.mtime[idx_send] = now_time + self.Mplen[idx_send] / self.sym_rate
self.Mstime[idx_send] = now_time
self.Tplen += np.sum(self.Mplen[idx_send])
return np.min(self.mtime)
def _slotted_aloha_protocol(self, now_time: float) -> float:
"""Slotted ALOHA 协议的单步事件处理。
与 Pure ALOHA 的主要区别是所有发送开始时间被对齐到下一个时隙边界。
时隙长度等于一个分组持续时间,因此冲突只会发生在同一时隙内。
"""
if self.slot is None:
self.slot = self.packet_length / self.sym_rate
if now_time < 0: # 初始化
self.mgtime = np.array(
[self._next_packet_time(i, 0.0) for i in range(self.Mnum)]
)
self.mtime = (np.floor(self.mgtime / self.slot) + 1) * self.slot
self.Mstate = np.zeros(self.Mnum, dtype=int)
self.Mstime = np.full(self.Mnum, -np.inf)
return np.min(self.mtime)
# 处理成功传输
idx_success = np.where((self.mtime == now_time) & (self.Mstate == TRANSMIT))[0]
if len(idx_success) > 0:
idx = idx_success[0]
self.Spnum += 1
self.Splen += self.Mplen[idx]
self.Wtime += now_time - self.mgtime[idx]
self.Mstate[idx] = STANDBY
self.mgtime[idx] = self._next_packet_time(idx, now_time)
self.mtime[idx] = (np.floor(self.mgtime[idx] / self.slot) + 1) * self.slot
# 处理冲突
idx_collision = np.where((self.mtime == now_time) & (self.Mstate == COLLISION))[
0
]
if len(idx_collision) > 0:
self.Mstate[idx_collision] = STANDBY
self.mtime[idx_collision] = now_time - self.Rint * np.log(
1 - np.random.rand(len(idx_collision))
)
self.mtime[idx_collision] = (
np.floor(self.mtime[idx_collision] / self.slot) + 1
) * self.slot
# 处理发送
idx_send = np.where(self.mtime == now_time)[0]
if len(idx_send) > 0:
self.Mstate[idx_send] = TRANSMIT
self.mtime[idx_send] = now_time + self.Mplen[idx_send] / self.sym_rate
self.mtime[idx_send] = (
np.round(self.mtime[idx_send] / self.slot) * self.slot
)
self.Mstime[idx_send] = now_time
self.Tplen += np.sum(self.Mplen[idx_send])
return np.min(self.mtime)
def _np_csma_protocol(self, now_time: float) -> float:
"""非坚持 CSMA 协议的单步事件处理。
终端有分组待发时先进行载波侦听。若信道空闲则立即发送;
若信道忙则按指数退避等待下一次尝试。该实现体现的是
non-persistent CSMA 的“忙则退避,不持续监听”策略。
"""
if now_time < 0: # 初始化
self.mgtime = np.array(
[self._next_packet_time(i, 0.0) for i in range(self.Mnum)]
)
self.Mstime = np.full(self.Mnum, -np.inf)
self.mtime = self.mgtime.copy()
self.Mstate = np.zeros(self.Mnum, dtype=int)
return np.min(self.mtime)
# 处理成功传输
idx_success = np.where((self.mtime == now_time) & (self.Mstate == TRANSMIT))[0]
if len(idx_success) > 0:
idx = idx_success[0]
self.Spnum += 1
self.Splen += self.Mplen[idx]
self.Wtime += now_time - self.mgtime[idx]
self.Mstate[idx] = STANDBY
self.mgtime[idx] = self._next_packet_time(idx, now_time)
self.mtime[idx] = self.mgtime[idx]
# 处理冲突
idx_collision = np.where((self.mtime == now_time) & (self.Mstate == COLLISION))[
0
]
if len(idx_collision) > 0:
self.Mstate[idx_collision] = STANDBY
self.mtime[idx_collision] = now_time - self.Rint * np.log(
1 - np.random.rand(len(idx_collision))
)
# 处理待发送
idx_standby = np.where((self.mtime == now_time) & (self.Mstate == STANDBY))[0]
if len(idx_standby) > 0:
self.Tplen += np.sum(self.Mplen[idx_standby])
for jj in idx_standby:
if self._carrier_sense(now_time) == 0: # 信道空闲
self.Mstate[jj] = TRANSMIT
self.Mstime[jj] = now_time
self.mtime[jj] = now_time + self.Mplen[jj] / self.sym_rate
else: # 信道忙
self.mtime[jj] = now_time - self.Rint * np.log(1 - random.random())
return np.min(self.mtime)
def _run_single_simulation(self, protocol_func, G: float) -> Dict[str, Any]:
"""运行单个业务负载 G 下的一次完整仿真。
主循环采用离散事件推进:协议函数负责处理当前事件并返回下一事件时间;
运动模型在事件处理前更新终端位置;信道模型在事件处理后判断当前重叠发送。
当成功分组数达到 spend 时停止,并返回吞吐量、冲突次数、平均等待时间等指标。
"""
# 重置状态
self._reset_states()
# 计算参数。
# G 是归一化业务负载。Tint 由泊松到达模型反推得到,用于控制平均到达间隔。
self.Tint = -self.Ttime / np.log(1 - G / self.Mnum)
self.Rint = self.Tint
# 改进1:每次仿真根据 traffic_mode 初始化普通/突发业务源。
self._init_traffic_state()
# 改进2:每次仿真重新初始化运动模型状态。
self._init_mobility()
# 初始化终端
now_time = protocol_func(-1)
# 主仿真循环。每次迭代只推进到下一个离散事件,而不是按固定时间步扫描。
while True:
# 改进2:在每个事件处理前,根据当前仿真时间更新终端位置。
self._update_positions(now_time)
next_time = protocol_func(now_time)
if self.Spnum >= self.spend:
break
# 检查传输状态。TRANSMIT 和 COLLISION 都代表当前有信号占用信道,
# 因此都需要进入信道判决函数。
idx = np.where((self.Mstate == TRANSMIT) | (self.Mstate == COLLISION))[0]
# 改进3:统一使用信道处理函数完成冲突判断和 SINR 捕获。
self._resolve_channel(idx)
now_time = next_time
# 计算统计量
traffic = self.Tplen / self.sym_rate / now_time
throughput = self.Splen / self.sym_rate / now_time
return {
"G": G,
"traffic": traffic,
"throughput": throughput,
"total_time": now_time,
"success_packets": self.Spnum,
# 新增统计指标,用于报告中分析冲突、时延和捕获效果。
"collision_count": self.collision_count,
"avg_delay": self.Wtime / max(self.Spnum, 1),
"capture_success_count": self.capture_success_count,
}
def _get_protocol_function(self, protocol_idx: int):
"""获取协议对应的仿真函数"""
if protocol_idx == 0:
return self._pure_aloha_protocol
elif protocol_idx == 1:
return self._slotted_aloha_protocol
else: # protocol_idx == 2
return self._np_csma_protocol
def simulate_single_protocol(self, protocol_idx: int) -> List[Dict[str, Any]]:
"""仿真单个协议在多个业务负载 G 下的性能曲线。
G_values 对应报告中的吞吐量-负载曲线横轴。这里已移除理论值计算,
只保留仿真测得的实际业务负载和吞吐量。
"""
# 获取协议函数
protocol_func = self._get_protocol_function(protocol_idx)
if protocol_idx == 2: # np-CSMA需要重置时隙参数
self.slot = None
results = []
# 业务负载循环
G_values = list(np.arange(0.1, 1.1, 0.1)) + list(np.arange(1.2, 2.1, 0.2))
for i, G in enumerate(G_values):
if G >= self.Mnum:
break
# 运行仿真
result = self._run_single_simulation(protocol_func, G)
results.append(result)
return results
def simulate_all_protocols(self) -> Dict[str, List[Dict[str, Any]]]:
"""依次仿真所有三种协议,并打印每种协议的耗时。"""
all_results = {}
for i, protocol_name in enumerate(self.protocols):
start_time = time.time()
results = self.simulate_single_protocol(i)
elapsed_time = time.time() - start_time
all_results[protocol_name] = results
print(f"{protocol_name} 仿真完成,耗时: {elapsed_time:.2f} 秒")
return all_results
def config_summary(self) -> Dict[str, Any]:
"""返回当前实验组的关键参数。
该字典会同时写入 JSON、Markdown 和终端输出,是复现实验配置的主要依据。
"""
return {
"Mnum": self.Mnum,
"spend": self.spend,
"traffic_mode": self.traffic_mode,
"mobility_model": self.mobility_model,
"mobility_enabled": self.mobility_enabled,
"channel_model": self.channel_model,
"shadow_std_db": self.shadow_std_db,
"burst_ratio": self.burst_ratio,
"burst_factor": self.burst_factor,
"waypoint_min_speed": self.waypoint_min_speed,
"waypoint_max_speed": self.waypoint_max_speed,
"waypoint_max_pause_time": self.waypoint_max_pause_time,
"mobility_update_interval": self.mobility_update_interval,
"radius_m": self.r,
"tx_power_dbm": self.tpowdBm,
"path_loss_exponent": self.alfa,
"packet_length_symbols": self.packet_length,
"symbol_rate": self.sym_rate,
"sinr_threshold_db": self.tcn,
}
def protocol_file_name(protocol_name: str) -> str:
"""将协议名称转换成适合文件名使用的形式。"""
return protocol_name.replace(" ", "_")
def to_jsonable(value: Any) -> Any:
"""将 numpy 类型转成 JSON 可序列化的 Python 原生类型。
仿真结果中包含 numpy 标量和数组,`json.dumps` 不能直接处理这些类型。
该函数递归遍历 dict/list/tuple,把 numpy 类型转换为 int、float 或 list。
"""
if isinstance(value, dict):
return {key: to_jsonable(item) for key, item in value.items()}
if isinstance(value, list):
return [to_jsonable(item) for item in value]
if isinstance(value, tuple):
return [to_jsonable(item) for item in value]
if isinstance(value, np.integer):
return int(value)
if isinstance(value, np.floating):
return float(value)
if isinstance(value, np.ndarray):
return value.tolist()
return value
def compute_statistics(
results: Dict[str, List[Dict[str, Any]]],
) -> Dict[str, Dict[str, Any]]:
"""按协议汇总统计指标。
输入是一组实验中三种协议的原始曲线数据,输出是报告和柱状图需要的
汇总指标,包括平均吞吐量、最大吞吐量、最佳 G、总冲突次数、
平均等待时间和捕获成功次数。
"""
statistics = {}
for protocol_name, protocol_results in results.items():
# 吞吐量数组用于查找最大值和波动情况。
throughputs = np.array(
[row["throughput"] for row in protocol_results], dtype=float
)
best_idx = int(np.argmax(throughputs))
best_row = protocol_results[best_idx]
statistics[protocol_name] = {
"avg_throughput": float(np.mean(throughputs)),
"max_throughput": float(np.max(throughputs)),
"min_throughput": float(np.min(throughputs)),
"std_throughput": float(np.std(throughputs)),
"best_G": float(best_row["G"]),
"best_traffic": float(best_row["traffic"]),
"total_collision_count": int(
sum(row["collision_count"] for row in protocol_results)
),
"mean_avg_delay": float(
np.mean([row["avg_delay"] for row in protocol_results])
),
"total_capture_success_count": int(
sum(row["capture_success_count"] for row in protocol_results)
),
}
return statistics
def build_comparison(experiments: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
"""生成 baseline 与 enhanced 的对比统计。
该函数不重新计算原始曲线,只比较两组实验已经汇总出的 statistics。
结果按协议组织,方便 Markdown 中按协议展示,也方便生成总对比表。
"""
baseline_stats = experiments["baseline"]["statistics"]
enhanced_stats = experiments["enhanced"]["statistics"]
by_protocol = {}
for protocol_name in baseline_stats:
base = baseline_stats[protocol_name]
enhanced = enhanced_stats[protocol_name]
max_delta = enhanced["max_throughput"] - base["max_throughput"]
by_protocol[protocol_name] = {
"baseline_max_throughput": base["max_throughput"],
"enhanced_max_throughput": enhanced["max_throughput"],
"max_throughput_delta": max_delta,
"max_throughput_delta_pct": (max_delta / base["max_throughput"] * 100)
if base["max_throughput"]
else None,
"baseline_collision_count": base["total_collision_count"],
"enhanced_collision_count": enhanced["total_collision_count"],
"collision_count_delta": enhanced["total_collision_count"]
- base["total_collision_count"],
"baseline_mean_avg_delay": base["mean_avg_delay"],
"enhanced_mean_avg_delay": enhanced["mean_avg_delay"],
"mean_avg_delay_delta": enhanced["mean_avg_delay"] - base["mean_avg_delay"],
"baseline_capture_success_count": base["total_capture_success_count"],
"enhanced_capture_success_count": enhanced["total_capture_success_count"],
"capture_success_count_delta": enhanced["total_capture_success_count"]
- base["total_capture_success_count"],
}
return {"by_protocol": by_protocol}
def result_rows(
group_name: str, results: Dict[str, List[Dict[str, Any]]]
) -> List[Dict[str, Any]]:
"""将单组实验的曲线结果展开成表格行。
每一行对应“实验组 + 协议 + 一个业务负载 G”的结果,供终端输出、
Markdown 表格和后续 DataFrame 转换复用。
"""
rows = []
for protocol_name, protocol_results in results.items():
for row in protocol_results:
rows.append(
{
"实验组": group_name,
"协议": protocol_name,
"业务负载(G)": row["G"],
"实际业务负载": row["traffic"],
"吞吐量": row["throughput"],
"总时间(秒)": row["total_time"],
"成功包数": row["success_packets"],
"冲突次数": row["collision_count"],
"平均等待时间": row["avg_delay"],
"捕获成功次数": row["capture_success_count"],
}
)
return rows
def all_results_dataframe(simulation_data: Dict[str, Any]) -> pd.DataFrame:
"""将所有实验组的原始曲线结果合并为一个 DataFrame。"""
rows = []
for group_name, experiment in simulation_data["experiments"].items():
rows.extend(result_rows(group_name, experiment["results"]))
return pd.DataFrame(rows)
def statistics_dataframe(simulation_data: Dict[str, Any]) -> pd.DataFrame:
"""将 baseline/enhanced 的统计指标整理成 DataFrame。"""
rows = []
for group_name, experiment in simulation_data["experiments"].items():
for protocol_name, stats in experiment["statistics"].items():
rows.append(
{
"实验组": group_name,
"协议": protocol_name,
"平均吞吐量": stats["avg_throughput"],
"最大吞吐量": stats["max_throughput"],
"最小吞吐量": stats["min_throughput"],
"吞吐量标准差": stats["std_throughput"],
"最佳G": stats["best_G"],
"最佳实际业务负载": stats["best_traffic"],
"冲突次数": stats["total_collision_count"],
"平均等待时间": stats["mean_avg_delay"],
"捕获成功次数": stats["total_capture_success_count"],
}
)
return pd.DataFrame(rows)
def comparison_dataframe(simulation_data: Dict[str, Any]) -> pd.DataFrame:
"""将 baseline 与 enhanced 的差值指标整理成 DataFrame。"""
rows = []
for protocol_name, comparison in simulation_data["comparison"][
"by_protocol"
].items():
rows.append(
{
"协议": protocol_name,
"baseline最大吞吐量": comparison["baseline_max_throughput"],
"enhanced最大吞吐量": comparison["enhanced_max_throughput"],
"最大吞吐量差值": comparison["max_throughput_delta"],
"最大吞吐量变化(%)": comparison["max_throughput_delta_pct"],
"baseline冲突次数": comparison["baseline_collision_count"],
"enhanced冲突次数": comparison["enhanced_collision_count"],
"冲突次数差值": comparison["collision_count_delta"],
"baseline平均等待时间": comparison["baseline_mean_avg_delay"],
"enhanced平均等待时间": comparison["enhanced_mean_avg_delay"],
"平均等待时间差值": comparison["mean_avg_delay_delta"],
"baseline捕获成功次数": comparison["baseline_capture_success_count"],
"enhanced捕获成功次数": comparison["enhanced_capture_success_count"],
"捕获成功次数差值": comparison["capture_success_count_delta"],
}
)
return pd.DataFrame(rows)
def format_number(value: Any) -> str:
"""统一终端和 Markdown 表格中的数字显示格式。"""
if value is None:
return ""
if isinstance(value, (bool, np.bool_)):
return str(bool(value))
if isinstance(value, (int, np.integer)):
return str(int(value))
if isinstance(value, (float, np.floating)):
return f"{float(value):.6f}"
return str(value)
def format_table_value(header: str, value: Any) -> str:
"""按列名决定表格单元格格式。
计数字段必须显示为整数;吞吐量、时间等连续值保留小数。
"""
integer_columns = {"成功包数", "冲突次数", "捕获成功次数", "Mnum", "spend"}
if header in integer_columns and value is not None:
return str(int(round(float(value))))
return format_number(value)
def markdown_table(headers: List[str], rows: List[List[Any]]) -> str:
"""根据表头和二维数据生成 GitHub 风格 Markdown 表格。"""
output = [
"| " + " | ".join(headers) + " |",
"| " + " | ".join(["---"] * len(headers)) + " |",
]
for row in rows:
output.append(
"| "
+ " | ".join(
format_table_value(header, item) for header, item in zip(headers, row)
)
+ " |"
)
return "\n".join(output)
def dataframe_to_markdown(df: pd.DataFrame) -> str:
"""将 DataFrame 转换为 Markdown 表格字符串。"""
rows = df.values.tolist()
return markdown_table(list(df.columns), rows)
def aloha_theory_curve(protocol_name: str, g_values: np.ndarray):
"""返回 ALOHA 协议的经典理论吞吐量曲线。
np-CSMA 没有直接对应的 ALOHA 理论参考曲线,因此返回 None。
"""
if protocol_name == "Pure ALOHA":
return g_values * np.exp(-2 * g_values), r"理论值 $S=Ge^{-2G}$"
if protocol_name == "Slotted ALOHA":
return g_values * np.exp(-g_values), r"理论值 $S=Ge^{-G}$"
return None
def print_dataframe(title: str, df: pd.DataFrame):
"""在终端打印一个带标题的 DataFrame。
终端输出与 Markdown/JSON 使用同一批数据源,避免不同输出之间出现口径差异。
"""
print(f"\n{title}")
print("-" * len(title))
if df.empty:
print("(无数据)")
return
print(
df.to_string(
index=False,
formatters={
col: format_number for col in df.columns if df[col].dtype.kind in "fiu"
},
)
)
def plot_single_group_figures(
simulation_data: Dict[str, Any], figures_dir: Path
) -> List[str]:
"""绘制每个实验组、每个协议的单独吞吐量-负载曲线。
输出数量为 2 组实验 x 3 种协议,共 6 张图。每张图只绘制一个实验组下
一个 MAC 协议的仿真结果;Pure ALOHA 和 Slotted ALOHA 额外叠加对应的
经典理论吞吐量曲线,np-CSMA 不叠加 ALOHA 理论曲线。
"""
simulation_color = "#2563EB"
theory_color = "#4B5563"
figure_paths = []
for group_name, experiment in simulation_data["experiments"].items():
for protocol_name, results in experiment["results"].items():
df = pd.DataFrame(results)
fig, ax = plt.subplots(figsize=(9, 6))
ax.plot(
df["G"],
df["throughput"],
color=simulation_color,
marker="o",
linestyle="-",
linewidth=2.2,
markersize=5.5,
label="仿真结果",
)
theory_g = np.linspace(0, 2.2, 300)
theory_curve = aloha_theory_curve(protocol_name, theory_g)
if theory_curve is not None:
theory_s, theory_label = theory_curve
ax.plot(
theory_g,
theory_s,
color=theory_color,
linestyle="--",
linewidth=2.0,
label=theory_label,
)
ax.set_title(f"{group_name} - {protocol_name} 吞吐量-负载曲线")
ax.set_xlabel("业务负载 G")
ax.set_ylabel("吞吐量 S")
ax.set_xlim(0, 2.2)
ax.set_ylim(0, 0.8)
ax.legend()
fig.tight_layout()
output_path = (
figures_dir / f"{group_name}_{protocol_file_name(protocol_name)}.png"
)
fig.savefig(output_path, dpi=300, bbox_inches="tight")
plt.close(fig)
figure_paths.append(str(output_path))
return figure_paths
def plot_protocol_comparison_figures(
simulation_data: Dict[str, Any], figures_dir: Path
) -> List[str]:
"""绘制每个协议的 baseline vs enhanced 对比曲线。
输出数量为 3 张图,每张图只比较一种协议在两组实验中的吞吐量变化。
"""
figure_paths = []
for protocol_name in simulation_data["metadata"]["protocols"]:
rows = []
for group_name, experiment in simulation_data["experiments"].items():
for result in experiment["results"][protocol_name]:
rows.append(
{
"实验组": group_name,
"业务负载 G": result["G"],
"吞吐量 S": result["throughput"],
}
)
df = pd.DataFrame(rows)
fig, ax = plt.subplots(figsize=(9, 6))
sns.lineplot(
data=df,
x="业务负载 G",
y="吞吐量 S",
hue="实验组",
style="实验组",
markers=True,
dashes=False,
linewidth=2.2,
ax=ax,
)
ax.set_title(f"{protocol_name} baseline vs enhanced 吞吐量-负载曲线")
ax.set_xlim(0, 2.2)
ax.set_ylim(0, 0.8)
fig.tight_layout()
output_path = (
figures_dir
/ f"compare_{protocol_file_name(protocol_name)}_baseline_vs_enhanced.png"
)
fig.savefig(output_path, dpi=300, bbox_inches="tight")
plt.close(fig)
figure_paths.append(str(output_path))
return figure_paths
def plot_all_protocols_comparison(
simulation_data: Dict[str, Any], figures_dir: Path
) -> str:
"""将三种协议、两组实验的吞吐量曲线绘制到同一张图中。
该图用于报告中的总览对比:颜色区分协议,线型/标记区分实验组。
"""
rows = []
for group_name, experiment in simulation_data["experiments"].items():
for protocol_name, results in experiment["results"].items():
for result in results:
rows.append(
{
"实验组": group_name,
"协议": protocol_name,
"业务负载 G": result["G"],
"吞吐量 S": result["throughput"],
}
)
df = pd.DataFrame(rows)
fig, ax = plt.subplots(figsize=(11, 7))
sns.lineplot(
data=df,
x="业务负载 G",
y="吞吐量 S",
hue="协议",
style="实验组",
markers=True,
linewidth=2.0,
ax=ax,
)
ax.set_title("baseline vs enhanced 吞吐量-负载曲线")
ax.set_xlim(0, 2.2)
ax.set_ylim(0, 0.8)
fig.tight_layout()
output_path = figures_dir / "compare_all_protocols_baseline_vs_enhanced.png"
fig.savefig(output_path, dpi=300, bbox_inches="tight")
plt.close(fig)
return str(output_path)
def plot_statistics_bar(simulation_data: Dict[str, Any], figures_dir: Path) -> str:
"""绘制统计指标分组柱状图。
三个子图分别展示最大吞吐量、冲突次数和平均等待时间;
每个子图中按协议分组,并用 hue 区分 baseline 与 enhanced。
"""
stats_df = statistics_dataframe(simulation_data)
metrics = [
("最大吞吐量", "最大吞吐量"),
("冲突次数", "冲突次数"),
("平均等待时间", "平均等待时间"),
]
fig, axes = plt.subplots(1, 3, figsize=(16, 5))
for ax, (column, title) in zip(axes, metrics):
sns.barplot(data=stats_df, x="协议", y=column, hue="实验组", ax=ax)
ax.set_title(title)
ax.set_xlabel("协议")
ax.set_ylabel(title)
ax.tick_params(axis="x", rotation=15)
fig.suptitle("统计指标对比")
fig.tight_layout()
output_path = figures_dir / "compare_statistics_bar.png"
fig.savefig(output_path, dpi=300, bbox_inches="tight")
plt.close(fig)
return str(output_path)
def generate_figures(simulation_data: Dict[str, Any], output_dir: Path) -> List[str]:
"""生成报告要求的全部图像,并返回相对输出目录的路径列表。"""
figures_dir = output_dir / "figures"
figures_dir.mkdir(parents=True, exist_ok=True)
figure_paths = []
figure_paths.extend(plot_single_group_figures(simulation_data, figures_dir))
figure_paths.extend(plot_protocol_comparison_figures(simulation_data, figures_dir))
figure_paths.append(plot_all_protocols_comparison(simulation_data, figures_dir))
figure_paths.append(plot_statistics_bar(simulation_data, figures_dir))
return [str(Path(path).relative_to(output_dir)) for path in figure_paths]
def write_json_report(simulation_data: Dict[str, Any], output_dir: Path):
"""写出完整 JSON 结果。
JSON 保留原始曲线、统计指标、对比指标、配置和图片路径,适合后续二次分析。
"""
output_path = output_dir / "simulation_results.json"
output_path.write_text(
json.dumps(to_jsonable(simulation_data), ensure_ascii=False, indent=2),
encoding="utf-8",
)
def write_markdown_report(simulation_data: Dict[str, Any], output_dir: Path):
"""写出 Markdown 结果报告。
Markdown 按一级标题区分 Baseline、Enhanced、Baseline vs Enhanced 和 Figures,
按二级标题区分参数、协议结果、统计指标和对比表,便于直接复制到实验报告。
"""
lines = [
"# 仿真实验结果",
"",
f"生成时间:{simulation_data['metadata']['generated_at']}",
"",
]
for group_title, group_name in [("Baseline", "baseline"), ("Enhanced", "enhanced")]:
experiment = simulation_data["experiments"][group_name]
lines.extend([f"# {group_title}", "", "## 参数设置", ""])
config_rows = [[key, value] for key, value in experiment["config"].items()]
lines.extend([markdown_table(["参数", "值"], config_rows), ""])
for protocol_name, results in experiment["results"].items():
lines.extend([f"## {protocol_name} Results", ""])
df = pd.DataFrame(result_rows(group_name, {protocol_name: results})).drop(
columns=["实验组", "协议"]
)
lines.extend([dataframe_to_markdown(df), ""])
lines.extend(["## Statistics", ""])
stats_rows = []
for protocol_name, stats in experiment["statistics"].items():
stats_rows.append(
[
protocol_name,
stats["avg_throughput"],
stats["max_throughput"],
stats["best_G"],
stats["total_collision_count"],
stats["mean_avg_delay"],
stats["total_capture_success_count"],
]
)
lines.extend(
[
markdown_table(
[
"协议",
"平均吞吐量",
"最大吞吐量",
"最佳G",
"冲突次数",
"平均等待时间",
"捕获成功次数",
],
stats_rows,
),
"",
]
)
lines.extend(["# Baseline vs Enhanced", ""])
comparison_df = comparison_dataframe(simulation_data)
for protocol_name in simulation_data["metadata"]["protocols"]:
lines.extend([f"## {protocol_name} 对比", ""])
protocol_df = comparison_df[comparison_df["协议"] == protocol_name]
lines.extend([dataframe_to_markdown(protocol_df), ""])
lines.extend(["## 统计指标对比", "", dataframe_to_markdown(comparison_df), ""])
lines.extend(["# Figures", ""])
for figure in simulation_data["figures"]:
lines.append(f"- {figure}")
lines.append("")
(output_dir / "simulation_report.md").write_text("\n".join(lines), encoding="utf-8")
def print_terminal_report(simulation_data: Dict[str, Any]):
"""在终端一次性输出 baseline、enhanced 和两组对比结果。"""
print("\n" + "=" * 80)
print("baseline 和 enhanced 仿真实验结果")
print("=" * 80)
for group_name in ["baseline", "enhanced"]:
experiment = simulation_data["experiments"][group_name]
print(f"\n# {group_name}")
config_df = pd.DataFrame(
[{"参数": key, "值": value} for key, value in experiment["config"].items()]
)
print_dataframe("参数设置", config_df)
for protocol_name, results in experiment["results"].items():
df = pd.DataFrame(result_rows(group_name, {protocol_name: results})).drop(
columns=["实验组", "协议"]
)
print_dataframe(f"{protocol_name} Results", df)
stats_df = statistics_dataframe({"experiments": {group_name: experiment}})
print_dataframe(f"{group_name} Statistics", stats_df)
print_dataframe("Baseline vs Enhanced 对比", comparison_dataframe(simulation_data))
print("\n输出文件")
print(f"- {simulation_data['metadata']['output_dir']}/simulation_results.json")
print(f"- {simulation_data['metadata']['output_dir']}/simulation_report.md")
for figure in simulation_data["figures"]:
print(f"- {simulation_data['metadata']['output_dir']}/{figure}")
def main():
"""运行基线组和增强组两组对比实验。
主函数负责命令行参数解析、随机种子设置、输出目录创建、两组实验执行、
数据汇总、绘图和结果写出。具体协议行为由 WirelessNetworkSimulator 完成。
"""
parser = argparse.ArgumentParser(description="无线随机接入协议仿真")
parser.add_argument("--mnum", type=int, default=100, help="终端数量")
parser.add_argument(
"--spend", type=int, default=10000, help="每个业务负载下的成功包数"
)
parser.add_argument(
"--output-root", type=Path, default=Path("outputs"), help="结果输出根目录"
)
parser.add_argument("--seed", type=int, default=None, help="随机种子")
args = parser.parse_args()
if args.seed is not None:
random.seed(args.seed)
np.random.seed(args.seed)
setup_chinese_font()
timestamp = time.strftime("%Y%m%d_%H%M%S")
output_dir = args.output_root / timestamp
output_dir.mkdir(parents=True, exist_ok=True)
print("=" * 60)
print("无线随机接入协议仿真:基线模型与增强模型对比")
print("=" * 60)
print(f"输出目录: {output_dir}")
# baseline 和 enhanced 使用同一套 SINR 判决,区别在业务源、运动模型和信道衰落层次。
experiment_groups = [
# 基线组:原始泊松业务、静态终端、路径损耗 + 6 dB 阴影衰落。
(
"baseline",
{
"traffic_mode": "poisson",
"mobility_model": "static",
"channel_model": "log_distance",
"shadow_std_db": 6.0,
},
),
# 增强组:混合突发业务、随机路点、路径损耗 + 6 dB 阴影衰落 + Rayleigh 快衰落。
(
"enhanced",
{
"traffic_mode": "mixed",
"mobility_model": "random_waypoint",
"channel_model": "rayleigh",
"shadow_std_db": 6.0,
"mobility_update_interval": 0.01,
},
),
]
simulation_data = {
"metadata": {
"generated_at": time.strftime("%Y-%m-%d %H:%M:%S"),
"output_dir": str(output_dir),
"protocols": ["Pure ALOHA", "Slotted ALOHA", "np-CSMA"],
"Mnum": args.mnum,
"spend": args.spend,
"seed": args.seed,
},
"experiments": {},
"comparison": {},
"figures": [],
}
for group_name, config in experiment_groups:
print(f"\n正在运行 {group_name} 实验")
# 根据实验组配置创建仿真器,保持主流程一致,便于对比。
simulator = WirelessNetworkSimulator(
Mnum=args.mnum, spend=args.spend, result_prefix=group_name, **config
)
start_total_time = time.time()
all_results = simulator.simulate_all_protocols()
total_elapsed_time = time.time() - start_total_time
print(f"{group_name} 实验完成,耗时: {total_elapsed_time:.2f} 秒")
simulation_data["experiments"][group_name] = {
"config": simulator.config_summary(),
"elapsed_time": total_elapsed_time,
"results": all_results,
"statistics": compute_statistics(all_results),
}
simulation_data["comparison"] = build_comparison(simulation_data["experiments"])
simulation_data["figures"] = generate_figures(simulation_data, output_dir)
write_json_report(simulation_data, output_dir)
write_markdown_report(simulation_data, output_dir)
print_terminal_report(simulation_data)
print("\n所有实验已完成。")
if __name__ == "__main__":
main()
python