简介:这份资源是面向多Agent系统建模与仿真学习者的Repast完整教程与实例合集,适合社会科学、经济学及复杂系统方向的研究者、学生和工程师,用于从零掌握Repast的安装配置、模型构建与运行分析。压缩包共72个文件,约303KB,以htm与html网页文档为主体,辅以css样式、gif与bmp图像及package-list索引,整体呈离线文档站结构,便于本地浏览与检索。内容围绕Model、Agent、Environment等核心概念展开,涵盖行为定义、交互规则、Jython与Groovy脚本控制、可视化界面定制、参数调试及数据收集分析,并配有H2R等实践案例,帮助读者体验从概念到实现的完整流程。目前已有729人学习,适合希望系统入门Repast并借助实例巩固操作技能的中级学习者。
1. 从一份“跑不起来”的仿真作业说起:repast 完整教程和实例到底能解决什么
如果你搜到这份 repast 完整教程和实例,大概率不是想听我讲多智能体仿真有多重要,而是手里有个具体活儿卡住了:模型建好了,运行按钮点下去要么报错,要么跑出来的曲线像心电图乱跳,要么根本不知道从哪改参数。这份资源就是冲着这个场景来的——它把 repast 的安装、项目结构、调度机制、参数扫描和结果导出串成一条能照着走的线,而不是丢给你一堆 API 文档让你自己悟。适合谁?做社会网络演化、疾病传播、市场博弈这类基于 Agent 建模的从业者,以及被导师要求“用 repast 复现一篇论文”的学生。它不教你 Java 语法,但能让你把仿真跑通、把数据拿出来、把图做出来。
2. repast 仿真环境搭建与第一个可运行实例:从零到跑通
2.1 选 repast simphony 还是 repast HPC:先看你的算力边界
repast 家族里最常被拿来跑实例的是 Repast Simphony,它基于 Java 和 Eclipse 插件体系,适合单机多核、Agent 数量在十万级以内的场景。Repast HPC 则是给 MPI 集群准备的,Agent 数量上百万、需要跨节点通信时才值得折腾。我一般会建议:如果你只是复现论文里的网络演化或博弈模型,Simphony 足够;如果你要跑城市级交通流或者大规模流行病模拟,再考虑 HPC。这份教程和实例默认走 Simphony 路线,因为它的学习曲线相对平缓,而且实例里的代码可以直接在 Eclipse 里调试。
选型还有一个容易被忽略的点:Simphony 的调度器是离散事件驱动的,而 HPC 更偏向时间步同步。如果你模型里的 Agent 行为是异步触发的,Simphony 的Schedule机制会更自然;如果是固定步长迭代,两者差别不大。常见做法是先用 Simphony 把逻辑跑通,再根据性能瓶颈决定要不要迁移到 HPC。
2.2 安装与项目骨架:别在 JDK 版本上翻车
Repast Simphony 对 JDK 版本有要求,目前主流版本需要 JDK 11 或 17,JDK 8 虽然能跑但部分新特性会报错。安装步骤我拆成可抄的流程:
# 1. 确认 JDK 版本,建议 17 java -version # 2. 下载 Repast Simphony 的 Eclipse 集成包(官网有完整版) # 解压后直接运行 eclipse.exe,不要单独装 Eclipse 再装插件,容易缺依赖 # 3. 新建项目时选择 "Repast Simphony Project" # 项目结构会自动生成 src、context、parameters 等目录逻辑说明:Repast Simphony 的 Eclipse 集成包已经内置了所有依赖,单独装 Eclipse 再手动装插件是新手最常见的翻车点,因为插件版本和 Eclipse 版本经常对不上。参数说明:java -version输出的版本号必须是 11 或 17,如果是 8 或 21,建议换掉,21 太新会有反射相关的兼容问题。
项目建好后,你会看到src下有一个ContextBuilder类,这是整个仿真的入口。它负责创建 Agent、设置投影(比如网络、网格、连续空间)和调度器。实例里通常会有一个ModelInitializer,里面用@ScheduledMethod注解标记 Agent 的行为方法。这个注解是 repast 的核心,它决定了 Agent 在什么时间、以什么顺序执行。
2.3 第一个实例:把 Agent 和调度器串起来
下面是一个最小可运行的 Agent 类,模拟一个简单的传染过程:
// 文件:src/example/Agent.java package example; import repast.simphony.engine.schedule.ScheduledMethod; import repast.simphony.context.Context; import repast.simphony.util.ContextUtils; public class Agent { private boolean infected; private double infectionRate = 0.3; public Agent(boolean infected) { this.infected = infected; } // 每个 tick 执行一次,优先级为 1 @ScheduledMethod(start = 1, interval = 1, priority = 1) public void step() { if (infected) { // 获取上下文,遍历邻居 Context<Object> context = ContextUtils.getContext(this); for (Object obj : context.getObjects(Agent.class)) { Agent other = (Agent) obj; if (!other.infected && Math.random() < infectionRate) { other.infected = true; } } } } public boolean isInfected() { return infected; } }逻辑说明:@ScheduledMethod的start=1表示从第 1 个 tick 开始执行,interval=1表示每 tick 执行一次,priority=1决定同一 tick 内多个方法的执行顺序,数值越小越先执行。ContextUtils.getContext(this)是 repast 提供的静态方法,用来从 Agent 反查它所在的上下文。参数说明:infectionRate是传染概率,你可以把它改成从parameters.xml读取,这样就能做参数扫描。
这个实例虽然简单,但它覆盖了 repast 仿真的三个核心要素:Agent 定义、调度注解、上下文访问。很多教程只贴代码不说priority的作用,结果读者跑出来的结果和论文对不上,就是因为调度顺序变了。
3. 参数扫描与结果导出:让仿真数据能进论文图表
3.1 用 parameters.xml 做批量实验,别手动改代码
Repast Simphony 内置了参数扫描功能,你不需要写循环去改代码。在项目根目录下有一个parameters.xml,里面可以定义参数的不同取值组合。比如:
<parameters> <parameter name="infectionRate" type="double" displayName="Infection Rate"> <values>0.1, 0.3, 0.5, 0.7</values> </parameter> <parameter name="initialInfected" type="int" displayName="Initial Infected"> <values>1, 5, 10</values> </parameter> </parameters>逻辑说明:Repast 会自动把这些参数组合成 4×3=12 次运行,每次运行的结果会单独保存。你可以在 Eclipse 的运行配置里选择 “Parameter Sweep”,然后指定输出目录。参数说明:type支持double、int、boolean和String,displayName是给图形界面看的,不影响运行。
这里有个血泪经验:如果你在代码里用RandomHelper生成随机数,每次运行的种子要单独设置,否则 12 次运行的结果会完全一样。Repast 默认会为每次运行生成不同的种子,但如果你手动new Random(),就会覆盖掉这个机制。
3.2 结果导出的三种方式:CSV、数据库和自定义监听器
Repast 提供了几种结果收集方式,最常用的是DataSets和DataSinks。我一般会在ContextBuilder里加一个DataSink,把每个 tick 的统计数据写进 CSV:
// 在 ContextBuilder.build() 方法里添加 DataSink sink = new DataSink("output/infection_data.csv"); sink.addColumn("tick"); sink.addColumn("infected_count"); // 在 Agent 的 step 方法里记录 @ScheduledMethod(start = 1, interval = 1, priority = 10) public void record() { Context<Object> context = ContextUtils.getContext(this); long count = context.getObjects(Agent.class).stream() .filter(a -> ((Agent) a).isInfected()) .count(); // 写入当前 tick 和感染数 // 注意:DataSink 的写入需要配合 Schedule 的 tick 计数 }逻辑说明:DataSink是 repast 提供的轻量级数据收集器,它会在每次record方法执行时追加一行。priority=10保证它在所有 Agent 行为之后执行,这样统计的是当前 tick 的最终状态。参数说明:output/infection_data.csv是相对路径,实际文件会生成在项目运行目录下。如果你需要更复杂的数据结构,可以用DataSets配合Histogram或TimeSeries。
另一种方式是直接用 JDBC 写入数据库,适合需要长期存储和 SQL 查询的场景。但大多数论文实验用 CSV 就够了,导出后用 Python 或 R 画图,比在 Java 里折腾图表库省事得多。
3.3 用 R 或 Python 做后处理:把 CSV 变成论文里的图
仿真跑完只是第一步,把数据变成能放进论文的图才是终点。我通常用 Python 的 pandas 和 matplotlib:
import pandas as pd import matplotlib.pyplot as plt # 读取参数扫描生成的多个 CSV df = pd.read_csv("output/infection_data.csv") # 按感染率分组画图 for rate, group in df.groupby("infectionRate"): plt.plot(group["tick"], group["infected_count"], label=f"rate={rate}") plt.xlabel("Tick") plt.ylabel("Infected Count") plt.legend() plt.savefig("infection_curve.png", dpi=300)逻辑说明:groupby按参数值分组,每组画一条曲线,这样能直观对比不同参数下的传播速度。参数说明:dpi=300是论文投稿的常见要求,plt.legend()的位置可以用loc参数调整,避免遮挡曲线。
这里有个坑:Repast 输出的 CSV 有时候会带 BOM 头,pandas 读取时第一列名会变成\ufefftick,加encoding='utf-8-sig'就能解决。这个细节很多教程不提,但实际跑的时候一定会遇到。
4. 避坑与常见问题排查:那些让仿真跑不起来的细节
4.1 现象:运行时报NoClassDefFoundError或ClassNotFoundException
原因:Repast Simphony 的类加载机制和普通 Java 项目不同,它用 OSGi 框架管理依赖。如果你手动往lib目录塞了 jar 包,但没有在MANIFEST.MF里声明,运行时就会找不到类。解决:不要手动加 jar,用 Eclipse 的 “Add External JARs” 功能,或者把依赖放到repast.simphony.runtime的插件目录下。如果必须手动加,检查META-INF/MANIFEST.MF里的Bundle-ClassPath是否包含你的 jar。
4.2 现象:仿真跑了一半卡住,日志没有任何输出
原因:最常见的是调度器死锁。比如两个 Agent 在同一个 tick 里互相等待对方释放资源,或者@ScheduledMethod的priority设置导致某个方法永远排不上。解决:先用Schedule的调试模式打印每个 tick 的执行顺序,确认没有循环等待。另外检查interval是否设成了 0,这会导致方法只执行一次然后调度器空转。
4.3 现象:参数扫描时每次运行结果完全一样
原因:随机数种子被固定了。Repast 默认会为每次运行生成不同的种子,但如果你在代码里用了new Random(12345)或者RandomHelper.setSeed(),就会覆盖掉默认行为。解决:删掉手动设置种子的代码,改用RandomHelper.getRandom()获取随机数。如果确实需要可复现的结果,把种子写进parameters.xml,让每次运行读取不同的值。
4.4 现象:CSV 文件里只有表头,没有数据行
原因:DataSink的写入方法没有被调度器触发。常见情况是你把record方法写在了 Agent 类里,但 Agent 没有被添加到上下文,或者@ScheduledMethod的start值大于仿真总 tick 数。解决:检查ContextBuilder里是否用context.add(agent)添加了 Agent,以及start是否从 1 开始。另外确认DataSink的addColumn在build方法里调用,而不是在 Agent 构造函数里。
4.5 现象:图形界面显示正常,但导出数据时内存溢出
原因:DataSets默认会把所有 tick 的数据保存在内存里,如果仿真跑了 10 万 tick,每个 tick 又有几千个 Agent,内存很快就不够。解决:改用DataSink流式写入,或者设置DataSets的bufferSize参数,定期刷到磁盘。另一个办法是减少记录频率,比如每 10 个 tick 记录一次,而不是每个 tick 都记。
5. 进阶技巧:用自定义投影和网络结构让仿真更贴近真实
5.1 自定义投影:不只是网格和网络
Repast 内置了网格、连续空间和网络三种投影,但实际模型里经常需要更复杂的空间关系,比如地理信息系统里的多边形区域,或者社交网络里的多层关系。这时候可以继承Projection类写自定义投影。我一般会先看NetworkProjection的源码,照着它的接口实现add、remove和getObjects方法。自定义投影的好处是,你可以在投影内部维护索引结构,比如用 R 树加速空间查询,这样 Agent 数量上去之后性能不会崩。
5.2 网络结构的动态演化:边比节点更难处理
很多实例只讲了怎么创建静态网络,但真实场景里网络是动态变化的——新关系建立、旧关系断裂。Repast 的Network接口支持addEdge和removeEdge,但如果你在 Agent 的step方法里直接改网络,可能会触发ConcurrentModificationException。常见做法是用一个EdgeModifier类,把所有边的增删操作缓存起来,在 tick 结束时统一执行。这样既避免了并发问题,也方便记录网络演化的历史。
5.3 验证仿真结果:怎么知道模型没跑偏
仿真跑通不等于结果可信。我一般会做三件事:第一,用极端参数跑一遍,比如感染率设为 0 和 1,看结果是否符合预期;第二,和解析解对比,比如 SIR 模型有微分方程解,仿真曲线应该和它趋势一致;第三,做敏感性分析,看输出对哪个参数最敏感。如果这三步都过了,结果才敢往论文里放。从那以后我每次跑新模型,都强制走一遍极端参数测试,不然心里没底。希望帮到你。
本文还有配套的精品资源,点击获取