在Python中开发基于代理的仿真框架

在上一篇文章中,我构建了一个基于代理的简单模拟模型,其中包含可以位于战场网格上的代理组。该模型是使用matplotlib进行可视化的Python编码。我继续进行了简单的模拟运行,显示了一个战斗场景及其结果。

下面,我对问题进行了重塑,尝试清理代码并将功能模块化为可重用的功能。结果将是一个模块化框架和通用方法描述,可用于构建非常高级的基于代理的仿真模型。

代理被建模为一个类,如下所示:

# 类,将代理定义为抽象数据类型
class agent:
    # 类,将代理定义为抽象数据类型
    def __init__(self,x,y,group):
        self.life = 100 # 特工的生活得分
        self.x = x
        self.y = y
        self.group = group

战场本身被建模为二维网格阵列,如下所示:

# 在python中使用列表推导创建空的100 x 100列表
battlefield = [[None for i in range(0,100)] for i in range(0,100)]

可以使用agentCreator-function将特工组填充到战场网格上:

#定义用于创建代理并将其分配给网格的函数
def agentCreator(size,group,groupList,field,n,m):
    #遍历整个组,即1000个单位
    for j in range(0,size):
        #选择随机可用位置
        while True:
            #随机x坐标
            x = random.choice(range(0,n))
            #随机y坐标
            y = random.choice(range(0,m))
            #检查现货是否可用;如果没有,请再次重申
            if field[x][y] == None:
                field[x][y] = agent(x=x,y=y,group=group)
                #将代理对象引用附加到组列表
                groupList.append(field[x][y])
                #退出while循环;现场拍摄
                break

为了在战场网格上以生命点的形式绘制具有积极生命值的特工,我创建了一个绘制函数,在整个模拟过程中可以随时调用该函数以获取当前战斗状态的快照:

#从matplotlib导入pyplot和颜色
from matplotlib import pyplot, colors
#定义用于绘制战场的功能(所有仍然活着的特工)
def plotBattlefield(populationArr, plotTitle):
    #使用matplotlib中的颜色,定义颜色图
    colormap = colors.ListedColormap(["lightgrey","green","blue"])
    #使用pyplot定义图形大小
    pyplot.figure(figsize = (12,12))
    #使用pyplot添加标题
    pyplot.title(plotTitle, fontsize = 24)
    #使用pyplot添加x和y标签
    pyplot.xlabel("x coordinates", fontsize = 20)
    pyplot.ylabel("y coordinates", fontsize = 20)
    #使用pyplot调整x和y轴刻度
    pyplot.xticks(fontsize = 16)
    pyplot.yticks(fontsize = 16)
    #使用pyplot中的.imshow()方法可视化代理位置
    pyplot.imshow(X = populationArr, cmap = colormap)

我将需要的另一个函数是一个可以将战场状态映射到包含1.0值的数字二维数组的函数,如果类型A的代理仍位于并处于二维战场网格的给定单元中并且仍处于活动状态,则该函数如果存在类型B的代理,则值为2.0。我使用此映射过程为绘图函数提供数字网格,而不是包含抽象对象(即定制类)的网格:

#此功能将战场网格映射到数字网格,其中A代表1,A代表2,B没有代表0
def mapBattlefield(battlefieldArr):
    #.imshow()需要一个带有float元素的矩阵;
    populationArr = [[0.0 for i in range(0,100)] for i in range(0,100)]
    #如果座席类型为A,则输入1.0;如果类型B,则为2.0
    for i in range(1,100):
        for j in range(1,100):
            if battlefieldArr[i][j] == None: 
                pass
            elif battlefieldArr[i][j].group == "A": 
                populationArr[i][j] = 1.0 # 1.0 means "A"
            else: 
                populationArr[i][j] = 2.0 # 2.0 means "B"
    #返回映射值
    return(populationArr)

使用这些模型组件,我创建了一个初始战场人口并使用matplotlib绘制了代理位置。这是在下面的代码中完成的,并且类似于我以前的文章,只是在这种情况下,我使用了模块化功能。设置初始战场网格的功能将在下面转移到单独的功能。然后执行该功能。

