利用AnyLogic软件搭建多智能体模型验证Lanchester方程线性律和平方律
写在前面
这篇文档是一篇备忘文档。记录了我使用AnyLogic搭建智能体群,并用其验证Lanchester方程线性律和平方律的过程。Lanchester方程是一种数学解析模型,用以模拟对战过程中交战双方作战能力的损耗情况,它分为线性律和平方律。
线性律的表达式为
{
d
y
d
t
=
?
α
,
d
x
d
t
=
?
β
(1)
\left \{ \begin{matrix} \frac{\text{d}y}{\text{d}t}=-\alpha,\\ \frac{\text{d}x}{\text{d}t}=-\beta \end{matrix} \right. \tag{1}
{dtdy?=?α,dtdx?=?β?(1)
式中,
x
(
t
)
x(t)
x(t) 和
y
(
t
)
y(t)
y(t) 分别是红、蓝方在
t
t
t 时刻的作战能力,
α
\alpha
α 和
β
\beta
β 分别是红方对蓝方的损耗系数和蓝方对红方的损耗系数。损耗系数为单位时间内一方可摧毁另一方多少作战能力的意思。线性律适用于描述冷兵器对抗的作战过程,对抗双方贴身肉搏,一对一捉对拼杀。
平方律的表达式为
{
d
y
d
t
=
?
α
x
,
d
x
d
t
=
?
β
y
(2)
\left \{ \begin{matrix} \frac{\text{d}y}{\text{d}t}=-\alpha x, \\ \frac{\text{d}x}{\text{d}t}=-\beta y \end{matrix} \right. \tag{2}
{dtdy?=?αx,dtdx?=?βy?(2)
式中,
x
(
t
)
x(t)
x(t) 、
y
(
t
)
y(t)
y(t) 、
α
\alpha
α 和
β
\beta
β 的意义与式(1) 一致。平方律适用于描述火器对抗的作战过程,对抗双方远距离点火力射击,也能体现现代战争中集中优势兵力原则对于作战过程的影响。
也有学者使用Lanchester方程对历史战例进行分析,取得了比较好的效果。此外,许多实时策略游戏(例如星际争霸2)、战棋游戏(例如火焰之纹章)、兵棋游戏(例如红龙)等不同单位之间的损耗过程也参考了Lanchester方程进行设置,所以Lanchester方程应用的十分广泛。
借鉴上面的现状,我在想,有没有一种方式可以用来验证一下Lanchester方程的正确性。真要进行一场战斗是不可能了,因为这样代价太大了,而且也不是我们普通人可以实施的方案。所以呢,为了得到作战数据,采用了仿真手段进行作战过程的模拟,看看能不能从这里面得到一些有用的结论。
加上AnyLogic这个软件在仿真界比较常见,也比较权威、专业,许多学者用它来做多智能体模型、系统动力学分析,应用领域十分广泛,有供应链和物流、制造业、市场和竞争、项目和资产管理、社会和生态动力学、国防等领域。因此,我选用AnyLogic搭建多智能体模型,用以验证Lanchester方程线性律和平方律。
这篇教程里面已经默认读者在电脑上安装了AnyLogic。没有安装的读者可以去AnyLogic官网申请个人学习版。
1. 创建新模型
点击左上角“文件”,再点击“新建”,最后点击“模型”。
新建模型,模型名字根据自己喜好起,位置放在一个自己之后比较好找的位置,模型时间单位选秒(或者其他单位均可)。设置完毕后,点击“完成”。
这样一个模型界面就建立好了,如果没有出意外的话,会看到下面这样的画面。
2. 创建场景
这里说的场景是指在Main 里面的场景,分为三个部分,
- 规定好智能体移动的范围,相当于一个平台;
- 把智能体画出来;
- 添加智能体需要的参数和变量,以及需要导出的参数变量。
这里需要用到“面板”中的“演示”里面的各种形状,包括:
经过一番画图和添加参数,会得到下面这样的场景。
图片中的1、2、3和上面写到序号1、2、3相对应。1对应的就是平台,这里我设置的尺寸是600*600 ,设置参数的步骤在第3节说。
2对应的是智能体,红色小箭头就代表了智能体,可以用折线画出来;红色箭头后面只有一点点大的红色小点可以用矩形画出来,后面用来产生动画效果的轨迹;红色箭头右下角的黄色几何图形代表该智能体的瞄准方向,可以用直线和矩形画出来。
3对应的是智能体需要的参数和变量,在下一节会详细讲怎么添加以及设置参数。
在这里说一下3中有个红色的方块和蓝色的方块,这两个方块可以用矩形画出来,是用来设置智能体的颜色的,它们的设置如下:
3. 添加场景参数
这里分了如下几种类型的参数:
- 参数;
- 变量;
- 数据集;
- excel文件;
- 事件;
- 函数;
- 智能体群。
3.1 参数
按照如下顺序把参数添加进场景中:“面板”——>“智能体”——>“智能体组件”——>“参数”,鼠标左键单击就可以把参数拖拽进场景中了,设置参数名称、类型和默认值在“属性”栏中设置。鼠标左键单击参数图标左边的小圆圈就可以了(注意:点文字是没有用的),像下图一样设置参数。
这里给出所有用到的参数的名称、类型和默认值。下面这些参数用于智能体combatSatSwarm 的参数设置。
名称 | 类型 | 默认值 | 名称 | 类型 | 默认值 |
---|
dt | double | 1 | sensorRange | double | 150 | height | double | 600 | collisionRange | double | 5 | width | double | 600 | deathRangeD | double |
10
0
?
100^*
100? | swarmSizeRD | double | 500 | deathRangeK | double |
1
0
?
10^*
10? | swarmSizeRK | double | 500 | minimumSpeed | 速度(米每秒) | 2 | swarmSizeBD | double | 170 | maximumSpeed | 速度(米每秒) | 5 | swarmSizeBK | double | 170 | turnAngle | double | 0.23 | hitPointRD | double | 1 | acceleration | double | 0.05 | hitPointRK | double | 1 | deathConstant | double | 0.23109 | hitPointBD | double | 10 | attackConstant | double | 1.69 | hitPointBK | double | 10 | retreatConstant | double | 1.0 |
注:数字上标带有*号说明该数据作过修改,按照上述参数设置不能得到有效仿真结果。
3.2 变量
按照如下顺序把变量添加进场景中:“面板”——>“智能体”——>“智能体组件”——>“变量”,操作与添加参数的操作一样,鼠标左键单击拖拽进场景中即可,同样在“属性”栏中设置名称和类型,初始值不用设置。
这里给出所有用到的变量的名称、类型。下面4种变量用于记录对抗过程中4种不同类型兵种的数量变化。
名称 | 类型 | 名称 | 类型 |
---|
forceRDVar | double | forceRKVar | double | forceBDVar | double | forceBKVar | double |
3.3 数据集
按照如下顺序把数据集添加进场景中:“面板”——>“分析”——>“数据”——>“数据集”,操作与添加参数的操作一样,鼠标左键单击拖拽进场景中即可,有两个数据集,(1)forceRed 和(2)forceBlue 。它们的属性按照下面的图片设置。这两个数据集用于存储红方和蓝方的兵力数量变化情况,接下来会导出至excel文件中。
forceRed 的属性如下。 forceBlue 的属性如下。
3.4 excel文件
按照如下步骤设置:
- 在电脑上某一位置新建一个excel文件,这里为了方便,我把excel文件创建在了AnyLogic模型文件夹里,命名为
data.xlsx ; - 按照如下顺序把excel文件添加进场景中:“面板”——>“连接”——>“连接”——>“Excel文件”,操作与添加参数的操作一样,鼠标左键单击拖拽进场景中即可;
- 在属性栏中,先设置好“名称”,再在“浏览”中找到刚才新建的excel文件,点击“确定”即可。
这样就把新建好的excel文件链接到AnyLogic模型中了。后面会通过代码把模型中储存的数据导出至外部的excel文件中,会在第3.7节中说明。
3.5 事件
按照如下顺序把事件添加进场景中:“面板”——>“智能体”——>“智能体组件”——>“事件”,操作与添加参数的操作一样,鼠标左键单击拖拽进场景中即可。
这里我设置了两个事件,step 和collectData ,代码写在“属性”栏的“行动”中,下面一一说明,每行代码的意义我会详细注释在代码旁边,这里使用的语言是JAVA。
事件: step
功能说明: 用于执行智能体的移动
引用函数:
size() - BIF
get() - BIF
step()
--------------------------------------
for (int i = 0; i < combatSatSwarms.size(); i++) {
combatSatSwarms.get(i).step();
}
注:BIF 即 Build in Function 内置函数。
step() 函数是智能体群combatSatSwarm 的内置函数,在第5章会介绍。
事件step 的属性按照上图设置,类型选择到时 ,模式选择循环 ,复发时间(即循环时间)为1秒 ,即1秒触发1次step 事件。
事件: collectData
功能说明: 用于收集红方和蓝方兵力数量变化数据
引用函数:
size() - BIF
get() - BIF
add() - BIF
--------------------------------------
for (int i = 0; i < combatSatSwarms.size(); i++) {
if(combatSatSwarms.get(i).color==rectRed.getFillColor()){
if(combatSatSwarms.get(i).type==RD){
forceRDVar += 1;
}
else if(combatSatSwarms.get(i).type==RK){
forceRKVar += 1;
}
}
else if(combatSatSwarms.get(i).color==rectBlue.getFillColor()){
if(combatSatSwarms.get(i).type==BD){
forceBDVar += 1;
}
else if(combatSatSwarms.get(i).type==BK){
forceBKVar += 1;
}
}
}
forceRed.add(forceRDVar,forceRKVar);
forceBlue.add(forceBDVar,forceBKVar);
forceRDVar = 0;
forceRKVar = 0;
forceBDVar = 0;
forceBKVar = 0;
事件collectData 的属性按照上图设置,类型选择到时 ,模式选择循环 ,复发时间(即循环时间)为1秒 ,即1秒触发1次collectData 事件。
3.6 函数
按照如下顺序把函数添加进场景中:“面板”——>“智能体”——>“智能体组件”——>“函数”,操作与添加参数的操作一样,鼠标左键单击拖拽进场景中即可。
函数只有1个,injectTeam ,用于在对抗初始时刻添加红方和蓝方的兵力。
同样地,
- 要写代码,写在“属性栏”里面的“函数体”中;
- 记得添加参数,参数即为函数的输入。
Function: injectTeam()
Input:
1. color [Color]
2. type [TypeOption]
3. minX [double]
4. w [double]
5. minY [double]
6. h [double]
7. meanHeadig [double]
8. teamSize [double]
9. hitPoint [double]
10. deathRange [double]
Constant:
1. teamSize [int]
Output: void
-----------------------------------------------------------
for (int i = 0; i < teamSize; i++) {
CombatSatSwarm css = add_combatSatSwarms();
css.set_color(color);
css.set_type(type);
css.set_hitPoint(hitPoint);
css.set_deathRange(deathRange);
css.set_initX(uniform(minX, minX + w));
css.set_initY(uniform(minY, minY + h));
css.set_initHeading(meanHeading);
}
点击“+”即可添加参数。如果要删除参数的话,可以先选中要删除的参数,再点击“-”号。调换参数位置需要先选中该参数,然后点击上箭头或者下箭头。这里的参数输入顺序是很重要的,在调用函数时,输入参数的顺序不能乱。
3.7 Main设置
在Main 中,点击空白处,”属性“栏会变成下面这样。
我们要设置的是“启动时”和“销毁时”的函数。
“启动时”的函数即在整个模型启动时运行一次,设置如下:
injectTeam(rectRed.getFillColor(), RD, width/2-width/8, width/8, 0,
height/4, 0, swarmSizeRD,hitPointRD,deathRangeD);
injectTeam(rectBlue.getFillColor(), BD, width/2+width/8, width/8, 0,
height/4, Math.PI, swarmSizeBD,hitPointBD,deathRangeD);
“启动时”的函数即在整个模型关闭时运行一次,设置如下:
data.writeDataSet(forceRed,1,2,1);
data.writeDataSet(forceBlue,1,2,3);
4. 创建智能体群
创建智能体有七个步骤。
第一步:选择创建的智能体类型。按照如下顺序点击:
第二步: 创建智能体类型。这一步会出现两个选项,“我想创建新智能体类型”和“我想使用现有智能体类型”,在一开始创建智能体的时候,一般选择“我想创建新智能体类型”。等建立了一个智能体后,想要创建相同类型的智能体,可以选择“我想使用现有智能体类型”。点击下一步。 第三步:填写智能体名称。要填写的地方有两个,一个是新类型名,一个是智能体群名。类型名是这一类智能体的统称,首字母一般要大写,写好类型名后,智能体群名会自动补上。这里我只填写了新类型名CombatSatSwarm ,智能体群名自动补上了combatSatSwarm 。“我正在‘从头’创建智能体群”大多数情况下都得选择上,下面两个选项“使用数据库表”和“智能体将在流程图中使用”一般不选。点击下一步。 第四步:选择智能体2D/3D模型。按照自己需要选择模型即可,这里我选择了无 ,因为在Main 中已经画好了智能体的形状。可以选择的选项有3D、2D和无,要注意这三个选项的不同。点击下一步。 第五步:添加参数。左键单击<添加新…>来添加参数,再在参数框里面写参数名字,然后选择好类型,再点击下一步。
按照下表设置参数。
名称 | 类型 | 默认值 |
---|
acceleration | double | 0.05 | attackConstant | double | 1 | collisionRange | double | 5 | color | Color | gold | deathConstant | double | 0.23109 | deathRange | double | 10 | dt | double | 5 | height | double | 500 | hitPoint | double | 不填 | initHeading | double | uniform() * 2 * Math.PI | initSpeed | 速度(米每秒) | uniform(minimumSpeed, maximumSpeed) | initX | double | uniform() * width | initY | double | uniform() * height | maximumSpeed | 速度(米每秒) | 1.0 | minimumSpeed | 速度(米每秒) | 0.5 | retreatConstant | double | 1 | sensorRange | double | 500 | turnAngle | double | 0.03 | type | TypeOption | 不填 | width | double | 500 |
一共出现了4种类型的参数,分别是double 、Color 、速度(米每秒) 和TypeOption ,下面分别说明这4种类型的参数怎么设置。
double 类型的参数按照下图方式设置即可。 Color 类型的参数按照下图方式设置即可。 速度(米每秒) 类型的参数按照下图方式设置即可。TypeOption 类型的参数按照下图方式设置即可。
注意!!!完成一项参数设置之后,不要点击“下一步”,也不要按回车键,左键单击<添加新…>才能添加新的参数!
第六步:创建群大小。有两个选项,“创建群具有xx个智能体”和“创建初始为空的群”,我会在模型运行时添加智能体,按需选择。我发现的规律:要是一些固定不变的智能体群,例如房子、发电机组,可以选择创建群具有xx个智能体;要是一些在运行过程中会有数量变化的智能体群,例如轰炸机、鸟群,可以选择创建初始为空的群,我会在模型运行时添加智能体。点击下一步。 第七步:配置环境。目前我能用到了有两个地方,一个是大小,一个是应用随机布局。大小的话根据模型实际需要设置,应用随机布局是否勾选同样看要解决的问题实际要求。点击完成。 好了!CombatSatSwarm 就这样创建好了!
5. 添加智能体群参数
来看看一共有多少参数。 有下面这些:
5.1 参数
智能体的参数已经在第4章详细说明了,在这里不再赘述。
5.2 变量
和第3.2节添加变量添加进场景中的顺序一样。按照这个顺序来,“面板”——>“智能体”——>“智能体组件”——>“变量”,鼠标左键单击拖拽进场景中即可,同样在“属性”栏中设置名称和类型,初始值不用设置。
这里给出所有用到的变量的名称、类型。
名称 | 类型 | 默认值 |
---|
flock | Main | get_Main() | heading | double | initHeading | x | double | initX | y | double | initY | friendsNumber | double | 不填 | friendsX | double | 不填 | friendsY | double | 不填 | nearestFriend | CombatSatSwarm | 不填 | enemiesNumber | double | 不填 | enemiesX | double | 不填 | enemiesY | double | 不填 | nearestEnemy | CombatSatSwarm | 不填 | dead | boolean | false | attacking | boolean | false | colorDarker | 其他…Color | gold.darker() | colorDarkerDarker | 其他…Color | gold.darker().darker() | friendsHeading | double | 不填 | friendsSpeed | double | 不填 | deathRangeNumber | double | 不填 | enemiesHeading | double | 不填 | enemiesSpeed | double | 不填 | traceX | 其他…double[] | new double[10]; | traceY | 其他…double[] | new double[10]; |
get_Main() 为得到Main 这个智能体的类型;gold.darker() 为gold 这个属性的颜色黑一级;gold.darker().darker() 为gold 这个属性黑两级。这俩函数意思就是让判断为死亡的智能体消失在场景中。
5.3 事件
用于判断智能体死亡之后直接移除的事件,为death 。
设置如下。
5.4 函数
这里是最复杂的函数设置,也是整个AnyLogic模型的精髓,我从上往下以此介绍。
首先列写一下所有的函数。
- attack()
- avoidCollision()
- avoidWalls()
- decide()
- hangOutWithFriends()
- isDead()
- isFriend()
- isInRange()
- move()
- retreat()
- senseFlock()
- slowDown()
- speedUp()
- step()
- turnAway()
- turnLeft()
- turnRight()
- turnToward()
5.4.1. Function: attack()
Function: attack()-选择是否加速或者减速
Input: null
Constant:
1. attacking [boolean]
2. speed [double]
3. enemiesX [double]
4. enemiesY [double]
5. enemiesSpeed [double]
Output: void
Function call:
1. speedUp()
2. slowDown()
-----------------------------------------------------------
attacking = true;
turnToward(enemiesX, enemiesY);
if (enemiesSpeed > speed) {
speedUp();
}
else if (enemiesSpeed < speed) {
slowDown();
}
5.4.2. Function: avoidCollision()
Function: avoidCollision()-避免碰撞友军
Input: null
Constant:
1. x [boolean]
2. y [double]
3. heading [double]
4. nearestFriend [Boid]
Output: void
Function call:
1. speedUp()
2. slowDown()
3. turnAway()
4. cos()-BIF
5. sin()-BIF
-----------------------------------------------------------
CombatSatSwarm b = nearestFriend;
if (b == null)
return;
turnAway(b.x, b.y);
double dx = b.x - x;
double dy = b.y - y;
if (cos(heading) * cos(b.heading) + sin(heading) * sin(b.heading) > 0) {
if (dx * cos(heading) + dy * sin(heading) < 0) {
slowDown();
} else {
speedUp();
}
} else {
if (dx * cos(heading) + dy * sin(heading) < 0) {
slowDown();
}
}
5.4.3. Function: avoidWalls()
Function: avoidWalls()-避免碰撞墙面
Input: null
Constant:
1. x [boolean]
2. y [double]
3. heading [double]
4. width [double]
5. height [double]
6. sensorRange [double]
Output: void
Function call:
1. speedUp()
2. slowDown()
3. cos()-BIF
4. sin()-BIF
-----------------------------------------------------------
double dxr = width - x;
double dyd = height - y;
if (cos(heading) > 0) {
if (sin(heading) < 0) {
if (dxr < sensorRange) {
slowDown();
for (int i = 0; i < 1 + (int) (sensorRange / dxr); i++) turnLeft();
} else if (y < sensorRange) {
slowDown();
for (int i = 0; i < 1 + (int) (sensorRange / y); i++) turnRight();
}
} else {
if (dxr < sensorRange) {
slowDown();
for (int i = 0; i < 1 + (int) (sensorRange / dxr); i++) turnRight();
} else if (dyd < sensorRange) {
slowDown();
for (int i = 0; i < 1 + (int) (sensorRange / dyd); i++) turnLeft();
}
}
} else {
if (sin(heading) < 0) {
if (x < sensorRange) {
slowDown();
for (int i = 0; i < 1 + (int) (sensorRange / x); i++) turnRight();
} else if (y < sensorRange) {
slowDown();
for (int i = 0; i < 1 + (int) (sensorRange / y); i++) turnLeft();
}
} else {
if (x < sensorRange) {
slowDown();
for (int i = 0; i < 1 + (int) (sensorRange / x); i++) turnLeft();
} else if (dyd < sensorRange) {
slowDown();
for (int i = 0; i < 1 + (int) (sensorRange / dyd); i++) turnRight();
}
}
}
5.4.4. Function: decide()-决策智能体动作
Function: decide()
Input: null
Constant:
1. speed [boolean]
2. friendsNumber [double]
3. nearestFriend [Boid]
4. enemiesNumber [double]
5. nearestEnemies [Boid]
6. dead [boolean]
7. attacking [boolean]
Output: void
Function call:
1. isDead()
2. restart()-BIF
3. attack()
4. retreat()
5. avoidCollision()
6. hangOutWithFriends()
7. avoidwalls()
-----------------------------------------------------------
attacking = false;
if (isDead()) {
speed = 0;
dead = true;
death.restart(20);
} else {
if (enemiesNumber > 0) {
double friendsToEnemies = (friendsNumber + 1) / (double)enemiesNumber;
if (friendsToEnemies > attackConstant) {
attack();
} else if (friendsToEnemies < retreatConstant) {
retreat();
}
} else if (nearestFriend != null) {
avoidCollision();
} else {
hangOutWithFriends();
}
avoidWalls();
}
5.4.5. Function: hangOutWithFriends()
Function: hangOutWithFriends()-周围没有敌人也没有友军的随机游走
Input: null
Constant:
1. speed [boolean]
2. friendsNumber [double]
3. friendsX [double]
4. friendsY [double]
Output: void
Function call:
1. speedUp()
2. slowDown()
3. turnLeft()
4. turnRight()
-----------------------------------------------------------
if (friendsNumber > 0) {
turnToward(friendsX, friendsY);
if (friendsSpeed > speed) {
speedUp();
} else if (friendsSpeed < speed) {
slowDown();
}
} else {
slowDown();
if (uniform() < 0.5)
turnLeft();
else
turnRight();
}
5.4.6. Function: isDead()
Function: isDead()-判断是否死亡
Input: null
Constant:
1. deathRangeNumber [boolean]
Output: true or false
Function call: null
-----------------------------------------------------------
if (hitPoint == 0){
return true;
}else{
return false;
}
5.4.7. Function: isFriend()
Function: isFriend()-判断是否为友军
Input:
1. b [Boid]
Constant: null
Output: color.equals(b.color)
Function call:
1. color.equal()
-----------------------------------------------------------
return color.equals(b.color);
5.4.8. Function: isInRange()
Function: isInRange()-判断是否在范围内
Input:
1. b [Boid]
2. range [double]
Constant:
1. x [double]
2. y [double]
Output: dx * dx + dy * dy < range * range
Function call: null
-----------------------------------------------------------
double dx = b.x - x;
double dy = b.y - y;
return dx * dx + dy * dy < range * range;
5.4.9. Function: move()
Function: move()-移动
Input: null
Constant:
1. heading [double]
2. speed [double]
3. x [double]
4. y [double]
5. traceX [double[]]
6. traceY [double[]]
7. dt [double]
8. width [double]
9. height [double]
Output: null
Function call:
1. length()-BIF
-----------------------------------------------------------
x += speed * dt * cos(heading);
y += speed * dt * sin(heading);
if (x > width)
x -= width;
else if (x < 0)
x += width;
if (y > height)
y -= height;
else if (y < 0)
y += height;
for (int i = traceX.length - 1; i > 0; i--) {
traceX[i] = traceX[i-1];
traceY[i] = traceY[i-1];
}
traceX[0] = x;
traceY[0] = y;
5.4.10. Function: retreat()
Function: retreat()-撤退
Input: null
Constant:
1. enemiesX [double]
2. enemiesY [double]
Output: null
Function call:
1. turnAway()
2. speedUp()
-----------------------------------------------------------
turnAway(enemiesX, enemiesY);
speedUp();
5.4.11. Function: senseFlock()
Function: senseFlock()-探测友军或者敌军
Input: null
Constant:
1. flock [Flock]
2. heading [double]
3. speed [double]
4. x [double]
5. y [double]
6. friendsNumber [double]
7. friendsX [double]
8. friendsY [double]
9. nearestFriend [Boid]
10. enemiesNumber [double]
11. enemiesX [double]
12. enemiesY [double]
13. nearestEnemy [Boid]
14. friendsHeading [double]
15. friendsSpeed [double]
16. deathRangeNumber [double]
17. enemiesHeading [double]
18. enemiesSpeed [double]
19. sensorRange [double]
20. collisionRange [double]
21. deathRange [double]
Output: null
Function call:
1. isFriend()
2. isInRange()
3. Math.sqrt-BIK
4. size()-BIF
5. get()-BIF
-----------------------------------------------------------
friendsNumber = 0;
double totalFriendsX = 0.0;
double totalFriendsY = 0.0;
double totalFriendsSpeed = 0.0;
double totalFriendsHeading = 0.0;
enemiesNumber = 0;
double totalEnemiesX = 0.0;
double totalEnemiesY = 0.0;
double totalEnemiesSpeed = 0.0;
double totalEnemiesHeading = 0.0;
nearestFriend = null;
nearestEnemy = null;
deathRangeNumber = 0;
double distanceToFriend = collisionRange;
for (int i = 0; i < flock.boids.size(); i++) {
CombatSatSwarm b = flock.boids.get(i);
if (b.dead || b == this)
continue;
if (isFriend(b)) {
if (isInRange(b, distanceToFriend)) {
nearestFriend = b;
distanceToFriend = Math.sqrt((b.x - x) * (b.x - x) + (b.y - y) * (b.y - y));
}
if (isInRange(b, sensorRange)) {
friendsNumber++;
totalFriendsX += b.x;
totalFriendsY += b.y;
totalFriendsSpeed += b.speed;
totalFriendsHeading += b.heading;
}
} else {
if (isInRange(b, deathRange)) {
deathRangeNumber++;
b.hitPoint -= 1;
if (b.hitPoint < 0){
b.hitPoint = 0;
}
}
if (isInRange(b, sensorRange)) {
enemiesNumber++;
totalEnemiesX += b.x;
totalEnemiesY += b.y;
totalEnemiesSpeed += b.speed;
totalEnemiesHeading += b.heading;
}
}
}
if (friendsNumber == 0) {
friendsHeading = heading;
friendsSpeed = speed;
friendsX = x;
friendsY = y;
} else {
friendsHeading = totalFriendsHeading / friendsNumber;
friendsSpeed = totalFriendsSpeed / friendsNumber;
friendsX = totalFriendsX / friendsNumber;
friendsY = totalFriendsY / friendsNumber;
}
if (enemiesNumber == 0) {
enemiesHeading = heading;
enemiesSpeed = speed;
enemiesX = x;
enemiesY = y;
} else {
enemiesHeading = totalEnemiesHeading / enemiesNumber;
enemiesSpeed = totalEnemiesSpeed / enemiesNumber;
enemiesX = totalEnemiesX / enemiesNumber;
enemiesY = totalEnemiesY / enemiesNumber;
}
5.4.12. Function: slowDown()
Function: slowDown()-减速
Input: null
Constant:
1. minimumSpeed
2. speed
3. acceleration
4. dt
Output: null
Function call:
1. limitMin()-BIF
-----------------------------------------------------------
speed = limitMin(minimumSpeed, speed - acceleration * dt);
5.4.13. Function: speedUp()
Function: speedUp()-加速
Input: null
Constant:
1. minimumSpeed
2. speed
3. acceleration
4. dt
Output: null
Function call:
1. limitMax()-BIF
-----------------------------------------------------------
speed = limitMax(speed + acceleration * dt, maximumSpeed);
5.4.14. Function: step()
Function: step()
Input: null
Constant:
1. dead
Output: null
Function call:
1. senseFlock()
2. decide()
3. move()
-----------------------------------------------------------
if (! dead) {
senseFlock();
decide();
move();
}
5.4.15. Function: turnAway()
Function: turnAway()-远离其他智能体
Input:
1. xPos [double]
2. yPos [double]
Constant:
1. x [double]
2. y [double]
3. heading [double]
Output: null
Function call:
1. turnLeft()
2. turnRight()
3. sin()-BIF
4. cos()-BIF
-----------------------------------------------------------
double dx = xPos - x;
double dy = yPos - y;
if (cos(heading) * dy - sin(heading) * dx > 0)
turnLeft();
else
turnRight();
5.4.16. Function: turnLeft()
Function: turnLeft()-左转
Input: null
Constant:
1. heading [double]
2. dt [double]
3. turnAngle [double]
Output: null
Function call:
1. Math.PI-BIK
-----------------------------------------------------------
heading = (heading - turnAngle * dt) % (2 * Math.PI);
5.4.17. Function: turnRight()
Function: turnRight()-右转
Input: null
Constant:
1. heading [double]
2. dt [double]
3. turnAngle [double]
Output: null
Function call:
1. Math.PI-BIK
-----------------------------------------------------------
heading = (heading + turnAngle * dt) % (2 * Math.PI);
5.4.18. Function: turnToward()
Function: turnToward()-计算 heading
Input:
1. xPos [double]
2. yPos [double]
Constant:
1. x [double]
2. y [double]
3. heading [double]
Output: null
Function call:
1. turnLeft()
2. turnRight()
3. sin()-BIF
4. cos()-BIF
-----------------------------------------------------------
double dx = xPos - x;
double dy = yPos - y;
if (cos(heading) * dy - sin(heading) * dx < 0)
turnLeft();
else
turnRight();
5.5 添加智能体几何图形的参数
点击“工程”中的Main ,回到Main 场景,再点击Main 下面的演示 。这里要设置:
satAgent ;rect ;line ;line1 ;line2 ;rect7
的几何参数,让智能体动起来。
5.5.1 satAgent
线颜色:无色
填充颜色:combatSatSwarms.get(index).dead ? combatSatSwarms.get(index).colorDarkerDarker : combatSatSwarms.get(index).color
线宽:1pt
线样式:下拉列表第一个
X:combatSatSwarms.get(index).x
Y:combatSatSwarms.get(index).y
Z:0
Z-高度:10
旋转,弧度:combatSatSwarms.get(index).heading
其余不用设置。
5.5.2 rect
线颜色:无色
填充颜色:combatSatSwarms.get(index).attacking ? combatSatSwarms.get(index).colorDarker : null
线宽:1pt
线样式:下拉列表第一个
X:combatSatSwarms.get(index).enemiesX - 10
宽度:20
Y:combatSatSwarms.get(index).enemiesY - 10
高度:20
Z:0
Z-高度:10
旋转,弧度:0.0 度
其余不用设置。
5.5.3 line
线颜色:无色
填充颜色:combatSatSwarms.get(index).attacking ? combatSatSwarms.get(index).colorDarker : null
线宽:1pt
线样式:下拉列表第一个
X:combatSatSwarms.get(index).enemiesX - 5
dX:10
Y:combatSatSwarms.get(index).enemiesY
dY:0
Z:0
Z-高度:10
其余不用设置。
5.5.4 line1
线颜色:无色
填充颜色:combatSatSwarms.get(index).attacking ? combatSatSwarms.get(index).colorDarker : null
线宽:1pt
线样式:下拉列表第一个
X:combatSatSwarms.get(index).enemiesX
dX:0
Y:combatSatSwarms.get(index).enemiesY - 5
dY:10
Z:0
Z-高度:10
其余不用设置。
5.5.5 line2
线颜色:无色
填充颜色:combatSatSwarms.get(index).attacking ? combatSatSwarms.get(index).colorDarkerDarker : null
线宽:1pt
线样式:下拉列表第一个
X:combatSatSwarms.get(index).x
dX:combatSatSwarms.get(index).enemiesX - combatSatSwarms.get(index).x
Y:combatSatSwarms.get(index).y
dY:combatSatSwarms.get(index).enemiesY - combatSatSwarms.get(index).y
Z:0
Z-高度:10
其余不用设置。
5.5.2 rect7
线颜色:无色
填充颜色:combatSatSwarms.get(index/10).colorDarker
线宽:1pt
线样式:下拉列表第一个
X:combatSatSwarms.get(index/10).traceX[ index%10 ]
宽度:2
Y:combatSatSwarms.get(index/10).traceY[ index%10 ]
高度:10
Z:0
Z-高度:10
旋转,弧度:0.0 度
其余不用设置。
6. 仿真验证
到这里,所有的设置全部完成了。
点击AnyLogic的运行按钮即可运行模型。
输出结果会导出至已经设置好的excel文件中。
需要注意的是,验证平方律在Main 启动时行动中让RD 和BD 使用injectTeam 即可;验证线性律在Main 启动时行动中让RK 和BK 使用injectTeam 。
同时,还有在MATLAB中写出相应代码仿真Lanchester平方律和线性律,代码如下:
clear;clc;close all;
tic;
t_interval = 1e-2;
t_total = 0.6;
x_d0 = 500;
x_k0 = 500;
y_d0 = 170;
y_k0 = 170;
phi_d_11 = 1;
phi_d_12 = 0;
phi_k_11 = 0;
phi_k_12 = 0;
psi_d_11 = 1;
psi_d_12 = 0;
psi_k_11 = 0;
psi_k_12 = 0;
a_d_11 = 1;
a_d_12 = 1;
a_k_11 = 1;
a_k_12 = 1;
b_d_11 = 10;
b_d_12 = 10;
b_k_11 = 10;
b_k_12 = 10;
x0 = [x_d0 x_k0 y_d0 y_k0];
opts = odeset('RelTol',1e-6,'AbsTol',1e-6); % 设置求解选项
uvSpt.u_d0 = u_d0; uvSpt.u_k0 = u_k0; uvSpt.v_d0 = v_d0; uvSpt.v_k0 = v_k0;
matAlc.phi_d_11 = phi_d_11; matAlc.phi_d_12 = phi_d_12;
matAlc.phi_k_11 = phi_k_11; matAlc.phi_k_12 = phi_k_12;
matAlc.psi_d_11 = psi_d_11; matAlc.psi_d_12 = psi_d_12;
matAlc.psi_k_11 = psi_k_11; matAlc.psi_k_12 = psi_k_12;
matAtr.a_d_11 = a_d_11; matAtr.a_d_12 = a_d_12;
matAtr.a_k_11 = a_k_11; matAtr.a_k_12 = a_k_12;
matAtr.b_d_11 = b_d_11; matAtr.b_d_12 = b_d_12;
matAtr.b_k_11 = b_k_11; matAtr.b_k_12 = b_k_12;
[t_timout, res] = ode45(@fAsyLanCompare,(0:t_interval:t_total),x0,opts,uvSpt,matAlc,matAtr);
toc;
%% 函数模块
function res = fAsyLanCompare(t,x,uvSpt,matAlc,matAtr)
x_d0 = x(1);
x_k0 = x(2);
y_d0 = x(3);
y_k0 = x(4);
u_d0 = uvSpt.u_d0;
u_k0 = uvSpt.u_k0;
v_d0 = uvSpt.v_d0;
v_k0 = uvSpt.v_k0;
phi_d_11 = matAlc.phi_d_11;
phi_d_12 = matAlc.phi_d_12;
phi_k_11 = matAlc.phi_k_11;
phi_k_12 = matAlc.phi_k_12;
psi_d_11 = matAlc.psi_d_11;
psi_d_12 = matAlc.psi_d_12;
psi_k_11 = matAlc.psi_k_11;
psi_k_12 = matAlc.psi_k_12;
a_d_11 = matAtr.a_d_11;
a_d_12 = matAtr.a_d_12;
a_k_11 = matAtr.a_k_11;
a_k_12 = matAtr.a_k_12;
b_d_11 = matAtr.b_d_11;
b_d_12 = matAtr.b_d_12;
b_k_11 = matAtr.b_k_11;
b_k_12 = matAtr.b_k_12;
% 根据构建的模型用代码复现
dy_d = -phi_d_11*(a_d_11*x_d0) ...
-phi_k_11*(a_k_11) + v_d0;
dy_k = -phi_d_12*(a_d_12*x_d0) ...
-phi_k_12*(a_k_12) + v_k0;
dx_d = -psi_d_11*(b_d_11*y_d0) ...
-psi_k_11*(b_k_11) + u_d0;
dx_k = -psi_d_12*(b_d_12*y_d0) ...
-psi_k_12*(b_k_12) + u_k0;
% 如果兵力为零,则导数也为零
if x_d0 <= 0
dx_d = 0;
end
if x_k0 <= 0
dx_k = 0;
end
if y_d0 <= 0
dy_d = 0;
end
if y_k0 <= 0
dy_k = 0;
end
% 记录结果
res =[dx_d;dx_k;dy_d;dy_k];
end
比较两者结果,最后会得到如下曲线图。 曲线十分贴合,成功验证。
到这里就结束了,如果需要PDF文件或者交流,欢迎通过邮箱联系我:lordofdapanji@foxmail.com
|