更新日志

本文档记录 PyNetIM 项目的所有重要变更。

格式基于 Keep a Changelog

[0.4.5] - 2026-04-04

新增

SI 和 SIR 扩散模型 (C++ 后端):

  • 新增 SusceptibleInfectedModel - SI 传播模型

  • 新增 SusceptibleInfectedRecoveredModel - SIR 传播模型

  • 支持参数设置:``beta``(感染概率)、``gamma``(恢复概率)、``max_steps``(最大步数)

  • 支持单次模拟和 Monte Carlo 多轮模拟

  • 支持多线程并行模拟

  • 支持激活节点记录和激活频率记录

Graph 构造函数支持统一权重:

  • IMGraphCpp(num_nodes, edges, weights=1.0) - 默认所有边权重为 1.0

  • IMGraphCpp(num_nodes, edges, 0.3) - 所有边使用统一权重 0.3

  • IMGraphCpp(num_nodes, edges, [0.1, 0.2, ...]) - 每条边使用各自的权重

API 更新

SusceptibleInfectedModel:

  • 构造函数:SusceptibleInfectedModel(graph, seeds, beta=0.1, max_steps=100, record_activated=False, record_activation_frequency=False)

  • set_beta(beta: float) - 设置感染概率

  • set_max_steps(max_steps: int) - 设置最大传播步数

  • run_single_simulation(seed=None) -> int - 单次模拟

  • run_monte_carlo_diffusion(rounds, seed=None, use_multithread=False, num_threads=0) -> float - Monte Carlo 模拟

  • get_activated_nodes() -> Set[int] - 获取激活节点集合

  • get_activation_frequency() -> List[int] - 获取激活频率

SusceptibleInfectedRecoveredModel:

  • 构造函数:SusceptibleInfectedRecoveredModel(graph, seeds, beta=0.1, gamma=0.1, record_activated=False, record_activation_frequency=False)

  • set_beta(beta: float) - 设置感染概率

  • set_gamma(gamma: float) - 设置恢复概率

  • run_single_simulation(seed=None) -> int - 单次模拟

  • run_monte_carlo_diffusion(rounds, seed=None, use_multithread=False, num_threads=0) -> float - Monte Carlo 模拟

  • get_activated_nodes() -> Set[int] - 获取激活节点集合

  • get_activation_frequency() -> List[int] - 获取激活频率

IMGraphCpp:

  • weights 参数现在支持 float 类型,表示所有边使用统一权重

代码重构

扩散模型文件分离:

  • 将扩散模型从单一文件分离到独立文件

  • independent_cascade.h - IC 模型

  • linear_threshold.h - LT 模型

  • susceptible_infected.h - SI 模型

  • susceptible_infected_recovered.h - SIR 模型

  • common.h - 共享代码(ObjectPool、RNG 工具函数)

测试

新增完整测试覆盖:

  • test_ic_model.py - IC 模型完整测试

  • test_lt_model.py - LT 模型完整测试

  • test_si_model.py - SI 模型完整测试

  • test_sir_model.py - SIR 模型完整测试

  • 单线程和多线程结果一致性验证通过

  • 参数验证测试通过(beta、gamma 范围检查)

技术细节

修改文件:

  • src/pynetim/cpp/include/common.h - 共享工具代码

  • src/pynetim/cpp/include/independent_cascade.h - IC 模型

  • src/pynetim/cpp/include/linear_threshold.h - LT 模型

  • src/pynetim/cpp/include/susceptible_infected.h - SI 模型

  • src/pynetim/cpp/include/susceptible_infected_recovered.h - SIR 模型

  • src/pynetim/cpp/include/diffusion_model.h - 主头文件

  • src/pynetim/cpp/bindings/si_bind.cpp - SI 模型绑定

  • src/pynetim/cpp/bindings/sir_bind.cpp - SIR 模型绑定

  • src/pynetim/cpp/include/Graph.h - Graph 类统一权重支持

  • src/pynetim/cpp/bindings/graph_bind.cpp - Graph 绑定更新