#创建初始战场网格的函数
def initBattlefield(populationSizeA,populationSizeB,battlefieldArr):
    #使用Python中的列表理解来初始化新的空战场网格
    battlefieldArr = [[None for i in range(0,100)] for i in range(0,100)]
    #为以后创建包含代理引用的空列表,键入A和B
    agents_A = []
    agents_B = []
    #为A和B组的特工分配随机点;
    import random
    agentCreator(size = populationSizeA,
                    group = "A",
                    groupList = agents_A,
                    field = battlefieldArr,
                    n = 100,
                    m = 100)
    agentCreator(size = populationSizeB,
                    group = "B",
                    groupList = agents_B,
                    field = battlefieldArr,
                    n = 100,
                    m = 100)
    # return populated battlefield grid
    return(battlefieldArr)

#对两个组的人口总数都执行上述函数
battlefield = initBattlefield(populationSizeA=1000,populationSizeB=1000,battlefieldArr = battlefield)

#绘制战场状态
plotBattlefield(populationArr = mapBattlefield(battlefield), 
                    plotTitle = "battlefield before simulation run (green = A, blue = B)")

然后,在上一篇文章中,我基于以下规则进行了模拟运行:

A组的策略是始终在每一轮中都击中相同的特工

B组有随机且独立的攻击敌人的策略。这意味着B型每个代理都会攻击该代理范围内的随机选择的代理。

在以下条件下进行仿真:

1)每轮是一次迭代

2)在每个回合中,每个特工都可以在其作用范围内攻击一个特工

3)代理的范围是在模拟开始时定义的,默认值为10

4)如果特工死亡,他将不再位于战场上

5)代理人的生活得分等于或低于零时会死亡

6)每个特工都有随机分布的攻击力,范围从10到60

7)在每个回合中,所有特工都可以发起攻击

就像我以前的一篇文章一样,我现在将迭代50轮战斗。每回合之后,生命力得分为非阳性的特工将从战场网格中移除。与以前的文章有所不同,我现在将功能模块化。

首先,我定义一个用于从战场网格中删除特工的函数:

# 当生命分数并非严格为正时,用于从战场网格中删除特工的功能
def removeDeadAgents(battlefieldArr):
    # 识别生活得分为或低于分数的代理商-并将其从网格中删除
    for i in range(0,len(battlefieldArr)):
        for j in range(0,len(battlefieldArr)):
            if battlefieldArr[i][j]:
                if battlefieldArr[i][j].life <= 0:
                    # 由于生活得分并非严格为正,因此请删除此代理
                    battlefieldArr[i][j] = None

接下来,我定义一个为A型代理实施战斗策略的函数:

# 为A型特工实施一轮战斗的功能
def oneRoundAgentA(i,j,attackRange):
    found_i = None
    found_j = None
    # 每次迭代以相同顺序查看邻居单元
    for k in range(i-attackRange,i+attackRange+1):
        for l in range(j-attackRange,j+attackRange+1):
            # 检查负索引值;如果是这样-打破!
            if k < 0 or l < 0:
                break
                # 检查索引值是否大于99,如果这样的话,请中断!
            if k > 99 or l > 99:
                break
            if battlefield[k][l]:
                if battlefield[k][l].group == "B": 
                    if found_i == None:
                        found_i = k
                        found_j = l
                    
    # 对已识别的敌人造成伤害
    if found_i:
        battlefield[found_i][found_j].life = battlefield[found_i][found_j].life - random.randint(10,60)

然后,我对B型代理执行相同的操作:

# 为B型特工实施一轮战斗的功能
def oneRoundAgentB(i,j,attackRange):
    # 首先检查周围的一个牢房中是否有敌人
    enemy_found = False
    for k in range(i-attackRange,i+attackRange+1):
        for l in range(j-attackRange,j+attackRange+1):
            # 检查负索引,如果是,则中断下一次迭代
            if k < 0 or l < 0:
                break
                # 检查索引值是否大于99(如果是)
            if k > 99 or l > 99:
                break
            if battlefield[k][l] != None:
                if battlefield[k][l].group == "A":
                    enemy_found = True
    # 选择一个随机行和一个随机列
    found_i = None
    found_j = None
    while enemy_found and found_i == None:
        k = random.randint(i-attackRange,i+attackRange)
        l = random.randint(j-attackRange,j+attackRange)
        # 检查负索引,如果是,则继续下一个迭代
        if k < 0 or l < 0:
            continue
        # 检查索引值> 99,如果是,则继续
        if k > 99 or l > 99:
            continue
        if k != i:
            if battlefield[k][l]: 
                if battlefield[k][l].group == "A":
                    found_i = k
                    found_j = l
        else:
            if l != j:
                if battlefield[k][l]:
                    if battlefield[k][l].group == "A":
                        found_i = k
                        found_j = l
    # 对已识别的敌人造成伤害
    if found_i:
        battlefield[found_i][found_j].life = battlefield[found_i][found_j].life - random.randint(10,60)

