我之前介绍了我正在研究的基于代理的建模和仿真框架的第一个 sprint。在这篇博文中,我在 Python 中实现了一个基于代理的隔离模型。类似的模型,有一些调整和偏差,例如用于理解社会隔离。这篇博文的主要目的是提供另一个 Python 中基于代理的建模和模拟的演示。使用的框架是 Python 中的abm_framework。
Python中基于代理的建模框架
我开发了一个隔离模型,作为我正在使用 Python 开发的基于代理的建模框架的另一个应用示例。你可以在这里下载 GitHub 上的框架:
GitHub 存储库包含一个演示文件夹。示例应用程序可在此处获得。在本文发表时,还没有那么多演示模型可用。我将在接下来的几周内添加更多演示模型,并且现有模块将被扩展以支持更广泛的基于代理的建模应用程序。
在abm_framework中,重要的模块如下:
- framework.py用于建模,例如代理、代理群体和模拟环境
- data.py用于管理用于写入和读取模拟结果的数据库操作
- stats.py用于绘制模拟结果(也独立于模拟运行执行)
- config.py用于例如指定相关路径,例如数据库文件路径
- animation.py用于动画模拟结果
下图总结了GitHub 上abm_framework仓库的内容。

文件model.py是仿真模型本身。对于手头文章中描述的示例,我将文件命名为segregation.py。模型文件实现了一个特定的模型。该框架(包含上述所有模块)用作工具箱。换句话说,该框架旨在更容易实现特定的仿真模型。
基于代理的概念隔离模型描述
由于我正在使用我在之前的文章中介绍的基于代理的建模框架,因此我正在开发一个基于网格的基于代理的仿真模型。也就是说,我将代理分配给网格。因此,代理具有位置。代理也有一个具有指定半径的邻域。代理之间的交互发生在这个社区内。下图取自我之前关于Python 中基于代理的 SIR模型实现的文章,说明了这个概念。

在这个示例性的基于代理的隔离模型中,我通过实现以下流程图来促进隔离机制:

