AnyLogic中使用Python Part 2:将Python转化为模型所用的方法
在GitHub的年度报告中,从编程语言的受欢迎程度(即代码库贡献)来看,Python的排名仅次于JavaScript,是世界上第二大受欢迎的语言。Python是一种用于强化学习、数据处理和数据展示的流行语言。AnyLogic本身是一款以多方法建模为设计理念的仿真工具平台,集成更多好用的工具,让仿真建模更灵活是AnyLogic一直在做的工作。因此,AnyLogic 也推出了能够应用Python代码或文件的方式,极大的扩张模型的灵活度以及简化我们的工作量。本文我们会通过一个简单的示例模型,为您在AnyLogic中应用Python提供思路方法。
问题描述
在Python的广泛流行下,使用AnyLogic建模时,可能会遇到下列的情况:
- 找到一个算法,正好是你需要的,但是,只有Python编写的版本;
- 想要编写一个复杂的算法,在Python中显然更容易编写;
- Python的专有库中有你想要使用的算法;
- 你的任务是使用仿真作为测试平台,测试经过训练的AI策略;
- ……
代码级别的熟练建模是对AnyLogic的高级应用,是需要具备一定的编程专业知识的,因此,我们不能指望通过一两篇文章就能学到所有的用法。本文将通过一些简单的示例,为您在AnyLogic中与其他编程语言交互使用提供指引。
方法1:手动将Python代码转换成Java代码
这种方法可以说是比较“简单粗暴”的。但是要能“简单”的用这种方法,你可能需要具备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的方法,可以解决上述情景。
方法2:通过Pypeline直接调用Python代码
这种方法需要借助转为Python开发的AnyLogic API —— Pypeline库,通过Pypeline可以实现直接调用本地安装的Python运行相应的Python代码或文件。相较于直接进行代码转换,这种方法是要简单的多,但需要注意,AnyLogic本身是以Java为原生脚本语言的,使用Pypeline可以实现对Python的调用,但要占用额外的计算资源,因此对计算机资源有要求的模型,需要合理使用。
特点:使用方法简单,用法类似于AnyLogic的内置行业库,可以实现与Python交互的目的。
注意:
- 需要专门安装Pypeline以及Python。
- 需要一定的专业知识,熟悉Java和Python及AnyLogic。
注:关于Pypeline的介绍和安装,可以参考我们的另一篇文章:AnyLogic中使用Python Part 1:Pypeline的安装及使用。
适应场景:
直接使用Python源代码,无需移植到Java;
可以在Python中编写复杂的算法,在Java中直接调用,可选择语言直接传递对象/数据;
能够使用任何 Python 专有库;
使用仿真作为测试平台,测试经过训练的AI策略;
……
示例1:Traveling Salesman模型
我们以AnyLogic自带的示例模型Traveling Salesman为例,说明AnyLogic使用Pypeline实现与Python语言的交互。
模型定义了一个推销员行程优化模型。在这个模型中,存在一组城市信息,销售员以其中一处为始发地城市,也作为行程的归属地。模型仿真了在给定任意数量、位置的城市作为目标城市后,销售员从始发地城市开始出发,途经所有目标城市,最终回到始发地城市的过程。目标是找到整个行程的最短路线。为了优化出行顺序,我们通过Pypeline库使用了Python库OR-tools(由 Google 提供)。
模型与tsp_solver.py交互
- Main智能体的启动时:创建python文件里FacilityOrderSolver类的实例solver,将城市间距离的二维列表和始发地城市智能体的下标传给solver。
- Order 智能体的buildNewOrder函数:将订单的destinations传给solver的solve函数,获取solve函数计算后的最优送货顺序。
下面我们做一个详细的说明。
外部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方法写入:
- "import random"导入Python随机
- "from tsp_solver import FacilityOrderSolver"导入所需模块及其其中所用的方法。
- String.format( // pass in distance matrix and home index
"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的返回值,存储该订单对应的所有目的地节点的下标。
获取从Python中计算后的最优运输路线
注:此部分在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);
说明:
输入参数:订单的目的地城市的下标数组
算法返回:改变了顺序的订单的目的地城市的下标数组,数组下标的顺序即为运输的先后顺序,即最优运输路径,卡车将依次去往数组存储的下标对应的城市。
示例2:调用Python文件中的一个方法
假设我们在模型中需要使用Python文件Function中的一个函数——velocity方法。
注:
1.下面仅演示方法,无相关的示例模型;
2.假设模型使用的Pypeline插件,使用默认名称,即:pyCommunicator。
要用到的velocity方法
- 在模型中的Main智能体>>智能体行动>>启动时,代码区调用function中的velocity方法。
- 在需要调用velocity方法的函数中,键入下列代码,获取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代码建模,一起期待吧~
建议出个视频
您好 我在使用pyCommunicatior时出现以下错误:
Error during model creation:
com/anylogic/engine/presentation/ColorConstants
Caused by: com.anylogic.engine.presentation.ColorConstants
java.lang.NoClassDefFoundError: com/anylogic/engine/presentation/ColorConstants
at com.anylogic.libraries.pypeline.Jsonifier.<clinit>(Jsonifier.java:1955)
at model1.Main.instantiate_pyCommunicator_xjal(Main.java:125)
at model1.Main.instantiateBaseStructureThis_xjal(Main.java:263)
at model1.Main.<init>(Main.java:246)
at model1.Simulation.createRoot(Simulation.java:155)
at model1.Simulation.createRoot(Simulation.java:1)
请问有解决方法嘛