实现细节:

  • SI 模型:每个时间步,感染节点以概率 beta 尝试感染邻居

  • SIR 模型:在 SI 基础上,感染节点每步以概率 gamma 恢复

  • 参数验证:beta、gamma ∈ (0, 1],max_steps > 0

  • 多线程种子预生成策略确保单线程和多线程结果完全一致

使用示例

import pynetim.cpp.graph as im_graph
import pynetim.cpp.diffusion_model as diffusion_model

# 创建图(统一权重)
graph = im_graph.IMGraphCpp(100, [(i, i+1) for i in range(99)], 0.3)
seeds = {0}

# SI 模型
si = diffusion_model.SusceptibleInfectedModel(graph, seeds, beta=0.3)
avg = si.run_monte_carlo_diffusion(1000, seed=42)
print(f"SI 平均感染节点数: {avg:.2f}")

# SIR 模型
sir = diffusion_model.SusceptibleInfectedRecoveredModel(graph, seeds, beta=0.3, gamma=0.1)
avg = sir.run_monte_carlo_diffusion(1000, seed=42)
print(f"SIR 平均感染节点数: {avg:.2f}")

# 多线程模拟
avg = sir.run_monte_carlo_diffusion(10000, seed=42, use_multithread=True)
print(f"多线程 SIR 平均感染节点数: {avg:.2f}")

# 记录激活节点
sir = diffusion_model.SusceptibleInfectedRecoveredModel(
    graph, seeds, beta=0.3, gamma=0.1, record_activated=True
)
count = sir.run_single_simulation(seed=42)
activated = sir.get_activated_nodes()
print(f"激活节点: {activated}")

[0.4.4] - 2026-03-30

新增

分离模拟执行和结果获取:

  • run_single_simulation() 现在只返回激活节点数量(int),不再返回节点集合

  • 新增 get_activated_nodes() 方法,专门用于获取记录的激活节点集合

  • 只有在 record_activated=True 时,get_activated_nodes() 才返回有效数据

  • record_activated=False 时,get_activated_nodes() 返回空集合

多轮模拟记录激活节点并集:

  • run_monte_carlo_diffusion() 现在支持记录所有试验的激活节点并集

  • record_activated=True 时,get_activated_nodes() 返回所有试验中被激活过的节点(并集)

  • 对于单次模拟:返回该次模拟的激活节点集合

  • 对于 Monte Carlo 模拟:返回所有试验的激活节点并集

激活频率记录功能:

  • 新增 record_activation_frequency 参数(默认为 false),用于控制是否记录每个节点的激活频率

  • 新增 set_record_activation_frequency(record) 方法,可在运行时动态开启/关闭记录功能

  • 新增 get_activation_frequency() 方法,返回每个节点被激活的次数

  • 只有在 record_activation_frequency=True 时,get_activation_frequency() 才返回有效数据

  • record_activation_frequency=False 时,get_activation_frequency() 返回全 0 的列表

  • 可以同时开启 record_activatedrecord_activation_frequency,记录两种数据

API 更新