我使用已经实现的功能继续模拟战斗:

for counter in range(0,50): # 在这种情况下,我进行了50次迭代
    # 遍历战场上的所有细胞
    for x in range(0,len(battlefield)):
        for y in range(0,len(battlefield)):
            # 检查相应单元格内是否有代理
            if battlefield[x][y] != None:
                # 根据类型:执行相应的攻击策略
                if battlefield[x][y].group == "A":
                    # 这个A型特工的一场战斗
                    oneRoundAgentA(i = x, j = y,attackRange=10)
                else: 
                    # 这个B型特工的一场战斗
                    oneRoundAgentB(i = x, j = y,attackRange=10)
    # 识别生活得分为或低于分数的代理商-并将其从网格中删除
    removeDeadAgents(battlefieldArr = battlefield)
# 阴谋战场状态
plotBattlefield(populationArr = mapBattlefield(battlefield), 
                plotTitle = "battlefield after 50 iterations (green = A, blue = B)")

到目前为止,这在内容上与先前文章中介绍的简单模拟研究相同。

我现在想添加一个图表来分析战斗结果和战斗本身的进展。因此,我定义了一个附加的绘图功能,该功能可按类型绘制仍在运行的代理数量。

我还实现了一个功能,用于更新仍处于活动状态的代理的时间序列。

# 用于更新代理活动时间序列的功能
def calcAgentsAliveA(resultsA,battlefieldArr):
    # 这些变量将用于计算存活的代理数量
    countA = 0
    countB = 0
    # 遍历网格,查找代理并更新相关计数
    for i in range(0,len(battlefieldArr)):
        for j in range(0,len(battlefieldArr)):
            if battlefieldArr[i][j]:
                if battlefieldArr[i][j].group == "A":
                    countA = countA + 1
                else:
                    countB = countB + 1
    # 更新结果列表并返回
    resultsA.append(countA)
    return(resultsA)

# 用于更新代理活动时间序列的功能
def calcAgentsAliveB(resultsB,battlefieldArr):
    # 这些变量将用于计算存活的代理数量
    countA = 0
    countB = 0
    # 遍历网格,查找代理并更新相关计数
    for i in range(0,len(battlefieldArr)):
        for j in range(0,len(battlefieldArr)):
            if battlefieldArr[i][j]:
                if battlefieldArr[i][j].group == "A":
                    countA = countA + 1
                else:
                    countB = countB + 1
    # 更新结果列表并返回
    resultsB.append(countB)
    return(resultsB)

# 绘制仍然存在的代理数量的函数
def plotNumberOfAgentsAlive(plotTitle,iterations,resultsA,resultsB):
    from matplotlib import pyplot
    pyplot.figure(figsize = (12,12))
    pyplot.title(plotTitle, fontsize = 24)
    pyplot.xlabel("iteration", fontsize = 20)
    pyplot.ylabel("agents still alive", fontsize = 20)
    pyplot.xticks(fontsize = 16)
    pyplot.yticks(fontsize = 16)
    ax = pyplot.subplot()
    ax.plot(iterations,resultsA, label = "type a agents")
    ax.plot(iterations,resultsB, label = "type b agents")
    ax.legend(fontsize=16)

现在,我可以使用显示图形来进行另一次模拟运行。由于我将运行各种模拟场景,因此我还将模拟运行写入一个函数:

