文译自OptaPlanner官方网站,由资深软件工程师Walter Medvedeo先生发表的规划示例。
本文描述了通过OptaPlanner实现订单快速且优化拣货过程,在满足订单固定规则的前提下,实现购物车行驶最小距离的规划优化。
以下为译文
在COVID-19大流行的年头,我们看到很多商店和超市以多种方向调整了他们的业务。有时候这些调整每天都在变化。例如,由于线下客户访问量急剧减少,线上订单呈指数式增长。
这些商家中有多少人准备好以最佳的方式来应对这种增长呢?
在本文,我们展示了一个新的OptaPlanner 快速入门示例 - order-picking(拣货),展示如何通过最优的方式完成在线订单。
关于订单拣货问题
订单拣货问题由一组需要准备交付给不同客户的订单构成,每笔订单由一组订单项(即订单所需的商品)组成。这些产品分布在仓库或超市的货架上,并占据一定体积的空间。要完成订单,一组可用的购物车沿着超市中已计算好的路径逐一选择订单所需的商品。在这条拣货路径中,每个步骤都会选择一个商品。仓库内的商品位置决定了购物车的行驶路径,而每个购物车内的载物空间被划分成若干个指定体积的"桶"
订单拣货问题的目标是算出一个拣货计划,该计划列明了每辆购物车的路径,并考虑以下约束:
- 最小化购物车的行驶距离
- 所有订单所需的商品均拣选完毕
- 不同订单的商品不能混合在同一个桶中,因此,每台购物车上的每个订单必须预留足够大的桶
- 放进一个桶的商品体积之和不得大于桶的体积
- 尽可能减少将一个订单拆分到不同的购物车进行拣货的情况
下图展示了订单拣货问题输入数据和计算拣货计划的过程:
仓库结构
为了表述拣货问题,仓库被定义为一组按行和列组织的货架。产品位于货架的左右两侧。因此,产品的位置由货架号、行号和左右侧3个位置元素决定了产品的位置。例如下图右侧有三组位置元素:E,2 表示货架号(即每个方格),RIGHT表示货架的右侧,1表示第一行。(Kent Zhang:这个系统实为仓储的坐标,各位可按自己的仓储位置设计来定义)。
图中每个方格为一个货架,货架分左右两侧,每侧有多行可放置货物。
在新的快速入门示例中拣货优化程序的项目结构
下图展示了包含quickstart项目的maven项目结构:
拣货程序位置新的快速入门示例包中,名为order-picking
服务端代码位于src/main/java文件夹,通过4个包对代码进行组织:
bootstrap
用于生成应用程序启动时快速入门使用的随机(但可重现)数据集的帮助程序类。
domain
包含了用于呈现问题模型的域模型,及OptaPlanner用到的规划类,例如,PlanningSolution类和PlanningEntity类型。
solver
包含了ConstraintProvider的实现。(即实现约束的类)
rest
包含用于前端调用,用于启动和停止规划,及获取并显示最佳方案的 REST API.
前端的代码位于src/main/resources/META-INF/resources/ 文件夹,该文件夹有一个文件index.html, 包含了基本的HTML文件结构并可以装入可以调用服务REST API并生成Web动态页面的脚本文件app.js.
规划模型类图
订单拣货约束
所有订单拣货约束通过Constraint Streams API实现,可以在类org.acme.orderpicking.solver.OrderPickingConstraintProvider中找到这些约束的实现方式。
在该类中提供了以下约束:
requiredNumberOfBuckets
一个硬约束,用于强制要求一个购物车中必须存在足够数量的货物桶,来容纳沿着一个订单拣货路径中所拣取的所有货物,且所拣货物不越过桶的容量,此外,属于不同订单的货物不分装在同一个桶中。
minimizeDistanceFromPreviousTrolleyStep
一个软约束,用于最小化购物车前后两个拣货点之间的距离。
minimizeDistanceFromLastTrolleyStepToPathOrigin
一个软约束,用于最小化购物车最后一个拣货点与起始点之间距离。
约束minimizeDistanceFromPreviousTrolleyStep作用于拣货路径上每两个点之间,约束minimizeDistanceFromLastTrolleyStepToPathOrigin则作用于最后一个拣货点与起始点之间。两者组合从而实现整体路径距离的最小化。
minimizeOrderSplitByTrolley
一个软约束,用于让尽可能少的订单的货物被拆分到不同的购物车。
执行说明
当你首次打开Order picker程序时,你将看不到任何已运算好的拣货计划或拣货路径,不用担心这是正常的,因为规划求解器还未开始运行。在开始执行求解运算前我们先花点时间看看初始化数据集,你可以点击页面上“Unassigned”页签来查看这些数据。(如下图为购物车的数据)
购物车列表
订单1未分配的数据
执行规划求解
点击Solve按钮启动规划运算,当求解器启动后你将看到"Picking Plan"页签中将会动态显示规划运算过程中的信息。
提示: 当规划引擎在运行时,运算所得的结果会每隔2秒刷新一次到页面上,从而导致屏幕刷新的效果,你可以通过点击“Stop”按钮来消除这种刷新效果。
购物车导航
通过页面上的“Map”页签可以查看各个购物车如何根据规划结果的路径,在仓库中导航访问各个拣货点。
运行快速入门程序
根据以下步骤运行快速入门程序:
1. 从Github中克隆optaplanner-quickstarts库到本地。
$ git clone GitHub - kiegroup/optaplanner-quickstarts: OptaPlanner quick starts for AI optimization: many use cases shown in many different technologies.
2. 通过以下命令切换到development分支
$ cd optaplanner-quickstarts
$ git checkout development
3. 切换到use-cases/order-picking 文件夹.
$ cd use-cases/order-picking
4. 在Quarkus的开发模式中启动Order Picking的快速入门程序:
$ mvn quarkus:dev
5. 在浏览器中打开http://localhost:8080 并点击“Solve”按钮。
详细方案支援,VX: 13631823503
以下为非原文内容:
最后附上该示例的视频讲解和Github链接,大家可以自行更深入研究。