IndependentCascadeModel:

  • run_single_simulation() 的返回值从 Set[int] 改为 ``int``(激活节点数量)

  • 新增 get_activated_nodes() -> Set[int] 方法,用于获取记录的激活节点集合

  • 新增 get_activation_frequency() -> List[int] 方法,用于获取每个节点的激活频率

  • 新增 set_record_activation_frequency(record: bool) 方法,用于动态开启/关闭激活频率记录

  • run_single_simulation()seed 参数类型从 int 改为 int | None,默认值从 0 改为 None

  • run_monte_carlo_diffusion()seed 参数类型从 int 改为 int | None,默认值从 0 改为 None

  • 构造函数新增 record_activation_frequency 参数(默认为 false

LinearThresholdModel:

  • run_single_simulation() 的返回值从 Set[int] 改为 ``int``(激活节点数量)

  • 新增 get_activated_nodes() -> Set[int] 方法,用于获取记录的激活节点集合

  • 新增 get_activation_frequency() -> List[int] 方法,用于获取每个节点的激活频率

  • 新增 set_record_activation_frequency(record: bool) 方法,用于动态开启/关闭激活频率记录

  • run_single_simulation()seed 参数类型从 int 改为 int | None,默认值从 0 改为 None

  • run_monte_carlo_diffusion()seed 参数类型从 int 改为 int | None,默认值从 0 改为 None

  • 构造函数新增 record_activation_frequency 参数(默认为 false

优化

改进随机种子生成机制:

  • run_single_simulation()run_monte_carlo_diffusion() 方法现在都支持 seed=None 参数

  • seed=None 时,使用多个 std::random_device 值的组合来增强随机性

  • seed=固定值 时,使用固定种子,确保模拟结果可重现

  • 改进后的随机种子生成方式解决了在某些平台(虚拟环境、容器)上 std::random_device 返回相同值的问题

  • 使用 std::seed_seq 和多个随机值组合,确保每次调用都能得到不同的结果

多线程随机种子一致性:

  • run_monte_carlo_diffusion() 采用种子预生成策略

  • 在开始任何模拟之前,使用主随机数生成器预生成所有试验的种子

  • 每个试验使用独立的种子,确保单线程和多线程结果完全一致

  • 多线程只是改变了试验的执行顺序和并行度,但没有改变每个试验使用的随机数序列

修复

修复 ``set_record_activated()`` 的清空逻辑:

  • set_record_activated(False) 被调用时,现在会清空 last_activated_nodes

  • 避免了返回过期的激活节点数据

  • 确保当 record_activated=False 时,get_activated_nodes() 总是返回空集合

修复 ``set_record_activation_frequency()`` 的清空逻辑:

  • set_record_activation_frequency(False) 被调用时,现在会清空 activation_frequency

  • 避免了返回过期的激活频率数据

  • 确保当 record_activation_frequency=False 时,get_activation_frequency() 总是返回全 0 的列表

测试

新增功能验证测试:

  • IC 模型单次模拟测试(简单链式图、星形图、低权重图)

  • LT 模型单次模拟测试

  • 多个种子节点测试

  • record_activated=False 测试

  • set_record_activated() 动态切换测试

  • 多次调用 get_activated_nodes() 测试

  • 无向图测试

  • 随机种子功能测试

  • 多轮模拟测试(单线程和多线程)

  • 大规模模拟测试(10000 轮)

  • record_activated 对多轮模拟性能影响测试

  • 多轮模拟 vs 单次模拟对比测试

  • 编译和运行测试全部通过

测试结果:

  • 简单链式图 (权重=1.0): 激活节点数量=5, 激活节点集合={0, 1, 2, 3, 4} ✅

  • 星形图 (权重=1.0): 激活节点数量=5, 激活节点集合={0, 1, 2, 3, 4} ✅

  • 低权重图 (权重=0.01): 激活节点数量=1, 激活节点集合={0} ✅

  • 多个种子节点: 激活节点数量=6, 激活节点集合={0, 1, 2, 3, 4, 5} ✅

  • record_activated=False: 返回激活节点数量,不记录节点集合 ✅

  • record_activated=True: 返回激活节点数量,记录节点集合 ✅

  • 动态切换 record_activated: 正确清空和记录节点集合 ✅

  • 多次调用 get_activated_nodes(): 返回相同的集合 ✅

  • 无向图: 正确处理无向图的传播 ✅

  • 随机种子 (seed=None): 每次模拟结果可能不同 ✅

  • 固定种子 (seed=42): 多次模拟结果完全相同 ✅

  • 单线程和多线程: 多轮模拟结果完全一致(差异 < 1e-10)✅

  • 固定种子可重现性: 3次运行结果完全相同 ✅

  • record_activated 影响: 不影响多轮模拟结果(差异 = 0.0000)✅

  • 大规模模拟: 10000 轮模拟稳定运行 ✅

技术细节

修改文件:

  • src/pynetim/cpp/include/diffusion_model.h - 添加核心功能实现、改进随机种子生成、修复清空逻辑

  • src/pynetim/cpp/bindings/ic_bind.cpp - IC 模型 Python 绑定,更新 API 文档

  • src/pynetim/cpp/bindings/lt_bind.cpp - LT 模型 Python 绑定,更新 API 文档

  • src/pynetim/cpp/diffusion_model/independent_cascade_model.pyi - 类型提示

  • src/pynetim/cpp/diffusion_model/linear_threshold_model.pyi - 类型提示

实现细节:

  • run_single_trial 方法新增 activated_nodes 参数,用于记录激活节点

  • 添加 last_activated_nodes 成员变量(mutable std::set<int>),存储最后一次模拟的激活节点

  • record_activatedtrue 时,遍历所有节点收集激活的节点到 last_activated_nodes

  • record_activatedfalse 时,不收集激活节点,last_activated_nodes 保持为空

  • run_single_simulation() 返回激活节点数量(int),不再返回节点集合

  • 新增 get_activated_nodes() 方法,返回 last_activated_nodes 的副本

  • set_record_activated() 方法在设置为 false 时清空 last_activated_nodes

  • 使用独立的随机数生成器确保每次模拟的独立性

  • 保持向后兼容性,record_activated 默认为 false

  • 随机种子生成改进: 使用多个 std::random_device 值和 std::seed_seq 组合

  • 多线程种子预生成策略: 在开始任何模拟之前,使用主随机数生成器预生成所有试验的种子

  • Python 绑定层检测 seed 参数是否为 None,自动转换为 use_random_seed 标志

  • record_activated 参数不影响 run_monte_carlo_diffusion() 的性能和结果

  • 多线程模式下,每个线程使用独立的随机数生成器,确保线程安全和结果一致性

使用示例

# IC 模型
from pynetim.cpp.diffusion_model import IndependentCascadeModel

# 不记录激活节点(默认)
ic_model = IndependentCascadeModel(graph, seeds)
count = ic_model.run_single_simulation()  # 随机种子
print(f"激活节点数量: {count}")

# 记录激活节点
ic_model = IndependentCascadeModel(graph, seeds, record_activated=True)
count = ic_model.run_single_simulation(seed=42)  # 固定种子
activated_nodes = ic_model.get_activated_nodes()
print(f"激活节点数量: {count}")
print(f"激活的节点: {activated_nodes}")

# 动态切换记录
ic_model.set_record_activated(True)
count = ic_model.run_single_simulation()
activated_nodes = ic_model.get_activated_nodes()

ic_model.set_record_activated(False)
count = ic_model.run_single_simulation()
activated_nodes = ic_model.get_activated_nodes()  # 空集合

# LT 模型
from pynetim.cpp.diffusion_model import LinearThresholdModel

lt_model = LinearThresholdModel(graph, seeds, theta_l=0.0, theta_h=1.0, record_activated=True)
count = lt_model.run_single_simulation()  # 随机种子
activated_nodes = lt_model.get_activated_nodes()
print(f"激活节点数量: {count}")
print(f"激活的节点: {activated_nodes}")

# 多轮模拟
avg_count = ic_model.run_monte_carlo_diffusion(1000)  # 随机种子
print(f"平均激活节点数: {avg_count:.2f}")

avg_count = ic_model.run_monte_carlo_diffusion(1000, seed=42)  # 固定种子
print(f"平均激活节点数: {avg_count:.2f}")

# 多线程多轮模拟
avg_count = ic_model.run_monte_carlo_diffusion(1000, seed=42, use_multithread=True, num_threads=4)
print(f"平均激活节点数: {avg_count:.2f}")

# 激活频率记录
ic_model = IndependentCascadeModel(graph, seeds, record_activation_frequency=True)
avg_count = ic_model.run_monte_carlo_diffusion(1000, seed=42)
freq = ic_model.get_activation_frequency()
print(f"节点激活频率: {freq}")

# 同时记录激活节点和频率
ic_model = IndependentCascadeModel(graph, seeds, record_activated=True, record_activation_frequency=True)
avg_count = ic_model.run_monte_carlo_diffusion(1000, seed=42)
activated_nodes = ic_model.get_activated_nodes()  # 所有试验的激活节点并集
freq = ic_model.get_activation_frequency()  # 每个节点的激活次数
print(f"所有试验中被激活过的节点: {activated_nodes}")
print(f"节点激活频率: {freq}")

[0.4.3] - 2026-03-30

新增

IC 和 LT 模型新增激活节点记录功能:

  • 添加 record_activated 参数(默认为 false),用于控制是否记录种子集激活的节点

  • 添加 set_record_activated(record) 方法,可在运行时动态开启/关闭记录功能

  • 添加 run_single_simulation(seed=0) 方法,用于单次独立传播模拟

  • 每次调用 run_single_simulation 都会进行一次独立的传播模拟,从种子集出发,返回该次模拟中被激活的所有节点

  • 支持通过不同的 seed 参数进行多次独立的模拟实验

API 更新

IndependentCascadeModel:

  • 构造函数新增 record_activated 参数

  • 新增 set_record_activated(record: bool) 方法

  • 新增 run_single_simulation(seed: int = 0) -> Set[int] 方法

LinearThresholdModel:

  • 构造函数新增 record_activated 参数

  • 新增 set_record_activated(record: bool) 方法

  • 新增 run_single_simulation(seed: int = 0) -> Set[int] 方法

测试

新增功能验证测试:

  • IC 模型单次模拟测试

  • LT 模型单次模拟测试

  • 多次独立模拟测试(使用不同 seed)

  • set_record_activated 动态切换测试

  • 编译和运行测试全部通过

技术细节

修改文件:

  • src/pynetim/cpp/include/diffusion_model.h - 添加核心功能实现

  • src/pynetim/cpp/bindings/ic_bind.cpp - IC 模型 Python 绑定

  • src/pynetim/cpp/bindings/lt_bind.cpp - LT 模型 Python 绑定

  • src/pynetim/cpp/diffusion_model/independent_cascade_model.pyi - 类型提示

  • src/pynetim/cpp/diffusion_model/linear_threshold_model.pyi - 类型提示

实现细节:

  • run_single_trial 方法新增 activated_nodes 参数,用于记录激活节点

  • record_activatedtrue 时,遍历所有节点收集激活的节点

  • 使用独立的随机数生成器确保每次模拟的独立性

  • 保持向后兼容性,record_activated 默认为 false

使用示例

# IC 模型
from pynetim.cpp.diffusion_model import IndependentCascadeModel

ic_model = IndependentCascadeModel(graph, seeds, record_activated=True)
activated_nodes = ic_model.run_single_simulation(seed=42)
print(f"激活的节点: {activated_nodes}")

# LT 模型
from pynetim.cpp.diffusion_model import LinearThresholdModel

lt_model = LinearThresholdModel(graph, seeds, theta_l=0.0, theta_h=1.0, record_activated=True)
activated_nodes = lt_model.run_single_simulation(seed=42)
print(f"激活的节点: {activated_nodes}")

# 动态开启/关闭记录
ic_model.set_record_activated(True)

[0.4.2] - 2026-03-24

修复

C++ 扩展线程安全修复:

  • 修复 IndependentCascadeModelLinearThresholdModel 在多线程模式下的线程安全问题

  • 通过参数传递替代直接访问成员变量 seeds,消除跨线程共享 std::set 的状态

  • 解决了与 PyTorch 等多线程库结合使用时的 segmentation fault 问题

  • 经过严格测试:5000 次迭代、8 线程并发、10 图并发均稳定运行

  • 单线程和多线程结果完全一致,确保正确性

测试

新增严格验证测试:

  • 单线程/多线程一致性测试

  • 长时间多线程压力测试 (2000 次迭代)

  • 多线程并发访问测试 (8 线程)

  • 多图并发处理测试 (10 个图)

  • 极限压力测试 (5000 次迭代)

[0.4.0] - 2026-03-23

新增

  • C++ 图支持: 为所有工具函数添加了对 ``IMGraphCpp``(C++ 图)的完整支持

    • set_edge_weight() - 支持 C++ 图的边权重设置

    • infection_threshold() - 支持 C++ 图的感染阈值计算

    • graph_statistics() - 支持 C++ 图的统计信息计算

    • graph_density() - 支持 C++ 图的密度计算

    • connectivity_analysis() - 支持 C++ 图的连通性分析

优化

性能优化:

  • infection_threshold() - 避免重复计算度字典

  • graph_statistics() - 使用 Counter 替代手动循环统计度分布

  • connectivity_analysis() - 合并有向图和无向图的分支逻辑,消除重复代码

  • graph_density() - 修复整数除法问题,确保返回浮点数

代码质量:

  • 删除无用的 TYPE_CHECKING 代码块

  • 统一函数签名,支持多种图类型

  • 优化导入语句,添加缺失的 networkx 模块

修复

Bug 修复:

  • 修复 utils.py 中缺失的 import networkx as nx 导入

  • 修复 graph_density() 函数中的整数除法 // 2 导致的精度丢失

  • 修复 infection_threshold() 函数中重复调用 dict(graph.degree()) 的性能问题

文档

学术参考文献: 为所有主要模型和算法添加了学术参考文献

传播模型:

  • Linear Threshold Model - Granovetter (1978)

  • Independent Cascade Model - Kempe et al. (2003)

  • SIR Model - Kermack & McKendrick (1927)

  • SI Model - Kermack & McKendrick (1927)

影响力最大化算法:

  • Degree Discount Algorithm - Chen et al. (2009)

  • Reverse Influence Sampling (RIS) - Borgs et al. (2014)

  • IMM Algorithm - Tang et al. (2015)

工具函数:

  • infection_threshold() - Pastor-Satorras & Vespignani (2001, 2015)

新增文档:

  • UTILS_OPTIMIZATION_SUMMARY.md - utils.py 优化总结

  • OPTIMIZATION_SUMMARY.md - C++ 模块优化总结

  • REFERENCES_DOCUMENTATION.md - 参考文献添加文档

  • CHANGELOG.md - 本更新日志

兼容性

向后兼容: 所有修改都保持了向后兼容性

  • NetworkX 图的使用方式完全不变

  • 新增的 C++ 图支持是可选的

  • 函数签名使用 Union 类型,支持两种图类型

技术细节

  • 图类型检测: 使用 hasattr(graph, 'num_nodes') and hasattr(graph, 'directed') 检测 C++ 图

  • 类型注解: 使用 TYPE_CHECKING 避免循环导入 IMGraphCpp

  • 统一接口: NetworkX 图和 C++ 图使用相同的函数接口

[0.3.0] - 2025-XX-XX

新增

  • 初始版本发布

  • 实现了多种影响力最大化算法

  • 实现了多种传播模型

  • 提供了 Python 和 C++ 双实现

版本说明

PyNetIM 遵循 语义化版本控制 (Semantic Versioning):

  • 主版本号 (MAJOR): 不兼容的 API 修改

  • 次版本号 (MINOR): 向下兼容的功能性新增

  • 修订号 (PATCH): 向下兼容的问题修正

版本类型

  • 主版本更新: 重大架构变更、API 不兼容

  • 次版本更新: 新增功能、性能优化

  • 修订版本更新: Bug 修复、文档更新

更新分类

功能新增

  • 新的算法实现

  • 新的传播模型

  • 新的图操作功能

性能优化

  • 算法效率提升

  • 内存使用优化

  • 计算速度提升

Bug 修复

  • 代码错误修复

  • 边界情况处理

  • 兼容性问题修复

文档改进

  • API 文档更新

  • 示例代码更新

  • 学术参考文献添加

内部改进

  • 代码重构

  • 测试覆盖提升

  • 构建流程优化

贡献指南

如果您想为 PyNetIM 做出贡献,请:

  1. Fork 本仓库

  2. 创建特性分支 (git checkout -b feature/AmazingFeature)

  3. 提交更改 (git commit -m 'Add some AmazingFeature')

  4. 推送到分支 (git push origin feature/AmazingFeature)

  5. 开启 Pull Request

许可证

本项目采用 MIT 许可证 - 详见 LICENSE 文件。

联系方式

最后更新: 2026-04-04