# 定义功能以进行模拟运行
def simulationRun(iterationLimit,attackRange,showPlot):
    iterations = []
    resultsA = []
    resultsB = []
    # 在这种情况下,我进行了50次迭代
    for counter in range(0,iterationLimit): 
        iterations.append(counter+1)
        resultsA = calcAgentsAliveA(resultsA,battlefield)
        resultsB = calcAgentsAliveB(resultsB,battlefield)
        #遍历战场上的所有单元
        for x in range(0,len(battlefield)):
            for y in range(0,len(battlefield)):
                # 检查相应单元格内是否有代理
                if battlefield[x][y]:
                    # 根据类型:执行相应的攻击策略
                    if battlefield[x][y].group == "A":
                        # 这个A型特工的一场战斗
                        oneRoundAgentA(i = x, j = y, attackRange = attackRange)
                    else: 
                        # 这个B型特工的一场战斗
                        oneRoundAgentB(i = x, j = y, attackRange = attackRange)
        # 识别生活得分为或低于分数的代理商-并将其从网格中删除
        removeDeadAgents(battlefieldArr = battlefield)
    # 绘制战斗进度,但前提是应显示绘制
    if showPlot:
        plotNumberOfAgentsAlive("battle progression",iterations,resultsA,resultsB)
    # 返回结果
    return([resultsA,resultsB])

我现在只能使用一行代码来进行模拟运行:

battlefield = initBattlefield(populationSizeA = 1000,populationSizeB = 1000,battlefieldArr = battlefield)
results = simulationRun(iterationLimit = 50, attackRange = 10, showPlot = True)

看来类型B的代理具有某种有效的攻击策略。但是,如果战斗开始时他们的人数稍微减少了怎么办?在下面,我绘制了与1000个初始A代理和950个初始B代理的战斗中的战斗进度。

battlefield = initBattlefield(populationSizeA = 1000,populationSizeB = 950,battlefieldArr = battlefield)
results = simulationRun(iterationLimit = 50, attackRange = 10, showPlot = True)

如果攻击距离减小到5,会发生什么?

battlefield = initBattlefield(populationSizeA = 1000,populationSizeB = 1000,battlefieldArr = battlefield)
results = simulationRun(iterationLimit = 50, attackRange = 5, showPlot = True)

特工的初始战场位置是随机的。因此,最终战斗结果也可能是随机分配的。我想调查结果在多大程度上是随机的。为此,我实现了灵敏度测试功能,该功能可以一遍又一遍地重复仿真。此函数将返回可以在直方图中可视化的结果,表示模拟结束时存活的代理总数。在这种情况下,我将模拟重复50次。

下面我实现进行敏感性测试的功能:

# 该功能用于对战斗结果进行敏感性测试
def sensitivityTest(iterationLimit,attackRange,runs):
    # 表明战场是一个全球可变的全球战场
    # 空列表,其中包含战斗结束时各个类型的特工的最终数量
    outcomeA = []
    outcomeB = []
    # 以定义的攻击范围和迭代极限重复仿真,进行“运行”次数
    for i in range(0,runs):
        # 在必须初始化每个模拟运行战场之前
        battlefield = initBattlefield(populationSizeA = 1000,populationSizeB = 950,battlefieldArr = battlefield)
        # 进行模拟运行
        results = simulationRun(iterationLimit=iterationLimit,attackRange = attackRange,showPlot = False)
        # 将结果附加到相关结果列表中
        outcomeA.append(results[0][iterationLimit-1])
        outcomeB.append(results[1][iterationLimit-1])
    # 在带有两个子列表的列表中返回结果
    return([outcomeA,outcomeB])

下面我实现直方图绘制功能:

# 绘制直方图的功能
def plotHistogram(plotTitle,resultsA,resultsB):
    from matplotlib import pyplot
    pyplot.figure(figsize = (12,12))
    pyplot.title(plotTitle, fontsize = 24)
    pyplot.xlabel("number of agents still alive", fontsize = 20)
    pyplot.ylabel("absolute frequency", fontsize = 20)
    pyplot.xticks(fontsize = 16)
    pyplot.yticks(fontsize = 16)
    ax = pyplot.subplot()
    ax.hist(resultsA, bins=20, histtype="bar",color="red",label="agent A",alpha=0.25)
    ax.hist(resultsB, bins=20, histtype="bar",color="blue",label="agent B",alpha=0.25)
    ax.legend(fontsize=16)

最后,下面的代码执行可见度测试并绘制结果。

# 执行灵敏度测试
results = sensitivityTest(iterationLimit = 50,attackRange = 5,runs = 50)
plotHistogram(plotTitle = "distribution of agents",resultsA = results[0], resultsB = results[1])

攻击范围是5。

使用这种方法,可以开发非常先进的基于代理的仿真模型。

Leave a Reply

发表评论

电子邮件地址不会被公开。 必填项已用*标注

Close

功能