对于每次迭代,我都会随机选择一个代理。然后我计算代理当前单元格中的效用影响。这种效用影响是通过迭代所有相邻代理来计算的。如果邻居与代理本身属于“同一类型”,则效用影响会增加(积极影响)。如果邻居不是“同一类型”,则效用影响会降低(负面影响)。
接下来,选择网格上的一个随机空闲单元,并计算该单元中假设的效用影响。对于该计算,适用与上述相同的逻辑。如果该单元中的假设效用影响为正,则代理将重新定位到该位置并再次更新其效用。如果不是,则选择另一个空闲单元并应用相同的测试。该过程自身重复到最大搜索限制,即指定的最大重复次数。
Python中基于代理的隔离模型实现
下面是实现基于代理的隔离模型的代码。
__author__ = "Linnart Felkl"
__email__ = "LinnartSF@gmail.com"
if __name__ == "__main__":
print("demo starts")
import sys
from pathlib import Path
file = Path(__file__).resolve()
parent, root = file.parent, file.parents[1]
sys.path.append(str(root))
# remove the current file's directory from sys.path, unless already removed
try:
sys.path.remove(str(parent))
except ValueError:
pass
import data
import stats
import config
import framework
import random
import animation
# setup database manager and connection
db = data.Database("sqlite3", config.path_databasefile)
db_manager = data.Manager(db)
# create an empty environment
env = framework.Environment(1, True, 20, 20, db_manager)
# create initial population of healthy humans
attrs = ["utility","type"]
datatypes = ["REAL","TEXT"]
pops = framework.Populations(amount = 2, env = env, db_manager = db_manager, attributes = attrs, datatypes = datatypes)
pops.add_population(name = "natives",
size = 50,
attributes = attrs,
datatypes = datatypes,
initialvals = [100, "native"]
)
pops.add_population(name = "immigrants",
size = 50,
attributes = attrs,
datatypes = datatypes,
initialvals = [100, "immigrant"]
)
# setup simulation
sim = framework.Simulation(1000)
# make sure that environment and agents tables in database are set
pops.write_env_to_db(sim.Iteration)
pops.write_agents_to_db(sim.Iteration)
agents = pops.get_agents()
# other model specific global settings
_max_search = 10
_impactarea = 1
# execute simulation run
while sim.run():
# select one random agent
agent = random.choice(agents)
# get that agents neighbourhood
neighbours = env.get_neighbourhood(agent, mode = "moore", radius = _impactarea)
util_is = 0.0
# if there are neighbours, then recalculate the utility of the agent
for o in neighbours:
if o.get_attr_value("type") == agent.get_attr_value("type"):
util_is += 10
else:
util_is += -10
# update agent utility
agent.increase_attr_value("utility",util_is)
# for search up to maximum limit of random free cells
cells = env.get_freecells(n = _max_search)
for c in cells:
util_new = 0.0
neighbours = env.get_neighbourhood(c, "moore", radius = _impactarea)
for o in neighbours:
if o.get_attr_value("type") == agent.get_attr_value("type"):
util_new += 10
else:
util_new += -10
if util_new > util_is:
# relocate agent, then break loop
env.relocate(agent, c)
agent.increase_attr_value("utility",util_new)
break
# update results in database, for agents and for environment
if (sim.Iteration % 10) == 0:
pops.write_agents_to_db(sim.Iteration)
pops.write_env_to_db(sim.Iteration)
pops.write_density_to_db(sim.Iteration)
# get dataframes with simulation results
agents_df = db_manager.get_agentsdf()
env_df = db_manager.get_environmentdf()
density_df = db_manager.get_densitydf()
# visualize simulation data
stats.set_fontsizes(8,10,12)
stats.plot_grid_occupation(env_df, ["natives","immigrants"], colors = ["#F52D2D","#4A87F1"], maxtime=0, markersize = 150.0)
stats.save_plot("segplt_early_ia1_50agents_1000it")
stats.plot_grid_occupation(env_df, ["natives","immigrants"], colors = ["#F52D2D","#4A87F1"], maxtime=500, markersize = 150.0)
stats.save_plot("segplt_intermediate_ia1_50agents_1000it")
stats.plot_grid_occupation(env_df, ["natives","immigrants"], colors = ["#F52D2D","#4A87F1"], maxtime=1000, markersize = 150.0)
stats.save_plot("segplt_late_ia1_50agents_1000it")
stats.plot_avgattr_lines(["utility"], agents_df)
stats.save_plot("avgutil_ia1_50agents_1000it")
animation.animate_grid_occupation(
df = env_df,
filename = "segvid_ia1_50agents_1000it",
population = ["natives","immigrants"],
colors = ["#F52D2D","#4A87F1"],
tpf = 0.20, # time per frame
mintime = 0,
maxtime = 1000,
markersize = 150.0
)
animation.animate_density(
df = density_df,
filename = "segdens_ia1_50agents_1000it",
attr = "utility",
defaultsize = 150,
color = "#F52D2D",
tpf = 0.20,
maxtime = 1000
)
# end program
db.close()
print("demo ends")
由于这只是一篇示范文章,演示了如何将基于网格的基于代理的建模框架用于基于代理的隔离模型,因此我只执行了 250 次迭代。对于有限数量的迭代,我可以将当前系统状态(代理属性值、网格占用等)写入我的结果数据库。但是,如果我有更多的迭代次数,我应该只将迭代的一个子集写入我的数据库。例如,每 10 次迭代。同样,为了使动画创建更快,我可以例如通过仅从数据库中提取每第 n 个模拟时间来子集结果。当使用matplotlib制作动画时,我还应避免使用loc = “best”进行图例位置选择。
基于代理的隔离模型仿真结果
在本节中,我将展示上述模拟运行生成的可视化。可视化以标准化的形式出现。我通过使用我的 GitHub 存储库 (abm_framework) 创建了它们。也就是说,该框架支持用于存储模拟结果的数据库管理,以及模拟结果的可视化和动画化。
下图显示了模拟开始时的网格占用。

最后,在 1000 次迭代后可视化网格占用的图。

为了完成本节,我还想查看整个模拟期间的平均代理效用。我可以使用 abm_framework 的 stats.py 提供的plot_avgattr_lines ( )函数来可视化这一点。

平均公用事业线不断增加。这表明代理人平均处于积累更多效用的状态。这种状态很可能不是静止的,并且存在隔离模型,例如限制代理的最大效用,或者只考虑环境变化时效用的变化。还有一些基于代理的隔离模型,仅当相应代理的情况“足够糟糕”时才允许代理重新定位(即代理可以容忍公用事业损失达到一定限度)。
如前所述,stats.py模块也支持动画。在下一节中,将展示基于代理的隔离模型的两个动画。
基于代理的隔离模型的动画
对于这个示例性框架实现,我使用了两个标准化动画。第一个显示随时间变化的代理位置。
第二个动画显示了效用密度如何随时间变化。形成了具有非常高效用的集群。同时,其他领域的效用非常低(与其他代理相比)。
如果我愿意,我现在可以继续仔细研究代理效用等的分布。但是,这篇博文旨在展示我的基于代理的框架(目前处于早期阶段)如何应用于隔离模型发展。我现在已经这样做了。我将在以后的博客文章中记录 ABM 框架的其他示例性应用。
相关的基于代理的建模内容
我们的博客上提供了与基于代理的建模和基于代理的仿真相关的其他内容。以下是您可能感兴趣的一些相关文章:

专业领域为优化和仿真的工业工程师(R,Python,SQL,VBA)
Leave a Reply