在Python的广泛流行下,使用AnyLogic建模时,可能会遇到下列的情况:
代码级别的熟练建模是对AnyLogic的高级应用,是需要具备一定的编程专业知识的,因此,我们不能指望通过一两篇文章就能学到所有的用法。本文将通过一些简单的示例,为您在AnyLogic中与其他编程语言交互使用提供指引。
这种方法可以说是比较“简单粗暴”的。但是要能“简单”的用这种方法,你可能需要具备Java、Python以及AnyLogic的专业知识。
特点:无需安装额外的插件,操作简单。
注意:需要一定的专业知识,熟悉Java和Python及AnyLogic。
很多时候我们用到的Python代码,可能是一段写好的代码段,而我们要使用的也是算法本身而不是必须借助Python,因此,如果可能,我们转换成对应的AnyLogic组件+Java代码会比直接调用Python运行代码要便捷且迅速的多。
我们举例来说:
1.在Python中的导入部分
Import部分的转换可以是与之对应的jar包导入,或者在下方用到此模块功能时自行去写对应的java方法去实现Python此模块函数的功能。
2. 新建并初始化对应的参数
Python代码如下:
转换到AnyLogic中,就是创建参数,设置成对应的类型及初始值
3.Python方法转变
这一部分相对而言是需要更多专业知识的,举例可能参考价值并不大。比如下方,Python的np.zeros方法,到AnyLogic中需要我们改写为相对应的初始化数组。
这时候需要将Python中对应方法所返回的值来写对应的java代码。
将Python方法的转换,通常是改写为AnyLogic的函数方法,便于模型调用,将Python代码块转换成Java代码块作为函数体。
Python方法代码
AnyLogic函数方法
看到这里,想必也很容易发现,这种直接转换的方式更适应于代码量相对并不多的情况,如果我们要使用的是一个代码量非常大,或是要使用Python独有的算法库,亦或是要借助Python训练AI算法,等等,这些情况通过代码转换显然是不现实的。接下来,我们来说另一种使用Python的方法,可以解决上述情景。
这种方法需要借助转为Python开发的AnyLogic API —— Pypeline库,通过Pypeline可以实现直接调用本地安装的Python运行相应的Python代码或文件。相较于直接进行代码转换,这种方法是要简单的多,但需要注意,AnyLogic本身是以Java为原生脚本语言的,使用Pypeline可以实现对Python的调用,但要占用额外的计算资源,因此对计算机资源有要求的模型,需要合理使用。
特点:使用方法简单,用法类似于AnyLogic的内置行业库,可以实现与Python交互的目的。
注意:
注:关于Pypeline的介绍和安装,可以参考我们的另一篇文章:AnyLogic中使用Python Part 1:Pypeline的安装及使用。
适应场景:
直接使用Python源代码,无需移植到Java;
可以在Python中编写复杂的算法,在Java中直接调用,可选择语言直接传递对象/数据;
能够使用任何 Python 专有库;
使用仿真作为测试平台,测试经过训练的AI策略;
……
我们以AnyLogic自带的示例模型Traveling Salesman为例,说明AnyLogic使用Pypeline实现与Python语言的交互。
模型定义了一个推销员行程优化模型。在这个模型中,存在一组城市信息,销售员以其中一处为始发地城市,也作为行程的归属地。模型仿真了在给定任意数量、位置的城市作为目标城市后,销售员从始发地城市开始出发,途经所有目标城市,最终回到始发地城市的过程。目标是找到整个行程的最短路线。为了优化出行顺序,我们通过Pypeline库使用了Python库OR-tools(由 Google 提供)。
模型与tsp_solver.py交互
下面我们做一个详细的说明。
外部python文件,tsp_solver.py
class: FacilityOrderSolver
函数:
__init__(self, full_distance_matrix: List[List[float]], home_index: int)
初始化环境,确定归属地、城市节点,已经城市间的距离。
full_distance_matrix:表示所有城市节点之间距离的二维列表。
home_index:作为归属地或起始点的城市节点的下标。
solve(self, indices_to_visit: List[int] = None)
调用优化算法,将初始送货顺序数组优化为最优送货顺序数组。
indices_to_visit:初始送货顺序数组。
返回值:优化后的送货顺序数组。
模型初始化并生成初始订单
注:此部分在Main智能体属性的启动时中
生成始发地城市
将模型中所有城市之间的距离数组传入到Python代码块中进行计算。
//获取始发地城市智能体下标
home_index = facilities.findFirst(h -> h.isHomeFacility).getIndex();
// 生成、初始化并导入solver
pyCom.run(
"import random", // 订单智能体内用来生成随机数
"from tsp_solver import FacilityOrderSolver",//从python文件里导入FacilityOrderSolver类
String.format( //参数是城市间距离的二维列表和始发地城市智能体的下标
"solver = FacilityOrderSolver(%s, %s)", //solver是FacilityOrderSolver类的实例
pyCom.toJson(buildDistanceMatrix()),
home_index
)
);
//生成初始订单
if (newOrderOnStartup)
buildNewOrder();
说明:
1.导入的Pypeline插件命名为pyCom
2.通过run方法写入:
"solver = FacilityOrderSolver(%s, %s)",
pyCom.toJson(buildDistanceMatrix()),
home_index
)
导入所需的起点及各位置之间的坐标距离,其中home_index表示模型中的原点,buildDistanceMatrix()表示各位置之间的距离
随机选择numToChoose个城市作为Order智能体的目的地
注:此部分在Order智能体的buildRandOrder函数中
随机选择numToChoose个城市作为Order智能体的目的地,以数组的形式存储目的地城市智能体的下标。
//随机生成目的地个数
int numToChoose = uniform_discr(3, main.facilities.size()-1);
//随机选择numToChoose个城市作为Order智能体的目的地
//code:字符串格式的python语句,用来生成numToChoose个数值,这些数值是0到城市总数之间的整数,并且不包含始发地在内。
String code = String.format("random.sample( set(range(%s)) - set([%s]), %s )", main.facilities.size(), main.home_index, numToChoose,main.home_index);
//toVisit:执行code的返回值,存储该订单对应的所有目的地节点的下标。
int[] toVisit = main.pyCom.runResults(int[].class, code);
return toVisit;
说明:
numToChoose:订单对应的目的地个数
code:字符串格式的python语句,用来生成numToChoose个数值,这些数值是0到城市总数之间的整数,并且不包含始发地在内。
toVisit:执行code的返回值,存储该订单对应的所有目的地节点的下标。
注:此部分在Main智能体的buildNewOrder函数中
//生成订单智能体
Order o = add_orders();
//获取订单智能体的目的地城市下标数组
int[] dests = o.destinations;
//获取经算法处理后有序的目的地城市下标数组
int[] newDests = pyCom.runResults(
int[].class,
String.format(
"solver.solve(%s)['order']",
Arrays.toString(dests)
)
);
//将优化后的数组重新赋值给订单智能体的destinations
o.set_destinations(newDests);
//卡车按顺序运输
send(o, truck);
说明:
输入参数:订单的目的地城市的下标数组
算法返回:改变了顺序的订单的目的地城市的下标数组,数组下标的顺序即为运输的先后顺序,即最优运输路径,卡车将依次去往数组存储的下标对应的城市。
假设我们在模型中需要使用Python文件Function中的一个函数——velocity方法。
注:
1.下面仅演示方法,无相关的示例模型;
2.假设模型使用的Pypeline插件,使用默认名称,即:pyCommunicator。
要用到的velocity方法
说明:
pyCommunicator. toJson():通过PyCommunicator插件带的toJson方法,将模型中 x,y表示的二维数组转化为json的形式。
String.format():将python代码转换成字符串形式。
pyCommunicator.run():执行python代码。
pyCommunicator.runResults(): 获取经过Python代码计算后的值。
在Python越来越方便的今天,很多的机器学习等技术以Python为基础进行,再辅助上AnyLogic的可扩展性,让Java与Python在同一平台竞相散发功用,也让建模仿真的应用愈加广泛且适应各类需求。
需要特别说明的是,一是要想真正的在AnyLogic使用Python是需要具备相关的专业知识的。二是,目前版本的AnyLogic都是以Java作为原生脚本语言的,虽然借助Pypeline可以调用Python运行相关文件,但需要额外的计算资源,并且运行速度会受到Python中代码运行的影响,因此,对于规模较大的模型需要注意到这一点。另外,AnyLogic的Python版本在去年已经开发了测试版,或许在不久的将来我们可以直接在AnyLogic中使用Python代码建模,一起期待吧~