
先说个背景。我之前在带本科生做社会网络课题时很多人一听到仿真建模四个字就头皮发麻觉得必须得先啃完《概率论》《图论》《程序设计》三座大山才有资格动手。但NetLogo这个软件完全是另一条路——它的设计初衷就是让非计算机背景的研究者也能快速搭建多智能体模型。前两篇我讲了环境配置和界面基本操作这一篇直接进入编程环节。我会用一个完整的社交网络信息扩散模型串起全部知识点你跟着走一遍基本就能掌握NetLogo的核心编程思路后面再自己加需求也有底气了。1. 先搞清楚NetLogo里的三层世界观1.1 海龟、瓦片、观察者到底各管什么NetLogo的编程模型脱胎于Logo语言但加了一层非常关键的抽象整个仿真世界被分成了三类主体分别叫turtles海龟/智能体、patches瓦片/网格和observer观察者。turtles是能动的主体。在社会网络仿真里一个人、一家公司、一只动物都可以抽象成一只turtle。它们在二维世界里可以移动、变色、创建连接。每个turtle自带一些内置属性比如xcor和ycor是坐标color是颜色size是大小heading是朝向。这些你不用自己定义直接用就行。patches是静态的格子。整个仿真世界被切分成网格每个格子就是一个patch。它们的坐标是整数比如(0, 0)、(1, 1)。在社会网络模型里patches一般用来表示空间环境——比如某个区域的资源量、污染程度、人口密度。如果你研究的网络跟地理位置没关系那patches基本就是背景板但千万别以为它没用后面做空间扩展时你就会回来找它。observer是上帝之手。它不对应任何仿真对象负责全局调度。你写的setup、go这些过程本质上都是observer在执行ask turtles这个命令就是observer在向所有turtle下发指令。用一句大白话总结turtles是演员patches是舞台observer是导演。理解了这三层权限边界你的代码逻辑就会清晰得多。1.2 从命令中心到代码编辑器NetLogo的界面有三大页签Interface交互界面、Info模型说明、Code代码编辑。很多人上手第一件事就是跑去Code页签写代码写完之后一头雾水不知道发生了什么。我的建议是先在Interface底部的Command Center命令中心里敲指令边敲边看世界变化等逻辑理顺了再搬到Code编辑器里组织成过程。举个例子你可以在Command Center里输入create-turtles 10回车之后世界中间立刻出现10只小海龟随机朝向和颜色。再看它们的坐标show [xcor] of turtles这一步会直接在命令中心打印出10个坐标值。这种边输边看的交互方式是NetLogo对新手最友好的地方。等你对每个命令的效果心里有数了再去Code页签写完整的过程。2. NetLogo编程语法够用就好的那套核心2.1 变量全局、个体、局部三层NetLogo的变量系统是理解整个编程的关键因为它跟谁在持有这个变量强相关。首先是全局变量用globals关键字在代码顶部声明。它在整个仿真运行期间都存在任何主体都能访问。比如后面案例里的infected-count就是全局变量。globals [ infected-count ]其次是个体变量也叫主体自有变量。每个turtle可以有自己的turtles-own每个patch可以有patches-own。比如社会网络里每个人的感染状态、免疫状态都属于个体变量。turtles-own [ infected? immunity? ]注意NetLogo的变量命名规则如果名字里要表达布尔含义习惯性在后面加个问号比如infected?读起来就是已感染吗非常直观。最后是局部变量用let关键字在过程内部创建只在当前过程内有效。它是临时变量用来存中间计算结果避免污染全局命名空间。let total 0有个新手特别容易踩的坑在NetLogo里你用set给变量赋值之前这个变量必须先声明过。如果在turtles-own里没有声明直接set age 20运行时会直接报错you cant set variable age。2.2 命令和报告器一个执行动作一个返回结果NetLogo把程序元素分成两大类command命令和reporter报告器。命令是执行动作的比如create-turtles、fd、set、ask它们执行完之后不返回任何值。报告器则是计算并返回一个值比如count turtles、random 10、one-of turtles。这个区分的价值在于你不能把一个命令写在一个需要值的位置。比如写let x fd 1就是错的因为fd命令不返回值。反过来show count turtles是可以的因为count turtles是报告器。在实际使用中最常见的组合模式是ask turtles with [ color red ] [ set size 2 ]这句的意思是observer筛选出所有color red的turtle让它们每个都执行set size 2。with是一个筛选报告器它会过滤出一个子集。2.3 流程控制if、ask和循环NetLogo里最重要的流程控制不是for而是ask。因为仿真本质上就是多个主体并行行动所以你得用ask把指令下发到每个主体。ask后面跟的主体集合可以是turtles、patches也可以是某个筛选出来的子集。条件判断用if和ifelseif infected? [ set color red ] ifelse infected? [ set color red ] [ set color blue ]ifelse跟一般语言的if-else一样但注意括号的位置和两个分支的顺序中间用方括号分隔。循环方面最常用的是loop配合stop或者repeat以及列表遍历用的foreach。不过在社会网络模型里循环往往被go过程取代了——每个tick就是一个时间步go过程会被反复调用这本身就是一种循环。3. 社会网络仿真里的核心编程模式3.1 用turtles代表节点用links代表关系社会网络仿真的第一步是把抽象的关系网络翻译成NetLogo的实体。节点好办用turtles关系需要用到NetLogo的另一类主体——links连接。links是NetLogo里非常巧妙的设计。它也是一种主体但它的存在必须依附于两个turtle。你创建一条链接时实际上是从某个turtle出发连接到另一个turtle。另一个关键点是链接分有向directed-links和无向undirected-links两种。如果你研究的是微博关注关系那应该用有向链接如果研究的是微信好友那应该是无向链接。ask turtle 0 [ create-link-with turtle 1 ]这行代码会在turtle 0和turtle 1之间建立一条无向链接。如果turtle 0已经和turtle 1有链接再次执行会报错所以创建之前通常要检查if not link-neighbor? turtle 1 [ create-link-with turtle 1 ]有了links你就能用网络分析的本体了。link-neighbors会返回和某个turtle直接相连的所有turtledegree度、count link-neighbors这些指标都能直接算出来。3.2 建网络的三板斧随机连、环形连、按规则连入门阶段你不需要自己从零写复杂的网络生成算法。NetLogo自带的nw扩展就是干这个的但我建议你第一遍先手动建一个简单网络理解底层逻辑。最常见的建网方法有三种。第一种是随机连接每个节点随机选择K个其他节点建立连接。这种网络接近ER随机图。create-turtles 30 ask turtles [ create-links-with n-of 3 other turtles ]n-of 3 other turtles表示从除了自己以外的其他turtle里随机抽3个。这种写法简洁但有个小瑕疵other turtles是排除了自己的所以每个节点不会连到自己。第二种是环形网络把turtles排成一个圈每个节点连接前后各K个邻居。环形网络是研究小世界现象的基础。create-turtles 30 layout-circle turtles max-pxcor - 1 ask turtles [ let my-id 0 ask other turtles [ if abs (my-id - [who] of self) 2 [ ; who是内置编号 if not link-neighbor? myself [ create-link-with myself ] ] ] ]不过新手在这里容易绕晕建议直接用nw扩展的generate-watts-strogatz函数。第三种是按属性规则连接比如每个新节点优先连接度高的节点无标度网络的核心逻辑。NetLogo自带的模型库里就有这个例子你可以参考Model Library里的Preferential Attachment模型。3.3 网络怎么演化加点、删边、改权重真实社会网络不是静态的。人会加入、退出关系会建立、破裂。NetLogo里的演化逻辑通常放在go过程里每个tick执行一次。添加节点create-turtles 1 [ set color blue create-links-with n-of 2 turtles ]删除节点ask one-of turtles [ ask my-links [ die ] die ]注意删除节点的同时要记得删除它的链接否则NetLogo会报错——链接必须依附于节点存在。对链接做权重动态更新时可以用links-own声明一个weight变量然后每次tick根据某种规则更新。比如亲密度越高权重越大或者信息越传播权重越衰减。links-own [ weight ]设置权重ask links [ set weight 1 ]4. 实操案例30人小圈子里的信息扩散模型说了这么多理论现在动手做一个完整的模型。场景设定一个30人的小群体每个人随机认识另外3个人无向关系。某天有一个人获得了一条劲爆消息每次单位时间他/她会以一定概率把消息告诉每个认识的人而刚听到消息的人下一轮也会继续传播。我们想观察这个消息最终能传多远速度快不快4.1 模型参数和界面控件的设计思路打开NetLogo在Interface页签里你需要拖入几个控件一个setup按钮关联到setup过程一个go按钮关联到go过程勾选Forever无限重复执行选项一个probability滑动条范围0到100默认30控制传播概率一个monitor监视器显示当前感染人数一个plot图显示感染人数随时间的变化曲线完成界面布局之后点鼠标右键每个控件都能设置关联的过程名和参数范围。这里的核心是probability滑动条的值可以直接在代码里用probability这个变量引用不需要你额外声明。4.2 setup过程的代码拆解打开Code页签先写setupglobals [ infected-count ] turtles-own [ infected? immunity? ] to setup clear-all reset-ticks set infected-count 0 create-turtles 30 [ set color blue set size 2 set infected? false set immunity? false ] ask turtles [ create-links-with n-of 3 other turtles ] layout-circle turtles max-pxcor - 1 ask one-of turtles [ set infected? true set color red set infected-count infected-count 1 ] end这段代码的逻辑链是先清空世界重置时钟初始化全局计数再创建30个turtles设置初始属性然后每个turtle随机生成3条无向链接用layout-circle把节点均匀排成一个圆这样看起来不乱最后随机挑一个turtle设置为感染者。这里有个细节值得提layout-circle turtles max-pxcor - 1。max-pxcor是世界的最大横坐标默认大概是16所以圆的半径用15刚好把节点排在世界边缘以内。如果你的世界尺寸不同布局效果会不一样但规则是一样的——半径留白一点别让节点贴边。4.3 go过程每一轮传播逻辑怎么写go过程是这个模型的心脏它描述了每个tick里发生的所有事情to go ask turtles with [infected?] [ ask link-neighbors [ if not infected? and not immunity? [ if random-float 100 probability [ set infected? true set color red ] ] ] ] set infected-count count turtles with [infected?] tick end我用一句话先概括执行流程遍历当前所有感染者对每个感染者的每个邻居以probability%的概率把对方也感染。很多新手读到这里会卡在一个地方ask turtles with [infected?]这一步如果某个turtle在这一轮里被感染了它会不会立刻在本轮里继续传播一轮答案是不会。因为NetLogo的ask是先确定好主体集合再逐一执行。即使执行过程中某个turtle的infected?变成了true它也不会被加入本轮集合中。这个特性保证了模型的时间步逻辑不会混乱。更新感染人数用了count报告器配合with筛选。注意这里是先算完所有传染操作再统一统计人数所以计数是准确的当前tick结束后的状态。tick是NetLogo的时钟每执行一次go调用时钟加一。如果你的界面里放了plot可以在这个位置更新图的数据plot infected-count4.4 运行模型、观察现象和参数调优点击setup再点go你应该能看到红色节点像涟漪一样在圆圈里扩散。如果感染曲线每次都快速达到100%说明传播概率设太高了没有对比价值如果曲线几乎不增长说明概率太低或初始节点不够。这里没有标准答案关键是你作为建模者要能从输出中读到结构信息。我实测下来值得观察的现象有三个。第一感染规模增长大多不是线性的而是S型曲线。先慢感染者少接触面窄中间陡增感染节点多了扩散面广后期平缓大部分节点要么感染过、要么与感染节点隔绝。第二网络度对传播速度有明显影响。把create-links-with n-of 3 other turtles里的3改成5再运行一次你会发现扩散速度明显加快。这是因为每个节点的平均度提高了网络连通性更好。第三如果把网络变成多个互不连通的孤岛传播就会局部化。你可以在创建链接时限制在同一群组内观察信息永远传不到其他群组的效果。这种桥接断裂是真实社会中非常常见的传播阻断原因。5. 常见问题与调试技巧实录5.1 新手最容易翻车的五类报错我在带学生做NetLogo实验时总结了五类出现频率最高的报错场景这里直接给你一个速查表。报错场景原因解决办法Nothing named SIZE has been defined变量名拼写错误或未声明检查turtles-own声明和赋值语句NetLogo变量名区分大小写Requested random number is 0random的参数传了0报错检查是不是把random-float写成了random或者检查参数是否为正整数create-link-with: only two turtles can be in a link试图创建重复链接或自链接创建前加if not link-neighbor?判断ask: expected 2 inputsask语法写错缺了方括号ask turtles [ ... ]方括号不能省Cant find turtle 5引用了一个已经被die的turtle检查删除节点时是否同步删除了相关链接这里面我特别想强调变量名的问题。NetLogo不区分size和Size但你声明了Size却用了size就出问题。有一次我帮学生看代码查了半天最后发现他把turtles-own里写的变量名是Infected?代码里用的是小写infected?NetLogo直接当成两个变量初始值全变成false逻辑判断一团糟。5.2 调试三板斧show、watch、单步执行NetLogo没有传统意义上的断点调试器但它的调试手段比很多人想的要实用。第一板斧是show。在命令中心输入show count turtles马上能看到数字。如果你想在一段过程中临时输出某个值可以直接在代码里加show语句运行后会在命令中心打印出来。第二板斧是watch。在命令中心输入watch turtle 3界面视图会锁定并高亮显示编号为3的turtle你就能像个跟踪器一样观察它每一帧的行为。配合watch-me放在局部过程里可以对满足特定条件的实体做动态追踪。第三板斧是单步执行。在Interface页签的go按钮上鼠标右键可以勾选Step once或者不勾选Forever。这样每点一下按钮只执行一个tick方便你逐步观察状态变化。如果模型跑得太快看不清也可以在go过程开头加一句wait 0.5让它慢半拍。5.3 模型规模大了怎么办NetLogo在几千个智能体时运行还算流畅但一旦超过几万速度就会有明显下降。社会网络仿真如果节点数上了万需要注意三件事。第一减少不必要的ask重复遍历。比如每次tick里都用ask turtles [ ... ]去扫描所有节点即使大部分节点状态没变化也会消耗时间。可以改用ask turtles with [infected?]让筛选发生在遍历之前。第二尽量少用layout类命令。像layout-spring这类力导向布局算法本质上是迭代计算每隔几帧调用一次可以但如果每tick都调用仿真实时会卡到难以接受。第三使用NetLogo自带的BehaviorSpace工具做参数扫描时注意设置合理的迭代次数。BehavioSpace可以自动化跑多组不同参数的仿真并导出结果但如果你设置的范围过大比如让probability从0到100步长1再跑1000个tick一次实验就要几分钟了。写在最后的一点体会在这套系列文章写到这里之前我一直在想怎么把NetLogo这个工具讲得不像说明书。说真的它最打动我的地方不是语法多高级、性能多强而是它让建模思维变成了一种近乎直觉的东西。你不需要先背一百个函数只需要想清楚这世界里有谁、它们之间有什么关系、每一刻发生什么变化。然后把这个想法翻译成turtles、links和ask模型就跑起来了。信息扩散、谣言传播、舆论演化、社交网络成长这些听起来高大上的概念在NetLogo里都是半小时能搭出雏形的事。如果你顺着这篇的思路自己动手跑通了这个模型建议你再往前走一步把节点换成个体态度偏好把传播规则换成同质化吸引你会发现同一套骨架能装很多不同的血肉。那才是NetLogo真正好玩的地方。