简介:基于Matlab与Python的无线传感器网络仿真资源,面向网络仿真研究、课程设计与竞赛准备人群,覆盖五种网络拓扑生成、基于RSSI测距的多边定位、CSMA/CA五种退避策略对比、粒子群算法优化节点覆盖率、改进LEACH路由协议与经典算法对比等实验。包内共85个文件,包含17个py、12个m源码,34个png结果图,6个md说明文档,另有mat数据文件等,压缩包仅1.46MB,轻量易用。目前已有602人学习。资源按RSSI_positioning、Network_topology、MAC协议实验、节点覆盖实验、路由协议实验等模块清晰组织,每个模块都配有源码、数据与说明,便于直接运行、修改参数和复现不同效果。对于希望快速上手WSN仿真、对比多种策略性能的读者,这是一份完整可扩展的实验框架。
1. 五种拓扑一次生成:这个无线传感器网络仿真项目到底解决什么
做无线传感器网络仿真的人,最烦的不是算法跑不动,而是连“对照组拓扑”都要手工搭:论文要求在同一场景下对比星型、树型、网状、环形、簇型五张网络图,可现实是每换一种拓扑就要重新调一遍坐标、改一遍连接、查一遍连通性,一个不留神还会连出只有图片效果、实际没法跑协议的假网络。
这个项目就是把拓扑搭建从“手工作坊”变成流水线:Matlab 侧负责用矩阵运算生成五种拓扑的节点坐标与邻接矩阵,Python 侧负责把 NetworkX 图结构画出来,并汇总连通度、平均跳数、能耗等指标;数据文件统一落盘为 CSV,两边各读各的,互不干扰。整个流程跑完,五张拓扑图加一张指标表同时到手,后续路由协议直接读邻接矩阵,不用再碰坐标细节。
它适合正在做路由协议对比实验、毕业设计需要拓扑场景支撑、或准备投稿论文但缺一套标准仿真底图的读者。哪怕你只是刚把 pandas 和 matplotlib 装好、还在纠结怎么把一张图画出五种效果,这套方案也能让你少走不少弯路。
2. 为什么用 Matlab 加 Python:分工边界、目录结构与统一数据格式
先说结论:这个组合不是为了让项目显得“技术栈全”,而是 WSN 仿真这件事本身就被天然切成了两半——一半是矩阵计算,一半是图论与可视化。Matlab 强在矩阵运算和通信仿真生态,Python 强在 NetworkX 和 Matplotlib 这类分析绘图工具链,两者通过 CSV 做数据交换,正好各干各擅长的活。
2.1 Matlab 出数学模型,Python 出分析图:这个分工怎么来的
无线传感器网络拓扑生成,本质上是在做矩阵运算。一个 n 节点网络的连接关系就是一个 n×n 的邻接矩阵,节点的几何布局就是两个 n×1 的坐标向量。Matlab 处理这类数据几乎不用写显式循环,一条向量化的表达式就能把全网的邻居关系算完,这对生成随机部署和分层拓扑来说非常顺手。
Python 这边的角色是“把矩阵变成能放进论文的图和能写进实验分析的指标”。NetworkX 把大多数图论算法都封装好了:连通分量、最短路径、节点度分布、平均跳数,全都是一两行调用。如果你非要让 Matlab 去画网络图,也不是不行,但交互式调整布局、批量导出 PNG、做数据聚合这些事,体验明显差一截。所以常见做法是:Matlab 算好一切数据,Python 只负责读取、画图、算指标。
需要提醒的是,Matlab 侧尽量用 R2020 之后的版本,脚本里大多数函数对老版本也能兼容;Python 侧先确认你已经完成了 python 安装,并装好 numpy、pandas、networkx、matplotlib 这几个库,缺哪个补哪个,版本没有太苛刻的要求,NetworkX 用 2.x 或 3.x 都行。
数据交换的接口,我一般这样定:节点坐标写成node_coords.csv,邻接矩阵写成adjacency_full.csv,所有拓扑共享同一套坐标底图,只换邻接关系。这样后面画图、对比、跑路由协议,动的是同一个输入文件,不会出现两套数据对不上的问题。
2.2 数据交换格式:节点坐标 + 邻接矩阵,一条 CSV 走天下
坐标文件是最底层的数据,五种拓扑都从它出发。生成方式很简单,随机撒点 + 固定随机种子。
% gen_coords.m n = 50; % 传感器节点数量 area = 100; % 仿真区域边长,单位米 rng(42); % 固定种子,保证复现 coords = area * rand(n, 2); T = table((1:n)', coords(:,1), coords(:,2), ... 'VariableNames', {'id', 'x', 'y'}); writetable(T, 'node_coords.csv'); fprintf('已生成 %d 个节点的坐标文件\n', n);这段代码里最值得说参数的是rng(42)和area。rng(42)锁死随机数生成器的状态,否则每次运行坐标全变,后面所有拓扑对比就失去意义;area = 100是 100 米 × 100 米的方形区域,这是 WSN 仿真里最常见的默认尺度,通信半径的取值会围绕这个尺度来定。用writetable而不是csvwrite写文件,是为了保留表头,Python 那边用 pandas 读进来时直接按列名取值,不容易把第一列节点编号误当成坐标。
Python 侧读取:
import pandas as pd df = pd.read_csv('node_coords.csv') x = df['x'].to_numpy() y = df['y'].to_numpy()下面生成全连通底图,也就是基于通信半径的邻居关系矩阵。这个矩阵是后面星型、树型、网状、簇型的“原材料”。
% gen_adjacency.m r = 25; % 通信半径,单位米 d2 = (coords(:,1) - coords(:,1)').^2 + ... (coords(:,2) - coords(:,2)').^2; adj = sqrt(d2) <= r; % 两点距离 <= r 就认为能直连 adj(1:n+1:end) = 0; % 对角线清零,节点不连自己 dlmwrite('adjacency_full.csv', adj, 'precision', '%d');逻辑本身一句话就能说清:先算所有节点对的欧氏距离矩阵,再跟通信半径比较,得到一个 0/1 矩阵。adj(1:n+1:end) = 0是把对角线位置全部置零,避免把“自己到自己”误算成一条边。这里用了dlmwrite并指定precision, '%d',因为邻接矩阵是整数矩阵,写成 0 和 1 就够了,别让 Matlab 默认的科学计数法格式把数据弄脏。
这三个参数要一起调:节点数n、区域边长area、通信半径r。在 100×100 区域里撒 50 个节点、取 25 米半径,平均每个节点的邻居数量大概在 8 到 10 个左右,整体图是连通的,也不会稠密到失去拓扑区分度。r 取太小,图会碎成好几块;取太大,五种拓扑生成出来全都接近全连通网,对比实验就没意义了。
2.3 目录结构:五个 Matlab 脚本对应五种拓扑
项目内部我建议按工具链和产物分成两个大目录,数据文件单独放一层。
| 目录/文件 | 作用 |
|---|---|
| matlab/gen_coords.m | 生成节点坐标 CSV |
| matlab/gen_adjacency.m | 生成通信半径底图邻接矩阵 |
| matlab/gen_star_topology.m | 生成星型拓扑 |
| matlab/gen_ring_topology.m | 生成环形拓扑 |
| matlab/gen_tree_topology.m | 生成树型拓扑 |
| matlab/gen_mesh_topology.m | 生成网状拓扑 |
| matlab/gen_cluster_topology.m | 生成簇型拓扑 |
| python/visualize_topology.py | 读取 CSV,绘制五张拓扑图 |
| python/compute_metrics.py | 计算连通度、跳数、能耗等指标 |
| data/ | 保存所有 CSV 中间产物 |
| output/ | 保存最终 PNG 拓扑图和指标表 |
约定好这个目录结构,最大的好处是 Matlab 和 Python 之间不需要人为传文件,跑完 Matlab 脚本直接进 Python 出图,整个流程可以写成一个 batch 脚本或者 Makefile 一条命令跑完。下面两章分别把这五个拓扑的生成脚本和 Python 可视化讲透。
3. 五种拓扑的生成逻辑:从几何布局到连接规则
五种拓扑不是五个互相独立的数学框架,而是同一套坐标底图 + 不同连接规则。这个思路非常重要:你不需要为每一种拓扑单独设计节点位置,很多连接规则甚至可以在生成完之后再覆盖原先的邻接矩阵。下面按从简单到复杂的顺序过一遍。
3.1 星型和环形:几何布局直接定连接
星型拓扑在无线传感器网络里就是“一个汇聚节点 + 一圈普通节点”,普通节点之间不允许直连,所有数据都先到汇聚节点再向外转发。实现时最直接的做法是把汇聚节点放在区域中心,其余节点均匀分布在半径 40 米的圆周上,然后只保留汇聚节点到每个普通节点的边。
% gen_star_topology.m n = 50; sink_idx = 1; radius = 40; % 圆周半径,要小于通信半径 theta = linspace(0, 2*pi, n)'; theta(1) = []; % 去掉第一个角度,避免和 sink 坐标重叠 coords_star = zeros(n, 2); coords_star(sink_idx, :) = [50, 50]; coords_star(2:end, 1) = 50 + radius * cos(theta(1:n-1)); coords_star(2:end, 2) = 50 + radius * sin(theta(1:n-1)); adj_star = zeros(n, n); adj_star(sink_idx, 2:end) = 1; adj_star(2:end, sink_idx) = 1; % 关键:普通节点之间即使物理距离足够近,也必须保持无连接这里有个容易翻车的细节:如果直接拿第一节里的邻居矩阵adjacency_full.csv来改,普通节点之间会因为距离小于通信半径而自动连上,星型就变成全网状了。所以星型拓扑一定要“从零构建邻接矩阵”,而不是在底图上去删边。
环形拓扑的几何布局则把 50 个节点均匀放在圆周上,每个节点只连前后相邻两个节点。相邻节点在圆周上的弧长是2*pi*radius/n,只要这个距离小于通信半径,链路就成立。
% gen_ring_topology.m radius = 45; theta = linspace(0, 2*pi, n+1)'; theta(end) = []; coords_ring = [50 + radius*cos(theta), 50 + radius*sin(theta)]; adj_ring = zeros(n, n); for i = 1:n j = mod(i, n) + 1; % 当前节点的下一个邻居 adj_ring(i, j) = 1; adj_ring(j, i) = 1; end这个 for 循环里只有一种邻居关系,边界情况由mod处理:第 n 个节点的下一个节点是第 1 个,正好首尾相连。如果你想要两跳可达的冗余环,可以把每个节点的连接数从 1 改成 2,也就是同时连前后各两个邻居,代码里再套一层循环即可。
3.2 树型拓扑:BFS 生成和层级深度控制
树型拓扑是无线传感器网络数据采集的标准形态:数据从叶子节点逐级向上转发,最终汇聚到根节点。生成树的关键在于“无环”和“全连通”,最稳妥的办法是广度优先搜索 BFS,从根节点出发逐层访问邻居,把第一次访问到的节点挂到当前节点下面。
% gen_tree_topology.m % 输入:adj 是通信半径底图邻接矩阵(adjacency_full.csv 读入) root = 1; n = size(adj, 1); visited = false(n, 1); parent = zeros(n, 1); adj_tree = zeros(n, n); queue = root; visited(root) = true; while ~isempty(queue) cur = queue(1); queue(1) = []; % 出队 nbrs = find(adj(cur, :)); for nb = nbrs if ~visited(nb) visited(nb) = true; parent(nb) = cur; adj_tree(cur, nb) = 1; adj_tree(nb, cur) = 1; queue(end+1) = nb; % 入队 end end end核心逻辑一句话:每个节点只被访问一次,访问它的时候,谁先访问到它,谁就是它的父节点。visited数组保证不会出现回边,所以生成的图天然无环;BFS 又保证所有可达节点都会被访问到,所以只要底图是连通的,生成树就覆盖全部节点。
实际仿真里你不会只想要一棵“随便长”的树,还得控制树的深度。很多时候 sink 节点需要在 3 到 5 跳之内收集完全部数据,所以要把深度参数显式加进去。
max_depth = 4; depth = zeros(n, 1); % 在 BFS 循环里加一层判断: % if ~visited(nb) && depth(cur) < max_depth % depth(nb) = depth(cur) + 1; % ... % end加了深度限制之后,如果底图连接比较稀疏,会有节点因为跳数受限而无法入树,这时要么调大max_depth,要么扩大通信半径让底图更稠密。树型拓扑的坐标布局我一般按层排列:根在第 0 层顶部,第 1 层在第一行,同一层的节点从左到右展开,这样画出来的图层次感非常强,也方便数跳数。
3.3 网状和簇型:连通度下限与簇头选举
网状拓扑的生成逻辑和星型相反:它希望网络尽可能多路径连通,保证单个节点失效后数据还能绕路。生成时最实用的办法是“每个节点至少连最近的 k 个邻居”,k 就是连通度下限。
% gen_mesh_topology.m k = 3; % 每个节点至少 3 条边 adj_mesh = zeros(n, n); for i = 1:n dist_i = sqrt(sum((coords - coords(i,:)).^2, 2)); [~, order] = sort(dist_i); cnt = 0; for j = order' if j == i continue; end adj_mesh(i, j) = 1; adj_mesh(j, i) = 1; cnt = cnt + 1; if cnt >= k break; end end end adj_mesh = max(adj_mesh, adj_mesh'); % 强制对称这里有一个值得注意的点:直接做“近邻连接”之后,整个图不一定是连通的,可能出现左侧一群节点和右侧一群节点各自成环、中间没有桥的情况。所以生成之后必须做连通性检查,如果all(visited)不为真,就要把 k 提高,或者在两个连通分量之间额外补一条最近边。max(adj_mesh, adj_mesh')这步不是多余的,因为循环里你给 i 连了 j,但给 j 循环时可能因为只取前 k 个邻居而没连回 i,最后会造成非对称矩阵,跑很多图算法都会出错。
簇型拓扑则模拟无线传感器网络里最经典的分簇结构:所有节点划分成若干个簇,每个簇内选一个簇头,普通节点只跟簇头通信,簇头之间再构建上层骨干网。这里最常见的手段是 K-means 聚类。
% gen_cluster_topology.m num_clusters = 5; [idx, cxy] = kmeans(coords, num_clusters); adj_cluster = zeros(n, n); cluster_head = zeros(num_clusters, 1); for ci = 1:num_clusters members = find(idx == ci); d_mem = sqrt(sum((coords(members,:) - cxy(ci,:)).^2, 2)); [~, mi] = min(d_mem); cluster_head(ci) = members(mi); % 簇内普通节点连接簇头 for m = members' if m ~= cluster_head(ci) adj_cluster(m, cluster_head(ci)) = 1; adj_cluster(cluster_head(ci), m) = 1; end end end % 簇头之间按距离互连 for a = 1:num_clusters for b = a+1:num_clusters if norm(cxy(a,:) - cxy(b,:)) <= 50 adj_cluster(cluster_head(a), cluster_head(b)) = 1; adj_cluster(cluster_head(b), cluster_head(a)) = 1; end end endK-means 的k要你事先给定,常见取值范围是 3 到 7,取决于总节点数和区域大小。簇头选取用的是“离质心最近”,这是最简单的静态选举法;如果你想模拟 LEACH 协议里的随机轮换簇头,可以把选簇头的逻辑换成每个节点生成一个随机数、簇内取最大者,这是后续做能耗均衡实验最常见的扩展点。
4. 用 Python 把拓扑画出来:NetworkX 可视化与指标计算
Matlab 端把五个adjacency_*.csv写完之后,Python 这边的工作就两件事:画图、算指标。这一章给的是可以直接复制的完整脚本,注意先确认你的环境里networkx已经装好。
4.1 一次画五张图:NetworkX 读取邻接矩阵
读取邻接矩阵用numpy.loadtxt就够,delimiter=','指定逗号分隔。读进来之后用nx.from_numpy_array转成 NetworkX 的图对象,这个对象后面画图、算指标都是同一个入口。
# visualize_topology.py import networkx as nx import matplotlib.pyplot as plt import numpy as np def adj_to_graph(csv_path): adj = np.loadtxt(csv_path, delimiter=',') return nx.from_numpy_array(adj) G = adj_to_graph('data/adjacency_mesh.csv') # 环形和簇型用固定布局,其它用 spring 布局 if 'ring' in csv_path: pos = nx.circular_layout(G) elif 'cluster' in csv_path: pos = nx.kamada_kawai_layout(G) else: pos = nx.spring_layout(G, seed=42, k=0.8) nx.draw_networkx(G, pos=pos, node_size=100, with_labels=False, edge_color='gray', node_color='steelblue') plt.axis('off') plt.savefig('output/topology_mesh.png', dpi=300)参数里最值得调的是spring_layout的k。它控制节点之间的斥力大小,k越大,节点分得越开,边越长;节点数超过 80 的时候,k取 0.5 到 1.0 之间比较合适,再大会让整张图超出画布边界。seed=42同样是为了布局可复现,不然每次运行节点位置都会漂移,论文里前后两张图对不上是很尴尬的事。
树型拓扑的布局要特殊处理,因为 NetworkX 没有内置“按树层排布”的布局函数。最简单的办法是自己写一个基于 BFS 深度的坐标生成函数,把每个节点放成(层内序号, -层数),这样根在顶部,子节点逐层向下展开。
def tree_layout(G, root=0): depth = {root: 0} queue = [root] while queue: cur = queue.pop(0) for nb in G.neighbors(cur): if nb not in depth: depth[nb] = depth[cur] + 1 queue.append(nb) layers = {} for node, d in depth.items(): layers.setdefault(d, []).append(node) pos = {} for d, nodes in layers.items(): for i, node in enumerate(nodes): pos[node] = (i, -d) return pos这段代码本质上就是第三节里 BFS 生成树的镜像,换到 Python 这边只是为了算坐标。注意queue.pop(0)只适合节点数少的情况,大批量节点时改成collections.deque避免弹出操作的开销。
4.2 指标计算:连通度、平均跳数、能耗预估
拓扑生成得对不对,不能只靠眼睛看图,要用数字说话。核心指标三个:平均度、连通分量数、平均最短路径跳数。前两个反映拓扑的健壮性,后一个直接关系到数据转发的延迟。
# compute_metrics.py import networkx as nx import numpy as np def topology_metrics(csv_path): G = nx.from_numpy_array(np.loadtxt(csv_path, delimiter=',')) degrees = [d for _, d in G.degree()] avg_deg = np.mean(degrees) comps = nx.number_connected_components(G) avg_hop = None if comps == 1: avg_hop = nx.average_shortest_path_length(G) return { 'nodes': G.number_of_nodes(), 'edges': G.number_of_edges(), 'avg_degree': round(avg_deg, 2), 'connected_components': comps, 'avg_hop': avg_hop, }nx.average_shortest_path_length只有在图连通时才有定义,所以要先判断comps == 1。生成的树型拓扑一定连通且无环,平均跳数相对大;星型拓扑的平均跳数接近 2,因为普通节点到 sink 一跳、普通节点之间要经 sink 中转共两跳;网状拓扑的平均跳数最小,但边数会大很多。这些差异成为后续对比实验的依据。
能耗预估算是一个重要扩展。在无线传感器网络里,最常用的是简化的一阶无线电模型:发送端要消耗发射电路和功率放大两部分能量,接收端只消耗接收电路能量。直接按这个模型算每条边的能耗,可以给不同拓扑一个初步的能耗排序。
E_elec = 50e-9 # 收发电路耗能,J/bit E_amp = 100e-12 # 功率放大耗能,J/bit/m^2 packet_bits = 4000 # 每个数据包大小,bit def link_energy(dist_m): return (E_elec + E_amp * dist_m**2) * packet_bits def network_energy(G, pos): total = 0 for u, v in G.edges(): d = np.linalg.norm(np.array(pos[u]) - np.array(pos[v])) total += 2 * link_energy(d) # 收发各一份 return totalE_elec和E_amp这两个参数是仿真的“玄学”集中地:不同论文差一到两个数量级都不奇怪。关键是你要用同一组参数跑所有拓扑,让差异来自拓扑本身而不是参数设置;如果要跟别人论文里的绝对数值对比,就得把模型的量纲和单位全部对齐,否则能耗数值差出十倍先不用惊讶,先检查是不是放大器系数抄错了。
5. 避坑指南:拓扑仿真中五个常见的翻车现场
这一章直接把我踩过的坑按“什么现象、什么原因、怎么解决”列出来,每条都是能抄作业的排查模板。做拓扑仿真的人十个里有八个会在下面这些问题上花掉一整天,提前知道能省很多事。
5.1 节点坐标随机生成时重叠,可视化出来整张图糊成一团
现象:坐标撒完后画图,发现图上某几个节点完全叠在一起,节点数和边数看着都对,但图毫无可读性。原因:均匀分布随机坐标在小区域高密度场景下一定会产生重叠,特别是节点数超过 60 后,重叠概率明显上升。解决:生成坐标后做一次最小间距检查,把间距过近的点重新抖动。
min_dist = 2.0 for i in range(n): for j in range(i+1, n): d = np.linalg.norm(coords[i] - coords[j]) if d < min_dist: coords[j] += np.random.uniform(-1, 1, 2)这是在原有坐标上做微调,不是重新生成整张图,可以保持大多数节点的位置稳定。如果追求更严格的均匀分布,可以用网格采样后在网格内加随机抖动,但那种方式会让拓扑看起来太规则,反而偏离 WSN 的真实随机部署。
5.2 生成的拓扑有孤立节点,路由算法直接白跑
现象:跑完生成脚本,兴奋地把邻接矩阵喂给协议仿真,结果协议跑出来丢包率接近百分百,查了半天才发现底图里有几个节点的度是 0,压根没连上。原因:通信半径取值太小,或者节点坐标恰好落在区域边缘,导致覆盖率不足。解决:生成后立刻做连通性检查,不要等到协议仿真阶段再排查。
node_degree = sum(adj, 2); isolated = find(node_degree == 0); if ~isempty(isolated) fprintf('发现 %d 个孤立节点\n', length(isolated)); end这一步应该作为所有拓扑生成脚本的公共出口。更稳妥的做法是在 Python 侧用nx.is_connected(G)再过一道,两边各查一次,确保数据文件本身没问题。孤立节点的常见补救方法是调大通信半径 r,或者对孤立节点单独补边。
5.3 随机种子没固定,两个语言跑的仿真对不上
现象:Matlab 那边生成了一套数据,Python 这边重新跑一遍可视化,发现节点位置和连接关系全变了,实验记录没法对上,还以为脚本写错了。原因:Matlab 的rand和 Python 的random默认都不固定种子,重现性无从谈起。解决:Matlab 统一用rng(42)固定,Python 端所有涉及随机的入口也固定seed=42,包括spring_layout里的seed、K-means 的kmeans(..., 'Options', statset('UseSubstreams', true))。
更根本的解决方案是:Matlab 生成完 CSV 之后,Python 端永远只读 CSV,不再重新生成坐标或邻接矩阵。把数据生成和数据消费分开,种子问题自动消失。
5.4 CSV 精度丢失,Python 读回来差了几条边
现象:Matlab 里看邻接矩阵明明有边,Python 读进来却少了,排查半天发现是距离刚好卡在通信半径阈值附近。原因:csvwrite默认保留 5 位有效数字,坐标数据被四舍五入后,两个节点间的距离从略小于 25 变成略大于 25,连通判定翻转。解决:写坐标用writetable,写邻接矩阵用dlmwrite(..., 'precision', '%d'),从源头保证数据精度。如果你的数据已经生成完毕,可以在 Python 侧统一留一个tol=1e-6的容差,把adj[tol]修正为 0,但这种做法是事后补救,不如生成时把精度做对。
5.5 能耗模型参数照抄论文,五张拓扑的能耗差异直接消失
现象:拿着第一节里的链路能耗公式去跑五种拓扑,发现星型、树型、簇型的总能耗都差不多,完全没法支撑“某某拓扑更省电”的结论。原因:一阶无线电模型里E_amp的数量级对结果影响巨大。如果你把通信距离的单位写成千米而不是米,或者把E_amp直接抄成某个论文里针对大尺度场景的系数,在小尺度 WSN 里放大器耗能会被计算得过低,链路能耗几乎全由E_elec决定,拓扑差异就被抹平了。解决:先固定区域尺度,100 米级别用d²模型,超过 300 米再考虑d⁴多路径衰落;E_amp的量纲必须是J/bit/m²,不能是纯系数。参数写在一个独立的配置区或者 CSV 配置文件里,别散落在代码角落。
6. 验证与进阶:从静态拓扑走向路由协议仿真
6.1 自检方式:先过三关再进实验
拿到五张拓扑之后,先跑一组自动检查,不要直接进协议仿真。第一关是结构检查:邻接矩阵必须对称,对角线必须为 0,节点度数不能出现 0。第二关是连通性检查:除了特定场景外,全图必须连通,树型要用nx.is_tree验证无环。第三关是差异检查:五种拓扑的边数、平均跳数、平均度必须拉开差距,如果两个拓扑指标几乎一样,说明生成参数没调好,实验对照组失去意义。
import networkx as nx import numpy as np for name in ['star', 'ring', 'tree', 'mesh', 'cluster']: G = nx.from_numpy_array(np.loadtxt(f'data/adjacency_{name}.csv', delimiter=',')) adj = nx.to_numpy_array(G) assert (adj == adj.T).all(), f'{name}: 矩阵非对称' assert not np.any(np.diag(adj)), f'{name}: 对角线不为零' assert nx.is_connected(G), f'{name}: 图不连通' if name == 'tree': assert nx.is_tree(G), f'{name}: 不是树' print(name, 'edges:', G.number_of_edges(), 'avg_hop:', round(nx.average_shortest_path_length(G), 2))6.2 进阶方向:从静态到动态
静态拓扑只是第一步。无线传感器网络真正复杂的地方在于拓扑会随时间变化,节点可能因为电量耗尽而退出网络,新节点也可能中途加入。下一步比较自然的做法是给 Matlab 脚本加一个“节点失效”过程:每一轮随机删除若干低能量节点,重新计算邻接矩阵,观察网络是否出现分割,这是连通性保持问题最经典的仿真实验。
再往下走就是把路由协议套进来。有了 CSV 邻接矩阵,你可以直接在 Python 里实现泛洪、最短路径转发、LEACH 分簇调度等协议,每一轮仿真结束更新节点剩余能量,把五张拓扑的生存时间曲线画在一起。到这一步,你已经从“生成拓扑”跨到了“协议对比”,前面所有数据格式的约定都会在这一阶段发挥作用。
6.3 一点习惯
我自己的习惯是每生成一套数据就在项目根目录放一个config.yaml或params.txt,把节点数、区域边长、通信半径、随机种子、能耗模型系数全记下来。拓扑仿真里最坑的不是算法不会写,而是跑了半个月之后想复盘,发现当时用的参数已经想不起来了。固定种子加参数台账,这两件事做好,几乎所有复现问题都能避免。
这个方向最大的价值在于:一旦数据和生成逻辑分离,你就可以把同样的坐标和邻接矩阵喂给任何协议、任何指标分析代码,再也不用每次从头搭仿真。希望这篇解析能帮你在做无线传感器网络拓扑仿真时少走几步弯路,把时间花在值得做的算法和实验上。
本文还有配套的精品资源,点击获取