跳转至

包装既有模拟器(Path C)

你已经有一个能跑的模拟器——Mesa 模型、自研 ABM、领域专用引擎——想让它 作为 SocioVerse 研究运行:纵向循环、面板存储、版本化、报告,全套能力。 Path C 把它包装到四个核心接口后面,对原始源码零改动

黄金法则:永不修改遗留代码

原模拟器的源码逐字节保持原样。所有胶水都住在你的研究目录里。 这让被包装的项目保持可升级,也让适配保持诚实——与原版的一致性 (parity)始终可证。

一次适配长什么样

studies/<your_study>/
├── adapter/
│   ├── engine_seam.py      # 缝合层(SEAM)—— 定位并构造遗留引擎
│   └── providers.py        # 四个接口实现,经由缝合层调用
├── study.yaml              # 发现字段:legacy_simulator: "<name>"、……
└── environment/ population/ simulation/   # 标准产物

1. 立起 engine-seam(缝合层)

缝合层是研究内部的一个上下文管理器:注入配置(路径、工作区覆写), 并在其内部构造遗留引擎——通过 import,或者当遗留代码读取模块级状态时 通过 monkey-patch。Core 永远不调用缝合层;只有你的 provider 用它。

别混淆两个「引擎」:

  • Core 引擎 = LongitudinalSimulator,原样复用;
  • engine-seam = 你包在遗留引擎外面的胶水。

2. 把四个接口映射到遗留引擎上

接口 典型映射
EnvironmentProvider 投影遗留世界状态;把观察内容标注进四个象限(宏观/局部 × 物理/信息);定时事件与广播路由进 advance_to(t);内生更新进 apply
PopulationProvider 遗留 agent → Persona确定性持久 idgroup_key 作为批处理分组;邻居/网络关系编码为 InteractionStructure
DecisionModel 调用遗留引擎的(批式)决策步;结果投影为类型化 Action——保持批式,不做逐 agent LLM 循环
MetricCollector 包装既有指标;DuckDB 存储直接复用 Core 的

3. 编写产物并注册

与任何研究相同:四份产物,加上 provider 类上的 @register。填好 study.yaml 的发现字段——尤其是 legacy_simulator: "<name>"——让研究 加入 catalog,未来匹配的问题就能路由到它的 fork(Path A)而不是重建。

4. 证明一致性(parity)

信任这层包装之前,先断言它复现遗留轨迹:

  • 原版与包装版用同一个种子各跑一遍,
  • 所有 LLM 调用由确定性客户端替身,
  • 逐步对比两条轨迹。

仓库的测试套件里有一个可参照的一致性测试范例。

来自真实迁移的坑

步数计数器耦合

遗留引擎常携带隐式的步数状态——例如内部读取 len(metrics_history),而 __init__ 预置了一条 step-0 记录。你的 collector 必须严格贴合这个契约(t ≥ 1 追加、t = 0 返回预置行), 否则遗留逻辑会悄悄失步。

一个进程一个研究

monkey-patch 模块全局量的缝合层不是并行安全的。一个进程只跑一个 研究;批量实验用进程池,不用线程。

钉死遗留依赖

被包装的研究继承遗留项目的依赖约束(例如某个钉死的 Mesa 版本)。 把它们声明为研究专属的 extra,而不是放宽 Core 的依赖——Core 本身 保持精简。

如果遗留项目在仓库之外,用环境变量把研究指向它(随仓库提供的参考迁移 用的是 SV_ABM_ROOT),不要硬编码路径。