使用红帽构建的 OptaPlanner 开发解析程序


Red Hat Build of OptaPlanner 8.33

摘要

本文档论述了如何使用红帽构建的 OptaPlanner 开发解析程序,以查找规划问题的最佳解决方案。

前言

您可以使用红帽构建的 OptaPlanner 开发决定规划问题的最佳解决方案的解析程序。OptaPlanner 是红帽构建的 OptaPlanner 的内置组件。您可以在 Red Hat Build of OptaPlanner 中使用 solvers 作为服务的一部分,以优化具有特定限制的有限资源。

使开源包含更多

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。我们从这四个术语开始:master、slave、黑名单和白名单。由于此项工作十分艰巨,这些更改将在即将推出的几个发行版本中逐步实施。详情请查看 CTO Chris Wright 信息

部分 I. 红帽构建的 OptaPlanner 8.33 发行注记

本发行注记列出了新功能,并为 Red Hat Build of OptaPlanner 8.33 提供升级说明。

要从 OptaPlanner 8.13 升级到 Red Hat Build of OptaPlanner 8.33,按以下顺序合并以前的 OptaPlanner 版本:

流程

  1. 在浏览器中打开 OptaPlanner Upgrade Recipe 8 页面。
  2. 完成您要升级的第一个版本的说明,例如 From 8.13.0.Final 到 8.14.0.Final
  3. 重复说明,直到您升级到 8.32.0.Final。

第 2 章 Red Hat build of OptaPlanner 8.33 新功能

本节重点介绍红帽构建的 OptaPlanner 8.33 的新功能。

注意

Bavet 是用于快速分数计算的功能。Bavet 目前仅适用于 OptaPlanner 的社区版本。它不适用于 Red Hat Build of OptaPlanner 8.33。

2.1. OptaPlanner 和 Red Hat build of Quarkus 平台

红帽构建的 OptaPlanner 现在与红帽构建的 Quarkus 平台集成。平台工件依赖项版本(包括 OptaPlanner 依赖项)在 Quarkus 产品(BOM)文件 com.redhat.quarkus.platform:quarkus-bom 中维护。当您将 Quarkus BOM 文件与 OptaPlanner 项目搭配使用时,您不需要指定哪些依赖项版本协同工作。相反,您可以将 Quarkus BOM 文件导入到 pom.xml 配置文件,其中依赖项版本包含在 < dependencyManagement> 部分中。因此,您不需要列出由 pom.xml 文件中的指定 BOM 管理的独立 Quarkus 依赖项版本。

2.2. 值范围自动检测

在大多数情况下,计划变量和值范围之间的链接会被自动探测到。因此,@ValueRangeProvider 注释不再需要提供 id 属性。同样,规划变量不再需要通过 valueRangeProviderRefs 属性引用值范围供应商。

不需要更改代码或配置更改。如果您希望清晰地引用您的值范围供应商,您可以继续明确引用您的值范围。

2.3. xstream 支持已弃用

本发行版本中弃用了使用 XStream 序列化到 XML 的 OptaPlanner 支持。optaplanner-persistence-xstream 模块及其包含的所有类现已弃用,并将在以后的 Red Hat Build of OptaPlanner 发行版本中删除。要继续序列化到 XML,请切换到 optaplanner-persistence-jaxb 模块。

optaplanner-examples 模块中的所有示例都使用 optaplanner-persistance-jackson 模块重构为 JSON。Quickstarts 会受到这些更改的影响,因为它们已序列化到 JSON 中。

2.4. 从发行版本中删除了 OptaPlanner 示例

以下示例已从 optaplanner-examples 目录中删除:

  • 批量调度
  • cheap Time
  • Coach Shuttle Gathering
  • 参与
  • rock Tour

Red Hat build of OptaPlanner 8.33 提供了提高的稳定性和固定问题。

  • 带有链变量的域在运行时会失败,并带有 ClassCastException [PLANNER-2798]
  • CS-D Stream reuse fail [PLANNER-2884]
  • Optimiz subList reversal [PLANNER-2808]
  • Unresolved dependencies: org.optaplanner:optaplanner-constraint-streams-bavet:jar:8.29.0.Final-redhat-00009 [RHBOP-33]

部分 II. 红帽构建的 OptaPlanner 入门

作为自定义规则开发人员,您可以使用 Red Hat Build of OptaPlanner 来查找根据一组有限资源和特定限制规划问题的最佳解决方案。

使用本文档开始使用 OptaPlanner 开发解析程序。

第 4 章 红帽构建的 OptaPlanner 简介

OptaPlanner 是一个轻量级、可嵌入的规划引擎,可优化规划问题。它帮助普通 Java 站有效地解决规划问题,它将优化 heuristics 和 metaheuristics 与非常有效的分数计算相结合。

例如,OptaPlanner 帮助解决各种用例:

  • 员工/人员 :它有助于为 nurses 创建时间表并跟踪个人管理。
  • 指导 时间表:帮助安排较少活动、课程、技术和技术演示。
  • Shop Schedules :它跟踪库存装配行、机器队列规划和工作强制任务规划。
  • Cutting Stock :通过减少纸张和钢材等消耗来最小化浪费。

每个机构都面临规划问题;也就是说,它们为产品和服务提供有限的受限资源集合(如员工、资产、时间和销售)。

OptaPlanner 是 Apache 软件许可证 2.0 下的开源软件。它是 100% 纯 Java,在大多数 Java 虚拟机(JVM)上运行。

4.1. 后向兼容性

optaPlanner 将 API 和实现分开:

  • 公共 API :软件包命名空间 org.optaplanner.core.apiorg.optaplanner.benchmark.apiorg.optaplanner.test.apiorg.optaplanner.persistence.api 中的所有类在将来的次版本和补丁版本中完全兼容。在个别情况下,如果主版本号改变,一些特定的类可能有一些不兼容的更改,但这些更改将 在升级方法中 明确记录。
  • XML 配置 :XML solver 配置对所有元素向后兼容,但需要使用非公共 API 类的元素除外。XML solver 配置由软件包命名空间 org.optaplanner.core.configorg.optaplanner.benchmark.config 中的类定义。
  • 实施类 :所有其他类 都不能 向后兼容。在以后的主版本或次版本中会改变。升级方法描述了 相关的更改,以及如何在升级到更新的版本时解决它们。

4.2. 规划问题

规划问题 具有最佳目标,基于有限资源和特定限制。最佳目标可以是任意数量的事情,例如:

  • 最大化原位 - 最佳目标会导致最高的概率。
  • 最小化托管空间 - 最佳目标对环境的影响最小。
  • 最大化员工或客户的产品 - 优先选择员工或客户需求的最佳目标。

实现这些目标的能力取决于可用资源的数量。例如,以下资源可能会受限制:

  • 人员数量
  • 时间量
  • 预算
  • 物理资产,如 machinery, vehicles, computer, buildings

您还必须考虑与这些资源相关的特定限制,如个人工作时间、他们使用某些机器或设备间的兼容性的能力。

Red Hat build of OptaPlanner 帮助 Java 人员有效地解决挑战问题。它将优化 heuristics 和 metaheuristics 与有效的分数计算相结合。

4.3. 规划问题中的 NP-completeness

提供的用例 可能是 NP-complete 或 NP-hard,这意味着适用以下语句:

  • 在合理的时间内,容易验证特定问题的解决方案。
  • 在合理的时间段内,无法简单查找问题的最佳解决方案。

这意味着您的问题可能比您预期的难度更大,因为这两种常见技术不会受到影响:

  • 禁止算法(即使更高级的变体)用时过长。
  • 一个快速算法,例如在 bin packing 问题 中,将放置在最大项目中,首先 返回来自于最佳解决方案。

通过使用高级优化算法,OptaPlanner 会在合理的时间为这些规划问题找到良好的解决方案。

4.4. 规划问题的解决方案

规划问题有很多解决方案。

几种解决方案类别:

可能的解决方案
可能的解决方案是任何解决方案,无论它是否会破坏任意数量的限制。规划问题通常具有大量可能的解决方案。其中许多解决方案都并不有用。
可行的解决方案
可行的解决方案是不会破坏任何(负)硬限制的解决方案。可行的解决方案数量相对于可能的解决方案数量。有时没有可行的解决方案。每个可行的解决方案都是可能的解决方案。
最佳解决方案
最佳解决方案是具有最高分数的解决方案。规划问题通常有几个最佳解决方案。它们始终至少有一个最佳解决方案,即使没有可行的解决方案,并且最佳解决方案不可行。
找到最佳解决方案
最佳解决方案是在指定时间内实现的最高分数的解决方案。最佳解决方案可能很可行,给定有足够的时间,它是一个最佳解决方案。

因此,可能的解决方案数量较大(如果正确计算),即使设置了小的数据也是如此。

optaplanner-examples/src distribution 文件夹中提供的示例中,大多数实例都有大量可能的解决方案。因为无法保证查找最佳解决方案,因此任何实施都被强制评估所有可能的解决方案的子集。

OptaPlanner 支持多种优化算法,以便有效地利用这一大量可能的解决方案。

根据用例,一些优化算法性能比其他算法更好,但无法提前知道。使用 OptaPlanner,您可以通过在几个 XML 或代码中更改 solver 配置来切换优化算法。

4.5. 有关规划问题的限制

通常,计划问题至少有两个级别的约束:

  • 一个 (负)硬限制 不能被破坏。

    例如,一个公司无法同时为两个不同的资源提供两个不同的项。

  • 如果可以避免,则 (负)软约束 不应中断。

    例如,Teacher A 不希望在每天的 afternoons 上参与。

有些问题也存在正限制:

  • 如果可能,应满足 正的软约束(或返回)。

    例如,Teacher B 与 Monday mornings 相似。

有些基本问题仅存在硬约束。有些问题有三个或更多级别的约束,如硬、中型和软限制。

这些限制定义了规划问题 的分数计算 (也称为适当的 功能)。规划问题的每个解决方案均以分数的形式进行评级。使用 OptaPlanner 时,分数约束使用面向对象的语言(如 Java 或 dols 规则)编写。

这种类型的代码灵活且可扩展的。

4.6. 红帽构建的 OptaPlanner 示例

红帽构建的 OptaPlanner 示例附带几个 OptaPlanner 示例。您可以检查示例的代码,并根据需要对其进行修改以满足您的需要。

注意

红帽不支持红帽构建的 OptaPlanner 发行版中包含的示例代码。

一些 OptaPlanner 示例解决了在技术 contests 中出出的问题。以下表中的 Contest 列列出了 contests。它也识别一个示例为 realisticunrealistic 用于 contest 的目的。最后的 contest 是符合以下标准的官方独立 contest:

  • 明确定义的实际用例
  • 真实限制
  • 多个真实数据集
  • 在特定硬件的特定时间限制中可重复生成结果
  • 领导和/或企业级操作人员的严重参与。

架构师 contests 提供 OptaPlanner 的目标比较,与研究软件和研究方面提供了目标比较。

Expand
表 4.1. 示例概述
示例Domain大小法国目录名称

N queens

1 个实体类

(1 个变量)

实体 IANA 256

值 IANA 256

搜索空间 10^616

Pointless (cheatable)

nqueens

Cloud balancing

1 个实体类

(1 个变量)

实体为 2400

值 IANA 800

搜索空间 10^6967

否(由我们定义)

cloudbalancing

trafficing Salesman

1 个实体类

(1 个链变量)

实体 IANA 980

980

搜索空间 10^2504

unrealistic TSP web

tsp

Tennis club 调度

1 个实体类

(1 个变量)

实体 72

值 IANA 7

搜索空间 10^60

否(由我们定义)

tennis

会议调度

1 个实体类

(2 个变量)

实体关联 10

320 和 IANA 5

搜索空间 10^320

否(由我们定义)

会议计划

类时间设置

1 个实体类

(2 个变量)

实体 IANA 434

值 IANA 25 和 IANA 20

搜索空间 10^1171

ITC 2007 跟踪 3

curriculumCourse

机器重新分配

1 个实体类

(1 个变量)

实体为 50000

值 IANA 5000

搜索空间 10^184948

最近迁移的 ROADEF 2012

machineReassignment

vehicle 路由

1 个实体类

(1 个链变量)

1 个影子实体类

(1 自动影子变量)

实体 55

值为 2750

搜索空间 10^8380

非弹性 VRP Web

vehiclerouting

带有时间窗的 vehicle 路由

所有 Vehicle 路由

(1 shadow 变量)

实体 55

值为 2750

搜索空间 10^8380

非弹性 VRP Web

vehiclerouting

项目作业调度

1 个实体类

(2 个变量)

(1 shadow 变量)

实体为 640

值 IANA ? 和 IANA ?

搜索空格

近远的 MISTA 2013

projectjobscheduling

任务分配

1 个实体类

(1 列表变量)

1 个影子实体类

(1 自动影子变量)

(1 影子变量)

实体为 20

值为 500

搜索空间 10^1168

没有定义

taskassigning

评估时间设置

2 个实体类(相同层次结构)

(2 个变量)

实体为 1096

值 IANA 80 和 IANA 49

搜索空间 10^3374

ITC 2007 跟踪 1

考试

Nurse rostering

1 个实体类

(1 个变量)

实体为 752

值 IANA 50

搜索空间 10^1277

2010 年 10 月 11 日

nurserostering

趋势

1 个实体类

(1 个变量)

实体 1560

78

搜索空间 10^2301

unrealistic TTP

trafficingtournament

指导调度

1 个实体类

(2 个变量)

实体 IANA 216

值 IANA 18 和 IANA 20

搜索空间 10^552

没有定义

指导调度

飞行人员的调度

1 个实体类

(1 个变量)

1 个影子实体类

(1 自动影子变量)

实体 4375

750

搜索空间 10^12578

没有定义

flightcrewscheduling

4.7. N queens

在一个 n 大小的象棋盘中放置 n 个皇后,没有两个皇后可以相互攻击。最常见的 n queens puzzle 是 8 个 queens puzzle,带有 n = 8

约束:

  • 使用 n 列的数量和 n 行。
  • n queens 放在按板上。
  • 没有两个 queens 可以相互攻击。queen 可以在同一个水平、垂直或 diagonal 行中对任何其他 queen 进行攻击。

本文档主要使用 4 queens puzzle 作为主要示例。

建议的解决方案可以是:

图 4.1. 错误解决方案,用于 4 queens puzzle

以上解决方案是错误,因为 queens A1B0 可以相互攻击(因此可能会 queens B0D0)。删除 queen B0 会遵循 "no two queens 可相互攻击"约束,但会破坏 "place n queens" 约束。

以下是正确的解决方案:

图 4.2. Four queens puzzle 的正确解决方案

所有约束都已满足,因此解决方案正确。

请注意,大多数 queens puzzles 具有多个正确的解决方案。我们将专注于查找特定 n 的正确解决方案,而不是查找特定 n 可能的正确解决方案数量。

问题大小

4queens   has   4 queens with a search space of    256.
8queens   has   8 queens with a search space of   10^7.
16queens  has  16 queens with a search space of  10^19.
32queens  has  32 queens with a search space of  10^48.
64queens  has  64 queens with a search space of 10^115.
256queens has 256 queens with a search space of 10^616.
Copy to Clipboard Toggle word wrap

n queens 示例的实现尚未优化,因为它作为 beginner 示例。然而,它可以轻松地处理 64 queens。通过一些更改,它已被显示,可以轻松地处理 5000 queens 等。

4.7.1. N queens 的域模型

本例使用域模型来解决四个 queens 问题。

  • 创建域模型

    良好的域模型将更方便地理解并解决您的规划问题。

    这是 n queens 示例的域模型:

    public class Column {
    
        private int index;
    
        // ... getters and setters
    }
    Copy to Clipboard Toggle word wrap
    public class Row {
    
        private int index;
    
        // ... getters and setters
    }
    Copy to Clipboard Toggle word wrap
    public class Queen {
    
        private Column column;
        private Row row;
    
        public int getAscendingDiagonalIndex() {...}
        public int getDescendingDiagonalIndex() {...}
    
        // ... getters and setters
    }
    Copy to Clipboard Toggle word wrap
  • 计算搜索空间.

    Queen 实例有一个 Column (例如: 0 是列 A, 1 is column B, …​) 和一个 Row (例如,0 为行 0,1 为行 1, …​)。

    根据列和行计算升序行和降序行。

    列和行索引从小时的左上角开始。

    public class NQueens {
    
        private int n;
        private List<Column> columnList;
        private List<Row> rowList;
    
        private List<Queen> queenList;
    
        private SimpleScore score;
    
        // ... getters and setters
    }
    Copy to Clipboard Toggle word wrap
  • 查找解决方案

    单个 NQueens 实例包含所有 Queen 实例的列表。它是 解决方案 实现,它将由 Solver 提供、解决并从 Solver 检索。

请注意,在四个 queens 示例中,NQueens getN () 方法始终返回四个。

图 4.3. Four Queens 的解决方案

Expand
表 4.2. 域模型中解决方案详情
 columnIndexrowIndexascendingDiagonalIndex (columnIndex + rowIndex)descendingDiagonalIndex (columnIndex - rowIndex)

A1

0

1

1 (**)

-1

B0

1

0 (*)

1 (**)

1

C2

2

2

4

0

D0

3

0 (*)

3

3

当两个 queens 共享相同的列时,行或 diagonal 行(如 ASP 和(DSL))可以相互攻击。

4.8. Cloud balancing

有关本例的详情,请参考 Red Hat Build of OptaPlanner 快速启动指南

4.9. Tending Salesman (TSP - 指导销售问题)

给定一个城市列表,找到一次访问每个城市的 Salesman 最旧的导览。

问题由 Wikipedia 定义。它是计算数学 中最严重的问题之一。然而,在现实环境中,它通常只是规划问题的一部分,以及其他限制,如员工的过渡限制。

问题大小

dj38     has  38 cities with a search space of   10^43.
europe40 has  40 cities with a search space of   10^46.
st70     has  70 cities with a search space of   10^98.
pcb442   has 442 cities with a search space of  10^976.
lu980    has 980 cities with a search space of 10^2504.
Copy to Clipboard Toggle word wrap

问题困难

尽管 TSP 的简单定义,但问题难以解决。因为这是一个 NP 硬问题(如大多数计划问题),因此特定问题数据集的最佳解决方案可能会在对问题数据集稍有变化时改变:

4.10. Tennis club 调度

每周 10nis club 都有四个团队相互活跃循环。为团队分配这四个点。

硬限制:

  • 冲突 :团队一次只能播放一次。
  • 不可用:有些团队在某些情况下不可用。

中等限制:

  • 公平分配:所有团队都应该扮演了相等的次数。

软限制:

  • 平均聚合:每个团队都应该在其他团队中达到相等次数。

问题大小

munich-7teams has 7 teams, 18 days, 12 unavailabilityPenalties and 72 teamAssignments with a search space of 10^60.
Copy to Clipboard Toggle word wrap

图 4.4. 域模型

4.11. 会议调度

为开始时间和房间分配每个会议。会议具有不同的持续时间。

硬限制:

  • 房间冲突:两个会议不得同时使用同一空间。
  • 需要的参与:人无法同时拥有两个需要满足的会议。
  • 需要的房间容量: 会议不能处于不满足所有会议的房间。
  • 在同一天开始和结束:该会议不应多次调度。

中等限制:

  • 首选参与:人不能同时拥有两种偏好会议,也不能同时具有首选和必需的会议。

软限制:

  • 更早的时间,而不是之后:尽快调度所有会议。
  • 会议间的中断:任何两个会议都应该至少有一个时间中断。
  • 重叠的会议:为了最大程度减少并行会议的数量,因此用户不必选择另一个会议。
  • 首先分配更大的空间:如果一个更大的空间可用,则应给这一房间分配任何会议,以便满足尽可能多的人,即使他们尚未注册该会议。
  • 房间稳定性:如果人连续满足两个或更短的时间中断,则他们最好处于同一间。

问题大小

50meetings-160timegrains-5rooms  has  50 meetings, 160 timeGrains and 5 rooms with a search space of 10^145.
100meetings-320timegrains-5rooms has 100 meetings, 320 timeGrains and 5 rooms with a search space of 10^320.
200meetings-640timegrains-5rooms has 200 meetings, 640 timeGrains and 5 rooms with a search space of 10^701.
400meetings-1280timegrains-5rooms has 400 meetings, 1280 timeGrains and 5 rooms with a search space of 10^1522.
800meetings-2560timegrains-5rooms has 800 meetings, 2560 timeGrains and 5 rooms with a search space of 10^3285.
Copy to Clipboard Toggle word wrap

将每位安排成一个 timeslot 并放入一个房间。

硬限制:

  • 竞争器冲突:通常不能在同一时间段内有两个竞争。
  • 领导冲突:通常不能在同一时间段内有两个竞争。
  • 空间 occupancy:两个公司不能在同一时间内位于同一空间中。
  • 不可用周期(为每个数据集指定):特定持久性不能分配给特定的周期。

软限制:

  • 房间容量: 房间的容量不应小于其领导活动数量。
  • 最小工作天数:同一课程的显示应被分成最少的天数。
  • 领导压缩性:属于同一技术的视图应相互相邻(在连续的时间段内)。
  • 房间稳定性:同一课程的了解应分配给同一房。

这个问题由 国际时间建立复合的 Competition 2007 跟踪 3 定义。

问题大小

comp01 has 24 teachers,  14 curricula,  30 courses, 160 lectures, 30 periods,  6 rooms and   53 unavailable period constraints with a search space of  10^360.
comp02 has 71 teachers,  70 curricula,  82 courses, 283 lectures, 25 periods, 16 rooms and  513 unavailable period constraints with a search space of  10^736.
comp03 has 61 teachers,  68 curricula,  72 courses, 251 lectures, 25 periods, 16 rooms and  382 unavailable period constraints with a search space of  10^653.
comp04 has 70 teachers,  57 curricula,  79 courses, 286 lectures, 25 periods, 18 rooms and  396 unavailable period constraints with a search space of  10^758.
comp05 has 47 teachers, 139 curricula,  54 courses, 152 lectures, 36 periods,  9 rooms and  771 unavailable period constraints with a search space of  10^381.
comp06 has 87 teachers,  70 curricula, 108 courses, 361 lectures, 25 periods, 18 rooms and  632 unavailable period constraints with a search space of  10^957.
comp07 has 99 teachers,  77 curricula, 131 courses, 434 lectures, 25 periods, 20 rooms and  667 unavailable period constraints with a search space of 10^1171.
comp08 has 76 teachers,  61 curricula,  86 courses, 324 lectures, 25 periods, 18 rooms and  478 unavailable period constraints with a search space of  10^859.
comp09 has 68 teachers,  75 curricula,  76 courses, 279 lectures, 25 periods, 18 rooms and  405 unavailable period constraints with a search space of  10^740.
comp10 has 88 teachers,  67 curricula, 115 courses, 370 lectures, 25 periods, 18 rooms and  694 unavailable period constraints with a search space of  10^981.
comp11 has 24 teachers,  13 curricula,  30 courses, 162 lectures, 45 periods,  5 rooms and   94 unavailable period constraints with a search space of  10^381.
comp12 has 74 teachers, 150 curricula,  88 courses, 218 lectures, 36 periods, 11 rooms and 1368 unavailable period constraints with a search space of  10^566.
comp13 has 77 teachers,  66 curricula,  82 courses, 308 lectures, 25 periods, 19 rooms and  468 unavailable period constraints with a search space of  10^824.
comp14 has 68 teachers,  60 curricula,  85 courses, 275 lectures, 25 periods, 17 rooms and  486 unavailable period constraints with a search space of  10^722.
Copy to Clipboard Toggle word wrap

图 4.5. 域模型

4.13. 机器重新分配(Google ROADEF 2012)

为机器分配每个进程。所有进程都已有原始(未优化)分配。每个进程都需要每个资源(如 CPU 或 RAM)的数量。这是 Cloud Balancing 示例的复杂版本。

硬限制:

  • 最大容量 :不得超过每台机器的每个资源的最大容量。
  • 冲突:同一服务的处理必须在不同的计算机上运行。
  • 分散:同一服务的处理必须在位置之间分散。
  • dependencies :根据另一个服务,服务的进程必须在其他服务进程的邻居运行。
  • 临时使用:有些资源是临时的,是原始机器作为新分配的机器的最大容量。

软限制:

  • load :不应超过每台机器的每个资源的安全容量。
  • Balance :通过平衡每台计算机上的可用资源来保留将来分配的空间。
  • 流程移动成本:流程有移动成本。
  • 服务移动成本:服务具有移动成本。
  • 机器移动成本:将进程从机器 A 移动到机器 B 具有另一个 A-B 的移动成本。

这个问题由 Google ROADEF/EURO Challenge 2012 定义。

图 4.6. 价值

问题大小

model_a1_1 has  2 resources,  1 neighborhoods,   4 locations,    4 machines,    79 services,   100 processes and 1 balancePenalties with a search space of     10^60.
model_a1_2 has  4 resources,  2 neighborhoods,   4 locations,  100 machines,   980 services,  1000 processes and 0 balancePenalties with a search space of   10^2000.
model_a1_3 has  3 resources,  5 neighborhoods,  25 locations,  100 machines,   216 services,  1000 processes and 0 balancePenalties with a search space of   10^2000.
model_a1_4 has  3 resources, 50 neighborhoods,  50 locations,   50 machines,   142 services,  1000 processes and 1 balancePenalties with a search space of   10^1698.
model_a1_5 has  4 resources,  2 neighborhoods,   4 locations,   12 machines,   981 services,  1000 processes and 1 balancePenalties with a search space of   10^1079.
model_a2_1 has  3 resources,  1 neighborhoods,   1 locations,  100 machines,  1000 services,  1000 processes and 0 balancePenalties with a search space of   10^2000.
model_a2_2 has 12 resources,  5 neighborhoods,  25 locations,  100 machines,   170 services,  1000 processes and 0 balancePenalties with a search space of   10^2000.
model_a2_3 has 12 resources,  5 neighborhoods,  25 locations,  100 machines,   129 services,  1000 processes and 0 balancePenalties with a search space of   10^2000.
model_a2_4 has 12 resources,  5 neighborhoods,  25 locations,   50 machines,   180 services,  1000 processes and 1 balancePenalties with a search space of   10^1698.
model_a2_5 has 12 resources,  5 neighborhoods,  25 locations,   50 machines,   153 services,  1000 processes and 0 balancePenalties with a search space of   10^1698.
model_b_1  has 12 resources,  5 neighborhoods,  10 locations,  100 machines,  2512 services,  5000 processes and 0 balancePenalties with a search space of  10^10000.
model_b_2  has 12 resources,  5 neighborhoods,  10 locations,  100 machines,  2462 services,  5000 processes and 1 balancePenalties with a search space of  10^10000.
model_b_3  has  6 resources,  5 neighborhoods,  10 locations,  100 machines, 15025 services, 20000 processes and 0 balancePenalties with a search space of  10^40000.
model_b_4  has  6 resources,  5 neighborhoods,  50 locations,  500 machines,  1732 services, 20000 processes and 1 balancePenalties with a search space of  10^53979.
model_b_5  has  6 resources,  5 neighborhoods,  10 locations,  100 machines, 35082 services, 40000 processes and 0 balancePenalties with a search space of  10^80000.
model_b_6  has  6 resources,  5 neighborhoods,  50 locations,  200 machines, 14680 services, 40000 processes and 1 balancePenalties with a search space of  10^92041.
model_b_7  has  6 resources,  5 neighborhoods,  50 locations, 4000 machines, 15050 services, 40000 processes and 1 balancePenalties with a search space of 10^144082.
model_b_8  has  3 resources,  5 neighborhoods,  10 locations,  100 machines, 45030 services, 50000 processes and 0 balancePenalties with a search space of 10^100000.
model_b_9  has  3 resources,  5 neighborhoods, 100 locations, 1000 machines,  4609 services, 50000 processes and 1 balancePenalties with a search space of 10^150000.
model_b_10 has  3 resources,  5 neighborhoods, 100 locations, 5000 machines,  4896 services, 50000 processes and 1 balancePenalties with a search space of 10^184948.
Copy to Clipboard Toggle word wrap

图 4.7. 域模型

4.14. vehicle 路由

使用一组电话,获取每个客户的对象并将其带入耗尽。每个 vehicle 都可以为多个客户提供服务,但它有有限的容量。

除了基本问题单(CVRP)外,还有一个带有时间窗(CVRPTW)的变体。

硬限制:

  • vehicle 容量: vehicle 无法执行更多项目,然后其容量。
  • 时间窗(只在 CVRPTW 中):

    • 轮转时间:从一个位置传输到另一个地方需要时间。
    • 客户服务持续时间: vehicle 必须在服务持续时间内保持给客户。
    • 客户就绪时间: vehicle 可在客户就绪时间之前到达,但必须等到为工作前的时间才会等待。
    • 客户到期时间:在客户到期前必须及时到达时间。

软限制:

  • 总距离:最小化所有 vehicles 的距离驱动的总距离(fuel 消耗)。

带容量的载体路由问题(CVRP)及其时间同步变体(CVRPTW)由 网络和早期优化(NEO) VRP 网站定义。

图 4.8. 价值

问题大小

CVRP 实例(没有时间窗):

belgium-n50-k10             has  1 depots, 10 vehicles and   49 customers with a search space of   10^74.
belgium-n100-k10            has  1 depots, 10 vehicles and   99 customers with a search space of  10^170.
belgium-n500-k20            has  1 depots, 20 vehicles and  499 customers with a search space of 10^1168.
belgium-n1000-k20           has  1 depots, 20 vehicles and  999 customers with a search space of 10^2607.
belgium-n2750-k55           has  1 depots, 55 vehicles and 2749 customers with a search space of 10^8380.
belgium-road-km-n50-k10     has  1 depots, 10 vehicles and   49 customers with a search space of   10^74.
belgium-road-km-n100-k10    has  1 depots, 10 vehicles and   99 customers with a search space of  10^170.
belgium-road-km-n500-k20    has  1 depots, 20 vehicles and  499 customers with a search space of 10^1168.
belgium-road-km-n1000-k20   has  1 depots, 20 vehicles and  999 customers with a search space of 10^2607.
belgium-road-km-n2750-k55   has  1 depots, 55 vehicles and 2749 customers with a search space of 10^8380.
belgium-road-time-n50-k10   has  1 depots, 10 vehicles and   49 customers with a search space of   10^74.
belgium-road-time-n100-k10  has  1 depots, 10 vehicles and   99 customers with a search space of  10^170.
belgium-road-time-n500-k20  has  1 depots, 20 vehicles and  499 customers with a search space of 10^1168.
belgium-road-time-n1000-k20 has  1 depots, 20 vehicles and  999 customers with a search space of 10^2607.
belgium-road-time-n2750-k55 has  1 depots, 55 vehicles and 2749 customers with a search space of 10^8380.
belgium-d2-n50-k10          has  2 depots, 10 vehicles and   48 customers with a search space of   10^74.
belgium-d3-n100-k10         has  3 depots, 10 vehicles and   97 customers with a search space of  10^170.
belgium-d5-n500-k20         has  5 depots, 20 vehicles and  495 customers with a search space of 10^1168.
belgium-d8-n1000-k20        has  8 depots, 20 vehicles and  992 customers with a search space of 10^2607.
belgium-d10-n2750-k55       has 10 depots, 55 vehicles and 2740 customers with a search space of 10^8380.

A-n32-k5  has 1 depots,  5 vehicles and  31 customers with a search space of  10^40.
A-n33-k5  has 1 depots,  5 vehicles and  32 customers with a search space of  10^41.
A-n33-k6  has 1 depots,  6 vehicles and  32 customers with a search space of  10^42.
A-n34-k5  has 1 depots,  5 vehicles and  33 customers with a search space of  10^43.
A-n36-k5  has 1 depots,  5 vehicles and  35 customers with a search space of  10^46.
A-n37-k5  has 1 depots,  5 vehicles and  36 customers with a search space of  10^48.
A-n37-k6  has 1 depots,  6 vehicles and  36 customers with a search space of  10^49.
A-n38-k5  has 1 depots,  5 vehicles and  37 customers with a search space of  10^49.
A-n39-k5  has 1 depots,  5 vehicles and  38 customers with a search space of  10^51.
A-n39-k6  has 1 depots,  6 vehicles and  38 customers with a search space of  10^52.
A-n44-k7  has 1 depots,  7 vehicles and  43 customers with a search space of  10^61.
A-n45-k6  has 1 depots,  6 vehicles and  44 customers with a search space of  10^62.
A-n45-k7  has 1 depots,  7 vehicles and  44 customers with a search space of  10^63.
A-n46-k7  has 1 depots,  7 vehicles and  45 customers with a search space of  10^65.
A-n48-k7  has 1 depots,  7 vehicles and  47 customers with a search space of  10^68.
A-n53-k7  has 1 depots,  7 vehicles and  52 customers with a search space of  10^77.
A-n54-k7  has 1 depots,  7 vehicles and  53 customers with a search space of  10^79.
A-n55-k9  has 1 depots,  9 vehicles and  54 customers with a search space of  10^82.
A-n60-k9  has 1 depots,  9 vehicles and  59 customers with a search space of  10^91.
A-n61-k9  has 1 depots,  9 vehicles and  60 customers with a search space of  10^93.
A-n62-k8  has 1 depots,  8 vehicles and  61 customers with a search space of  10^94.
A-n63-k9  has 1 depots,  9 vehicles and  62 customers with a search space of  10^97.
A-n63-k10 has 1 depots, 10 vehicles and  62 customers with a search space of  10^98.
A-n64-k9  has 1 depots,  9 vehicles and  63 customers with a search space of  10^99.
A-n65-k9  has 1 depots,  9 vehicles and  64 customers with a search space of 10^101.
A-n69-k9  has 1 depots,  9 vehicles and  68 customers with a search space of 10^108.
A-n80-k10 has 1 depots, 10 vehicles and  79 customers with a search space of 10^130.
F-n45-k4  has 1 depots,  4 vehicles and  44 customers with a search space of  10^60.
F-n72-k4  has 1 depots,  4 vehicles and  71 customers with a search space of 10^108.
F-n135-k7 has 1 depots,  7 vehicles and 134 customers with a search space of 10^240.
Copy to Clipboard Toggle word wrap

CVRPTW 实例(使用时间窗):

belgium-tw-d2-n50-k10    has  2 depots, 10 vehicles and   48 customers with a search space of   10^74.
belgium-tw-d3-n100-k10   has  3 depots, 10 vehicles and   97 customers with a search space of  10^170.
belgium-tw-d5-n500-k20   has  5 depots, 20 vehicles and  495 customers with a search space of 10^1168.
belgium-tw-d8-n1000-k20  has  8 depots, 20 vehicles and  992 customers with a search space of 10^2607.
belgium-tw-d10-n2750-k55 has 10 depots, 55 vehicles and 2740 customers with a search space of 10^8380.
belgium-tw-n50-k10       has  1 depots, 10 vehicles and   49 customers with a search space of   10^74.
belgium-tw-n100-k10      has  1 depots, 10 vehicles and   99 customers with a search space of  10^170.
belgium-tw-n500-k20      has  1 depots, 20 vehicles and  499 customers with a search space of 10^1168.
belgium-tw-n1000-k20     has  1 depots, 20 vehicles and  999 customers with a search space of 10^2607.
belgium-tw-n2750-k55     has  1 depots, 55 vehicles and 2749 customers with a search space of 10^8380.

Solomon_025_C101       has 1 depots,  25 vehicles and   25 customers with a search space of   10^40.
Solomon_025_C201       has 1 depots,  25 vehicles and   25 customers with a search space of   10^40.
Solomon_025_R101       has 1 depots,  25 vehicles and   25 customers with a search space of   10^40.
Solomon_025_R201       has 1 depots,  25 vehicles and   25 customers with a search space of   10^40.
Solomon_025_RC101      has 1 depots,  25 vehicles and   25 customers with a search space of   10^40.
Solomon_025_RC201      has 1 depots,  25 vehicles and   25 customers with a search space of   10^40.
Solomon_100_C101       has 1 depots,  25 vehicles and  100 customers with a search space of  10^185.
Solomon_100_C201       has 1 depots,  25 vehicles and  100 customers with a search space of  10^185.
Solomon_100_R101       has 1 depots,  25 vehicles and  100 customers with a search space of  10^185.
Solomon_100_R201       has 1 depots,  25 vehicles and  100 customers with a search space of  10^185.
Solomon_100_RC101      has 1 depots,  25 vehicles and  100 customers with a search space of  10^185.
Solomon_100_RC201      has 1 depots,  25 vehicles and  100 customers with a search space of  10^185.
Homberger_0200_C1_2_1  has 1 depots,  50 vehicles and  200 customers with a search space of  10^429.
Homberger_0200_C2_2_1  has 1 depots,  50 vehicles and  200 customers with a search space of  10^429.
Homberger_0200_R1_2_1  has 1 depots,  50 vehicles and  200 customers with a search space of  10^429.
Homberger_0200_R2_2_1  has 1 depots,  50 vehicles and  200 customers with a search space of  10^429.
Homberger_0200_RC1_2_1 has 1 depots,  50 vehicles and  200 customers with a search space of  10^429.
Homberger_0200_RC2_2_1 has 1 depots,  50 vehicles and  200 customers with a search space of  10^429.
Homberger_0400_C1_4_1  has 1 depots, 100 vehicles and  400 customers with a search space of  10^978.
Homberger_0400_C2_4_1  has 1 depots, 100 vehicles and  400 customers with a search space of  10^978.
Homberger_0400_R1_4_1  has 1 depots, 100 vehicles and  400 customers with a search space of  10^978.
Homberger_0400_R2_4_1  has 1 depots, 100 vehicles and  400 customers with a search space of  10^978.
Homberger_0400_RC1_4_1 has 1 depots, 100 vehicles and  400 customers with a search space of  10^978.
Homberger_0400_RC2_4_1 has 1 depots, 100 vehicles and  400 customers with a search space of  10^978.
Homberger_0600_C1_6_1  has 1 depots, 150 vehicles and  600 customers with a search space of 10^1571.
Homberger_0600_C2_6_1  has 1 depots, 150 vehicles and  600 customers with a search space of 10^1571.
Homberger_0600_R1_6_1  has 1 depots, 150 vehicles and  600 customers with a search space of 10^1571.
Homberger_0600_R2_6_1  has 1 depots, 150 vehicles and  600 customers with a search space of 10^1571.
Homberger_0600_RC1_6_1 has 1 depots, 150 vehicles and  600 customers with a search space of 10^1571.
Homberger_0600_RC2_6_1 has 1 depots, 150 vehicles and  600 customers with a search space of 10^1571.
Homberger_0800_C1_8_1  has 1 depots, 200 vehicles and  800 customers with a search space of 10^2195.
Homberger_0800_C2_8_1  has 1 depots, 200 vehicles and  800 customers with a search space of 10^2195.
Homberger_0800_R1_8_1  has 1 depots, 200 vehicles and  800 customers with a search space of 10^2195.
Homberger_0800_R2_8_1  has 1 depots, 200 vehicles and  800 customers with a search space of 10^2195.
Homberger_0800_RC1_8_1 has 1 depots, 200 vehicles and  800 customers with a search space of 10^2195.
Homberger_0800_RC2_8_1 has 1 depots, 200 vehicles and  800 customers with a search space of 10^2195.
Homberger_1000_C110_1  has 1 depots, 250 vehicles and 1000 customers with a search space of 10^2840.
Homberger_1000_C210_1  has 1 depots, 250 vehicles and 1000 customers with a search space of 10^2840.
Homberger_1000_R110_1  has 1 depots, 250 vehicles and 1000 customers with a search space of 10^2840.
Homberger_1000_R210_1  has 1 depots, 250 vehicles and 1000 customers with a search space of 10^2840.
Homberger_1000_RC110_1 has 1 depots, 250 vehicles and 1000 customers with a search space of 10^2840.
Homberger_1000_RC210_1 has 1 depots, 250 vehicles and 1000 customers with a search space of 10^2840.
Copy to Clipboard Toggle word wrap

4.14.1. vehicle 路由的域模型

带有时间窗域模型的 vehicle 路由大量使用 shadow 变量功能。这允许它更自然地表达限制,因为 arrivalTimedepartureTime 等属性直接在域模型中可用。

临时区分无线差异

在真实世界中,vehicles 不能从位置到位置跟随行:它们必须使用 roads 和 highways。从一个角度来说,这很重要:

对于优化算法,只要可以查找两个点之间的距离(最好是预先计算)。road 成本甚至不需要是一个距离。它还有可能是大量时间、经济的成本或加权功能。一些技术可用于预先计算 road 成本,如 GraphHopper (embeddable, offline Java engine), Open MapQuest (web service)和 Google Maps Client API (web service)。

另外,还有一些技术来呈现它,如 开发人员的 Leaflet 和 Google Map

甚至可以使用 GraphHopper 或 Google Map Directions 呈现实际 road 路由,但由于路由对高路重叠,看不到顺序:

请特别注意两个点之间的 road 成本与 OptaPlanner 中使用的优化标准相同。例如,GraphHopper 默认返回最快的路由,而不是最短的路由。不要使用最快速的 GPS 路由的 km (或 miles)距离来优化 OptaPlanner 中最短的往返:这会导致子优化解决方案,如下所示:

与流行不同,大多数用户都不希望使用最短的路由:他们希望使用最快的路由。它们更喜欢通常高路。它们更喜欢在 dirt roads 上正常的 roads。在现实环境中,最快速且最短的路由很少相同。

4.15. 项目作业调度

以时间和执行模式调度所有作业,以最小化项目延迟。每个作业都是项目的一部分。作业可以以不同的方式执行:每种方法是执行模式,它意味着不同的持续时间,但也不同的资源使用量。这是灵活的 作业跃点调度的形式

硬限制:

  • 作业优先级:只有所有前者作业都完成时,才可以启动作业。
  • 资源容量:不使用超过可用资源的资源。

    • 资源是本地的(同一项目的作业之间共享)或全局(在所有作业之间共享)
    • 资源是可续订(每天可用的容量)或不可续订(可续订的容量)

中等限制:

  • 项目总延迟:最小化每个项目的持续时间(makespan)。

软限制:

  • Total makespan:最小化整个多项目调度的持续时间。

这个问题由 MISTA 2013 挑战 定义。

问题大小

Schedule A-1  has  2 projects,  24 jobs,   64 execution modes,  7 resources and  150 resource requirements.
Schedule A-2  has  2 projects,  44 jobs,  124 execution modes,  7 resources and  420 resource requirements.
Schedule A-3  has  2 projects,  64 jobs,  184 execution modes,  7 resources and  630 resource requirements.
Schedule A-4  has  5 projects,  60 jobs,  160 execution modes, 16 resources and  390 resource requirements.
Schedule A-5  has  5 projects, 110 jobs,  310 execution modes, 16 resources and  900 resource requirements.
Schedule A-6  has  5 projects, 160 jobs,  460 execution modes, 16 resources and 1440 resource requirements.
Schedule A-7  has 10 projects, 120 jobs,  320 execution modes, 22 resources and  900 resource requirements.
Schedule A-8  has 10 projects, 220 jobs,  620 execution modes, 22 resources and 1860 resource requirements.
Schedule A-9  has 10 projects, 320 jobs,  920 execution modes, 31 resources and 2880 resource requirements.
Schedule A-10 has 10 projects, 320 jobs,  920 execution modes, 31 resources and 2970 resource requirements.
Schedule B-1  has 10 projects, 120 jobs,  320 execution modes, 31 resources and  900 resource requirements.
Schedule B-2  has 10 projects, 220 jobs,  620 execution modes, 22 resources and 1740 resource requirements.
Schedule B-3  has 10 projects, 320 jobs,  920 execution modes, 31 resources and 3060 resource requirements.
Schedule B-4  has 15 projects, 180 jobs,  480 execution modes, 46 resources and 1530 resource requirements.
Schedule B-5  has 15 projects, 330 jobs,  930 execution modes, 46 resources and 2760 resource requirements.
Schedule B-6  has 15 projects, 480 jobs, 1380 execution modes, 46 resources and 4500 resource requirements.
Schedule B-7  has 20 projects, 240 jobs,  640 execution modes, 61 resources and 1710 resource requirements.
Schedule B-8  has 20 projects, 440 jobs, 1240 execution modes, 42 resources and 3180 resource requirements.
Schedule B-9  has 20 projects, 640 jobs, 1840 execution modes, 61 resources and 5940 resource requirements.
Schedule B-10 has 20 projects, 460 jobs, 1300 execution modes, 42 resources and 4260 resource requirements.
Copy to Clipboard Toggle word wrap

4.16. 任务分配

将每个任务分配给员工队列中的 spot。每个任务都有一个持续时间,该持续时间会受到员工对任务的关联性级别的影响。

硬限制:

  • 行业:每个任务都需要一个或多个学习。员工必须拥有所有这些知识。

软级别 0 限制:

  • 关键任务:首先完成关键任务,早于主要和次要任务。

软级别 1 限制:

  • 最小化 makespan:缩短时间完成所有任务。

    • 首先,从最多的工作员工开始,再开始第二个工作员工,以创建公平和负载平衡。

软级别 2 约束:

  • 主要任务:尽快完成主要任务,早于次要任务。

软级别 3 限制:

  • 次要任务:尽快完成次要任务。

图 4.9. 价值

问题大小

24tasks-8employees   has  24 tasks, 6 skills,  8 employees,   4 task types and  4 customers with a search space of   10^30.
50tasks-5employees   has  50 tasks, 5 skills,  5 employees,  10 task types and 10 customers with a search space of   10^69.
100tasks-5employees  has 100 tasks, 5 skills,  5 employees,  20 task types and 15 customers with a search space of  10^164.
500tasks-20employees has 500 tasks, 6 skills, 20 employees, 100 task types and 60 customers with a search space of 10^1168.
Copy to Clipboard Toggle word wrap

图 4.10. 域模型

4.17. 评估时间设置(ITC 2007 跟踪 1 - 评估)

将每个课程计划到一个句点,并放入一个房间。多个公司可以同时共享同一房。

硬限制:

  • 评估冲突:不能在同一时间段内同时发生共享参与的两个技术。
  • 房间容量 :房间的容量必须始终足够高。
  • 持续时间: 周期的持续时间必须足以满足其所有销售期。
  • 与周期相关的硬限制(按数据集指定):

    • coincidence:两个指定的参与必须使用相同的句点(但可能为其他房间)。
    • 排除:两个指定的参与不得同时使用同一周期。
    • 之后:在另一个指定测试期后,必须在一个时间段内进行指定的考试。
  • 与空间相关的硬限制(每个数据集指定):

    • exclusive:一个指定的基准测试不必与任何其他课程共享其房间。

软限制(其中每个都具有参数损失):

  • 同一站不应在一行中有两个。
  • 同一成员不应在同一天上有两个活动。
  • 周期分布:共享的参与应是多个句点。
  • 混合持续时间:共享一个房间不应具有不同的持续时间。
  • 前端负载:在计划早期应该调度大量时间。
  • 周期损失(指定每个数据集):一些周期在使用时都有损失。
  • 空间损失(指定每个数据集):一些空间在使用时有损失。

它使用大量实际生命常量集。

这个问题由 国际时间建立复杂时间定义,即 2007 跟踪 1。Geoffrey De Smet 完成 4th,与非常早版本的 OptaPlanner 合作。因此,进行很多改进。

问题大小

exam_comp_set1 has  7883 students,  607 exams, 54 periods,  7 rooms,  12 period constraints and  0 room constraints with a search space of 10^1564.
exam_comp_set2 has 12484 students,  870 exams, 40 periods, 49 rooms,  12 period constraints and  2 room constraints with a search space of 10^2864.
exam_comp_set3 has 16365 students,  934 exams, 36 periods, 48 rooms, 168 period constraints and 15 room constraints with a search space of 10^3023.
exam_comp_set4 has  4421 students,  273 exams, 21 periods,  1 rooms,  40 period constraints and  0 room constraints with a search space of  10^360.
exam_comp_set5 has  8719 students, 1018 exams, 42 periods,  3 rooms,  27 period constraints and  0 room constraints with a search space of 10^2138.
exam_comp_set6 has  7909 students,  242 exams, 16 periods,  8 rooms,  22 period constraints and  0 room constraints with a search space of  10^509.
exam_comp_set7 has 13795 students, 1096 exams, 80 periods, 15 rooms,  28 period constraints and  0 room constraints with a search space of 10^3374.
exam_comp_set8 has  7718 students,  598 exams, 80 periods,  8 rooms,  20 period constraints and  1 room constraints with a search space of 10^1678.
Copy to Clipboard Toggle word wrap

4.17.1. 用于测试时间设置的域模型

下图显示了主要的评估域类:

图 4.11. 评估域类图

请注意,我们已将考试概念分成 考试 课程和主题课程。在解决时(这是计划实体类)的考试实例在它们的期间或房间属性发生变化时发生了变化。主题 PeriodRoom 实例在参与期间永远不会改变(这些是问题事实,就像某些其他类一样)。

4.18. Nurse rostering (INRC 2010)

对于每个转换,请分配一个操作方式进行转换。

硬限制:

  • 没有未分配的 转换(内置):每个转换都需要分配给员工。
  • 移动冲突 :员工每天只能有一个过渡。

软限制:

  • 合同费用。业务经常违反了这些情况,因此他们决定将它们定义为软限制,而非硬限制。

    • 最小和最大分配 :每个员工需要超过 x 过渡,且少于 y 过渡(取决于其合同)。
    • 最小和最大连续工作天数 :每个员工都需要连续在 x 和 y 天之间工作(取决于其合同)。
    • 最小和最大连续的免费天数 :每个员工都需要在 x 到 y 天之间自由使用(取决于其合同)。
    • 最小和最大连续的工作日 :每个员工都需要按行在 x 和 y 周线(取决于其合同)之间工作。
    • 完成 每周:每个员工都需要每天在一周内或根本不工作。
    • 周例期间 的相同迁移类型:对于同一员工的同一周例,每个每周过渡都必须相同。
    • 不支持的模式 :一行中不需要的转换类型的组合,例如,之后移动后接一个早期的转换,后接一个之后的移动。
  • 员工希望:

    • 请求的 一天:员工希望在特定日期上工作。
    • 第一天请求 :员工不希望在特定日期上工作。
    • 根据请求迁移 :员工希望分配给特定的移动。
    • 移动请求 :员工不想分配给特定的移动。
  • 备选技术 :分配给专家的员工应该在这一迁移所需的每个技术方面有障碍。

此问题由 国际 Nurse Rostering Competition 2010 定义。

图 4.12. 价值

问题大小

有三个数据集类型:

  • 过程:必须以秒为单位解决。
  • Medium:必须以分钟为单位解决。
  • 长:必须以小时为单位解决。
toy1          has 1 skills, 3 shiftTypes, 2 patterns, 1 contracts,  6 employees,  7 shiftDates,  35 shiftAssignments and   0 requests with a search space of   10^27.
toy2          has 1 skills, 3 shiftTypes, 3 patterns, 2 contracts, 20 employees, 28 shiftDates, 180 shiftAssignments and 140 requests with a search space of  10^234.

sprint01      has 1 skills, 4 shiftTypes, 3 patterns, 4 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.
sprint02      has 1 skills, 4 shiftTypes, 3 patterns, 4 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.
sprint03      has 1 skills, 4 shiftTypes, 3 patterns, 4 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.
sprint04      has 1 skills, 4 shiftTypes, 3 patterns, 4 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.
sprint05      has 1 skills, 4 shiftTypes, 3 patterns, 4 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.
sprint06      has 1 skills, 4 shiftTypes, 3 patterns, 4 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.
sprint07      has 1 skills, 4 shiftTypes, 3 patterns, 4 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.
sprint08      has 1 skills, 4 shiftTypes, 3 patterns, 4 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.
sprint09      has 1 skills, 4 shiftTypes, 3 patterns, 4 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.
sprint10      has 1 skills, 4 shiftTypes, 3 patterns, 4 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.
sprint_hint01 has 1 skills, 4 shiftTypes, 8 patterns, 3 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.
sprint_hint02 has 1 skills, 4 shiftTypes, 0 patterns, 3 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.
sprint_hint03 has 1 skills, 4 shiftTypes, 8 patterns, 3 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.
sprint_late01 has 1 skills, 4 shiftTypes, 8 patterns, 3 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.
sprint_late02 has 1 skills, 3 shiftTypes, 4 patterns, 3 contracts, 10 employees, 28 shiftDates, 144 shiftAssignments and 139 requests with a search space of  10^144.
sprint_late03 has 1 skills, 4 shiftTypes, 8 patterns, 3 contracts, 10 employees, 28 shiftDates, 160 shiftAssignments and 150 requests with a search space of  10^160.
sprint_late04 has 1 skills, 4 shiftTypes, 8 patterns, 3 contracts, 10 employees, 28 shiftDates, 160 shiftAssignments and 150 requests with a search space of  10^160.
sprint_late05 has 1 skills, 4 shiftTypes, 8 patterns, 3 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.
sprint_late06 has 1 skills, 4 shiftTypes, 0 patterns, 3 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.
sprint_late07 has 1 skills, 4 shiftTypes, 0 patterns, 3 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.
sprint_late08 has 1 skills, 4 shiftTypes, 0 patterns, 3 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and   0 requests with a search space of  10^152.
sprint_late09 has 1 skills, 4 shiftTypes, 0 patterns, 3 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and   0 requests with a search space of  10^152.
sprint_late10 has 1 skills, 4 shiftTypes, 0 patterns, 3 contracts, 10 employees, 28 shiftDates, 152 shiftAssignments and 150 requests with a search space of  10^152.

medium01      has 1 skills, 4 shiftTypes, 0 patterns, 4 contracts, 31 employees, 28 shiftDates, 608 shiftAssignments and 403 requests with a search space of  10^906.
medium02      has 1 skills, 4 shiftTypes, 0 patterns, 4 contracts, 31 employees, 28 shiftDates, 608 shiftAssignments and 403 requests with a search space of  10^906.
medium03      has 1 skills, 4 shiftTypes, 0 patterns, 4 contracts, 31 employees, 28 shiftDates, 608 shiftAssignments and 403 requests with a search space of  10^906.
medium04      has 1 skills, 4 shiftTypes, 0 patterns, 4 contracts, 31 employees, 28 shiftDates, 608 shiftAssignments and 403 requests with a search space of  10^906.
medium05      has 1 skills, 4 shiftTypes, 0 patterns, 4 contracts, 31 employees, 28 shiftDates, 608 shiftAssignments and 403 requests with a search space of  10^906.
medium_hint01 has 1 skills, 4 shiftTypes, 7 patterns, 4 contracts, 30 employees, 28 shiftDates, 428 shiftAssignments and 390 requests with a search space of  10^632.
medium_hint02 has 1 skills, 4 shiftTypes, 7 patterns, 3 contracts, 30 employees, 28 shiftDates, 428 shiftAssignments and 390 requests with a search space of  10^632.
medium_hint03 has 1 skills, 4 shiftTypes, 7 patterns, 4 contracts, 30 employees, 28 shiftDates, 428 shiftAssignments and 390 requests with a search space of  10^632.
medium_late01 has 1 skills, 4 shiftTypes, 7 patterns, 4 contracts, 30 employees, 28 shiftDates, 424 shiftAssignments and 390 requests with a search space of  10^626.
medium_late02 has 1 skills, 4 shiftTypes, 7 patterns, 3 contracts, 30 employees, 28 shiftDates, 428 shiftAssignments and 390 requests with a search space of  10^632.
medium_late03 has 1 skills, 4 shiftTypes, 0 patterns, 4 contracts, 30 employees, 28 shiftDates, 428 shiftAssignments and 390 requests with a search space of  10^632.
medium_late04 has 1 skills, 4 shiftTypes, 7 patterns, 3 contracts, 30 employees, 28 shiftDates, 416 shiftAssignments and 390 requests with a search space of  10^614.
medium_late05 has 2 skills, 5 shiftTypes, 7 patterns, 4 contracts, 30 employees, 28 shiftDates, 452 shiftAssignments and 390 requests with a search space of  10^667.

long01        has 2 skills, 5 shiftTypes, 3 patterns, 3 contracts, 49 employees, 28 shiftDates, 740 shiftAssignments and 735 requests with a search space of 10^1250.
long02        has 2 skills, 5 shiftTypes, 3 patterns, 3 contracts, 49 employees, 28 shiftDates, 740 shiftAssignments and 735 requests with a search space of 10^1250.
long03        has 2 skills, 5 shiftTypes, 3 patterns, 3 contracts, 49 employees, 28 shiftDates, 740 shiftAssignments and 735 requests with a search space of 10^1250.
long04        has 2 skills, 5 shiftTypes, 3 patterns, 3 contracts, 49 employees, 28 shiftDates, 740 shiftAssignments and 735 requests with a search space of 10^1250.
long05        has 2 skills, 5 shiftTypes, 3 patterns, 3 contracts, 49 employees, 28 shiftDates, 740 shiftAssignments and 735 requests with a search space of 10^1250.
long_hint01   has 2 skills, 5 shiftTypes, 9 patterns, 3 contracts, 50 employees, 28 shiftDates, 740 shiftAssignments and   0 requests with a search space of 10^1257.
long_hint02   has 2 skills, 5 shiftTypes, 7 patterns, 3 contracts, 50 employees, 28 shiftDates, 740 shiftAssignments and   0 requests with a search space of 10^1257.
long_hint03   has 2 skills, 5 shiftTypes, 7 patterns, 3 contracts, 50 employees, 28 shiftDates, 740 shiftAssignments and   0 requests with a search space of 10^1257.
long_late01   has 2 skills, 5 shiftTypes, 9 patterns, 3 contracts, 50 employees, 28 shiftDates, 752 shiftAssignments and   0 requests with a search space of 10^1277.
long_late02   has 2 skills, 5 shiftTypes, 9 patterns, 4 contracts, 50 employees, 28 shiftDates, 752 shiftAssignments and   0 requests with a search space of 10^1277.
long_late03   has 2 skills, 5 shiftTypes, 9 patterns, 3 contracts, 50 employees, 28 shiftDates, 752 shiftAssignments and   0 requests with a search space of 10^1277.
long_late04   has 2 skills, 5 shiftTypes, 9 patterns, 4 contracts, 50 employees, 28 shiftDates, 752 shiftAssignments and   0 requests with a search space of 10^1277.
long_late05   has 2 skills, 5 shiftTypes, 9 patterns, 3 contracts, 50 employees, 28 shiftDates, 740 shiftAssignments and   0 requests with a search space of 10^1257.
Copy to Clipboard Toggle word wrap

图 4.13. 域模型

4.19. 指导准入调度

指导准入调度(PAS),也称为 过程的计划,为每个接受到 站的个人分配一个 bed。在计划的调度期间,将 bed 分配给参与的持续时间。每个方面都属于一个房间,每个房间属于一个部门。修复了过期日期的 arrival 和 departure 日期。您只需要分配一个 bed。

这个问题的功能比有限制的数据集。当不需要分配所有计划实体时,最好根据需要分配任意数量的实体,而不会破坏硬约束。这在受限制的规划中称为。

硬限制:

  • 在同一月上,不能将两个公司分配给同一月。weight: -1000hard114 conflictNightCount.
  • 房间可以有 gender 限制:只有 females, only males, same gender in the same month or no gender limitations.weight: -50hard047ightCount.
  • 一个部门可以最少或最长期限。weight: -100hardöightCount.
  • 参与可能需要与特定设备相关的房间。weight: -50hard047ightCount.

中等限制:

  • 除非数据集超过限制,否则为 bed 分配每个优点。权重: -1medium047 DayCount.

软限制:

  • 公司可以为最大空间大小指定首选项,例如,如果公司需要单一空间。weight: -8soft theightCount.
  • 行业最能为于美国技术问题的一个部门分配。weight: -10soft theightCount.
  • 行业最能成为在技术固有问题方面特有的房间。Weight: -20soft theightCount.

    • 房间特殊性应该是优先级 1。Weight: -10soft the (priority - 1) nightCount.
  • 参与可以为具有特定设备的空间指定首选项。Weight: -20soft theightCount.

这个问题是 Kaho 的 Patient schedule 的一个变体,数据集来自真实世界的动机。

问题大小

overconstrained01 has 6 specialisms, 4 equipments, 1 departments,  25 rooms,  69 beds, 14 nights,  519 patients and  519 admissions with a search space of 10^958.
testdata01        has 4 specialisms, 2 equipments, 4 departments,  98 rooms, 286 beds, 14 nights,  652 patients and  652 admissions with a search space of 10^1603.
testdata02        has 6 specialisms, 2 equipments, 6 departments, 151 rooms, 465 beds, 14 nights,  755 patients and  755 admissions with a search space of 10^2015.
testdata03        has 5 specialisms, 2 equipments, 5 departments, 131 rooms, 395 beds, 14 nights,  708 patients and  708 admissions with a search space of 10^1840.
testdata04        has 6 specialisms, 2 equipments, 6 departments, 155 rooms, 471 beds, 14 nights,  746 patients and  746 admissions with a search space of 10^1995.
testdata05        has 4 specialisms, 2 equipments, 4 departments, 102 rooms, 325 beds, 14 nights,  587 patients and  587 admissions with a search space of 10^1476.
testdata06        has 4 specialisms, 2 equipments, 4 departments, 104 rooms, 313 beds, 14 nights,  685 patients and  685 admissions with a search space of 10^1711.
testdata07        has 6 specialisms, 4 equipments, 6 departments, 162 rooms, 472 beds, 14 nights,  519 patients and  519 admissions with a search space of 10^1389.
testdata08        has 6 specialisms, 4 equipments, 6 departments, 148 rooms, 441 beds, 21 nights,  895 patients and  895 admissions with a search space of 10^2368.
testdata09        has 4 specialisms, 4 equipments, 4 departments, 105 rooms, 310 beds, 28 nights, 1400 patients and 1400 admissions with a search space of 10^3490.
testdata10        has 4 specialisms, 4 equipments, 4 departments, 104 rooms, 308 beds, 56 nights, 1575 patients and 1575 admissions with a search space of 10^3922.
testdata11        has 4 specialisms, 4 equipments, 4 departments, 107 rooms, 318 beds, 91 nights, 2514 patients and 2514 admissions with a search space of 10^6295.
testdata12        has 4 specialisms, 4 equipments, 4 departments, 105 rooms, 310 beds, 84 nights, 2750 patients and 2750 admissions with a search space of 10^6856.
testdata13        has 5 specialisms, 4 equipments, 5 departments, 125 rooms, 368 beds, 28 nights,  907 patients and 1109 admissions with a search space of 10^2847.
Copy to Clipboard Toggle word wrap

图 4.14. 域模型

4.20. 趋势问题(TTP)

调度匹配 n 个团队数。

硬限制:

  • 每个团队都针对其他团队做两次操作两次:当家和一次性。
  • 每个团队在每个 timeslot 上都有一个匹配项。
  • 团队不能有超过三个以上的家或连续三个匹配。
  • 无重复者:与团队相比,不能连续地匹配同一两个。

软限制:

  • 最小化所有团队相换的总距离。

此问题在 Dan Trick 的网站(也包含了全局记录) 上定义。

问题大小

1-nl04     has  6 days,  4 teams and   12 matches with a search space of    10^5.
1-nl06     has 10 days,  6 teams and   30 matches with a search space of   10^19.
1-nl08     has 14 days,  8 teams and   56 matches with a search space of   10^43.
1-nl10     has 18 days, 10 teams and   90 matches with a search space of   10^79.
1-nl12     has 22 days, 12 teams and  132 matches with a search space of  10^126.
1-nl14     has 26 days, 14 teams and  182 matches with a search space of  10^186.
1-nl16     has 30 days, 16 teams and  240 matches with a search space of  10^259.
2-bra24    has 46 days, 24 teams and  552 matches with a search space of  10^692.
3-nfl16    has 30 days, 16 teams and  240 matches with a search space of  10^259.
3-nfl18    has 34 days, 18 teams and  306 matches with a search space of  10^346.
3-nfl20    has 38 days, 20 teams and  380 matches with a search space of  10^447.
3-nfl22    has 42 days, 22 teams and  462 matches with a search space of  10^562.
3-nfl24    has 46 days, 24 teams and  552 matches with a search space of  10^692.
3-nfl26    has 50 days, 26 teams and  650 matches with a search space of  10^838.
3-nfl28    has 54 days, 28 teams and  756 matches with a search space of  10^999.
3-nfl30    has 58 days, 30 teams and  870 matches with a search space of 10^1175.
3-nfl32    has 62 days, 32 teams and  992 matches with a search space of 10^1367.
4-super04  has  6 days,  4 teams and   12 matches with a search space of    10^5.
4-super06  has 10 days,  6 teams and   30 matches with a search space of   10^19.
4-super08  has 14 days,  8 teams and   56 matches with a search space of   10^43.
4-super10  has 18 days, 10 teams and   90 matches with a search space of   10^79.
4-super12  has 22 days, 12 teams and  132 matches with a search space of  10^126.
4-super14  has 26 days, 14 teams and  182 matches with a search space of  10^186.
5-galaxy04 has  6 days,  4 teams and   12 matches with a search space of    10^5.
5-galaxy06 has 10 days,  6 teams and   30 matches with a search space of   10^19.
5-galaxy08 has 14 days,  8 teams and   56 matches with a search space of   10^43.
5-galaxy10 has 18 days, 10 teams and   90 matches with a search space of   10^79.
5-galaxy12 has 22 days, 12 teams and  132 matches with a search space of  10^126.
5-galaxy14 has 26 days, 14 teams and  182 matches with a search space of  10^186.
5-galaxy16 has 30 days, 16 teams and  240 matches with a search space of  10^259.
5-galaxy18 has 34 days, 18 teams and  306 matches with a search space of  10^346.
5-galaxy20 has 38 days, 20 teams and  380 matches with a search space of  10^447.
5-galaxy22 has 42 days, 22 teams and  462 matches with a search space of  10^562.
5-galaxy24 has 46 days, 24 teams and  552 matches with a search space of  10^692.
5-galaxy26 has 50 days, 26 teams and  650 matches with a search space of  10^838.
5-galaxy28 has 54 days, 28 teams and  756 matches with a search space of  10^999.
5-galaxy30 has 58 days, 30 teams and  870 matches with a search space of 10^1175.
5-galaxy32 has 62 days, 32 teams and  992 matches with a search space of 10^1367.
5-galaxy34 has 66 days, 34 teams and 1122 matches with a search space of 10^1576.
5-galaxy36 has 70 days, 36 teams and 1260 matches with a search space of 10^1801.
5-galaxy38 has 74 days, 38 teams and 1406 matches with a search space of 10^2042.
5-galaxy40 has 78 days, 40 teams and 1560 matches with a search space of 10^2301.
Copy to Clipboard Toggle word wrap

4.21. 更便宜的时间调度

以时间和机器上调度所有任务,以最大程度降低功耗。电源价格因时间而异。这是 作业跃点调度的形式

硬限制:

  • 开始时间限制:每个任务都必须在最早的开始和最新开始限制之间启动。
  • 最大容量 :不得超过每台机器的每个资源的最大容量。
  • 启动和关闭 :每台机器必须在分配了任务的期间处于活跃状态。在任务之间,允许闲置,以避免启动和关闭成本。

中等限制:

  • 功耗:最小化整个时间表的总功耗。

    • 机器功耗:每个活跃或空闲的机器都会消耗电源,这取决于功耗(取决于该期间的电源价格)。
    • 任务功耗:每个任务都消耗电源,这推断了功耗(取决于其期间的电源价格)。
    • 机器启动和关闭成本 :机器每次启动或关闭时,都会产生额外的成本。

软限制(在原始问题定义中添加):

  • Start early: Prefer 更早地启动一个任务,而不是之后启动。

问题由 ICON 质询 定义。

问题大小

sample01   has 3 resources,   2 machines, 288 periods and   25 tasks with a search space of    10^53.
sample02   has 3 resources,   2 machines, 288 periods and   50 tasks with a search space of   10^114.
sample03   has 3 resources,   2 machines, 288 periods and  100 tasks with a search space of   10^226.
sample04   has 3 resources,   5 machines, 288 periods and  100 tasks with a search space of   10^266.
sample05   has 3 resources,   2 machines, 288 periods and  250 tasks with a search space of   10^584.
sample06   has 3 resources,   5 machines, 288 periods and  250 tasks with a search space of   10^673.
sample07   has 3 resources,   2 machines, 288 periods and 1000 tasks with a search space of  10^2388.
sample08   has 3 resources,   5 machines, 288 periods and 1000 tasks with a search space of  10^2748.
sample09   has 4 resources,  20 machines, 288 periods and 2000 tasks with a search space of  10^6668.
instance00 has 1 resources,  10 machines, 288 periods and  200 tasks with a search space of   10^595.
instance01 has 1 resources,  10 machines, 288 periods and  200 tasks with a search space of   10^599.
instance02 has 1 resources,  10 machines, 288 periods and  200 tasks with a search space of   10^599.
instance03 has 1 resources,  10 machines, 288 periods and  200 tasks with a search space of   10^591.
instance04 has 1 resources,  10 machines, 288 periods and  200 tasks with a search space of   10^590.
instance05 has 2 resources,  25 machines, 288 periods and  200 tasks with a search space of   10^667.
instance06 has 2 resources,  25 machines, 288 periods and  200 tasks with a search space of   10^660.
instance07 has 2 resources,  25 machines, 288 periods and  200 tasks with a search space of   10^662.
instance08 has 2 resources,  25 machines, 288 periods and  200 tasks with a search space of   10^651.
instance09 has 2 resources,  25 machines, 288 periods and  200 tasks with a search space of   10^659.
instance10 has 2 resources,  20 machines, 288 periods and  500 tasks with a search space of  10^1657.
instance11 has 2 resources,  20 machines, 288 periods and  500 tasks with a search space of  10^1644.
instance12 has 2 resources,  20 machines, 288 periods and  500 tasks with a search space of  10^1637.
instance13 has 2 resources,  20 machines, 288 periods and  500 tasks with a search space of  10^1659.
instance14 has 2 resources,  20 machines, 288 periods and  500 tasks with a search space of  10^1643.
instance15 has 3 resources,  40 machines, 288 periods and  500 tasks with a search space of  10^1782.
instance16 has 3 resources,  40 machines, 288 periods and  500 tasks with a search space of  10^1778.
instance17 has 3 resources,  40 machines, 288 periods and  500 tasks with a search space of  10^1764.
instance18 has 3 resources,  40 machines, 288 periods and  500 tasks with a search space of  10^1769.
instance19 has 3 resources,  40 machines, 288 periods and  500 tasks with a search space of  10^1778.
instance20 has 3 resources,  50 machines, 288 periods and 1000 tasks with a search space of  10^3689.
instance21 has 3 resources,  50 machines, 288 periods and 1000 tasks with a search space of  10^3678.
instance22 has 3 resources,  50 machines, 288 periods and 1000 tasks with a search space of  10^3706.
instance23 has 3 resources,  50 machines, 288 periods and 1000 tasks with a search space of  10^3676.
instance24 has 3 resources,  50 machines, 288 periods and 1000 tasks with a search space of  10^3681.
instance25 has 3 resources,  60 machines, 288 periods and 1000 tasks with a search space of  10^3774.
instance26 has 3 resources,  60 machines, 288 periods and 1000 tasks with a search space of  10^3737.
instance27 has 3 resources,  60 machines, 288 periods and 1000 tasks with a search space of  10^3744.
instance28 has 3 resources,  60 machines, 288 periods and 1000 tasks with a search space of  10^3731.
instance29 has 3 resources,  60 machines, 288 periods and 1000 tasks with a search space of  10^3746.
instance30 has 4 resources,  70 machines, 288 periods and 2000 tasks with a search space of  10^7718.
instance31 has 4 resources,  70 machines, 288 periods and 2000 tasks with a search space of  10^7740.
instance32 has 4 resources,  70 machines, 288 periods and 2000 tasks with a search space of  10^7686.
instance33 has 4 resources,  70 machines, 288 periods and 2000 tasks with a search space of  10^7672.
instance34 has 4 resources,  70 machines, 288 periods and 2000 tasks with a search space of  10^7695.
instance35 has 4 resources,  80 machines, 288 periods and 2000 tasks with a search space of  10^7807.
instance36 has 4 resources,  80 machines, 288 periods and 2000 tasks with a search space of  10^7814.
instance37 has 4 resources,  80 machines, 288 periods and 2000 tasks with a search space of  10^7764.
instance38 has 4 resources,  80 machines, 288 periods and 2000 tasks with a search space of  10^7736.
instance39 has 4 resources,  80 machines, 288 periods and 2000 tasks with a search space of  10^7783.
instance40 has 4 resources,  90 machines, 288 periods and 4000 tasks with a search space of 10^15976.
instance41 has 4 resources,  90 machines, 288 periods and 4000 tasks with a search space of 10^15935.
instance42 has 4 resources,  90 machines, 288 periods and 4000 tasks with a search space of 10^15887.
instance43 has 4 resources,  90 machines, 288 periods and 4000 tasks with a search space of 10^15896.
instance44 has 4 resources,  90 machines, 288 periods and 4000 tasks with a search space of 10^15885.
instance45 has 4 resources, 100 machines, 288 periods and 5000 tasks with a search space of 10^20173.
instance46 has 4 resources, 100 machines, 288 periods and 5000 tasks with a search space of 10^20132.
instance47 has 4 resources, 100 machines, 288 periods and 5000 tasks with a search space of 10^20126.
instance48 has 4 resources, 100 machines, 288 periods and 5000 tasks with a search space of 10^20110.
instance49 has 4 resources, 100 machines, 288 periods and 5000 tasks with a search space of 10^20078.
Copy to Clipboard Toggle word wrap

4.22. 资产类分配(Portfolio Optimization)

决定每个资产类参与的相对数量。

硬限制:

  • 风险最大值:标准总差异不得高于标准影响最大值。

  • 地区最大值:每个区域具有最多数量。
  • 扇区最大值:每个扇区具有最多数量。

软限制:

  • 最大化预期返回。

问题大小

de_smet_1 has 1 regions, 3 sectors and 11 asset classes with a search space of 10^4.
irrinki_1 has 2 regions, 3 sectors and 6 asset classes with a search space of 10^3.
Copy to Clipboard Toggle word wrap

大型数据集尚未被创建或测试,但不会造成问题。良好的数据源是 此资产评估网站

4.23. 指导调度

为每个电信分配一个 timeslot 和一个房间。Timeslots 可以重叠。可以使用 LibreOffice 或 Excel 编辑, 该文件可读写到和写入。

硬限制:

  • 对话类型 timeslot :对话的类型必须与 timeslot 的对话类型匹配。
  • 房间不可用时间:对话的空间必须在对话的 timeslot 期间可用。
  • 房间冲突:两个对话无法在重叠的 timeslot 期间使用相同的空间。
  • speaker 不可用时间:每个对话的 speaker 都必须在对话的 timeslot 期间可用。
  • speaker 冲突:两个对话无法在重叠的 timeslot 期间共享 speaker。
  • 通用目的 timeslot 和 room 标签:

    • speaker 所需的 timeslot 标签:如果 speaker 具有所需的 timeslot 标签,则必须将所有其或她的对话分配到与该标签的 timeslot。
    • speaker 禁止的 timeslot 标签:如果 speaker 具有禁止的 timeslot 标签,则其所有或她都不能分配给与该标签的 timeslot。
    • 对话所需的 timeslot 标签:如果通信具有所需的 timeslot 标签,则必须将其分配给具有该标签的 timeslot。
    • 禁止的 timeslot 标签:如果对话有禁止的 timeslot 标签,则无法将其分配给具有该标签的 timeslot。
    • speaker 所需的 room 标签:如果 speaker 具有必需的 room 标签,则必须将所有其或她的对话分配到具有该标签的房间。
    • speaker forbidden room tag:如果 speaker 具有禁止的 room 标签,则所有其或她的对话都不能分配给与该标签的房间。
    • 对话所需的 room 标签:如果对话具有所需的 room 标签,则必须将其分配给具有该标签的房间。
    • 对话空间标签 :如果对话具有禁止的 room 标签,则无法将其分配给具有该标签的房间。
  • 对话互斥的 timelousive-talks 标签:共享这样的标签不能调度到重叠的 timeslots 中。
  • 对话先决条件:在所有先决条件通信后,必须调度一个对话。

软限制:

  • 主题跟踪冲突:最小化在重叠 timeslots 期间共享主题标签的对话数量。
  • 扇区冲突:最小化在重叠 timeslots 期间共享相同的扇区标签的通信数量。
  • 内容使用者级别流程违反:对于每个内容标签,请在高级通信前调度简介。
  • Audience 级别区分 :对于每个时间线,最大化与不同使用者级别的对话数量。
  • 语言离散性:对于每个 timeslot,最大程度提高与不同语言对话的数量。
  • 通用目的 timeslot 和 room 标签:

    • speaker 首选 timeslot 标签:如果 speaker 具有首选的 timeslot 标签,则所有自己或她的对话都应分配给与该标签的 timeslot。
    • speaker undesired timeslot tag:如果 speaker 有一个不需要的 timeslot 标签,则不应将自己或她的对话分配给与该标签的 timeslot。
    • 与首选 timeslot 标签通信:如果对话有一个首选的 timeslot 标签,则应将其分配给具有该标签的 timeslot。
    • 对话不需要的 timeslot 标签:如果通信具有不需要的 timeslot 标签,则不应将其分配给具有该标签的 timeslot。
    • speaker 首选空间标签:如果 speaker 具有首选空间标签,则所有自己或她的对话都应分配给具有该标签的房间。
    • speaker undesired room tag:如果 speaker 有一个不需要的 room 标签,则不应将自己或她的对话分配给具有该标签的房间。
    • 通信首选空间标签:如果对话有一个首选的空间标签,则应将其分配给具有该标签的房间。
    • 对话不需要的房间标签:如果对话具有不需要的空间标签,则不应将其分配给具有该标签的房间。
  • 相同的日期:所有与共享主题标签或内容标签的对话都应在最少天数内调度(以同一天表示)。

图 4.15. 价值

问题大小

18talks-6timeslots-5rooms    has  18 talks,  6 timeslots and  5 rooms with a search space of  10^26.
36talks-12timeslots-5rooms   has  36 talks, 12 timeslots and  5 rooms with a search space of  10^64.
72talks-12timeslots-10rooms  has  72 talks, 12 timeslots and 10 rooms with a search space of 10^149.
108talks-18timeslots-10rooms has 108 talks, 18 timeslots and 10 rooms with a search space of 10^243.
216talks-18timeslots-20rooms has 216 talks, 18 timeslots and 20 rooms with a search space of 10^552.
Copy to Clipboard Toggle word wrap

4.24. rock 导览

从显示驱动电能总线显示,但计划仅显示可用天数。

硬限制:

  • 调度每个所需显示。
  • 调度尽可能显示。

中等限制:

  • 最大化专家机会。
  • 最小化推动时间。
  • 早于更新版本。

软限制:

  • 避免长时间花费时间。

问题大小

47shows has 47 shows with a search space of 10^59.
Copy to Clipboard Toggle word wrap

4.25. 飞行人员的调度

将 flights 分配给 pilots 和 flight 参与。

硬限制:

  • 所需技术:每个 flight 分配都有一个必需的技术。例如,flight AB0001 需要 2 个 pilots 和 3 个动态活动。
  • flight 冲突:每个员工可以同时参与一个 flight
  • 在两个 flights 之间传输:在两个 flights 之间,员工必须能够从 arrival airport 传输到 departure airport。例如,An 在 Brussels 到达 Brussels 于 10:00,在 Amsterdam 到达 15:00。
  • 员工不可用:员工必须在 flight 之日可用。例如,An 位于 1-Feb 上的 PTO 上。

软限制:

  • 首先从家分配
  • 最后分配位于家
  • 每个员工的负载均衡 flight 持续时间

问题大小

175flights-7days-Europe  has 2 skills, 50 airports, 150 employees, 175 flights and  875 flight assignments with a search space of  10^1904.
700flights-28days-Europe has 2 skills, 50 airports, 150 employees, 700 flights and 3500 flight assignments with a search space of  10^7616.
875flights-7days-Europe  has 2 skills, 50 airports, 750 employees, 875 flights and 4375 flight assignments with a search space of 10^12578.
175flights-7days-US      has 2 skills, 48 airports, 150 employees, 175 flights and  875 flight assignments with a search space of  10^1904.
Copy to Clipboard Toggle word wrap

您可以将 Red Hat Build of OptaPlanner 示例下载为红帽客户门户网站上的 Red Hat Build of OptaPlanner 源软件包的一部分。

注意

红帽构建的 OptaPlanner 没有 GUI 依赖项。它还在服务器或移动 JVM 上运行,就像在桌面上一样。

流程

  1. 导航到红帽客户门户网站中的 Software Downloads 页面(需要登录),然后从下拉菜单中选择产品和版本:

    • 产品: 红帽构建的 OptaPlanner
    • Version: 8.33
  2. 下载 红帽构建的 OptaPlanner 8.33 源分发
  3. 提取 rhbop-8.33.0-optaplanner-sources.zip 文件。

    提取的 org.optaplanner.optaplanner-8.33.0.Final-redhat-00004/optaplanner-examples/src/main/java/org/optaplanner/examples 目录包含示例源代码。

  4. 要构建示例,在 org.optaplanner.optaplanner-8.33.0.Final-redhat-00004 目录中输入以下命令:

    mvn clean install -Dquickly
    Copy to Clipboard Toggle word wrap
  5. 进入 examples 目录:

    optaplanner-examples
    Copy to Clipboard Toggle word wrap
  6. 要运行示例,请输入以下命令:

    mvn exec java
    Copy to Clipboard Toggle word wrap

红帽构建的 OptaPlanner 与 Red Hat build of Quarkus 平台集成。平台工件依赖项版本(包括 OptaPlanner 依赖项)在 Quarkus 产品(BOM)文件 com.redhat.quarkus.platform:quarkus-bom 中维护。您不需要指定哪些依赖项版本协同工作。相反,您可以将 Quarkus BOM 文件导入到 pom.xml 配置文件,其中依赖项版本包含在 < dependencyManagement> 部分中。因此,您不需要列出由 pom.xml 文件中的指定 BOM 管理的独立 Quarkus 依赖项版本。

其他资源

6.1. Apache Maven 和 Red Hat build of Quarkus

Apache Maven 是 Java 应用程序开发中使用的分布式构建自动化工具,用于创建、管理和构建软件项目。Maven 使用名为 Project Object Model(POM)文件的标准配置文件来定义项目并管理构建流程。POM 文件描述了使用 XML 文件生成项目打包和输出的模块和组件依赖项、构建顺序和目标。这可确保以正确、一致的方式构建项目。

Maven 存储库

Maven 存储库存储 Java 库、插件和其他构建构件。默认公共存储库是 Maven 2 Central Repository,但存储库可以是私有的和内部存储库,以在开发团队之间共享通用工件。也可从第三方提供存储库。

您可以将在线 Maven 存储库与 Quarkus 项目搭配使用,也可以下载 Red Hat build of Quarkus Maven 存储库。

Maven 插件

Maven 插件定义 POM 文件的一部分,该文件可以达到一个或多个目标。Quarkus 应用程序使用以下 Maven 插件:

  • Quarkus Maven 插件(quarkus-maven-plugin):启用 Maven 创建 Quarkus 项目,支持生成 uber-JAR 文件,并提供开发模式。
  • Maven Surefire 插件(maven-surefire-plugin):在构建生命周期的测试阶段使用,以便在应用程序上执行单元测试。插件生成包含测试报告的文本和 XML 文件。

6.1.1. 为在线存储库配置 Maven settings.xml 文件

您可以通过配置用户 settings.xml 文件,将在线 Maven 存储库与 Maven 项目一起使用。这是推荐的方法。与共享服务器上的存储库管理器或存储库一起使用的 Maven 设置提供更好的控制和领导项目。

注意

当您通过修改 Maven settings.xml 文件来配置存储库时,更改会应用到所有 Maven 项目。

流程

  1. 在文本编辑器中打开 Maven ~/.m2/settings.xml 文件或集成开发环境(IDE)。

    注意

    如果 ~/.m2/ 目录中没有 settings.xml 文件,请将 $MAVEN_HOME/.m2/conf/ 目录中的 settings.xml 文件复制到 ~/.m2/ 目录中。

  2. settings.xml 文件的 <profiles > 元素中添加以下行:

    <!-- Configure the Maven repository -->
    <profile>
      <id>red-hat-enterprise-maven-repository</id>
      <repositories>
        <repository>
          <id>red-hat-enterprise-maven-repository</id>
          <url>https://maven.repository.redhat.com/ga/</url>
          <releases>
            <enabled>true</enabled>
          </releases>
          <snapshots>
            <enabled>false</enabled>
          </snapshots>
        </repository>
      </repositories>
      <pluginRepositories>
        <pluginRepository>
          <id>red-hat-enterprise-maven-repository</id>
          <url>https://maven.repository.redhat.com/ga/</url>
          <releases>
            <enabled>true</enabled>
          </releases>
          <snapshots>
            <enabled>false</enabled>
          </snapshots>
        </pluginRepository>
      </pluginRepositories>
    </profile>
    Copy to Clipboard Toggle word wrap
  3. 将以下行添加到 settings.xml 文件的 < activeProfiles > 元素中,并保存文件。

    <activeProfile>red-hat-enterprise-maven-repository</activeProfile>
    Copy to Clipboard Toggle word wrap

6.1.2. 下载并配置 Quarkus Maven 存储库

如果您不想使用在线 Maven 存储库,您可以下载并配置 Quarkus Maven 存储库,以使用 Maven 创建 Quarkus 应用程序。Quarkus Maven 存储库包含 Java 开发人员通常用于构建应用程序的许多要求。此流程描述了如何编辑 settings.xml 文件来配置 Quarkus Maven 存储库。

注意

当您通过修改 Maven settings.xml 文件来配置存储库时,更改会应用到所有 Maven 项目。

流程

  1. 从红帽客户门户的软件下载页面(需要登录) 下载 Red Hat build of Quarkus Maven 存储库 ZIP 文件。
  2. 展开下载的存档。
  3. 将目录更改为 ~/.m2/ 目录,并在文本编辑器或集成开发环境(IDE)中打开 Maven settings.xml 文件。
  4. 将以下行添加到 settings.xml 文件的 & lt;profiles > 元素中,其中 QUARKUS_MAVEN_REPOSITORY 是您下载的 Quarkus Maven 存储库的路径。QUARKUS_MAVEN_REPOSITORY 的格式必须是 file://$PATH,如 file:///home/userX/rh-quarkus-2.13.7.GA-maven-repository/maven-repository

    <!-- Configure the Quarkus Maven repository -->
    <profile>
      <id>red-hat-quarkus-maven-repository</id>
      <repositories>
        <repository>
          <id>red-hat-quarkus-maven-repository</id>
          <url>QUARKUS_MAVEN_REPOSITORY</url>
          <releases>
            <enabled>true</enabled>
          </releases>
          <snapshots>
            <enabled>false</enabled>
          </snapshots>
        </repository>
      </repositories>
      <pluginRepositories>
        <pluginRepository>
          <id>red-hat-quarkus-maven-repository</id>
          <url>QUARKUS_MAVEN_REPOSITORY</url>
          <releases>
            <enabled>true</enabled>
          </releases>
          <snapshots>
            <enabled>false</enabled>
          </snapshots>
        </pluginRepository>
      </pluginRepositories>
    </profile>
    Copy to Clipboard Toggle word wrap
  5. 将以下行添加到 settings.xml 文件的 < activeProfiles > 元素中,并保存文件。

    <activeProfile>red-hat-quarkus-maven-repository</activeProfile>
    Copy to Clipboard Toggle word wrap
重要

如果您的 Maven 存储库包含过时的工件,您可能会在构建或部署项目时遇到以下 Maven 错误消息之一,其中 ARTIFACT_NAME 是缺少的工件的名称,PROJECT_NAME 是您要构建的项目的名称:

  • 缺少工件 PROJECT_NAME
  • [ERROR] 无法对项目 ARTIFACT_NAME; Could 没有解析 PROJECT_NAME的依赖关系

要解决这个问题,请删除 ~/.m2/repository 目录中的本地存储库的缓存版本,以强制下载最新的 Maven 工件。

您可以使用 Apache Maven 和 Quarkus Maven 插件使用红帽构建的 OptaPlanner 和 Quarkus 应用程序启动并运行。

先决条件

  • 已安装 OpenJDK 11 或更高版本。Red Hat build of Open JDK 位于红帽客户门户网站中的 Software Downloads 页面中(需要登录)。
  • 已安装 Apache Maven 3.8 或更高版本。Maven 可从 Apache Maven 项目网站 获得。

流程

  1. 在命令终端中,输入以下命令验证 Maven 是否使用 JDK 11,并且 Maven 版本是否为 3.8 或更高版本:

    mvn --version
    Copy to Clipboard Toggle word wrap
  2. 如果上述命令没有返回 JDK 11,请将 JDK 11 的路径添加到 PATH 环境变量中,然后再次输入上述命令。
  3. 要生成 Quarkus OptaPlanner quickstart 项目,请输入以下命令,其中 redhat-0000x 是 Quarkus BOM 文件的当前版本:

    mvn com.redhat.quarkus.platform:quarkus-maven-plugin:2.13.7.SP1-redhat-0000x:create \
        -DprojectGroupId=com.example \
        -DprojectArtifactId=optaplanner-quickstart  \
        -DplatformGroupId=com.redhat.quarkus.platform
        -DplatformArtifactId=quarkus-bom
        -DplatformVersion=2.13.7.SP1-redhat-0000x \
        -DnoExamples
        -Dextensions="resteasy,resteasy-jackson,optaplanner-quarkus,optaplanner-quarkus-jackson" \
    Copy to Clipboard Toggle word wrap

    这个命令在 ./optaplanner-quickstart 目录中创建以下元素:

    • Maven 结构
    • src/main/docker中的 Dockerfile 文件示例
    • 应用程序配置文件

      Expand
      表 6.1. mvn io.quarkus:quarkus-maven-plugin:2.13.7.SP1-redhat-0000x:create 命令中使用的属性
      属性描述

      projectGroupId

      项目的组 ID。

      projectArtifactId

      项目的工件 ID。

      extensions

      用于此项目的 Quarkus 扩展列表。如需 Quarkus 扩展的完整列表,请在命令行中输入 mvn quarkus:list-extensions

      noExamples

      创建带有项目结构的项目,但没有测试或类。

      projectGroupIDprojectArtifactID 属性的值用于生成项目版本。默认项目版本为 1.0.0-SNAPSHOT

  4. 要查看您的 OptaPlanner 项目,请将目录改为 OptaPlanner Quickstarts 目录:

    cd optaplanner-quickstart
    Copy to Clipboard Toggle word wrap
  5. 检查 pom.xml 文件。内容应类似以下示例:

    <?xml version="1.0"?>
    <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <modelVersion>4.0.0</modelVersion>
      <groupId>com.example</groupId>
      <artifactId>optaplanner-quickstart</artifactId>
      <version>1.0.0-SNAPSHOT</version>
      <properties>
        <compiler-plugin.version>3.8.1</compiler-plugin.version>
        <maven.compiler.release>11</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
        <quarkus.platform.group-id>com.redhat.quarkus.platform</quarkus.platform.group-id>
        <quarkus.platform.version>2.13.7.SP1-redhat-0000x</quarkus.platform.version>
        <skipITs>true</skipITs>
        <surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
      </properties>
      <dependencyManagement>
        <dependencies>
          <dependency>
            <groupId>${quarkus.platform.group-id}</groupId>
            <artifactId>${quarkus.platform.artifact-id}</artifactId>
            <version>${quarkus.platform.version}</version>
            <type>pom</type>
            <scope>import</scope>
          </dependency>
          <dependency>
            <groupId>${quarkus.platform.group-id}</groupId>
            <artifactId>quarkus-optaplanner-bom</artifactId>
            <version>${quarkus.platform.version}</version>
            <type>pom</type>
            <scope>import</scope>
          </dependency>
        </dependencies>
      </dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>org.optaplanner</groupId>
          <artifactId>optaplanner-quarkus</artifactId>
        </dependency>
        <dependency>
          <groupId>org.optaplanner</groupId>
          <artifactId>optaplanner-quarkus-jackson</artifactId>
        </dependency>
        <dependency>
          <groupId>io.quarkus</groupId>
          <artifactId>quarkus-resteasy-jackson</artifactId>
        </dependency>
        <dependency>
          <groupId>io.quarkus</groupId>
          <artifactId>quarkus-resteasy</artifactId>
        </dependency>
        <dependency>
          <groupId>io.quarkus</groupId>
          <artifactId>quarkus-arc</artifactId>
        </dependency>
        <dependency>
          <groupId>io.quarkus</groupId>
          <artifactId>quarkus-junit5</artifactId>
          <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>io.rest-assured</groupId>
          <artifactId>rest-assured</artifactId>
          <scope>test</scope>
        </dependency>
      </dependencies>
      <repositories>
        <repository>
          <releases>
            <enabled>true</enabled>
          </releases>
          <snapshots>
            <enabled>false</enabled>
          </snapshots>
          <id>redhat</id>
          <url>https://maven.repository.redhat.com/ga</url>
        </repository>
      </repositories>
      <pluginRepositories>
        <pluginRepository>
          <releases>
            <enabled>true</enabled>
          </releases>
          <snapshots>
            <enabled>false</enabled>
          </snapshots>
          <id>redhat</id>
          <url>https://maven.repository.redhat.com/ga</url>
        </pluginRepository>
      </pluginRepositories>
      <build>
        <plugins>
          <plugin>
            <groupId>${quarkus.platform.group-id}</groupId>
            <artifactId>quarkus-maven-plugin</artifactId>
            <version>${quarkus.platform.version}</version>
            <extensions>true</extensions>
            <executions>
              <execution>
                <goals>
                  <goal>build</goal>
                  <goal>generate-code</goal>
                  <goal>generate-code-tests</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>${compiler-plugin.version}</version>
            <configuration>
              <compilerArgs>
                <arg>-parameters</arg>
              </compilerArgs>
            </configuration>
          </plugin>
          <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>${surefire-plugin.version}</version>
            <configuration>
              <systemPropertyVariables>
                <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                <maven.home>${maven.home}</maven.home>
              </systemPropertyVariables>
            </configuration>
          </plugin>
          <plugin>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>${surefire-plugin.version}</version>
            <executions>
              <execution>
                <goals>
                  <goal>integration-test</goal>
                  <goal>verify</goal>
                </goals>
                <configuration>
                  <systemPropertyVariables>
                    <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
                    <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                    <maven.home>${maven.home}</maven.home>
                  </systemPropertyVariables>
                </configuration>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
      <profiles>
        <profile>
          <id>native</id>
          <activation>
            <property>
              <name>native</name>
            </property>
          </activation>
          <properties>
            <skipITs>false</skipITs>
            <quarkus.package.type>native</quarkus.package.type>
          </properties>
        </profile>
      </profiles>
    </project>
    Copy to Clipboard Toggle word wrap

您可以使用 code.quarkus.redhat.com 网站生成 Red Hat Build of OptaPlanner Quarkus Maven 项目,并自动添加和配置要在应用程序中使用的扩展。

本节介绍了生成 OptaPlanner Maven 项目并包括以下主题的过程:

  • 指定应用程序的基本详情。
  • 选择您要包含在项目中的扩展。
  • 使用项目文件生成可下载的存档。
  • 使用自定义命令编译和启动应用程序。

先决条件

  • 您有一个 Web 浏览器。

流程

  1. 在您的浏览器中打开 https://code.quarkus.redhat.com
  2. 指定项目详情:
  3. 输入项目的组名称。名称的格式遵循 Java 软件包命名约定,如 com.example
  4. 输入您要用于项目生成的 Maven 工件的名称,如 code-with-quarkus
  5. 选择 Build Tool > Maven 以指定您要创建 Maven 项目。选择的构建工具决定了项目:

    • 生成的项目的目录结构
    • 生成的项目中使用的配置文件格式
    • 在生成项目后,用来编译和启动应用程序的自定义构建脚本和命令会显示 code.quarkus.redhat.com

      注意

      红帽提供了对使用 code.quarkus.redhat.com 创建 OptaPlanner Maven 项目的支持。红帽不支持生成 Gradle 项目。

  6. 输入要在项目生成的工件中使用的版本。此字段的默认值为 1.0.0-SNAPSHOT。建议使用 语义版本 控制,但如果您偏好使用不同类型的版本。
  7. 输入构建工具在打包项目时生成的工件软件包名称。

    根据 Java 软件包命名约定,软件包名称应与用于项目的组名称匹配,但您可以指定不同的名称。

  8. 选择要作为依赖项包含的以下扩展:

    • RESTEasy JAX-RS (quarkus-resteasy)
    • resteasy Jackson (quarkus-resteasy-jackson)
    • optaPlanner AI constraint resolver (optaplanner-quarkus)
    • optaPlanner Jackson (optaplanner-quarkus-jackson)

      红帽为列表上的单个扩展提供不同级别的支持,由每个扩展名称旁的标签表示:

      • 红帽完全支持 SUPPORTED 扩展,用于生产环境中的企业级应用程序。
      • 技术预览功能支持范围 下,红帽对生产环境中的 支持 受到有限的支持。
      • 红帽不支持将 DEV-SUPPORT 扩展用于生产环境,但红帽提供的核心功能由红帽开发人员支持用于开发新应用程序。
      • DEPRECATED 扩展计划被替换为提供相同功能的较新的技术或实施。

        红帽不支持未标记的扩展用于生产环境。

  9. 选择 Generate your application 来确认您的选择并显示覆盖页面,其中包含包含您生成的项目的存档的下载链接。覆盖屏幕还显示可用于编译和启动应用程序的自定义命令。
  10. 选择 Download the ZIP 将带有生成的项目文件的归档保存到您的系统。
  11. 提取存档的内容。
  12. 进入包含您提取的项目文件的目录:

    cd <directory_name>
    Copy to Clipboard Toggle word wrap
  13. 以开发模式编译并启动应用程序:

    ./mvnw compile quarkus:dev
    Copy to Clipboard Toggle word wrap

您可以使用 Quarkus 命令行界面(CLI)创建 Quarkus OptaPlanner 项目。

先决条件

流程

  1. 创建 Quarkus 应用程序:

    quarkus create app -P io.quarkus:quarkus-bom:2.13.7.SP1-redhat-0000x
    Copy to Clipboard Toggle word wrap
  2. 要查看可用的扩展,请输入以下命令:

    quarkus ext -i
    Copy to Clipboard Toggle word wrap

    这个命令返回以下扩展:

    optaplanner-quarkus
    optaplanner-quarkus-benchmark
    optaplanner-quarkus-jackson
    optaplanner-quarkus-jsonb
    Copy to Clipboard Toggle word wrap
  3. 输入以下命令在项目的 pom.xml 文件中添加扩展:

    quarkus ext add resteasy-jackson
    quarkus ext add optaplanner-quarkus
    quarkus ext add optaplanner-quarkus-jackson
    Copy to Clipboard Toggle word wrap
  4. 在文本编辑器中打开 pom.xml 文件。文件的内容应类似以下示例:

    <?xml version="1.0"?>
    <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <modelVersion>4.0.0</modelVersion>
      <groupId>org.acme</groupId>
      <artifactId>code-with-quarkus-optaplanner</artifactId>
      <version>1.0.0-SNAPSHOT</version>
      <properties>
    	<compiler-plugin.version>3.8.1</compiler-plugin.version>
    	<maven.compiler.parameters>true</maven.compiler.parameters>
    	<maven.compiler.source>11</maven.compiler.source>
    	<maven.compiler.target>11</maven.compiler.target>
    	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    	<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    	<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
    	<quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
    	<quarkus.platform.version>2.13.7.SP1-redhat-0000x</quarkus.platform.version>
    	<surefire-plugin.version>3.0.0-M5</surefire-plugin.version>
      </properties>
      <dependencyManagement>
    	<dependencies>
      	<dependency>
        	<groupId>${quarkus.platform.group-id}</groupId>
        	<artifactId>${quarkus.platform.artifact-id}</artifactId>
        	<version>${quarkus.platform.version}</version>
        	<type>pom</type>
        	<scope>import</scope>
      	</dependency>
      	<dependency>
        	<groupId>io.quarkus.platform</groupId>
        	<artifactId>optaplanner-quarkus</artifactId>
        	<version>2.2.2.Final</version>
        	<type>pom</type>
        	<scope>import</scope>
      	</dependency>
    	</dependencies>
      </dependencyManagement>
      <dependencies>
    	<dependency>
      	<groupId>io.quarkus</groupId>
      	<artifactId>quarkus-arc</artifactId>
    	</dependency>
    	<dependency>
      	<groupId>io.quarkus</groupId>
      	<artifactId>quarkus-resteasy</artifactId>
    	</dependency>
    	<dependency>
      	<groupId>org.optaplanner</groupId>
      	<artifactId>optaplanner-quarkus</artifactId>
    	</dependency>
    	<dependency>
      	<groupId>org.optaplanner</groupId>
      	<artifactId>optaplanner-quarkus-jackson</artifactId>
    	</dependency>
    	<dependency>
      	<groupId>io.quarkus</groupId>
      	<artifactId>quarkus-resteasy-jackson</artifactId>
    	</dependency>
    	<dependency>
      	<groupId>io.quarkus</groupId>
      	<artifactId>quarkus-junit5</artifactId>
      	<scope>test</scope>
    	</dependency>
    	<dependency>
      	<groupId>io.rest-assured</groupId>
      	<artifactId>rest-assured</artifactId>
      	<scope>test</scope>
    	</dependency>
      </dependencies>
      <build>
    	<plugins>
      	<plugin>
        	<groupId>${quarkus.platform.group-id}</groupId>
        	<artifactId>quarkus-maven-plugin</artifactId>
        	<version>${quarkus.platform.version}</version>
        	<extensions>true</extensions>
        	<executions>
          	<execution>
            	<goals>
              	<goal>build</goal>
              	<goal>generate-code</goal>
              	<goal>generate-code-tests</goal>
            	</goals>
          	</execution>
        	</executions>
      	</plugin>
      	<plugin>
        	<artifactId>maven-compiler-plugin</artifactId>
        	<version>${compiler-plugin.version}</version>
        	<configuration>
          	<parameters>${maven.compiler.parameters}</parameters>
        	</configuration>
      	</plugin>
      	<plugin>
        	<artifactId>maven-surefire-plugin</artifactId>
        	<version>${surefire-plugin.version}</version>
        	<configuration>
          	<systemPropertyVariables>
            	<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
            	<maven.home>${maven.home}</maven.home>
          	</systemPropertyVariables>
        	</configuration>
      	</plugin>
    	</plugins>
      </build>
      <profiles>
    	<profile>
      	<id>native</id>
      	<activation>
        	<property>
          	<name>native</name>
        	</property>
      	</activation>
      	<build>
        	<plugins>
          	<plugin>
            	<artifactId>maven-failsafe-plugin</artifactId>
            	<version>${surefire-plugin.version}</version>
            	<executions>
              	<execution>
                	<goals>
                  	<goal>integration-test</goal>
                  	<goal>verify</goal>
                	</goals>
                	<configuration>
                  	<systemPropertyVariables>
                    	<native.image.path>${project.build.directory}/${project.build.finalName}-run</native.image.path>
                    	<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                    	<maven.home>${maven.home}</maven.home>
                  	</systemPropertyVariables>
                	</configuration>
              	</execution>
            	</executions>
          	</plugin>
        	</plugins>
      	</build>
      	<properties>
        	<quarkus.package.type>native</quarkus.package.type>
      	</properties>
    	</profile>
      </profiles>
    </project>
    Copy to Clipboard Toggle word wrap

部分 III. Red Hat build of OptaPlanner solver

OptaPlanner 的规划问题包括以下步骤:

  1. 将您的规划问题建模 为使用 @PlanningSolution 注解(例如,NQueens 类)标注的类。
  2. 配置 Solver (例如,任何 NQueens 实例的第一个 Fit 和 Tabu Search solver)。
  3. 从您的数据层 加载问题数据集 (例如 Four Queens 实例)。这是计划问题。
  4. 使用 Solver.solve (problem) 解决它,该 Solver.solve (problem)返回最佳解决方案。

第 7 章 配置红帽构建的 OptaPlanner solver

您可以使用以下方法配置 OptaPlanner solver:

  • 使用 XML 文件。
  • 使用 SolverConfig API。
  • 在域模型中添加类注解和 JavaBean 属性注解。
  • 控制 OptaPlanner 用来访问域的方法。
  • 定义自定义属性。

7.1. 使用 XML 文件配置 OptaPlanner solver

每个项目都有一个可以编辑的 solver 配置文件。& lt;EXAMPLE>SolverConfig.xml 文件位于 org.optaplanner.optaplanner-8.33.0.Final-redhat-00004/optaplanner-examples/src/main/resources/org/optaplanner/examples/<EXAMPLE> 目录中,其中 <EXAMPLE > 是 OptaPlanner 示例项目的名称。或者,您可以从带有 Solver Factory.createFromXmlFile () 的文件创建 SolverFactory。但是,出于可移植性的原因,建议使用 classpath 资源。

SolverSolverFactory 都有一个名为 Solution_ 的通用类型,这是代表规划问题和解决方案的类。

OptaPlanner 通过更改配置使其相对容易地切换优化算法。

流程

  1. 使用 Solver Factory 构建 Solver 实例。
  2. 配置 solver 配置 XML 文件:

    1. 定义模型。
    2. 定义 score 功能。
    3. 可选:配置优化算法。

      以下示例是 NQueens 问题的解析器 XML 文件:

      <?xml version="1.0" encoding="UTF-8"?>
      <solver xmlns="https://www.optaplanner.org/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="https://www.optaplanner.org/xsd/solver https://www.optaplanner.org/xsd/solver/solver.xsd">
        <!-- Define the model -->
        <solutionClass>org.optaplanner.examples.nqueens.domain.NQueens</solutionClass>
        <entityClass>org.optaplanner.examples.nqueens.domain.Queen</entityClass>
      
        <!-- Define the score function -->
        <scoreDirectorFactory>
          <scoreDrl>org/optaplanner/examples/nqueens/optional/nQueensConstraints.drl</scoreDrl>
        </scoreDirectorFactory>
      
        <!-- Configure the optimization algorithms (optional) -->
        <termination>
          ...
        </termination>
        <constructionHeuristic>
          ...
        </constructionHeuristic>
        <localSearch>
          ...
        </localSearch>
      </solver>
      Copy to Clipboard Toggle word wrap
      注意

      在某些环境中,如 192.168.1.0/24 和 JBoss 模块,在 JAR 文件中的 resolver 配置、分数 DRL 和域类等类路径资源可能不适用于 optaplanner-core JAR 文件的默认 ClassLoader。在这些情况下,为您的类的 ClassLoader 作为参数提供:

             SolverFactory<NQueens> solverFactory = SolverFactory.createFromXmlResource(
                     ".../nqueensSolverConfig.xml", getClass().getClassLoader());
      Copy to Clipboard Toggle word wrap
  3. 使用解析器配置 XML 文件配置 SolverFactory,作为 ClassLoader.getResource () 定义的类路径资源提供:

           SolverFasctory<NQueens> solverFactory = SolverFactory.createFromXmlResource(
                   "org/optaplanner/examples/nqueens/optional/nqueensSolverConfig.xml");
           Solver<NQueens> solver = solverFactory.buildSolver();
    Copy to Clipboard Toggle word wrap

7.2. 使用 Java API 配置 OptaPlanner solver

您可以使用 SolverConfig API 配置 solver。这对在运行时动态更改值特别有用。以下示例在在 NQueens 项目中构建 Solver 前更改基于系统属性的运行时间:

        SolverConfig solverConfig = SolverConfig.createFromXmlResource(
                "org/optaplanner/examples/nqueens/optional/nqueensSolverConfig.xml");
        solverConfig.withTerminationConfig(new TerminationConfig()
                        .withMinutesSpentLimit(userInput));

        SolverFactory<NQueens> solverFactory = SolverFactory.create(solverConfig);
        Solver<NQueens> solver = solverFactory.buildSolver();
Copy to Clipboard Toggle word wrap

solver 配置 XML 文件中的每个元素都作为 Config 类或软件包命名空间 org.optaplanner.core.config 中的 Config 类提供。这些配置 类是 XML 格式的 Java 表示。它们构建软件包命名空间 org.optaplanner.core.impl 的运行时组件,并将其编译成一个高效的 Solver

注意

要为每个用户请求动态配置 SolverFactory,请在初始化过程中构建模板 SolverConfig,并使用每个用户请求的复制构造器复制它。以下示例演示了如何在 NQueens 问题中执行此操作:

    private SolverConfig template;

    public void init() {
        template = SolverConfig.createFromXmlResource(
                "org/optaplanner/examples/nqueens/optional/nqueensSolverConfig.xml");
        template.setTerminationConfig(new TerminationConfig());
    }

    // Called concurrently from different threads
    public void userRequest(..., long userInput) {
        SolverConfig solverConfig = new SolverConfig(template); // Copy it
        solverConfig.getTerminationConfig().setMinutesSpentLimit(userInput);
        SolverFactory<NQueens> solverFactory = SolverFactory.create(solverConfig);
        Solver<NQueens> solver = solverFactory.buildSolver();
        ...
    }
Copy to Clipboard Toggle word wrap

7.3. OptaPlanner 注解

您必须指定域模型中的哪些类正在规划实体,哪些属性是规划变量等。使用以下方法之一为您的 OptaPlanner 项目添加注解:

  • 在域模型中添加类注解和 JavaBean 属性注解。属性注解必须位于 getter 方法上,而不是在 setter 方法上。注解的 getter 方法不需要公共。这是推荐的方法。
  • 在域模型中添加类注解和字段注解。注解的字段不需要是公共的。

7.4. 指定 OptaPlanner 域访问

默认情况下,OptaPlanner 使用反映访问您的域。与直接访问相比,反映是可靠的,但会慢。或者,您可以配置 OptaPlanner 以使用 Gizmo 访问域,这将生成字节码来直接访问域的字段和方法,而无需反映。但是,此方法有以下限制:

  • 规划注解只能在公共字段和公共 getters 上。
  • io.quarkus.gizmo:gizmo 必须位于 classpath 上。
注意

当您将 OptaPlanner 与 Quarkus 搭配使用时,这些限制不适用,因为 Gizmo 是默认域访问类型。

流程

要在 Quarkus 之外使用 Gizmo,请在 solver 配置中设置 domainAccessType

  <solver>
    <domainAccessType>GIZMO</domainAccessType>
  </solver>
Copy to Clipboard Toggle word wrap

7.5. 配置自定义属性

在 OptaPlanner 项目中,您可以添加自定义属性来解析实例化类和明确提到自定义属性的文档。

先决条件

  • 您有一个临时解决方案。

流程

  1. 添加自定义属性。

    例如,如果您的 EasyScoreCalculator 具有缓存的重度计算,并且您希望在一个基准中增加缓存大小,请添加 myCacheSize 属性:

      <scoreDirectorFactory>
        <easyScoreCalculatorClass>...MyEasyScoreCalculator</easyScoreCalculatorClass>
        <easyScoreCalculatorCustomProperties>
          <property name="myCacheSize" value="1000"/><!-- Override value -->
        </easyScoreCalculatorCustomProperties>
      </scoreDirectorFactory>
    Copy to Clipboard Toggle word wrap
  2. 为每个自定义属性添加公共集,该属性在构建 Solver 时调用。

    public class MyEasyScoreCalculator extends EasyScoreCalculator<MySolution, SimpleScore> {
    
            private int myCacheSize = 500; // Default value
    
            @SuppressWarnings("unused")
            public void setMyCacheSize(int myCacheSize) {
                this.myCacheSize = myCacheSize;
            }
    
        ...
    }
    Copy to Clipboard Toggle word wrap

    大多数值类型都被支持,包括 布尔值、Inint双、Mu titDecimalstringenums

第 8 章 OptaPlanner Solver

解决方法可以找到您规划问题的最佳解决方案。解决者一次只能解决一个规划问题实例。solvers 使用 Solver Factory 方法 构建:

public interface Solver<Solution_> {

    Solution_ solve(Solution_ problem);

    ...
}
Copy to Clipboard Toggle word wrap

A solver 应该只从单一线程访问,除了在 javadoc 中作为 thread-safe 记录的方法除外。solve () 方法记录当前线程。处理线程可能会导致 REST 服务的 HTTP 超时,它需要额外的代码来并行处理多个数据集。要避免这个问题,请使用 SolverManager

8.1. 解决问题

使用 solver 解决规划问题。

先决条件

  • 从 solver 配置构建的 Solver
  • 代表规划问题实例的 @PlanningSolution 注释

流程

提供规划问题作为 resolve ()方法 的参数。该解决方案将返回最佳解决方案。

以下示例解决了 NQueens 问题:

    NQueens problem = ...;
    NQueens bestSolution = solver.solve(problem);
Copy to Clipboard Toggle word wrap

在本例中,solv () 方法将返回 NQueens 实例,每个 Queen 分配给 Row

注意

提供给解析 (Solution) 方法的解决方案实例可以部分或完全初始化,通常是重复规划的情况。

图 8.1. 在 8ms 中为 Four Queens Puzzle (Also Optimal Solution)的最佳解决方案

根据问题大小和 solver 配置,解析 (Solution) 方法可能需要很长时间。Solver 智能处理可能解决方案的搜索空间,并记住其在技术期间遇到的最佳解决方案。根据多个因素,包括问题大小、Solver 提供的时间、解析器配置等,最佳解决方案 可能 是最佳解决方案

注意

提供给方法解析 (Solution) 的解决方案实例由 Solver 更改,但不会为最佳解决方案错误。

方法解析 (Solution)getBestSolution () 返回的解决方案实例很可能是提供给方法解析 (Solution) 的计划克隆,这意味着它是一种不同的实例。

8.2. solver 环境模式

通过 resolver 环境模式,您可以检测实施中的常见错误。它不会影响日志级别。

solver 有一个随机实例。有些解析器配置使用随机实例,很多超过其他实例。例如,Simulated Annealing 算法高度依赖于随机数字,而 Tabu Search 依赖于它来解决分数。环境模式会影响该随机实例的 seed。

您可以在 solver 配置 XML 文件中设置环境模式。以下示例设置 FAST_ASSERT 模式:

<solver xmlns="https://www.optaplanner.org/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://www.optaplanner.org/xsd/solver https://www.optaplanner.org/xsd/solver/solver.xsd">
  <environmentMode>FAST_ASSERT</environmentMode>
  ...
</solver>
Copy to Clipboard Toggle word wrap

以下列表描述了您可以在 resolver 配置文件中使用的环境模式:

  • FULL_ASSERT 模式打开所有断言,例如,增量分数计算在移动实施、约束、引擎本身等中不可修正的断言,对移动实施、约束、引擎本身等错误而造成故障。这个模式是可重复生成的。它还是入侵的,因为它调用方法 computeScore () 比非 assert 模式更频繁。FULL_ASSERT 模式非常慢,因为它不依赖于增量分数计算。
  • NON_INTRUSIVE_FULL_ASSERT 模式开启几个断言,针对移动实施中的一个错误、约束、引擎本身等。这个模式是可重复生成的。它不是预期的,因为它不会调用方法 compute Score (),比非 assert 模式更频繁。NON_INTRUSIVE_FULL_ASSERT 模式非常慢,因为它不依赖于增量分数计算。
  • FAST_ASSERT 模式打开大多数断言,例如 undoMove 的分数与 Move 的分数相同,对移动实施、约束、引擎本身等中的一个错误进行故障。这个模式是可重复生成的。它还是入侵的,因为它调用方法 computeScore () 比非 assert 模式更频繁。FAST_ASSERT 模式较慢。编写一个测试问题单,使用 FAST_ASSERT 模式运行您的规划问题。
  • REHQUCIBLE 模式是默认模式,因为在开发过程中建议使用它。在这个模式中,两个以同一 OptaPlanner 版本按相同的顺序执行相同的代码。这两个运行在每个步骤中都有相同的结果,除了以下备注除外。这可让您一致地重现错误。它还允许您对某些重构进行基准测试,如分数约束性能优化。

    注意

    尽管使用 REHQCIBLE 模式,但因为以下原因,您的应用程序可能仍然无法完全可重复生成:

    • 使用 HashSet 或其他 集合,其顺序在 JVM 运行用于规划实体或规划值的集合,而不是常规问题事实,特别是在解决方案实施中。使用 LinkedHashSet 替换它。
    • 合并一个耗时的依赖算法,最重要的是 Simulated Annealing 算法,以及花费的时间终止。分配的 CPU 时间有足够大的区别会影响时间 gradient 值。将 Simulated Annealing 算法替换为 Late Acceptance 算法,或使用步骤计数终止替换终止时间。
  • REHQUCIBLE 模式可能比 NON_REHQUCIBLE 模式稍慢。如果您的生产环境可从可重复生成的中受益,请在生产环境中使用此模式。在实践中,如果没有指定 seed,则 REHQUCIBLE 模式使用默认的固定随机 seed,而且会禁用某些并发优化,如工作窃取。
  • NON_REHQUCIBLE 模式比 REHQUCIBLE 模式稍快。在开发过程中避免使用它,因为它使调试和程序错误修复变得困难。如果在生产环境中可重复生成性并不重要,则在生产环境中使用 NON_REHQUCIBLE 模式。实际上,如果没有指定 seed,则此模式不使用固定的随机 seed。

8.3. 更改 OptaPlanner 解析器日志记录级别

您可以更改 OptaPlanner solver 中的日志级别,以查看解析器活动。以下列表描述了不同的日志记录级别:

  • 错误 :日志错误,但那些作为 RuntimeException 抛出到调用代码的除外。

    如果发生错误,OptaPlanner 通常会快速失败。它将抛出一个 RuntimeException 的子类,并将详细信息放到调用代码。为避免重复的日志消息,它不会将其记录为错误。除非调用代码明确捕获并消除 RuntimeException,否则 线程的默认 'ExceptionHandler 会将它记录为错误。同时,代码被破坏导致进一步的损害或模糊处理错误。

  • 警告 :日志可疑的情况
  • info :记录每个阶段和解析器本身
  • Debug :记录每个阶段的每个步骤
  • trace :记录每个阶段的每个步骤的每次移动
注意

指定 trace 日志记录会大大降低性能。但是,在开发过程中,追踪 日志记录非常有用,以发现瓶颈。

即使 调试日志记录 对于快速步骤算法(如 Late Acceptance 和 Simulated Annealing)的性能要下降,但不适用于降低步骤算法,如 Tabu Search。

trace 和 debug 日志记录都会导致多线程与大多数附加者的拥塞。

在 Eclipse 中,调试 到控制台的日志记录往往会因为分数计算速度超过 10000 每秒造成拥塞。IntelliJ 或 Maven 命令行都不受此问题的影响。

流程

将日志记录级别设置为 debug 日志记录,以查看阶段何时结束以及执行快速步骤。

以下示例显示了 debug 日志记录的输出:

INFO  Solving started: time spent (3), best score (-4init/0), random (JDK with seed 0).
DEBUG     CH step (0), time spent (5), score (-3init/0), selected move count (1), picked move (Queen-2 {null -> Row-0}).
DEBUG     CH step (1), time spent (7), score (-2init/0), selected move count (3), picked move (Queen-1 {null -> Row-2}).
DEBUG     CH step (2), time spent (10), score (-1init/0), selected move count (4), picked move (Queen-3 {null -> Row-3}).
DEBUG     CH step (3), time spent (12), score (-1), selected move count (4), picked move (Queen-0 {null -> Row-1}).
INFO  Construction Heuristic phase (0) ended: time spent (12), best score (-1), score calculation speed (9000/sec), step total (4).
DEBUG     LS step (0), time spent (19), score (-1),     best score (-1), accepted/selected move count (12/12), picked move (Queen-1 {Row-2 -> Row-3}).
DEBUG     LS step (1), time spent (24), score (0), new best score (0), accepted/selected move count (9/12), picked move (Queen-3 {Row-3 -> Row-2}).
INFO  Local Search phase (1) ended: time spent (24), best score (0), score calculation speed (4000/sec), step total (2).
INFO  Solving ended: time spent (24), best score (0), score calculation speed (7000/sec), phase total (2), environment mode (REPRODUCIBLE).
Copy to Clipboard Toggle word wrap

所有花费的时间都以毫秒为单位。

所有都记录到 SLF4J,这是一个一个简单的日志记录信息,它将每个日志消息委派给 Logback、Apache Commons Logging、Log4j 或 java.util.logging。为您选择的日志记录框架添加依赖项。

8.4. 使用 Logback 记录 OptaPlanner solver 活动

logback 是推荐的日志框架,用于 OptaPlanner。使用 Logback 记录 OptaPlanner solver 活动。

先决条件

  • 您有一个 OptaPlanner 项目。

流程

  1. 在您的 OptaPlanner 项目的 pom.xml 文件中添加以下 Maven 依赖项:

    注意

    您不需要添加额外的网桥依赖项。

        <dependency>
          <groupId>ch.qos.logback</groupId>
          <artifactId>logback-classic</artifactId>
          <version>1.x</version>
        </dependency>
    Copy to Clipboard Toggle word wrap
  2. logback.xml 文件中的 org.optaplanner 软件包上配置日志级别,如下例所示,其中 < LEVEL&gt; 是 第 8.4 节 “使用 Logback 记录 OptaPlanner solver 活动” 中列出的日志记录级别。

    <configuration>
    
      <logger name="org.optaplanner" level="<LEVEL>"/>
    
      ...
    
    </configuration>
    Copy to Clipboard Toggle word wrap
  3. 可选:如果您有一个多租户应用程序,其中多个 Solver 实例可能会同时运行,请将每个实例的日志分成单独的文件中:

    1. 使用 Mapped mailbox Context (MDC)组成 solve () 调用:

              MDC.put("tenant.name",tenantName);
              MySolution bestSolution = solver.solve(problem);
              MDC.remove("tenant.name");
      Copy to Clipboard Toggle word wrap
    2. 将您的日志记录器配置为为每个 ${tenant.name} 使用不同的文件。例如,在 logback.xml 文件中使用 SiftingAppender

        <appender name="fileAppender" class="ch.qos.logback.classic.sift.SiftingAppender">
          <discriminator>
            <key>tenant.name</key>
            <defaultValue>unknown</defaultValue>
          </discriminator>
          <sift>
            <appender name="fileAppender.${tenant.name}" class="...FileAppender">
              <file>local/log/optaplanner-${tenant.name}.log</file>
              ...
            </appender>
          </sift>
        </appender>
      Copy to Clipboard Toggle word wrap
      注意

      当运行多个解析程序或一个多线程解决时,大多数附加程序(包括控制台)会导致 debugtrace 日志记录的拥塞。切换到 async 附加程序以避免出现这个问题或关闭 debug 日志记录。

  4. 如果 OptaPlanner 没有识别新级别,请临时添加系统属性 -Dlogback.LEVEL=true 以进行故障排除。

8.5. 使用 Log4J 记录 OptaPlanner solver 活动

如果您已使用 Log4J,且您不想切换到其更快的成功者 Logback,您可以为 Log4J 配置 OptaPlanner 项目。

先决条件

  • 您有一个 OptaPlanner 项目
  • 您使用 Log4J 日志记录框架

流程

  1. 将网桥依赖项添加到项目 pom.xml 文件中:

        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-log4j12</artifactId>
          <version>1.x</version>
        </dependency>
    Copy to Clipboard Toggle word wrap
  2. log4j.xml 文件中的软件包 org.optaplanner 上配置日志级别,如下例所示,其中 < LEVEL&gt; 是 第 8.4 节 “使用 Logback 记录 OptaPlanner solver 活动” 中列出的日志级别。

    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    
      <category name="org.optaplanner">
        <priority value="<LEVEL>" />
      </category>
    
      ...
    
    </log4j:configuration>
    Copy to Clipboard Toggle word wrap
  3. 可选:如果您有一个多租户应用程序,其中多个 Solver 实例可能会同时运行,请将每个实例的日志分成单独的文件中:

    1. 使用 Mapped mailbox Context (MDC)组成 solve () 调用:

              MDC.put("tenant.name",tenantName);
              MySolution bestSolution = solver.solve(problem);
              MDC.remove("tenant.name");
      Copy to Clipboard Toggle word wrap
    2. 将您的日志记录器配置为为每个 ${tenant.name} 使用不同的文件。例如,在 logback.xml 文件中使用 SiftingAppender

        <appender name="fileAppender" class="ch.qos.logback.classic.sift.SiftingAppender">
          <discriminator>
            <key>tenant.name</key>
            <defaultValue>unknown</defaultValue>
          </discriminator>
          <sift>
            <appender name="fileAppender.${tenant.name}" class="...FileAppender">
              <file>local/log/optaplanner-${tenant.name}.log</file>
              ...
            </appender>
          </sift>
        </appender>
      Copy to Clipboard Toggle word wrap
      注意

      当运行多个解析程序或一个多线程解决时,大多数附加程序(包括控制台)会导致 debugtrace 日志记录的拥塞。切换到 async 附加程序以避免出现这个问题或关闭 debug 日志记录。

8.6. 监控解析器

OptaPlanner 通过 Micrometer (一个 Java 应用程序的指标检测库)公开指标。您可以将 Micrometer 与流行的监控系统一起使用,来监控 OptaPlanner solver。

要将 OptaPlanner Quarkus 应用程序配置为使用 Micrometer 和指定的监控系统,请将 Micrometer 依赖项添加到 pom.xml 文件中。

先决条件

  • 您有一个 Quarkus OptaPlanner 应用程序。

流程

  1. 将以下依赖项添加到应用程序的 pom.xml 文件中,其中 < MONITORING_SYSTEM& gt; 是 Micrometer 和 Quarkus 支持的监控系统:

    注意

    Prometheus 目前是唯一由 Quarkus 支持的监控系统。

    <dependency>
     <groupId>io.quarkus</groupId>
     <artifactId>quarkus-micrometer-registry-<MONITORING_SYSTEM></artifactId>
    </dependency>
    Copy to Clipboard Toggle word wrap
  2. 要在开发模式下运行应用程序,请输入以下命令:

    mvn compile quarkus:dev
    Copy to Clipboard Toggle word wrap
  3. 要查看应用程序的指标,请在浏览器中输入以下 URL:

    http://localhost:8080/q/metrics
    Copy to Clipboard Toggle word wrap

要将 Spring Boot OptaPlanner 应用程序配置为使用 Micrometer 和指定的监控系统,请将 Micrometer 依赖项添加到 pom.xml 文件中。

先决条件

  • 您有一个 Spring Boot OptaPlanner 应用程序。

流程

  1. 将以下依赖项添加到应用程序的 pom.xml 文件中,其中 < MONITORING_SYSTEM& gt; 是 Micrometer 和 Spring Boot 支持的监控系统:

    <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
     <groupId>io.micrometer</groupId>
     <artifactId>micrometer-registry-<MONITORING_SYSTEM></artifactId>
    </dependency>
    Copy to Clipboard Toggle word wrap
  2. 将配置信息添加到应用的 application.properties 文件中。如需更多信息,请参阅 Micrometer 网站。
  3. 要运行应用程序,请输入以下命令:

    mvn spring-boot:run
    Copy to Clipboard Toggle word wrap
  4. 要查看应用程序的指标,请在浏览器中输入以下 URL:

    http://localhost:8080/actuator/metrics

    注意

    使用以下 URL 作为 Prometheus scraper 路径: http://localhost:8080/actuator/prometheus

要将普通 Java OptaPlanner 应用程序配置为使用 Micrometer,您必须将所选监控系统的 Micrometer 依赖项和配置信息添加到项目的 POM.XML 文件中。

先决条件

  • 您有一个普通 Java OptaPlanner 应用程序。

流程

  1. 将以下依赖项添加到应用程序的 pom.xml 文件中,其中 < MONITORING_SYSTEM&gt; 是一个监控系统,使用 Micrometer 配置,& lt;VERSION > 是您使用的 Micrometer 版本:

    <dependency>
     <groupId>io.micrometer</groupId>
     <artifactId>micrometer-registry-<MONITORING_SYSTEM></artifactId>
     <version><VERSION></version>
    </dependency>
    <dependency>
     <groupId>io.micrometer</groupId>
     <artifactId>micrometer-core</artifactId>
     <version><VERSION></version>
    </dependency>
    Copy to Clipboard Toggle word wrap
  2. 将监控系统的 Micrometer 配置信息添加到项目的 pom.xml 文件的开头。如需更多信息,请参阅 Micrometer 网站。
  3. 在配置信息下方添加以下行,其中 &lt ;MONITORING_SYSTEM > 是您添加的监控系统:

    Metrics.addRegistry(<MONITORING_SYSTEM>);
    Copy to Clipboard Toggle word wrap

    以下示例演示了如何添加 Prometheus 监控系统:

    PrometheusMeterRegistry prometheusRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
    try {
        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
        server.createContext("/prometheus", httpExchange -> {
            String response = prometheusRegistry.scrape();
            httpExchange.sendResponseHeaders(200, response.getBytes().length);
            try (OutputStream os = httpExchange.getResponseBody()) {
                os.write(response.getBytes());
            }
        });
        new Thread(server::start).start();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    Metrics.addRegistry(prometheusRegistry);
    Copy to Clipboard Toggle word wrap
  4. 打开您的监控系统,以查看您的 OptaPlanner 项目的指标。公开以下指标:

    注意

    指标的名称和格式因 registry 而异。

    • OptaPlanner.solver.errors.total :自测量开始开始时发生的错误总数。
    • optaPlanner.solver.solve-length.active-count: 当前解决者的数量。
    • optaPlanner.solver.solve-length.seconds-max: 运行 longest-running 当前活跃解析程序的时间。
    • optaPlanner.solver.solve-length.seconds-duration-sum: 每个活跃解析器解析持续时间的总和。例如,如果有两个活跃的解析器,一个会运行一个三分钟,另一个一分钟,总地址时间为 4 分钟。

8.6.4. 其他指标

如需更详细的监控,您可以在 solver 配置中配置 OptaPlanner,以以性能成本监控其他指标。以下示例使用 BEST_SCORESCORE_CALCULATION_COUNT 指标:

<solver xmlns="https://www.optaplanner.org/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://www.optaplanner.org/xsd/solver https://www.optaplanner.org/xsd/solver/solver.xsd">
  <monitoring>
    <metric>BEST_SCORE</metric>
    <metric>SCORE_CALCULATION_COUNT</metric>
    ...
  </monitoring>
  ...
</solver>
Copy to Clipboard Toggle word wrap

您可以在此配置中启用以下指标:

  • SOLVE_DURATION (默认为 Micrometer 分段 ID: optaplanner.solver.solve.duration):测量活跃解析器的持续时间、活动解析器的数量以及所有活动解析器的累积持续时间。
  • ERROR_COUNT (默认启用, Micrometer measure ID: optaplanner.solver.errors):测量在过期时发生的错误数量。
  • SCORE_CALCULATION_COUNT (默认启用 Micrometer 分段 ID: optaplanner.solver.score.calculation.count) :测量执行 OptaPlanner 的分数计算数。
  • BEST_SCORE (Micrometer geo ID: optaplanner.solver.best.scoreö : 测量 OptaPlanner 已找到的最佳解决方案分数。每个分数级别都有单独的分段。例如,对于 Hard SoftScore,有 optaplanner.solver.best.score.hard.scoreoptaplanner.solver.best.score.soft.score 修饰符。
  • STEP_SCORE (Micrometer geo ID: optaplanner.solver.step.scorethe): 测量 OptaPlanner 所采取的每个步骤的分数。每个分数级别都有单独的分段。例如,对于 Hard SoftScore,有 optaplanner.solver.step.score.hard.scoreoptaplanner.solver.step.score.soft.score 修饰符。
  • BEST_SOLUTION_MUTATION (Micrometer measure ID: optaplanner.solver.best.solution.mutation: 测量连续最佳解决方案间更改的规划变量的数量。
  • MOVE_COUNT_PER_STEP (Micrometer geo ID: optaplanner.solver.step.move.count):测量步骤中评估的移动数量。
  • MEMORY_USE (Micrometer geo ID: jvm.memory.used):测量 JVM 之间使用的内存量。此指标不测量解析器使用的内存量;同一 JVM 上的两个地址将报告此指标的值相同的值。
  • CONSTRAINT_MATCH_TOTAL_BEST_SCORE (Micrometer the ID: optaplanner.solver.constraint.match.best.score : 评估每个约束对目前找到的最佳解决方案的影响。每个分数级别都有单独的分段,每个约束的标签。例如,对于软件包 "com.example" 中的 HardSoftScore 的约束"Minimize Cost",有带有标签 " constraint.package=com.example" 和 " constraint. name=Minimize Cost" 的 optaplanner.solver.match.best.score.soft.score.constraint.constraint.best.score.name=Minimize Cost"。
  • CONSTRAINT_MATCH_TOTAL_STEP_SCORE (Micrometer measure ID: optaplanner.solver.constraint.match.step.score114 : 评估每个约束对当前步骤的分数影响。每个分数级别都有单独的分段,每个约束的标签。例如,对于软件包 "com.example" 中的 HardSoftScore 的约束"Minimize Cost",带有标签 " constraint.package=com. example" 和 "constraint.name=Minimimize Cost" 的选择器。
  • PICKED_MOVE_TYPE_BEST_SCORE_DIFF (Micrometer the ID: optaplanner.solver.move.type.best.score.diff114):衡量特定移动类型提高了最佳解决方案。每个分数级别都有单独的分段,带有移动类型的标签。例如,对于进程的计算机的 Hard SoftScoreChangeMove,有 optaplanner.solver.move.type.best.type.best.diff.hard.score 和 optaplanner. solver.move.type.best.score.diff.soft.score traffics,标签 move.type=ChangeMove (Process.computer)
  • PICKED_MOVE_TYPE_STEP_SCORE_DIFF (Micrometer the ID: optaplanner.solver.move.type.step.score.diff114):衡量特定移动类型提高了最佳解决方案。每个分数级别都有单独的分段,带有移动类型的标签。例如,对于进程的计算机,对于 Hard SoftScoreChangeMove,有 optaplanner.solver.move.type.step.score.diff.hard.scoreoptaplanner.solver.move.type.step.score.diff.soft.score traffics,标签 move.type=ChangeMove (Process.computer)

8.7. 配置随机数生成器

许多 heuristics 和 metaheuristics 依赖于伪随机数生成器来移动选择,以解决分数、基于概率的移动接受等。在此期间,会重复使用同一随机实例,以提高随机值的可重复生成性、性能和统一分发。

随机 seed 是一个数字,用于初始化伪随机数生成器。

流程

  1. 可选: 要更改随机实例的随机 seed,请指定 randomSeed

    <solver xmlns="https://www.optaplanner.org/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://www.optaplanner.org/xsd/solver https://www.optaplanner.org/xsd/solver/solver.xsd">
      <randomSeed>0</randomSeed>
      ...
    </solver>
    Copy to Clipboard Toggle word wrap
  2. 可选: 要更改伪随机数生成器实现,请为下面的 resolver 配置文件中列出的 randomType 属性指定一个值,其中 < RANDOM_NUMBER_GENERATOR& gt; 是一个伪随机数生成器:

    <solver xmlns="https://www.optaplanner.org/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://www.optaplanner.org/xsd/solver https://www.optaplanner.org/xsd/solver/solver.xsd">
      <randomType><RANDOM_NUMBER_GENERATOR></randomType>
      ...
    </solver>
    Copy to Clipboard Toggle word wrap

    支持以下伪随机数生成器:

    • JDK (默认):标准随机数生成器实现(java.util.Random)
    • MERSENNE_TWISTERCommons Math的随机数生成器实现
    • WELL512A,WELL1024A,WELL19937A,WELL19937C,WELL44497AWELL44497B: Commons Math的 Random 数生成器实现

对于大多数用例,randomType 属性的值不会影响对多个数据集的最佳解决方案的平均质量。

第 9 章 OptaPlanner SolverManager

SolverManager 是一个或多个 Solver 实例的一个传真,以简化 REST 和其他企业服务中的潜在规划问题。

Solver.solve (…​) 方法不同,S olverManager 具有以下特征:

  • SolverManager.solve (…​) 会立即返回:它会调度一个异步问题,而不阻止调用线程。这可避免 HTTP 和其他技术的超时问题。
  • SolverManager.solve (…​) 并行解决了同一域的多个规划问题。

在内部,SolverManager 管理一组解析程序线程,它调用 Solver.solve (…​) 和消费者线程的线程池,它处理最佳解决方案更改了事件。

在 Quarkus 和 Spring Boot 中,SolverManager 实例会自动注入您的代码中。如果您使用 Quarkus 或 Spring Boot 以外的平台,请使用 create (…​) 方法构建 SolverManager 实例:

SolverConfig solverConfig = SolverConfig.createFromXmlResource(".../cloudBalancingSolverConfig.xml");
SolverManager<CloudBalance, UUID> solverManager = SolverManager.create(solverConfig, new SolverManagerConfig());
Copy to Clipboard Toggle word wrap

提交到 Solver Manager.solve (…​)方法的 每个问题都必须具有唯一的问题 ID。稍后调用 getSolverStatus (problemId)killEarly (problemId) 使用该问题 ID 来区分规划问题。问题 ID 必须是不可变类,如 Long字符串java.util.UUID

SolverManagerConfig 类有一个 parallelSolverCount 属性,用于控制并行运行多少解析程序。例如,如果 parallelSolverCount 属性设置为 4,并且您提交了五个问题,则 4 个问题将立即开始,并且第五个问题在第一个问题结束时启动。如果这些问题每五分钟解决,则五个问题需要 10 分钟才能完成。默认情况下,parallelSolverCount 设置为 AUTO,它将解析为 CPU 内核一半,而不考虑解析者的 moveThreadCount

要检索最佳解决方案,在参与后,通常会使用 SolverJob.getFinalBestSolution ()

CloudBalance problem1 = ...;
UUID problemId = UUID.randomUUID();
// Returns immediately
SolverJob<CloudBalance, UUID> solverJob = solverManager.solve(problemId, problem1);
...
CloudBalance solution1;
try {
    // Returns only after solving terminates
    solution1 = solverJob.getFinalBestSolution();
} catch (InterruptedException | ExecutionException e) {
    throw ...;
}
Copy to Clipboard Toggle word wrap

但是,在用户需要解决方案以及用户主动等待解决方案时,在用户需要解决方案前,对业务批处理问题都有更好的方法。

当前 SolverManager 实施在一台计算机节点上运行,但将来的工作旨在在云中分发解析程序负载。

9.1. 批量问题

批量参与是并行多个数据集。批量参与对时间特别有用:

  • 在每天的中间,通常有一些或没有问题更改。有些组织实施截止时间,例如,在周等 前提交所有一天的请求
  • resolvers 可以运行更长的时间(通常为几小时),因为 nobody 等待结果,CPU 资源通常较低。
  • 当员工到达下一工作日时,可以使用相应的解决方案。

流程

要批量解决问题,受 parallelSolverCount 限制,请为每个数据集调用 solve (…​) 创建以下类:

public class TimeTableService {

    private SolverManager<TimeTable, Long> solverManager;

    // Returns immediately, call it for every data set
    public void solveBatch(Long timeTableId) {
        solverManager.solve(timeTableId,
                // Called once, when solving starts
                this::findById,
                // Called once, when solving ends
                this::save);
    }

    public TimeTable findById(Long timeTableId) {...}

    public void save(TimeTable timeTable) {...}

}
Copy to Clipboard Toggle word wrap

9.2. 解决并侦听显示进度

当用户等待解决方案时运行某个问题时,用户可能需要等待几分钟或小时才能收到结果。为确保用户一切正常,通过显示最佳解决方案和最佳分数来显示进度。

流程

  1. 要处理中间最佳解决方案,请使用 solveAndListen (…​)

    public class TimeTableService {
    
        private SolverManager<TimeTable, Long> solverManager;
    
        // Returns immediately
        public void solveLive(Long timeTableId) {
            solverManager.solveAndListen(timeTableId,
                    // Called once, when solving starts
                    this::findById,
                    // Called multiple times, for every best solution change
                    this::save);
        }
    
        public TimeTable findById(Long timeTableId) {...}
    
        public void save(TimeTable timeTable) {...}
    
        public void stopSolving(Long timeTableId) {
            solverManager.terminateEarly(timeTableId);
        }
    
    }
    Copy to Clipboard Toggle word wrap

    这种实施使用数据库与 UI 通信,该 UI 会轮询数据库。更高级的实施将最佳解决方案直接推送到 UI 或消息传递队列。

  2. 当用户满足中间最佳解决方案且不想等待任何更好解决方案时,请调用 SolverManager.terminateEarly (problemId)

部分 IV. 红帽构建的 OptaPlanner 快速启动指南

Red Hat build of OptaPlanner 提供了以下快速启动指南,以演示 OptaPlanner 如何与不同的技术集成:

  • Red Hat build of OptaPlanner on the Red Hat build of Quarkus platform: a IANA timetable quick Start guide
  • Red Hat build of OptaPlanner on the Red Hat build of Quarkus platform: 一个 vaccination appointment scheduler quick start 指南
  • Red Hat build of OptaPlanner 基于 Red Hat build of Quarkus 平台:一个员工调度程序快速启动指南
  • Red Hat build of OptaPlanner on Spring Boot: 一个可快速开始指南
  • Red Hat build of OptaPlanner with Java solvers: 一个可快速开始指南

本指南指导您使用红帽构建的 OptaPlanner 约束创建 Red Hat build of Quarkus 应用程序的过程。您将构建 REST 应用程序,该应用程序针对领导和专业者进行了优化。

您的服务将通过 AI 遵循以下硬和软 调度限制,自动将附件实例分配给 TimeslotRoom 实例:

  • 空间最多可以同时有一个小时间。
  • 一个架构师可以同时参与一个小时间。
  • 每天最多可同时参与一个小时间。
  • 交互式人员更喜欢在单个房间参与。
  • 指导人员更喜欢参与较少的活动,并排解少量间的差距。

mathematly 认为是 NP 硬性问题。这意味着难以扩展。只需将所有可能的组合与括号一起进行迭代,可以在非平量数据集中使用数百万年,即使在超级计算器上也是如此。AI 的约束程序(如红帽构建的 OptaPlanner)具有以合理的时间提供接近优化解决方案的高级算法。认为是合理的时间,这取决于您的问题的目标。

先决条件

  • 已安装 OpenJDK 11 或更高版本。Red Hat build of Open JDK 位于红帽客户门户网站中的 Software Downloads 页面中(需要登录)。
  • 已安装 Apache Maven 3.8 或更高版本。Maven 可从 Apache Maven 项目网站 获得。
  • 提供了 IDE,如 IntelliJ IDEA、VSCode 或 Eclipse。
  • 红帽构建的 OptaPlanner Red Hat build of Quarkus 项目可用。有关创建 Red Hat Build of OptaPlanner Red Hat build of Quarkus 项目的说明,请参阅 红帽构建的 OptaPlanner 和 Quarkus 部分中的 " OptaPlanner 和 Quarkus"。

10.1. 对域对象建模

红帽构建的 OptaPlanner 时间项目的目标是将每项分配给一个时间插槽和房间。要做到这一点,添加三个类: Timeslotinit on 和 Room,如下图所示:

timeslot

Timeslot 类代表当减少活动为过期的时间间隔时,例如,Monday 10:30 - 11:30Tuesday 13:30 - 14:30。在这个示例中,所有时间段都有相同的持续时间,在 lunch 或其他中断过程中没有时间插槽。

一个时间插槽没有日期,因为每周计划只重复一次。不需要 持续规划。timeslot 被称为 问题事实,因为公司不会更改 Timeslot 实例。此类类不需要任何特定于 OptaPlanner 的注解。

房间

Room 类代表更小活动的位置,例如 Room ARoom B。在本例中,所有房间都没有容量限制,它们可以容纳所有定义。

空间 实例在竞争过程中不会改变,因此 Room 也是 问题事实

lesson

在小时间中,由周一类代表,受一组参与者代表的规定,例如,A.Turing for 9th rating 或 Chemistry by M.Curie 代表第 10 等级如果一个主题是按同一站组相同的每周的多次,则有多个仅可通过 id 区分的实例。例如,第 9 等级每周有六个数学。

在 OptaPlanner 期间,OptaPlanner 会更改周一类的 timeslotroom 字段,以将每个小时间分配给一个时间插槽和房间。因为 OptaPlanner 更改这些字段,因此 Apple on 是一个 规划实体

上图中的大部分字段都包含输入数据,但 orange 字段除外。在输入数据中取消分配较少的 timeslotroom 字段,并在输出数据中分配(而非 null)。OptaPlanner 在参与过程中会更改这些字段。此类字段称为 规划变量。为了使 OptaPlanner 能够识别它们,timeslotroom 字段都需要 @PlanningVariable 注释。它们包含类属性需要 @PlanningEntity 注释。

流程

  1. 创建 src/main/java/com/example/domain/Timeslot.java 类:

    package com.example.domain;
    
    import java.time.DayOfWeek;
    import java.time.LocalTime;
    
    public class Timeslot {
    
        private DayOfWeek dayOfWeek;
        private LocalTime startTime;
        private LocalTime endTime;
    
        private Timeslot() {
        }
    
        public Timeslot(DayOfWeek dayOfWeek, LocalTime startTime, LocalTime endTime) {
            this.dayOfWeek = dayOfWeek;
            this.startTime = startTime;
            this.endTime = endTime;
        }
    
        @Override
        public String toString() {
            return dayOfWeek + " " + startTime.toString();
        }
    
        // ********************************
        // Getters and setters
        // ********************************
    
        public DayOfWeek getDayOfWeek() {
            return dayOfWeek;
        }
    
        public LocalTime getStartTime() {
            return startTime;
        }
    
        public LocalTime getEndTime() {
            return endTime;
        }
    
    }
    Copy to Clipboard Toggle word wrap

    请注意,toString () 方法保留输出短,因此更易于阅读 OptaPlanner 的 DEBUGTRACE 日志,如稍后所示。

  2. 创建 src/main/java/com/example/domain/Room.java 类:

    package com.example.domain;
    
    public class Room {
    
        private String name;
    
        private Room() {
        }
    
        public Room(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return name;
        }
    
        // ********************************
        // Getters and setters
        // ********************************
    
        public String getName() {
            return name;
        }
    
    }
    Copy to Clipboard Toggle word wrap
  3. 创建 src/main/java/com/example/domain/Lesson.java 类:

    package com.example.domain;
    
    import org.optaplanner.core.api.domain.entity.PlanningEntity;
    import org.optaplanner.core.api.domain.variable.PlanningVariable;
    
    @PlanningEntity
    public class Lesson {
    
        private Long id;
    
        private String subject;
        private String teacher;
        private String studentGroup;
    
        @PlanningVariable(valueRangeProviderRefs = "timeslotRange")
        private Timeslot timeslot;
    
        @PlanningVariable(valueRangeProviderRefs = "roomRange")
        private Room room;
    
        private Lesson() {
        }
    
        public Lesson(Long id, String subject, String teacher, String studentGroup) {
            this.id = id;
            this.subject = subject;
            this.teacher = teacher;
            this.studentGroup = studentGroup;
        }
    
        @Override
        public String toString() {
            return subject + "(" + id + ")";
        }
    
        // ********************************
        // Getters and setters
        // ********************************
    
        public Long getId() {
            return id;
        }
    
        public String getSubject() {
            return subject;
        }
    
        public String getTeacher() {
            return teacher;
        }
    
        public String getStudentGroup() {
            return studentGroup;
        }
    
        public Timeslot getTimeslot() {
            return timeslot;
        }
    
        public void setTimeslot(Timeslot timeslot) {
            this.timeslot = timeslot;
        }
    
        public Room getRoom() {
            return room;
        }
    
        public void setRoom(Room room) {
            this.room = room;
        }
    
    }
    Copy to Clipboard Toggle word wrap

    bring on 类有一个 @PlanningEntity 注释,因此 OptaPlanner 知道该类在周五期间发生了变化,因为它包含一个或多个规划变量。

    timeslot 字段有一个 @PlanningVariable 注释,因此 OptaPlanner 知道它可以更改其值。要查找分配给此字段的潜在 Timeslot 实例,OptaPlanner 使用 valueRangeProviderRefs 属性连接到提供 List<Timeslot&gt; 的值范围供应商。有关值范围供应商的详情,请查看 第 10.3 节 “在计划解决方案中收集域对象”

    由于同样原因,room 字段也有一个 @PlanningVariable 注释。

10.2. 定义约束并计算分数

当遇到问题时,分数 代表特定解决方案的质量。分数越大。红帽构建的 OptaPlanner 会寻找最佳解决方案,这是可用时间提供的最高分数的解决方案。它可能 是最佳解决方案

因为 timetable 示例用例有硬和软限制,因此请使用 Hard SoftScore 类来代表分数:

  • 硬限制不能被破坏。例如: 一个房间可以同时有一个小时间。
  • 软限制不应中断。例如: 一个公司更喜欢在单个房间参与。

硬限制会与其他硬限制进行权重。软限制会与其他软限制加权。硬限制始终超过软限制,无论它们对应的权重是什么。

要计算分数,您可以实施 EasyScoreCalculator 类:

public class TimeTableEasyScoreCalculator implements EasyScoreCalculator<TimeTable> {

    @Override
    public HardSoftScore calculateScore(TimeTable timeTable) {
        List<Lesson> lessonList = timeTable.getLessonList();
        int hardScore = 0;
        for (Lesson a : lessonList) {
            for (Lesson b : lessonList) {
                if (a.getTimeslot() != null && a.getTimeslot().equals(b.getTimeslot())
                        && a.getId() < b.getId()) {
                    // A room can accommodate at most one lesson at the same time.
                    if (a.getRoom() != null && a.getRoom().equals(b.getRoom())) {
                        hardScore--;
                    }
                    // A teacher can teach at most one lesson at the same time.
                    if (a.getTeacher().equals(b.getTeacher())) {
                        hardScore--;
                    }
                    // A student can attend at most one lesson at the same time.
                    if (a.getStudentGroup().equals(b.getStudentGroup())) {
                        hardScore--;
                    }
                }
            }
        }
        int softScore = 0;
        // Soft constraints are only implemented in the "complete" implementation
        return HardSoftScore.of(hardScore, softScore);
    }

}
Copy to Clipboard Toggle word wrap

不幸的是,这个解决方案无法很好地扩展,因为它不可缩小:每次将小时间分配给不同的时区或空间时,所有定义都会被重新评估以计算新的分数。

更好的解决方案是创建一个 src/main/java/com/example/solver/TimeTableConstraintProvider.java 类来执行增量分数计算。这个类使用 OptaPlanner 的 ConstraintStream API,该 API 被 Java 8 Streams 和 SQL 增加。ConstraintProvider 扩展比 EasyScoreCalculator: O (n)而不是O(n)而不是 O(n)更好的 magnitude 顺序。

流程

创建以下 src/main/java/com/example/solver/TimeTableConstraintProvider.java 类:

package com.example.solver;

import com.example.domain.Lesson;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.api.score.stream.Constraint;
import org.optaplanner.core.api.score.stream.ConstraintFactory;
import org.optaplanner.core.api.score.stream.ConstraintProvider;
import org.optaplanner.core.api.score.stream.Joiners;

public class TimeTableConstraintProvider implements ConstraintProvider {

    @Override
    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
        return new Constraint[] {
                // Hard constraints
                roomConflict(constraintFactory),
                teacherConflict(constraintFactory),
                studentGroupConflict(constraintFactory),
                // Soft constraints are only implemented in the "complete" implementation
        };
    }

    private Constraint roomConflict(ConstraintFactory constraintFactory) {
        // A room can accommodate at most one lesson at the same time.

        // Select a lesson ...
        return constraintFactory.forEach(Lesson.class)
                // ... and pair it with another lesson ...
                .join(Lesson.class,
                        // ... in the same timeslot ...
                        Joiners.equal(Lesson::getTimeslot),
                        // ... in the same room ...
                        Joiners.equal(Lesson::getRoom),
                        // ... and the pair is unique (different id, no reverse pairs)
                        Joiners.lessThan(Lesson::getId))
                // then penalize each pair with a hard weight.
                .penalize(HardSoftScore.ONE_HARD)
                .asConstraint("Room conflict");
    }

    private Constraint teacherConflict(ConstraintFactory constraintFactory) {
        // A teacher can teach at most one lesson at the same time.
        return constraintFactory.forEach(Lesson.class)
                .join(Lesson.class,
                        Joiners.equal(Lesson::getTimeslot),
                        Joiners.equal(Lesson::getTeacher),
                        Joiners.lessThan(Lesson::getId))
                        .penalize(HardSoftScore.ONE_HARD)
                        .asConstraint("Teacher conflict");
    }

    private Constraint studentGroupConflict(ConstraintFactory constraintFactory) {
        // A student can attend at most one lesson at the same time.
        return constraintFactory.forEach(Lesson.class)
                .join(Lesson.class,
                        Joiners.equal(Lesson::getTimeslot),
                        Joiners.equal(Lesson::getStudentGroup),
                        Joiners.lessThan(Lesson::getId))
                        .penalize(HardSoftScore.ONE_HARD)
                        .asConstraint("Student group conflict");
    }

}
Copy to Clipboard Toggle word wrap

10.3. 在计划解决方案中收集域对象

TimeTable 实例将单个数据集的所有 TimeslotRoom 和 Appleon 实例嵌套。另外,由于它包含所有 lessons,每个都具有特定的计划变量状态,所以它是一个 规划解决方案,它具有分数:

  • 如果仅未被分配,则它是一个 未初始化的 解决方案,例如,分数为 -4init/0hard/0soft 的解决方案。
  • 如果它破坏硬限制,则是一个不可行的解决方案,例如,分数为 -2hard/-3soft 的解决方案。
  • 如果它遵循所有硬限制,那么它是一种可行的解决方案,例如,分数为 0hard/-7soft 的解决方案。

TimeTable 类有一个 @PlanningSolution 注释,因此 Red Hat Build of OptaPlanner 知道这个类包含所有输入和输出数据。

具体来说,这个类是问题的输入:

  • 带有所有时间插槽的 timeslotList 字段

    • 这是问题事实列表,因为它们在竞争过程中不会改变。
  • 包含所有空间的 roomList 字段

    • 这是问题事实列表,因为它们在竞争过程中不会改变。
  • 带有所有 lessons 的 lessonList 字段

    • 这是规划实体的列表,因为它们在竞争期间发生了变化。
    • 每个 周一

      • timeslotroom 字段的值通常仍然为空,因此未分配。它们正在规划变量。
      • 其他字段(如 subject、MrrslirpGroup )已填写。这些字段是问题属性。

但是,此类也是解决方案的输出:

  • 一个 lessonList 字段,其中每个 每天 实例在周五后都有非空 timeslotroom 字段
  • 代表输出解决方案的质量的 score 字段,例如 0hard/-5soft

流程

创建 src/main/java/com/example/domain/TimeTable.java 类:

package com.example.domain;

import java.util.List;

import org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty;
import org.optaplanner.core.api.domain.solution.PlanningScore;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;

@PlanningSolution
public class TimeTable {

    @ValueRangeProvider(id = "timeslotRange")
    @ProblemFactCollectionProperty
    private List<Timeslot> timeslotList;

    @ValueRangeProvider(id = "roomRange")
    @ProblemFactCollectionProperty
    private List<Room> roomList;

    @PlanningEntityCollectionProperty
    private List<Lesson> lessonList;

    @PlanningScore
    private HardSoftScore score;

    private TimeTable() {
    }

    public TimeTable(List<Timeslot> timeslotList, List<Room> roomList,
            List<Lesson> lessonList) {
        this.timeslotList = timeslotList;
        this.roomList = roomList;
        this.lessonList = lessonList;
    }

    // ********************************
    // Getters and setters
    // ********************************

    public List<Timeslot> getTimeslotList() {
        return timeslotList;
    }

    public List<Room> getRoomList() {
        return roomList;
    }

    public List<Lesson> getLessonList() {
        return lessonList;
    }

    public HardSoftScore getScore() {
        return score;
    }

}
Copy to Clipboard Toggle word wrap

值范围供应商

timeslotList 字段是一个值范围供应商。它包含 OptaPlanner 可以从中分配 to to the timeslot 字段的 Timeslot 实例。timeslotList 字段有一个 @ValueRangeProvider 注释来连接这两个注解,方法是将 id 与大括号中的 @PlanningVariablevalueRangeProviderRefs 匹配。

遵循同样的逻辑,roomList 字段也具有 @ValueRangeProvider 注释。

问题事实和规划实体属性

此外,OptaPlanner 需要知道它可以更改哪些附件,以及如何检索用于 TimeTableConstraintProviderTimeslotRoom 实例。

timeslotListroomList 字段有一个 @ProblemFactCollectionProperty 注解,因此您的 TimeTableConstraintProvider 可以从这些实例中选择。

lessonList 有一个 @PlanningEntityCollectionProperty 注解,因此 OptaPlanner 可以在将来更改它们,而您的 TimeTableConstraintProvider 也可以从这些中选择。

10.4. 创建 resolver 服务

REST 线程上的规划问题会导致 HTTP 超时问题。因此,Quarkus 扩展注入 SolverManager,它会在一个单独的线程池中运行 solvers,并可并行解决多个数据集。

流程

创建 src/main/java/org/acme/optaplanner/rest/TimeTableResource.java 类:

package org.acme.optaplanner.rest;

import java.util.UUID;
import java.util.concurrent.ExecutionException;
import javax.inject.Inject;
import javax.ws.rs.POST;
import javax.ws.rs.Path;

import org.acme.optaplanner.domain.TimeTable;
import org.optaplanner.core.api.solver.SolverJob;
import org.optaplanner.core.api.solver.SolverManager;

@Path("/timeTable")
public class TimeTableResource {

    @Inject
    SolverManager<TimeTable, UUID> solverManager;

    @POST
    @Path("/solve")
    public TimeTable solve(TimeTable problem) {
        UUID problemId = UUID.randomUUID();
        // Submit the problem to start solving
        SolverJob<TimeTable, UUID> solverJob = solverManager.solve(problemId, problem);
        TimeTable solution;
        try {
            // Wait until the solving ends
            solution = solverJob.getFinalBestSolution();
        } catch (InterruptedException | ExecutionException e) {
            throw new IllegalStateException("Solving failed.", e);
        }
        return solution;
    }

}
Copy to Clipboard Toggle word wrap

此初始实施会等待 solver 完成,这仍然可能导致 HTTP 超时。完整的实现可避免 HTTP 超时更小。

10.5. 设置解析器终止时间

如果您的计划应用程序没有终止设置或终止事件,则理论上会永久运行,并且实际上会导致 HTTP 超时错误。要防止发生这种情况,请使用 optaplanner.solver.termination.spent-limit 参数指定应用程序终止的时间长度。在大多数应用程序中,将时间设置为至少五分钟(5m)。但是,在 Timetable 示例中,将过期时间限制为 5 秒,这足够短,以避免 HTTP 超时。

流程

使用以下内容创建 src/main/resources/application.properties 文件:

quarkus.optaplanner.solver.termination.spent-limit=5s
Copy to Clipboard Toggle word wrap

10.6. 运行 7000 timetable 应用程序

创建 IANA 时间范围项目后,在开发模式下运行它。在开发模式中,您可以在应用程序运行时更新应用程序源和配置。您的更改将显示在正在运行的应用程序中。

先决条件

  • 您已创建了 phone timetable 项目。

流程

  1. 要将应用程序以开发模式编译,请从项目目录中输入以下命令:

    ./mvnw compile quarkus:dev
    Copy to Clipboard Toggle word wrap
  2. 测试 REST 服务。您可以使用任何 REST 客户端。以下示例使用 Linux 命令 curl 发送 POST 请求:

    $ curl -i -X POST http://localhost:8080/timeTable/solve -H "Content-Type:application/json" -d '{"timeslotList":[{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"}],"roomList":[{"name":"Room A"},{"name":"Room B"}],"lessonList":[{"id":1,"subject":"Math","teacher":"A. Turing","studentGroup":"9th grade"},{"id":2,"subject":"Chemistry","teacher":"M. Curie","studentGroup":"9th grade"},{"id":3,"subject":"French","teacher":"M. Curie","studentGroup":"10th grade"},{"id":4,"subject":"History","teacher":"I. Jones","studentGroup":"10th grade"}]}'
    Copy to Clipboard Toggle word wrap

    终止中指定的时间用 application.properties 文件中定义的时间后,服务会返回类似以下示例的输出:

    HTTP/1.1 200
    Content-Type: application/json
    ...
    
    {"timeslotList":...,"roomList":...,"lessonList":[{"id":1,"subject":"Math","teacher":"A. Turing","studentGroup":"9th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},"room":{"name":"Room A"}},{"id":2,"subject":"Chemistry","teacher":"M. Curie","studentGroup":"9th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"},"room":{"name":"Room A"}},{"id":3,"subject":"French","teacher":"M. Curie","studentGroup":"10th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},"room":{"name":"Room B"}},{"id":4,"subject":"History","teacher":"I. Jones","studentGroup":"10th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"},"room":{"name":"Room B"}}],"score":"0hard/0soft"}
    Copy to Clipboard Toggle word wrap

    请注意,您的应用程序为两个时间插槽和两个空间之一分配了四个小活动。另请注意,它符合所有硬限制。例如,M. Curie 的两个定义位于不同的时间段内。

  3. 要查看在过期期间 OptaPlanner 执行的操作,请查看服务器端的信息日志。以下是 info 日志输出示例:

    ... Solving started: time spent (33), best score (-8init/0hard/0soft), environment mode (REPRODUCIBLE), random (JDK with seed 0).
    ... Construction Heuristic phase (0) ended: time spent (73), best score (0hard/0soft), score calculation speed (459/sec), step total (4).
    ... Local Search phase (1) ended: time spent (5000), best score (0hard/0soft), score calculation speed (28949/sec), step total (28398).
    ... Solving ended: time spent (5000), best score (0hard/0soft), score calculation speed (28524/sec), phase total (2), environment mode (REPRODUCIBLE).
    Copy to Clipboard Toggle word wrap

10.7. 测试应用程序

良好的应用程序包括测试覆盖。测试限制以及您的时间表项目中的 solver。

10.7.1. 测试 prompt 时间可写入限制

要在隔离中测试 timetable 项目的约束,请在单元测试中使用 ConstraintVerifier。这会测试每个约束的发生与其他测试隔离的情况,这可在添加正确测试覆盖的新约束时降低维护。

当给定同一空间的三个较少时,此测试会验证约束 TimeTableConstraintProvider:: roomConflict,其中两个较少项具有相同的 timeslot,以匹配权重为 1。因此,如果约束权重为 10hard,它会按 -10hard 减少分数。

流程

创建 src/test/java/org/acme/optaplanner/solver/TimeTableConstraintProviderTest.java 类:

package org.acme.optaplanner.solver;

import java.time.DayOfWeek;
import java.time.LocalTime;

import javax.inject.Inject;

import io.quarkus.test.junit.QuarkusTest;
import org.acme.optaplanner.domain.Lesson;
import org.acme.optaplanner.domain.Room;
import org.acme.optaplanner.domain.TimeTable;
import org.acme.optaplanner.domain.Timeslot;
import org.junit.jupiter.api.Test;
import org.optaplanner.test.api.score.stream.ConstraintVerifier;

@QuarkusTest
class TimeTableConstraintProviderTest {

    private static final Room ROOM = new Room("Room1");
    private static final Timeslot TIMESLOT1 = new Timeslot(DayOfWeek.MONDAY, LocalTime.of(9,0), LocalTime.NOON);
    private static final Timeslot TIMESLOT2 = new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(9,0), LocalTime.NOON);

    @Inject
    ConstraintVerifier<TimeTableConstraintProvider, TimeTable> constraintVerifier;

    @Test
    void roomConflict() {
        Lesson firstLesson = new Lesson(1, "Subject1", "Teacher1", "Group1");
        Lesson conflictingLesson = new Lesson(2, "Subject2", "Teacher2", "Group2");
        Lesson nonConflictingLesson = new Lesson(3, "Subject3", "Teacher3", "Group3");

        firstLesson.setRoom(ROOM);
        firstLesson.setTimeslot(TIMESLOT1);

        conflictingLesson.setRoom(ROOM);
        conflictingLesson.setTimeslot(TIMESLOT1);

        nonConflictingLesson.setRoom(ROOM);
        nonConflictingLesson.setTimeslot(TIMESLOT2);

        constraintVerifier.verifyThat(TimeTableConstraintProvider::roomConflict)
                .given(firstLesson, conflictingLesson, nonConflictingLesson)
                .penalizesBy(1);
    }

}
Copy to Clipboard Toggle word wrap

请注意,ConstraintVerifier 在测试过程中如何忽略约束权重,即使这些约束权重在 ConstraintProvider 中硬编码。这是因为在进入生产环境前定期更改约束权重。这样,约束权重调整不会影响单元测试。

10.7.2. 测试 7000 timetable resolver

这个示例在 Red Hat build of Quarkus 平台上测试 Red Hat Build of OptaPlanner eXecut timetable 项目。它使用 JUnit 测试来生成测试数据集,并将其发送到 TimeTableController 来解决这个问题。

流程

  1. 使用以下内容创建 src/test/java/com/example/rest/TimeTableResourceTest.java 类:

    package com.exmaple.optaplanner.rest;
    
    import java.time.DayOfWeek;
    import java.time.LocalTime;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.inject.Inject;
    
    import io.quarkus.test.junit.QuarkusTest;
    import com.exmaple.optaplanner.domain.Room;
    import com.exmaple.optaplanner.domain.Timeslot;
    import com.exmaple.optaplanner.domain.Lesson;
    import com.exmaple.optaplanner.domain.TimeTable;
    import com.exmaple.optaplanner.rest.TimeTableResource;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.Timeout;
    
    import static org.junit.jupiter.api.Assertions.assertFalse;
    import static org.junit.jupiter.api.Assertions.assertNotNull;
    import static org.junit.jupiter.api.Assertions.assertTrue;
    
    @QuarkusTest
    public class TimeTableResourceTest {
    
        @Inject
        TimeTableResource timeTableResource;
    
        @Test
        @Timeout(600_000)
        public void solve() {
            TimeTable problem = generateProblem();
            TimeTable solution = timeTableResource.solve(problem);
            assertFalse(solution.getLessonList().isEmpty());
            for (Lesson lesson : solution.getLessonList()) {
                assertNotNull(lesson.getTimeslot());
                assertNotNull(lesson.getRoom());
            }
            assertTrue(solution.getScore().isFeasible());
        }
    
        private TimeTable generateProblem() {
            List<Timeslot> timeslotList = new ArrayList<>();
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(8, 30), LocalTime.of(9, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(9, 30), LocalTime.of(10, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(10, 30), LocalTime.of(11, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(13, 30), LocalTime.of(14, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(14, 30), LocalTime.of(15, 30)));
    
            List<Room> roomList = new ArrayList<>();
            roomList.add(new Room("Room A"));
            roomList.add(new Room("Room B"));
            roomList.add(new Room("Room C"));
    
            List<Lesson> lessonList = new ArrayList<>();
            lessonList.add(new Lesson(101L, "Math", "B. May", "9th grade"));
            lessonList.add(new Lesson(102L, "Physics", "M. Curie", "9th grade"));
            lessonList.add(new Lesson(103L, "Geography", "M. Polo", "9th grade"));
            lessonList.add(new Lesson(104L, "English", "I. Jones", "9th grade"));
            lessonList.add(new Lesson(105L, "Spanish", "P. Cruz", "9th grade"));
    
            lessonList.add(new Lesson(201L, "Math", "B. May", "10th grade"));
            lessonList.add(new Lesson(202L, "Chemistry", "M. Curie", "10th grade"));
            lessonList.add(new Lesson(203L, "History", "I. Jones", "10th grade"));
            lessonList.add(new Lesson(204L, "English", "P. Cruz", "10th grade"));
            lessonList.add(new Lesson(205L, "French", "M. Curie", "10th grade"));
            return new TimeTable(timeslotList, roomList, lessonList);
        }
    
    }
    Copy to Clipboard Toggle word wrap

    此测试会验证之后,所有较少记录都会被分配给一个时间插槽和房间。它还会验证它发现了一个可行的解决方案(没有硬约束中断)。

  2. src/main/resources/application.properties 文件中添加 test 属性:

    # The solver runs only for 5 seconds to avoid a HTTP timeout in this simple implementation.
    # It's recommended to run for at least 5 minutes ("5m") otherwise.
    quarkus.optaplanner.solver.termination.spent-limit=5s
    
    # Effectively disable this termination in favor of the best-score-limit
    %test.quarkus.optaplanner.solver.termination.spent-limit=1h
    %test.quarkus.optaplanner.solver.termination.best-score-limit=0hard/*soft
    Copy to Clipboard Toggle word wrap

通常,解析器会在 200 毫秒内找到可行的解决方案。请注意,application.properties 文件在测试期间如何覆盖 solver 终止,以便在找到可行的解决方案 (0hard thesoft) 时立即终止。这可避免硬编码一个固定时间,因为单元测试可能会在任意硬件上运行。这种方法可确保测试运行足够长,以找到可行的解决方案,即使在较慢的系统上也是如此。但是,即使在快速的系统上,它不会运行毫秒的时间超过其严格要求。

10.8. 日志记录

完成 Red Hat Build of OptaPlanner 旧时间后,您可以使用日志信息来帮助微调 ConstraintProvider 中的限制。查看 info 日志文件中的分数计算速度,以评估对您的限制的影响。以调试模式运行应用程序,以显示应用程序采取的每个步骤,或使用 trace 日志记录来记录每个步骤和每次移动。

流程

  1. 在固定时间内运行 7000 timetable 应用程序,例如五分钟。
  2. 查看日志文件中的分数计算速度,如下例所示:

    ... Solving ended: ..., score calculation speed (29455/sec), ...
    Copy to Clipboard Toggle word wrap
  3. 更改约束,在相同时间内再次运行 planning 应用程序,并查看日志文件中记录的分数计算速度。
  4. 以 debug 模式运行应用程序,以记录应用程序所做的每个步骤:

    • 要从命令行运行调试模式,请使用 -D 系统属性。
    • 要永久启用调试模式,请在 application.properties 文件中添加以下行:

      quarkus.log.category."org.optaplanner".level=debug
      Copy to Clipboard Toggle word wrap

      以下示例以 debug 模式显示 日志文件中 的输出:

      ... Solving started: time spent (67), best score (-20init/0hard/0soft), environment mode (REPRODUCIBLE), random (JDK with seed 0).
      ...     CH step (0), time spent (128), score (-18init/0hard/0soft), selected move count (15), picked move ([Math(101) {null -> Room A}, Math(101) {null -> MONDAY 08:30}]).
      ...     CH step (1), time spent (145), score (-16init/0hard/0soft), selected move count (15), picked move ([Physics(102) {null -> Room A}, Physics(102) {null -> MONDAY 09:30}]).
      ...
      Copy to Clipboard Toggle word wrap
  5. 使用 trace 日志记录来显示每个步骤,以及每个步骤的每次移动。

创建 Quarkus OptaPlanner Evolution 时间后,您可以将其与数据库集成,并创建一个基于 Web 的用户界面来显示时间表。

先决条件

  • 您有一个 Quarkus OptaPlanner eXecut timetable 应用程序。

流程

  1. 使用 Hibernate 和 Panache 将 TimeslotRoom 和 fencingon 实例存储在数据库中。如需更多信息,请参阅使用 Panache 的 Simplified Hibernate ORM
  2. 通过 REST 公开实例。如需更多信息,请参阅 编写 JSON REST 服务
  3. 更新 TimeTableResource 类,在单个事务中读取和写入 TimeTable 实例:

    package org.acme.optaplanner.rest;
    
    import javax.inject.Inject;
    import javax.transaction.Transactional;
    import javax.ws.rs.GET;
    import javax.ws.rs.POST;
    import javax.ws.rs.Path;
    
    import io.quarkus.panache.common.Sort;
    import org.acme.optaplanner.domain.Lesson;
    import org.acme.optaplanner.domain.Room;
    import org.acme.optaplanner.domain.TimeTable;
    import org.acme.optaplanner.domain.Timeslot;
    import org.optaplanner.core.api.score.ScoreManager;
    import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
    import org.optaplanner.core.api.solver.SolverManager;
    import org.optaplanner.core.api.solver.SolverStatus;
    
    @Path("/timeTable")
    public class TimeTableResource {
    
        public static final Long SINGLETON_TIME_TABLE_ID = 1L;
    
        @Inject
        SolverManager<TimeTable, Long> solverManager;
        @Inject
        ScoreManager<TimeTable, HardSoftScore> scoreManager;
    
        // To try, open http://localhost:8080/timeTable
        @GET
        public TimeTable getTimeTable() {
            // Get the solver status before loading the solution
            // to avoid the race condition that the solver terminates between them
            SolverStatus solverStatus = getSolverStatus();
            TimeTable solution = findById(SINGLETON_TIME_TABLE_ID);
            scoreManager.updateScore(solution); // Sets the score
            solution.setSolverStatus(solverStatus);
            return solution;
        }
    
        @POST
        @Path("/solve")
        public void solve() {
            solverManager.solveAndListen(SINGLETON_TIME_TABLE_ID,
                    this::findById,
                    this::save);
        }
    
        public SolverStatus getSolverStatus() {
            return solverManager.getSolverStatus(SINGLETON_TIME_TABLE_ID);
        }
    
        @POST
        @Path("/stopSolving")
        public void stopSolving() {
            solverManager.terminateEarly(SINGLETON_TIME_TABLE_ID);
        }
    
        @Transactional
        protected TimeTable findById(Long id) {
            if (!SINGLETON_TIME_TABLE_ID.equals(id)) {
                throw new IllegalStateException("There is no timeTable with id (" + id + ").");
            }
            // Occurs in a single transaction, so each initialized lesson references the same timeslot/room instance
            // that is contained by the timeTable's timeslotList/roomList.
            return new TimeTable(
                    Timeslot.listAll(Sort.by("dayOfWeek").and("startTime").and("endTime").and("id")),
                    Room.listAll(Sort.by("name").and("id")),
                    Lesson.listAll(Sort.by("subject").and("teacher").and("studentGroup").and("id")));
        }
    
        @Transactional
        protected void save(TimeTable timeTable) {
            for (Lesson lesson : timeTable.getLessonList()) {
                // TODO this is awfully naive: optimistic locking causes issues if called by the SolverManager
                Lesson attachedLesson = Lesson.findById(lesson.getId());
                attachedLesson.setTimeslot(lesson.getTimeslot());
                attachedLesson.setRoom(lesson.getRoom());
            }
        }
    
    }
    Copy to Clipboard Toggle word wrap

    这个示例包含一个 TimeTable 实例。但是,您可以为并行的多个站启用多租户和处理 TimeTable 实例。

    getTimeTable () 方法返回数据库的最新时间表。它使用 ScoreManager 方法(自动注入)计算该时间的分数,并使其可供 UI 使用。

    resolve () 方法启动一个作业,以解决当前时间集中,并将时间插槽和房间分配存储在数据库中。它使用 SolverManager.solveAndListen () 方法侦听中间最佳解决方案并相应地更新数据库。UI 使用它来显示后端仍在竞争期间的进度。

  4. 更新 TimeTableResourceTest 类,以反映 solve () 方法会立即返回,并轮询最新的解决方案,直到解析器完成:

    package org.acme.optaplanner.rest;
    
    import javax.inject.Inject;
    
    import io.quarkus.test.junit.QuarkusTest;
    import org.acme.optaplanner.domain.Lesson;
    import org.acme.optaplanner.domain.TimeTable;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.Timeout;
    import org.optaplanner.core.api.solver.SolverStatus;
    
    import static org.junit.jupiter.api.Assertions.assertFalse;
    import static org.junit.jupiter.api.Assertions.assertNotNull;
    import static org.junit.jupiter.api.Assertions.assertTrue;
    
    @QuarkusTest
    public class TimeTableResourceTest {
    
        @Inject
        TimeTableResource timeTableResource;
    
        @Test
        @Timeout(600_000)
        public void solveDemoDataUntilFeasible() throws InterruptedException {
            timeTableResource.solve();
            TimeTable timeTable = timeTableResource.getTimeTable();
            while (timeTable.getSolverStatus() != SolverStatus.NOT_SOLVING) {
                // Quick polling (not a Test Thread Sleep anti-pattern)
                // Test is still fast on fast machines and doesn't randomly fail on slow machines.
                Thread.sleep(20L);
                timeTable = timeTableResource.getTimeTable();
            }
            assertFalse(timeTable.getLessonList().isEmpty());
            for (Lesson lesson : timeTable.getLessonList()) {
                assertNotNull(lesson.getTimeslot());
                assertNotNull(lesson.getRoom());
            }
            assertTrue(timeTable.getScore().isFeasible());
        }
    
    }
    Copy to Clipboard Toggle word wrap
  5. 在这些 REST 方法之上构建 Web UI,以提供时间表的可视化表示。
  6. 查看 快速启动源代码

OptaPlanner 通过 Micrometer (一个 Java 应用程序的指标检测库)公开指标。您可以将 Micrometer 与 Prometheus 搭配使用,来监控 IANA 时间应用程序中的 OptaPlanner solver。

先决条件

  • 您已创建了 Quarkus OptaPlanner eXecut timetable 应用程序。
  • 已安装 Prometheus。有关安装 Prometheus 的详情,请查看 Prometheus 网站。

流程

  1. 将 Micrometer Prometheus 依赖项添加到 IANA timetable pom.xml 文件中:

    <dependency>
     <groupId>io.quarkus</groupId>
     <artifactId>quarkus-micrometer-registry-prometheus</artifactId>
    </dependency>
    Copy to Clipboard Toggle word wrap
  2. 启动 7000 timetable 应用程序:

    mvn compile quarkus:dev
    Copy to Clipboard Toggle word wrap
  3. 在网页浏览器中打开 http://localhost:8080/q/metric

您可以使用 OptaPlanner vaccination appointment scheduler 快速启动来开发一个高效和公平的调度。vaccination appointment scheduler 使用人工智能(AI)来排列人员优先级,并根据多个限制和优先级分配时间插槽。

先决条件

调度点有两种主要方法:系统可以让人选择一个点插槽(user-selects),或者系统分配一个插槽,并告知人在什么地方进行参与(系统自动分配)。OptaPlanner vaccination appointment 调度程序使用 system-automaticly-assigns 方法。使用 OptaPlanner vaccination appointment 调度程序,您可以创建一个应用程序,其中人员向系统提供信息,系统分配一个点。

这个方法的特性:

  • 点插槽会根据优先级分配。
  • 系统根据预配置的规划限制分配最佳点时间和位置。
  • 对于有限数量的故障点,系统不会受到大量用户负担。

这种方法通过使用规划限制为每个人创建分数,从而尽可能减少许多人。人员的分数决定何时获得缺陷。个人分数越大,他们可以获得较早的缺陷。

红帽构建的 OptaPlanner vaccination appointment 调度程序约束是硬、中型或软:

  • 硬限制不能被破坏。如果有任何硬约束无法正常工作,则计划不可激活,且无法执行:

    • capacity :在任何位置没有任何时间不用尽书 vaccine 容量。
    • Vaccine max age: 如果 vaccine 具有最长期限,请不要在第一次执行时将其管理的时间超过 vaccine 最长期限。确保为人获得适合其年龄的 vaccine 类型。例如,对于最长期限限制为 65 年,不要为旧人员分配 75 年。
    • 所需的 vaccine 类型:使用所需的 vaccine 类型。例如,vaccine 的第二个操作必须与第一个操作相同。
    • 就绪日期:管理指定日期或之后的 vaccine。例如,如果人收到第二个操作,请在特定 vaccine 类型的推荐最早的 vaccination 日期前不对其进行管理,例如 26 天。
    • 到期日期:管理指定日期或之前的 vaccine。例如,如果人收到第二个操作,请在特定 vaccine 的推荐时间前对其进行管理,例如,第一次执行 3 个月。
    • 限制最大旋转距离:将每个人分配给接近一组 vaccination 数据中心之一。这通常是三个数据中心之一。这个限制是通过旋转时间而不是距离来计算的,因此,一个人位于 urban 区域中的人通常具有比同一区域更低的距离。
  • 中等约束决定在没有足够容量为任何人分配点时没有达到点的点。这通过受限规划调用:

    • schedule second dose vaccinations:除非理想的日期不在规划窗口外,不要保留任何第二个 dose vaccination appointments unassigned。
    • 根据优先级评级调度人员:每个人都有优先级评级。这通常是它们的年龄,但如果它们是健康的 worker,则可能要高得多。仅保留具有最低优先级评级的用户。它们将在下一次运行中考虑。这个约束比之前的约束是软的,因为第二个操作总是优先于优先级的评级。
  • 软限制不应中断:

    • 首选凭证 :如果人拥有首选凭证中心,为他们授予该中心上的点。
    • 距离:最小化个人必须到达其分配的预测中心的距离。
    • 理想的日期:管理 vaccine on 或尽可能接近指定日期。例如,如果人收到第二个操作,请在特定 vaccine 的理想日期上对其进行管理,例如 28 天。这个约束比距离约束相比,避免在国家内向人发送半次,成为他们理想的日期。
    • 优先级评级:调度在计划窗口中之前具有较高的优先级评级的人员。这个约束比距离约束是软的,以避免在国家范围内发送半方。此约束也比理想的日期约束要软,因为第二个条件优先于优先级的评级。

硬限制会与其他硬限制进行权重。软限制会与其他软限制加权。但是,硬限制总是优先于中等和软限制。如果硬约束中断,则计划不可取。但是,如果没有硬约束,则考虑软和中等约束来确定优先级。由于用户通常比可用点插槽更多,所以您必须优先选择。最后,首先先分配第二个问题,以避免创建稍后系统的可能性。之后,用户会根据优先级评级进行分配。任何人都以优先级评级开始,这是其年龄。这比年龄较早的人优先选择旧的人员。之后,位于特定优先级组中的人员会收到几百个额外点。这因组的优先级而异。例如,nurses 可能会收到额外的 1000 点。这样,旧的 nurses 优先于 younger nurses,而您的 nurses 则优先于不是微不足的人员。下表演示了这个概念:

Expand
表 11.1. 优先级评级表
年龄作业(job)优先级评级

60

nurse

1060

33

nurse

1033

71

弃用

71

52

办公室 worker

52

11.1.2. Red Hat build of OptaPlanner solver

在 OptaPlanner 的核心上,使用问题数据集和覆盖规划限制和配置的引擎。问题数据集包含有关人员、vaccines 和 vaccination 数据中心的所有信息。该解析器通过各种数据组合进行,最终决定优化点计划,以及分配给特定中心上提示点的人员。以下图例显示了相应的调度:

11.1.3. 持续规划

持续规划是同时管理一个或多个即将推出的规划周期的技术,并每月重复该过程,每周、每天、每天、每小时或更频繁地重复此过程。规划窗口以指定间隔递增。下面的图图显示了每天更新的两周规划窗口:

两个每周规划窗口被分为一半。第一个每周处于 published 状态,第二个每周处于草案状态。人员在计划窗口发布的和草案部分中被分配到点。但是,只有计划窗口发布部分的人员才会收到它们的点。其他点仍然可以在下次运行中轻松更改。这样做可让 OptaPlanner 在再次运行 solver 时更改草案部分中的故障点(如果需要)。例如,如果需要第二天做的人具有周一日期,并且一个理想的日期为周五,OptaPlanner 不必为他们授予周一的解点。

您可以确定规划窗口的大小,但只了解问题空间的大小。问题空间是创建计划的所有各种元素。您提前规划的时间越大,问题空间越大。

11.1.4. 固定计划实体

如果您持续规划每天,在已经分配给人的两周内会存在点。为确保 appointments 没有双书,OptaPlanner 会将现有的点标记为分配,方法是固定它们。固定用于定位一个或多个特定的分配,并强制 OptaPlanner 调度这些固定分配。固定规划实体(如点点)在竞争过程中不会改变。

实体是否固定,还是由点状态决定。缩写可以有五个状态: OpenInvitedAcceptedRejectedRescheduled

注意

您实际上不会在快速启动演示代码中直接看到这些状态,因为 OptaPlanner 引擎只对点是否固定感兴趣。

您需要能够计划已经调度的点。固定 InvitedAccepted 状态的点。带有 OpenRescheduleRejected 状态的点不会被固定,并可用于调度。

在本例中,当解析器在发布和草案范围内在两个周计划窗口中运行时,它会搜索。除了未计划输入数据外,解析器还考虑任何未 固定实体,以及 Open、Reschedule 或 Rejected 状态。如果解析器每天运行,您会在运行 solver 前看到一个新的日期添加到计划中。

请注意,新一天的点已被分配,之前在规划窗口的草案部分调度的 Amy 和 Edna 会被调度到窗口的发布部分。这是因为 Gus 和 Hugo 请求重新调度。这不会造成任何混淆,因为 Amy 和 Edna 不会通知其草案日期。现在,由于他们在计划窗口的发布部分中有点,因此它们会收到通知并询问接受或拒绝它们的点,其点现已固定。

下载 OptaPlanner vaccination appointment 调度程序快速启动存档,以 Quarkus 开发模式启动它,并在浏览器中查看应用程序。Quarkus 开发模式允许您在其运行时进行更改和更新应用程序。

流程

  1. 导航到红帽客户门户网站中的 Software Downloads 页面(需要登录),然后从下拉菜单中选择产品和版本:

    • 产品: 红帽构建的 OptaPlanner
    • Version: 8.33
  2. 下载 Red Hat build of OptaPlanner 8.33 Quick Starts
  3. 提取 rhbop-8.33.0-optaplanner-quickstarts-sources.zip 文件。

    提取的 org.optaplanner.optaplanner-quickstarts-8.33.0.Final-redhat-00004/use-cases/vaccination-scheduling 目录包含示例源代码。

  4. 进入 org.optaplanner.optaplanner-quickstarts-8.33.0.Final-redhat-00004/use-cases/vaccination-scheduling 目录。
  5. 输入以下命令在开发模式中启动 OptaPlanner vaccination appointment 调度程序:

    $ mvn quarkus:dev
    Copy to Clipboard Toggle word wrap
  6. 要查看 OptaPlanner vaccination appointment 调度程序,请在 Web 浏览器中输入以下 URL。

    http://localhost:8080/
    Copy to Clipboard Toggle word wrap
  7. 要运行 OptaPlanner vaccination appointment scheduler,请点 Solve
  8. 更改源代码,然后按 F5 键刷新浏览器。请注意,您所做的更改现已可用。

当您完成 OptaPlanner vaccination appointment scheduler in quarkus:dev 模式的开发工作后,以传统的 jar 文件运行应用程序。

先决条件

  • 您已下载了 OptaPlanner vaccination appointment 调度程序快速启动。

流程

  1. 进入 /use-cases/vaccination-scheduling 目录。
  2. 要编译 OptaPlanner vaccination appointment 调度程序,请输入以下命令:

    $ mvn package
    Copy to Clipboard Toggle word wrap
  3. 要运行编译的 OptaPlanner vaccination appointment 调度程序,请输入以下命令:

    $ java -jar ./target/quarkus-app/quarkus-run.jar
    Copy to Clipboard Toggle word wrap
    注意

    要在端口 8081 上运行应用程序,请在上一命令中添加 -Dquarkus.http.port=8081

  4. 要启动 OptaPlanner vaccination appointment 调度程序,请在 Web 浏览器中输入以下 URL。

    http://localhost:8080/
    Copy to Clipboard Toggle word wrap

员工调度程序快速启动应用程序分配员工以在机构的不同位置上过渡。例如,您可以使用应用程序在 nurses 之间分发转换,保护任务在多个位置间移动,或者在 worker 间移动行。

最佳员工调度必须考虑多个变量。例如,在不同位置上移动需要不同的知识。另外,一些员工可能不适用于一些时间插槽,或者可能首选特定的时间插槽。此外,员工可以有一个合同,限制员工可在单一时间段内工作的小时数。

红帽为此入门应用程序构建的 OptaPlanner 规则使用硬和软限制。在优化过程中,计划器引擎可能无法违反硬限制,例如,如果员工不可用(sick),或者员工无法在单个迁移中工作两个点。Planner 引擎尝试遵循软限制,如员工的首选项无法进行特定的转换,但如果最佳解决方案需要,则可以违反它们。

先决条件

  • 已安装 OpenJDK 11 或更高版本。Red Hat build of Open JDK 位于红帽客户门户网站中的 Software Downloads 页面中(需要登录)。
  • 已安装 Apache Maven 3.8 或更高版本。Maven 可从 Apache Maven 项目网站 获得。
  • 提供了 IDE,如 IntelliJ IDEA、VSCode 或 Eclipse。

12.1. 下载并运行 OptaPlanner 员工调度程序

下载 OptaPlanner 员工调度程序快速启动存档,以 Quarkus 开发模式启动它,并在浏览器中查看应用程序。Quarkus 开发模式允许您在其运行时进行更改和更新应用程序。

流程

  1. 导航到红帽客户门户网站中的 Software Downloads 页面(需要登录),然后从下拉菜单中选择产品和版本:

    • 产品: 红帽构建的 OptaPlanner
    • Version: 8.33
  2. 下载 Red Hat build of OptaPlanner 8.33 Quick Starts
  3. 提取 rhbop-8.33.0-optaplanner-quickstarts-sources.zip 文件。
  4. 导航到 org.optaplanner.optaplanner-quickstarts-8.33.0.Final-redhat-00004/use-cases/employee-scheduling 目录。
  5. 输入以下命令在开发模式中启动 OptaPlanner 员工调度程序:

    $ mvn quarkus:dev
    Copy to Clipboard Toggle word wrap
  6. 要查看 OptaPlanner 员工调度程序,请在 Web 浏览器中输入以下 URL。

    http://localhost:8080/
    Copy to Clipboard Toggle word wrap
  7. 要运行 OptaPlanner 员工调度程序,请单击 Solve
  8. 更改源代码,然后按 F5 键刷新浏览器。请注意,您所做的更改现已可用。

12.2. 软件包并运行 OptaPlanner 员工调度程序

当您在 quarkus:dev 模式中对 OptaPlanner 员工调度程序完成开发工作后,以传统的 jar 文件运行应用程序。

先决条件

  • 您已下载了 OptaPlanner 员工的调度快速启动。

流程

  1. 进入 /use-cases/vaccination-scheduling 目录。
  2. 要编译 OptaPlanner 员工调度程序,请输入以下命令:

    $ mvn package
    Copy to Clipboard Toggle word wrap
  3. 要运行编译的 OptaPlanner 员工调度程序,请输入以下命令:

    $ java -jar ./target/quarkus-app/quarkus-run.jar
    Copy to Clipboard Toggle word wrap
    注意

    要在端口 8081 上运行应用程序,请在上一命令中添加 -Dquarkus.http.port=8081

  4. 要启动 OptaPlanner 员工调度程序,请在 Web 浏览器中输入以下 URL。

    http://localhost:8080/
    Copy to Clipboard Toggle word wrap

本指南指导您完成创建带有 OptaPlanner 约束(AI)的 Spring Boot 应用程序的过程。您将构建 REST 应用程序,该应用程序针对领导和专业者进行了优化。

您的服务将通过 AI 遵循以下硬和软 调度限制,自动将附件实例分配给 TimeslotRoom 实例:

  • 空间最多可以同时有一个小时间。
  • 一个架构师可以同时参与一个小时间。
  • 每天最多可同时参与一个小时间。
  • 交互式人员更喜欢在单个房间参与。
  • 指导人员更喜欢参与较少的活动,并排解少量间的差距。

mathematly 认为是 NP 硬性问题。这意味着难以扩展。只需将所有可能的组合与括号一起进行迭代,可以在非平量数据集中使用数百万年,即使在超级计算器上也是如此。AI 约束解析器(如 OptaPlanner)有高级算法,可在合理的时间内提供最接近的解决方案。认为是合理的时间,这取决于您的问题的目标。

先决条件

  • 已安装 OpenJDK 11 或更高版本。Red Hat build of Open JDK 位于红帽客户门户网站中的 Software Downloads 页面中(需要登录)。
  • 已安装 Apache Maven 3.8 或更高版本。Maven 可从 Apache Maven 项目网站 获得。
  • 提供了 IDE,如 IntelliJ IDEA、VSCode 或 Eclipse。

如果要看到带有 Spring Boot 产品的 Red Hat Build of OptaPlanner 的 SVVP timetable 项目的完整示例,请从红帽客户门户网站下载入门程序应用程序。

流程

  1. 导航到红帽客户门户网站中的 Software Downloads 页面(需要登录),然后从下拉菜单中选择产品和版本:

    • 产品: 红帽构建的 OptaPlanner
    • Version: 8.33
  2. 下载 Red Hat build of OptaPlanner 8.33 Quick Starts
  3. 提取 rhbop-8.33.0-optaplanner-quickstarts-sources.zip 文件。

    解压的 org.optaplanner.optaplanner-quickstarts-8.33.0.Final-redhat-00004/use-cases/114-timetabling 目录包含示例源代码。

  4. 进入 org.optaplanner.optaplanner-quickstarts-8.33.0.Final-redhat-00004/use-cases/rhcs-timetabling 目录。
  5. 下载 Red Hat Build of OptaPlanner 8.33.0 Maven Repositroy (rhbop-8.33.0-optaplanner-maven-repository.zip)。
  6. 提取 rhbop-8.33.0-optaplanner-maven-repository.zip 文件。
  7. rhbop-8.33.0-optaplanner/maven-repository 子目录的内容复制到 ~/.m2/repository 目录中。
  8. 进入 org.optaplanner.optaplanner-quickstarts-8.33.0.Final-redhat-00004/411/java-spring-boot 目录。
  9. 输入以下命令来构建 Spring Boot eXecut timetabling 项目:

    mvn clean install -DskipTests
    Copy to Clipboard Toggle word wrap
  10. 要构建 Spring Boot phone 时间建立项目,请输入以下命令:

    mvn spring-boot:run -DskipTests
    Copy to Clipboard Toggle word wrap
  11. 要查看项目,请在 Web 浏览器中输入以下 URL:

    http://localhost:8080/
    Copy to Clipboard Toggle word wrap

13.2. 对域对象建模

红帽构建的 OptaPlanner 时间项目的目标是将每项分配给一个时间插槽和房间。要做到这一点,添加三个类: Timeslotinit on 和 Room,如下图所示:

timeslot

Timeslot 类代表当减少活动为过期的时间间隔时,例如,Monday 10:30 - 11:30Tuesday 13:30 - 14:30。在这个示例中,所有时间段都有相同的持续时间,在 lunch 或其他中断过程中没有时间插槽。

一个时间插槽没有日期,因为每周计划只重复一次。不需要 持续规划。timeslot 被称为 问题事实,因为公司不会更改 Timeslot 实例。此类类不需要任何特定于 OptaPlanner 的注解。

房间

Room 类代表更小活动的位置,例如 Room ARoom B。在本例中,所有房间都没有容量限制,它们可以容纳所有定义。

空间 实例在竞争过程中不会改变,因此 Room 也是 问题事实

lesson

在小时间中,由周一类代表,受一组参与者代表的规定,例如,A.Turing for 9th rating 或 Chemistry by M.Curie 代表第 10 等级如果一个主题是按同一站组相同的每周的多次,则有多个仅可通过 id 区分的实例。例如,第 9 等级每周有六个数学。

在 OptaPlanner 期间,OptaPlanner 会更改周一类的 timeslotroom 字段,以将每个小时间分配给一个时间插槽和房间。因为 OptaPlanner 更改这些字段,因此 Apple on 是一个 规划实体

上图中的大部分字段都包含输入数据,但 orange 字段除外。在输入数据中取消分配较少的 timeslotroom 字段,并在输出数据中分配(而非 null)。OptaPlanner 在参与过程中会更改这些字段。此类字段称为 规划变量。为了使 OptaPlanner 能够识别它们,timeslotroom 字段都需要 @PlanningVariable 注释。它们包含类属性需要 @PlanningEntity 注释。

流程

  1. 创建 src/main/java/com/example/domain/Timeslot.java 类:

    package com.example.domain;
    
    import java.time.DayOfWeek;
    import java.time.LocalTime;
    
    public class Timeslot {
    
        private DayOfWeek dayOfWeek;
        private LocalTime startTime;
        private LocalTime endTime;
    
        private Timeslot() {
        }
    
        public Timeslot(DayOfWeek dayOfWeek, LocalTime startTime, LocalTime endTime) {
            this.dayOfWeek = dayOfWeek;
            this.startTime = startTime;
            this.endTime = endTime;
        }
    
        @Override
        public String toString() {
            return dayOfWeek + " " + startTime.toString();
        }
    
        // ********************************
        // Getters and setters
        // ********************************
    
        public DayOfWeek getDayOfWeek() {
            return dayOfWeek;
        }
    
        public LocalTime getStartTime() {
            return startTime;
        }
    
        public LocalTime getEndTime() {
            return endTime;
        }
    
    }
    Copy to Clipboard Toggle word wrap

    请注意,toString () 方法保留输出短,因此更易于阅读 OptaPlanner 的 DEBUGTRACE 日志,如稍后所示。

  2. 创建 src/main/java/com/example/domain/Room.java 类:

    package com.example.domain;
    
    public class Room {
    
        private String name;
    
        private Room() {
        }
    
        public Room(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return name;
        }
    
        // ********************************
        // Getters and setters
        // ********************************
    
        public String getName() {
            return name;
        }
    
    }
    Copy to Clipboard Toggle word wrap
  3. 创建 src/main/java/com/example/domain/Lesson.java 类:

    package com.example.domain;
    
    import org.optaplanner.core.api.domain.entity.PlanningEntity;
    import org.optaplanner.core.api.domain.variable.PlanningVariable;
    
    @PlanningEntity
    public class Lesson {
    
        private Long id;
    
        private String subject;
        private String teacher;
        private String studentGroup;
    
        @PlanningVariable(valueRangeProviderRefs = "timeslotRange")
        private Timeslot timeslot;
    
        @PlanningVariable(valueRangeProviderRefs = "roomRange")
        private Room room;
    
        private Lesson() {
        }
    
        public Lesson(Long id, String subject, String teacher, String studentGroup) {
            this.id = id;
            this.subject = subject;
            this.teacher = teacher;
            this.studentGroup = studentGroup;
        }
    
        @Override
        public String toString() {
            return subject + "(" + id + ")";
        }
    
        // ********************************
        // Getters and setters
        // ********************************
    
        public Long getId() {
            return id;
        }
    
        public String getSubject() {
            return subject;
        }
    
        public String getTeacher() {
            return teacher;
        }
    
        public String getStudentGroup() {
            return studentGroup;
        }
    
        public Timeslot getTimeslot() {
            return timeslot;
        }
    
        public void setTimeslot(Timeslot timeslot) {
            this.timeslot = timeslot;
        }
    
        public Room getRoom() {
            return room;
        }
    
        public void setRoom(Room room) {
            this.room = room;
        }
    
    }
    Copy to Clipboard Toggle word wrap

    bring on 类有一个 @PlanningEntity 注释,因此 OptaPlanner 知道该类在周五期间发生了变化,因为它包含一个或多个规划变量。

    timeslot 字段有一个 @PlanningVariable 注释,因此 OptaPlanner 知道它可以更改其值。要查找分配给此字段的潜在 Timeslot 实例,OptaPlanner 使用 valueRangeProviderRefs 属性连接到提供 List<Timeslot&gt; 的值范围供应商。有关值范围供应商的详情,请查看 第 13.4 节 “在计划解决方案中收集域对象”

    由于同样原因,room 字段也有一个 @PlanningVariable 注释。

13.3. 定义约束并计算分数

当遇到问题时,分数 代表特定解决方案的质量。分数越大。红帽构建的 OptaPlanner 会寻找最佳解决方案,这是可用时间提供的最高分数的解决方案。它可能 是最佳解决方案

因为 timetable 示例用例有硬和软限制,因此请使用 Hard SoftScore 类来代表分数:

  • 硬限制不能被破坏。例如: 一个房间可以同时有一个小时间。
  • 软限制不应中断。例如: 一个公司更喜欢在单个房间参与。

硬限制会与其他硬限制进行权重。软限制会与其他软限制加权。硬限制始终超过软限制,无论它们对应的权重是什么。

要计算分数,您可以实施 EasyScoreCalculator 类:

public class TimeTableEasyScoreCalculator implements EasyScoreCalculator<TimeTable> {

    @Override
    public HardSoftScore calculateScore(TimeTable timeTable) {
        List<Lesson> lessonList = timeTable.getLessonList();
        int hardScore = 0;
        for (Lesson a : lessonList) {
            for (Lesson b : lessonList) {
                if (a.getTimeslot() != null && a.getTimeslot().equals(b.getTimeslot())
                        && a.getId() < b.getId()) {
                    // A room can accommodate at most one lesson at the same time.
                    if (a.getRoom() != null && a.getRoom().equals(b.getRoom())) {
                        hardScore--;
                    }
                    // A teacher can teach at most one lesson at the same time.
                    if (a.getTeacher().equals(b.getTeacher())) {
                        hardScore--;
                    }
                    // A student can attend at most one lesson at the same time.
                    if (a.getStudentGroup().equals(b.getStudentGroup())) {
                        hardScore--;
                    }
                }
            }
        }
        int softScore = 0;
        // Soft constraints are only implemented in the "complete" implementation
        return HardSoftScore.of(hardScore, softScore);
    }

}
Copy to Clipboard Toggle word wrap

不幸的是,这个解决方案无法很好地扩展,因为它不可缩小:每次将小时间分配给不同的时区或空间时,所有定义都会被重新评估以计算新的分数。

更好的解决方案是创建一个 src/main/java/com/example/solver/TimeTableConstraintProvider.java 类来执行增量分数计算。这个类使用 OptaPlanner 的 ConstraintStream API,该 API 被 Java 8 Streams 和 SQL 增加。ConstraintProvider 扩展比 EasyScoreCalculator: O (n)而不是O(n)而不是 O(n)更好的 magnitude 顺序。

流程

创建以下 src/main/java/com/example/solver/TimeTableConstraintProvider.java 类:

package com.example.solver;

import com.example.domain.Lesson;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.api.score.stream.Constraint;
import org.optaplanner.core.api.score.stream.ConstraintFactory;
import org.optaplanner.core.api.score.stream.ConstraintProvider;
import org.optaplanner.core.api.score.stream.Joiners;

public class TimeTableConstraintProvider implements ConstraintProvider {

    @Override
    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
        return new Constraint[] {
                // Hard constraints
                roomConflict(constraintFactory),
                teacherConflict(constraintFactory),
                studentGroupConflict(constraintFactory),
                // Soft constraints are only implemented in the "complete" implementation
        };
    }

    private Constraint roomConflict(ConstraintFactory constraintFactory) {
        // A room can accommodate at most one lesson at the same time.

        // Select a lesson ...
        return constraintFactory.forEach(Lesson.class)
                // ... and pair it with another lesson ...
                .join(Lesson.class,
                        // ... in the same timeslot ...
                        Joiners.equal(Lesson::getTimeslot),
                        // ... in the same room ...
                        Joiners.equal(Lesson::getRoom),
                        // ... and the pair is unique (different id, no reverse pairs)
                        Joiners.lessThan(Lesson::getId))
                // then penalize each pair with a hard weight.
                .penalize(HardSoftScore.ONE_HARD)
                .asConstraint("Room conflict");
    }

    private Constraint teacherConflict(ConstraintFactory constraintFactory) {
        // A teacher can teach at most one lesson at the same time.
        return constraintFactory.forEach(Lesson.class)
                .join(Lesson.class,
                        Joiners.equal(Lesson::getTimeslot),
                        Joiners.equal(Lesson::getTeacher),
                        Joiners.lessThan(Lesson::getId))
                        .penalize(HardSoftScore.ONE_HARD)
                        .asConstraint("Teacher conflict");
    }

    private Constraint studentGroupConflict(ConstraintFactory constraintFactory) {
        // A student can attend at most one lesson at the same time.
        return constraintFactory.forEach(Lesson.class)
                .join(Lesson.class,
                        Joiners.equal(Lesson::getTimeslot),
                        Joiners.equal(Lesson::getStudentGroup),
                        Joiners.lessThan(Lesson::getId))
                        .penalize(HardSoftScore.ONE_HARD)
                        .asConstraint("Student group conflict");
    }

}
Copy to Clipboard Toggle word wrap

13.4. 在计划解决方案中收集域对象

TimeTable 实例将单个数据集的所有 TimeslotRoom 和 Appleon 实例嵌套。另外,由于它包含所有 lessons,每个都具有特定的计划变量状态,所以它是一个 规划解决方案,它具有分数:

  • 如果仅未被分配,则它是一个 未初始化的 解决方案,例如,分数为 -4init/0hard/0soft 的解决方案。
  • 如果它破坏硬限制,则是一个不可行的解决方案,例如,分数为 -2hard/-3soft 的解决方案。
  • 如果它遵循所有硬限制,那么它是一种可行的解决方案,例如,分数为 0hard/-7soft 的解决方案。

TimeTable 类有一个 @PlanningSolution 注释,因此 Red Hat Build of OptaPlanner 知道这个类包含所有输入和输出数据。

具体来说,这个类是问题的输入:

  • 带有所有时间插槽的 timeslotList 字段

    • 这是问题事实列表,因为它们在竞争过程中不会改变。
  • 包含所有空间的 roomList 字段

    • 这是问题事实列表,因为它们在竞争过程中不会改变。
  • 带有所有 lessons 的 lessonList 字段

    • 这是规划实体的列表,因为它们在竞争期间发生了变化。
    • 每个 周一

      • timeslotroom 字段的值通常仍然为空,因此未分配。它们正在规划变量。
      • 其他字段(如 subject、MrrslirpGroup )已填写。这些字段是问题属性。

但是,此类也是解决方案的输出:

  • 一个 lessonList 字段,其中每个 每天 实例在周五后都有非空 timeslotroom 字段
  • 代表输出解决方案的质量的 score 字段,例如 0hard/-5soft

流程

创建 src/main/java/com/example/domain/TimeTable.java 类:

package com.example.domain;

import java.util.List;

import org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty;
import org.optaplanner.core.api.domain.solution.PlanningScore;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;

@PlanningSolution
public class TimeTable {

    @ValueRangeProvider(id = "timeslotRange")
    @ProblemFactCollectionProperty
    private List<Timeslot> timeslotList;

    @ValueRangeProvider(id = "roomRange")
    @ProblemFactCollectionProperty
    private List<Room> roomList;

    @PlanningEntityCollectionProperty
    private List<Lesson> lessonList;

    @PlanningScore
    private HardSoftScore score;

    private TimeTable() {
    }

    public TimeTable(List<Timeslot> timeslotList, List<Room> roomList,
            List<Lesson> lessonList) {
        this.timeslotList = timeslotList;
        this.roomList = roomList;
        this.lessonList = lessonList;
    }

    // ********************************
    // Getters and setters
    // ********************************

    public List<Timeslot> getTimeslotList() {
        return timeslotList;
    }

    public List<Room> getRoomList() {
        return roomList;
    }

    public List<Lesson> getLessonList() {
        return lessonList;
    }

    public HardSoftScore getScore() {
        return score;
    }

}
Copy to Clipboard Toggle word wrap

值范围供应商

timeslotList 字段是一个值范围供应商。它包含 OptaPlanner 可以从中分配 to to the timeslot 字段的 Timeslot 实例。timeslotList 字段有一个 @ValueRangeProvider 注释来连接这两个注解,方法是将 id 与大括号中的 @PlanningVariablevalueRangeProviderRefs 匹配。

遵循同样的逻辑,roomList 字段也具有 @ValueRangeProvider 注释。

问题事实和规划实体属性

此外,OptaPlanner 需要知道它可以更改哪些附件,以及如何检索用于 TimeTableConstraintProviderTimeslotRoom 实例。

timeslotListroomList 字段有一个 @ProblemFactCollectionProperty 注解,因此您的 TimeTableConstraintProvider 可以从这些实例中选择。

lessonList 有一个 @PlanningEntityCollectionProperty 注解,因此 OptaPlanner 可以在将来更改它们,而您的 TimeTableConstraintProvider 也可以从这些中选择。

13.5. 创建 Timetable 服务

现在,您已准备好将所有内容放在一起,并创建一个 REST 服务。但是,在 REST 线程上计划问题会导致 HTTP 超时问题。因此,Spring Boot Start 注入 SolverManager,它会在一个单独的线程池中运行 solvers,并可并行解决多个数据集。

流程

创建 src/main/java/com/example/solver/TimeTableController.java 类:

package com.example.solver;

import java.util.UUID;
import java.util.concurrent.ExecutionException;

import com.example.domain.TimeTable;
import org.optaplanner.core.api.solver.SolverJob;
import org.optaplanner.core.api.solver.SolverManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/timeTable")
public class TimeTableController {

    @Autowired
    private SolverManager<TimeTable, UUID> solverManager;

    @PostMapping("/solve")
    public TimeTable solve(@RequestBody TimeTable problem) {
        UUID problemId = UUID.randomUUID();
        // Submit the problem to start solving
        SolverJob<TimeTable, UUID> solverJob = solverManager.solve(problemId, problem);
        TimeTable solution;
        try {
            // Wait until the solving ends
            solution = solverJob.getFinalBestSolution();
        } catch (InterruptedException | ExecutionException e) {
            throw new IllegalStateException("Solving failed.", e);
        }
        return solution;
    }

}
Copy to Clipboard Toggle word wrap

在本例中,初始实施会等待 solver 完成,这仍然可能导致 HTTP 超时。完整的 实现可避免 HTTP 超时更小。

13.6. 设置解析器终止时间

如果您的计划应用程序没有终止设置或终止事件,则理论上会永久运行,并且实际上会导致 HTTP 超时错误。要防止发生这种情况,请使用 optaplanner.solver.termination.spent-limit 参数指定应用程序终止的时间长度。在大多数应用程序中,将时间设置为至少五分钟(5m)。但是,在 Timetable 示例中,将过期时间限制为 5 秒,这足够短,以避免 HTTP 超时。

流程

使用以下内容创建 src/main/resources/application.properties 文件:

quarkus.optaplanner.solver.termination.spent-limit=5s
Copy to Clipboard Toggle word wrap

13.7. 使应用程序可执行

完成红帽构建的 OptaPlanner Spring Boot timetable 项目后,将所有内容打包成一个可执行的 JAR 文件,由标准 Java main () 方法驱动。

先决条件

  • 您有一个已完成的 OptaPlanner Spring Boot timetable 项目。

流程

  1. 使用以下内容创建 TimeTableSpringBootApp.java 类:

    package com.example;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class TimeTableSpringBootApp {
    
        public static void main(String[] args) {
            SpringApplication.run(TimeTableSpringBootApp.class, args);
        }
    
    }
    Copy to Clipboard Toggle word wrap
  2. 将由 Spring Initializr 创建的 src/main/java/com/example/DemoApplication.java 类替换为 TimeTableSpringBootApp.java 类。
  3. 运行 TimeTableSpringBootApp.java 类作为常规 Java 应用程序的主类。

13.7.1. 尝试 timetable 应用程序

启动 Red Hat Build of OptaPlanner Spring Boot timetable 应用程序后,您可以使用您想要的任何 REST 客户端测试 REST 服务。这个示例使用 Linux curl 命令来发送 POST 请求。

先决条件

  • OptaPlanner Spring Boot timetable 应用程序正在运行。

流程

使用以下命令:

$ curl -i -X POST http://localhost:8080/timeTable/solve -H "Content-Type:application/json" -d '{"timeslotList":[{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"}],"roomList":[{"name":"Room A"},{"name":"Room B"}],"lessonList":[{"id":1,"subject":"Math","teacher":"A. Turing","studentGroup":"9th grade"},{"id":2,"subject":"Chemistry","teacher":"M. Curie","studentGroup":"9th grade"},{"id":3,"subject":"French","teacher":"M. Curie","studentGroup":"10th grade"},{"id":4,"subject":"History","teacher":"I. Jones","studentGroup":"10th grade"}]}'
Copy to Clipboard Toggle word wrap

大约 5 秒后,终止花费 application.properties 中定义的时间,服务会返回类似以下示例的输出:

HTTP/1.1 200
Content-Type: application/json
...

{"timeslotList":...,"roomList":...,"lessonList":[{"id":1,"subject":"Math","teacher":"A. Turing","studentGroup":"9th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},"room":{"name":"Room A"}},{"id":2,"subject":"Chemistry","teacher":"M. Curie","studentGroup":"9th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"},"room":{"name":"Room A"}},{"id":3,"subject":"French","teacher":"M. Curie","studentGroup":"10th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},"room":{"name":"Room B"}},{"id":4,"subject":"History","teacher":"I. Jones","studentGroup":"10th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"},"room":{"name":"Room B"}}],"score":"0hard/0soft"}
Copy to Clipboard Toggle word wrap

请注意,应用程序将所有四个小项分配给两个时间插槽之一,以及两个空间之一。另请注意,它符合所有硬限制。例如,M. Curie 的两个定义位于不同的时间段内。

在服务器端,info 日志显示 OptaPlanner 在 5 秒内执行的操作:

... Solving started: time spent (33), best score (-8init/0hard/0soft), environment mode (REPRODUCIBLE), random (JDK with seed 0).
... Construction Heuristic phase (0) ended: time spent (73), best score (0hard/0soft), score calculation speed (459/sec), step total (4).
... Local Search phase (1) ended: time spent (5000), best score (0hard/0soft), score calculation speed (28949/sec), step total (28398).
... Solving ended: time spent (5000), best score (0hard/0soft), score calculation speed (28524/sec), phase total (2), environment mode (REPRODUCIBLE).
Copy to Clipboard Toggle word wrap

13.7.2. 构建应用程序

良好的应用程序包括测试覆盖。这个示例测试 OptaPlanner Spring Boot 应用程序的 Timetable Red Hat Build。它使用 JUnit 测试来生成测试数据集,并将其发送到 TimeTableController 来解决。

流程

使用以下内容创建 src/test/java/com/example/solver/TimeTableControllerTest.java 类:

package com.example.solver;

import java.time.DayOfWeek;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;

import com.example.domain.Lesson;
import com.example.domain.Room;
import com.example.domain.TimeTable;
import com.example.domain.Timeslot;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

@SpringBootTest(properties = {
        "optaplanner.solver.termination.spent-limit=1h", // Effectively disable this termination in favor of the best-score-limit
        "optaplanner.solver.termination.best-score-limit=0hard/*soft"})
public class TimeTableControllerTest {

    @Autowired
    private TimeTableController timeTableController;

    @Test
    @Timeout(600_000)
    public void solve() {
        TimeTable problem = generateProblem();
        TimeTable solution = timeTableController.solve(problem);
        assertFalse(solution.getLessonList().isEmpty());
        for (Lesson lesson : solution.getLessonList()) {
            assertNotNull(lesson.getTimeslot());
            assertNotNull(lesson.getRoom());
        }
        assertTrue(solution.getScore().isFeasible());
    }

    private TimeTable generateProblem() {
        List<Timeslot> timeslotList = new ArrayList<>();
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(8, 30), LocalTime.of(9, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(9, 30), LocalTime.of(10, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(10, 30), LocalTime.of(11, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(13, 30), LocalTime.of(14, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(14, 30), LocalTime.of(15, 30)));

        List<Room> roomList = new ArrayList<>();
        roomList.add(new Room("Room A"));
        roomList.add(new Room("Room B"));
        roomList.add(new Room("Room C"));

        List<Lesson> lessonList = new ArrayList<>();
        lessonList.add(new Lesson(101L, "Math", "B. May", "9th grade"));
        lessonList.add(new Lesson(102L, "Physics", "M. Curie", "9th grade"));
        lessonList.add(new Lesson(103L, "Geography", "M. Polo", "9th grade"));
        lessonList.add(new Lesson(104L, "English", "I. Jones", "9th grade"));
        lessonList.add(new Lesson(105L, "Spanish", "P. Cruz", "9th grade"));

        lessonList.add(new Lesson(201L, "Math", "B. May", "10th grade"));
        lessonList.add(new Lesson(202L, "Chemistry", "M. Curie", "10th grade"));
        lessonList.add(new Lesson(203L, "History", "I. Jones", "10th grade"));
        lessonList.add(new Lesson(204L, "English", "P. Cruz", "10th grade"));
        lessonList.add(new Lesson(205L, "French", "M. Curie", "10th grade"));
        return new TimeTable(timeslotList, roomList, lessonList);
    }

}
Copy to Clipboard Toggle word wrap

此测试会验证之后,所有较少记录都会被分配给一个时间插槽和房间。它还会验证它发现了一个可行的解决方案(没有硬约束中断)。

通常,解析器会在 200 毫秒内找到可行的解决方案。请注意,@SpringBootTest 注释 的属性 如何覆盖 solver 终止,以便在找到可行的解决方案(0hard soft)时立即终止。这可避免硬编码一个固定时间,因为单元测试可能会在任意硬件上运行。这种方法可确保测试运行足够长,以找到可行的解决方案,即使在较慢的系统上也是如此。但是,即使在快速的系统上,它不会运行毫秒的时间超过它。

13.7.3. 日志记录

完成 Red Hat Build of OptaPlanner Spring Boot timetable 应用程序后,您可以使用日志信息来帮助微调 ConstraintProvider 中的限制。查看 info 日志文件中的分数计算速度,以评估对您的限制的影响。以调试模式运行应用程序,以显示应用程序采取的每个步骤,或使用 trace 日志记录来记录每个步骤和每次移动。

流程

  1. 在固定时间内运行 timetable 应用程序,例如五分钟。
  2. 查看日志文件中的分数计算速度,如下例所示:

    ... Solving ended: ..., score calculation speed (29455/sec), ...
    Copy to Clipboard Toggle word wrap
  3. 更改约束,在相同时间内再次运行 planning 应用程序,并查看日志文件中记录的分数计算速度。
  4. 以 debug 模式运行应用程序以记录每个步骤:

    • 要从命令行运行调试模式,请使用 -D 系统属性。
    • 要更改 application.properties 文件中的日志记录,请在该文件中添加以下行:

      logging.level.org.optaplanner=debug
      Copy to Clipboard Toggle word wrap

      以下示例以 debug 模式显示 日志文件中 的输出:

      ... Solving started: time spent (67), best score (-20init/0hard/0soft), environment mode (REPRODUCIBLE), random (JDK with seed 0).
      ...     CH step (0), time spent (128), score (-18init/0hard/0soft), selected move count (15), picked move ([Math(101) {null -> Room A}, Math(101) {null -> MONDAY 08:30}]).
      ...     CH step (1), time spent (145), score (-16init/0hard/0soft), selected move count (15), picked move ([Physics(102) {null -> Room A}, Physics(102) {null -> MONDAY 09:30}]).
      ...
      Copy to Clipboard Toggle word wrap
  5. 使用 trace 日志记录来显示每个步骤,以及每个步骤的每次移动。

13.8. 添加数据库和 UI 集成

使用 Spring Boot 创建红帽构建的 OptaPlanner 应用程序示例后,添加数据库和 UI 集成。

前提条件

  • 您已创建了 OptaPlanner Spring Boot timetable 示例。

流程

  1. TimeslotRoom 和 Pricingon 创建 Java Persistence API (DSL)存储库。有关创建 JPA 存储库的详情,请参考在 Spring 网站上 使用 JPA 访问数据。
  2. 通过 REST 公开 JPA 存储库。有关公开存储库的详情,请参考 Spring 网站 使用 REST 访问 JPA 数据
  3. 构建 TimeTableRepository facade,在单个事务中读取和写入 TimeTable
  4. 按照以下示例所示调整 TimeTableController

    package com.example.solver;
    
    import com.example.domain.TimeTable;
    import com.example.persistence.TimeTableRepository;
    import org.optaplanner.core.api.score.ScoreManager;
    import org.optaplanner.core.api.solver.SolverManager;
    import org.optaplanner.core.api.solver.SolverStatus;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/timeTable")
    public class TimeTableController {
    
        @Autowired
        private TimeTableRepository timeTableRepository;
        @Autowired
        private SolverManager<TimeTable, Long> solverManager;
        @Autowired
        private ScoreManager<TimeTable> scoreManager;
    
        // To try, GET http://localhost:8080/timeTable
        @GetMapping()
        public TimeTable getTimeTable() {
            // Get the solver status before loading the solution
            // to avoid the race condition that the solver terminates between them
            SolverStatus solverStatus = getSolverStatus();
            TimeTable solution = timeTableRepository.findById(TimeTableRepository.SINGLETON_TIME_TABLE_ID);
            scoreManager.updateScore(solution); // Sets the score
            solution.setSolverStatus(solverStatus);
            return solution;
        }
    
        @PostMapping("/solve")
        public void solve() {
            solverManager.solveAndListen(TimeTableRepository.SINGLETON_TIME_TABLE_ID,
                    timeTableRepository::findById,
                    timeTableRepository::save);
        }
    
        public SolverStatus getSolverStatus() {
            return solverManager.getSolverStatus(TimeTableRepository.SINGLETON_TIME_TABLE_ID);
        }
    
        @PostMapping("/stopSolving")
        public void stopSolving() {
            solverManager.terminateEarly(TimeTableRepository.SINGLETON_TIME_TABLE_ID);
        }
    
    }
    Copy to Clipboard Toggle word wrap

    为了简单起见,此代码仅处理一次 TimeTable 实例,但易于启用多租户并并行处理不同高站的多个时间表实例。

    getTimeTable () 方法返回数据库的最新时间表。它使用 ScoreManager (自动注入)计算该时间的分数,以便 UI 能够显示分数。

    resolve () 方法启动一个作业,以解决当前时间集中,并将时间插槽和房间分配存储在数据库中。它使用 SolverManager.solveAndListen () 方法侦听中间最佳解决方案并相应地更新数据库。这可让 UI 在后端仍在竞争时显示进度。

  5. 现在,solv () 方法会立即返回,调整 TimeTableControllerTest,如下例所示:

    package com.example.solver;
    
    import com.example.domain.Lesson;
    import com.example.domain.TimeTable;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.Timeout;
    import org.optaplanner.core.api.solver.SolverStatus;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import static org.junit.jupiter.api.Assertions.assertFalse;
    import static org.junit.jupiter.api.Assertions.assertNotNull;
    import static org.junit.jupiter.api.Assertions.assertTrue;
    
    @SpringBootTest(properties = {
            "optaplanner.solver.termination.spent-limit=1h", // Effectively disable this termination in favor of the best-score-limit
            "optaplanner.solver.termination.best-score-limit=0hard/*soft"})
    public class TimeTableControllerTest {
    
        @Autowired
        private TimeTableController timeTableController;
    
        @Test
        @Timeout(600_000)
        public void solveDemoDataUntilFeasible() throws InterruptedException {
            timeTableController.solve();
            TimeTable timeTable = timeTableController.getTimeTable();
            while (timeTable.getSolverStatus() != SolverStatus.NOT_SOLVING) {
                // Quick polling (not a Test Thread Sleep anti-pattern)
                // Test is still fast on fast systems and doesn't randomly fail on slow systems.
                Thread.sleep(20L);
                timeTable = timeTableController.getTimeTable();
            }
            assertFalse(timeTable.getLessonList().isEmpty());
            for (Lesson lesson : timeTable.getLessonList()) {
                assertNotNull(lesson.getTimeslot());
                assertNotNull(lesson.getRoom());
            }
            assertTrue(timeTable.getScore().isFeasible());
        }
    
    }
    Copy to Clipboard Toggle word wrap
  6. 轮询最新的解决方案,直到解决方案完成。
  7. 要视觉化时间表,请在这些 REST 方法之上构建高级 Web UI。

OptaPlanner 通过 Micrometer (一个 Java 应用程序的指标检测库)公开指标。您可以将 Micrometer 与 Prometheus 搭配使用,来监控 IANA 时间应用程序中的 OptaPlanner solver。

先决条件

  • 您已创建了 Spring Boot OptaPlanner eXecut timetable 应用程序。
  • 已安装 Prometheus。有关安装 Prometheus 的详情,请查看 Prometheus 网站。

流程

  1. 进入 Technology /java-spring-boot 目录。
  2. 将 Micrometer Prometheus 依赖项添加到 IANA timetable pom.xml 文件中:

    <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
     <groupId>io.micrometer</groupId>
     <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>
    Copy to Clipboard Toggle word wrap
  3. 在 application.properties 文件中添加以下属性:

    management.endpoints.web.exposure.include=metrics,prometheus
    Copy to Clipboard Toggle word wrap
  4. 启动 7000 timetable 应用程序:

    mvn spring-boot:run
    Copy to Clipboard Toggle word wrap
  5. 在网页浏览器中打开 http://localhost:8080/actuator/prometheus

本指南指导您完成使用 OptaPlanner 约束创建简单 Java 应用程序的过程。您将构建一个命令行应用程序,该应用程序针对领导和架构师进行了优化,以优化电池时间:

...
INFO  Solving ended: time spent (5000), best score (0hard/9soft), ...
INFO
INFO  |            | Room A     | Room B     | Room C     |
INFO  |------------|------------|------------|------------|
INFO  | MON 08:30  | English    | Math       |            |
INFO  |            | I. Jones   | A. Turing  |            |
INFO  |            | 9th grade  | 10th grade |            |
INFO  |------------|------------|------------|------------|
INFO  | MON 09:30  | History    | Physics    |            |
INFO  |            | I. Jones   | M. Curie   |            |
INFO  |            | 9th grade  | 10th grade |            |
INFO  |------------|------------|------------|------------|
INFO  | MON 10:30  | History    | Physics    |            |
INFO  |            | I. Jones   | M. Curie   |            |
INFO  |            | 10th grade | 9th grade  |            |
INFO  |------------|------------|------------|------------|
...
INFO  |------------|------------|------------|------------|
Copy to Clipboard Toggle word wrap

您的应用程序将通过 AI 遵循硬和软调度 限制,将自动将附件实例分配给 TimeslotRoom 实例,例如:

  • 空间最多可以同时有一个小时间。
  • 一个架构师可以同时参与一个小时间。
  • 每天最多可同时参与一个小时间。
  • 指导人员更喜欢在同一房间所有较少的活动。
  • 指导人员更喜欢参与较少的活动,并排解少量间的差距。
  • 同一主题上连续的 小项是 一 个 一 个。

mathematly 认为是 NP 硬性问题。这意味着难以扩展。简单地强制迭代所有可能的组合,对于非平量数据集也需要数百万年,即使在超级计算器上也是如此。AI 约束解析器(如 OptaPlanner)有高级算法,可在合理的时间内提供最接近的解决方案。

先决条件

  • 已安装 OpenJDK (JDK) 11。Red Hat build of Open JDK 位于红帽客户门户网站中的 Software Downloads 页面中(需要登录)。
  • 已安装 Apache Maven 3.6 或更高版本。Maven 可从 Apache Maven 项目网站 获得。
  • IDE,如 IntelliJ IDEA、VSCode 或 Eclipse

您可以将 Maven 或 Gradle 用于 OptaPlanner Evolution 时间应用程序。创建构建文件后,添加以下依赖项:

  • OptaPlanner-core (复合范围)来解决 IANA 时间问题
  • optaPlanner-test (测试范围)测试 JUnit 计时限制
  • 一个实现,如 logback-classic (runtime 范围)来查看 OptaPlanner 采取的步骤

流程

  1. 创建 Maven 或 Gradle 构建文件。
  2. 在您的构建文件中添加 optaplanner-core、Optaplanner-testlogback-classic 依赖项:

    • 对于 Maven,将以下依赖项添加到 pom.xml 文件中:

        <dependency>
          <groupId>org.optaplanner</groupId>
          <artifactId>optaplanner-core</artifactId>
        </dependency>
      
        <dependency>
          <groupId>org.optaplanner</groupId>
          <artifactId>optaplanner-test</artifactId>
          <scope>test</scope>
        </dependency>
      
        <dependency>
          <groupId>ch.qos.logback</groupId>
          <artifactId>logback-classic</artifactId>
          <version>1.2.3</version>
        </dependency>
      Copy to Clipboard Toggle word wrap

      以下示例显示了完整的 pom.xml 文件。

      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
      
        <groupId>org.acme</groupId>
        <artifactId>optaplanner-hello-world-school-timetabling-quickstart</artifactId>
        <version>1.0-SNAPSHOT</version>
      
        <properties>
          <maven.compiler.release>11</maven.compiler.release>
          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      
          <version.org.optaplanner>8.33.0.Final-redhat-00004</version.org.optaplanner>
          <version.org.logback>1.2.3</version.org.logback>
      
          <version.compiler.plugin>3.8.1</version.compiler.plugin>
          <version.surefire.plugin>3.0.0-M5</version.surefire.plugin>
          <version.exec.plugin>3.0.0</version.exec.plugin>
        </properties>
      
        <dependencyManagement>
          <dependencies>
            <dependency>
              <groupId>org.optaplanner</groupId>
              <artifactId>optaplanner-bom</artifactId>
              <version>${version.org.optaplanner}</version>
              <type>pom</type>
              <scope>import</scope>
            </dependency>
            <dependency>
              <groupId>ch.qos.logback</groupId>
              <artifactId>logback-classic</artifactId>
              <version>${version.org.logback}</version>
            </dependency>
          </dependencies>
        </dependencyManagement>
      
        <dependencies>
          <dependency>
            <groupId>org.optaplanner</groupId>
            <artifactId>optaplanner-core</artifactId>
          </dependency>
          <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <scope>runtime</scope>
          </dependency>
      
          <!-- Testing -->
          <dependency>
            <groupId>org.optaplanner</groupId>
            <artifactId>optaplanner-test</artifactId>
            <scope>test</scope>
          </dependency>
        </dependencies>
      
        <build>
          <plugins>
            <plugin>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>${version.compiler.plugin}</version>
            </plugin>
            <plugin>
              <artifactId>maven-surefire-plugin</artifactId>
              <version>${version.surefire.plugin}</version>
            </plugin>
            <plugin>
              <groupId>org.codehaus.mojo</groupId>
              <artifactId>exec-maven-plugin</artifactId>
              <version>${version.exec.plugin}</version>
              <configuration>
                <mainClass>org.acme.schooltimetabling.TimeTableApp</mainClass>
              </configuration>
            </plugin>
          </plugins>
        </build>
      
        <repositories>
          <repository>
            <id>jboss-public-repository-group</id>
            <url>https://repository.jboss.org/nexus/content/groups/public/</url>
            <releases>
              <!-- Get releases only from Maven Central which is faster. -->
              <enabled>false</enabled>
            </releases>
            <snapshots>
              <enabled>true</enabled>
            </snapshots>
          </repository>
        </repositories>
      </project>
      Copy to Clipboard Toggle word wrap
    • 对于 Gradle,在 gradle.build 文件中添加以下依赖项:

      dependencies {
          implementation platform("org.optaplanner:optaplanner-bom:${optaplannerVersion}")
          implementation "org.optaplanner:optaplanner-core"
          testImplementation "org.optaplanner:optaplanner-test"
      
          runtimeOnly "ch.qos.logback:logback-classic:${logbackVersion}"
      }
      Copy to Clipboard Toggle word wrap

      以下示例显示了已完成的 gradle.build 文件。

      plugins {
          id "java"
          id "application"
      }
      
      def optaplannerVersion = "{optaplanner-version}"
      def logbackVersion = "1.2.9"
      
      group = "org.acme"
      version = "1.0-SNAPSHOT"
      
      repositories {
          mavenCentral()
      }
      
      dependencies {
          implementation platform("org.optaplanner:optaplanner-bom:${optaplannerVersion}")
          implementation "org.optaplanner:optaplanner-core"
          testImplementation "org.optaplanner:optaplanner-test"
      
          runtimeOnly "ch.qos.logback:logback-classic:${logbackVersion}"
      }
      
      java {
          sourceCompatibility = JavaVersion.VERSION_11
          targetCompatibility = JavaVersion.VERSION_11
      }
      
      compileJava {
          options.encoding = "UTF-8"
          options.compilerArgs << "-parameters"
      }
      
      compileTestJava {
          options.encoding = "UTF-8"
      }
      
      application {
          mainClass = "org.acme.schooltimetabling.TimeTableApp"
      }
      
      test {
          // Log the test execution results.
          testLogging {
              events "passed", "skipped", "failed"
          }
      }
      Copy to Clipboard Toggle word wrap

14.2. 对域对象建模

红帽构建的 OptaPlanner 时间项目的目标是将每项分配给一个时间插槽和房间。要做到这一点,添加三个类: Timeslotinit on 和 Room,如下图所示:

timeslot

Timeslot 类代表当减少活动为过期的时间间隔时,例如,Monday 10:30 - 11:30Tuesday 13:30 - 14:30。在这个示例中,所有时间段都有相同的持续时间,在 lunch 或其他中断过程中没有时间插槽。

一个时间插槽没有日期,因为每周计划只重复一次。不需要 持续规划。timeslot 被称为 问题事实,因为公司不会更改 Timeslot 实例。此类类不需要任何特定于 OptaPlanner 的注解。

房间

Room 类代表更小活动的位置,例如 Room ARoom B。在本例中,所有房间都没有容量限制,它们可以容纳所有定义。

空间 实例在竞争过程中不会改变,因此 Room 也是 问题事实

lesson

在小时间中,由周一类代表,受一组参与者代表的规定,例如,A.Turing for 9th rating 或 Chemistry by M.Curie 代表第 10 等级如果一个主题是按同一站组相同的每周的多次,则有多个仅可通过 id 区分的实例。例如,第 9 等级每周有六个数学。

在 OptaPlanner 期间,OptaPlanner 会更改周一类的 timeslotroom 字段,以将每个小时间分配给一个时间插槽和房间。因为 OptaPlanner 更改这些字段,因此 Apple on 是一个 规划实体

上图中的大部分字段都包含输入数据,但 orange 字段除外。在输入数据中取消分配较少的 timeslotroom 字段,并在输出数据中分配(而非 null)。OptaPlanner 在参与过程中会更改这些字段。此类字段称为 规划变量。为了使 OptaPlanner 能够识别它们,timeslotroom 字段都需要 @PlanningVariable 注释。它们包含类属性需要 @PlanningEntity 注释。

流程

  1. 创建 src/main/java/com/example/domain/Timeslot.java 类:

    package com.example.domain;
    
    import java.time.DayOfWeek;
    import java.time.LocalTime;
    
    public class Timeslot {
    
        private DayOfWeek dayOfWeek;
        private LocalTime startTime;
        private LocalTime endTime;
    
        private Timeslot() {
        }
    
        public Timeslot(DayOfWeek dayOfWeek, LocalTime startTime, LocalTime endTime) {
            this.dayOfWeek = dayOfWeek;
            this.startTime = startTime;
            this.endTime = endTime;
        }
    
        @Override
        public String toString() {
            return dayOfWeek + " " + startTime.toString();
        }
    
        // ********************************
        // Getters and setters
        // ********************************
    
        public DayOfWeek getDayOfWeek() {
            return dayOfWeek;
        }
    
        public LocalTime getStartTime() {
            return startTime;
        }
    
        public LocalTime getEndTime() {
            return endTime;
        }
    
    }
    Copy to Clipboard Toggle word wrap

    请注意,toString () 方法保留输出短,因此更易于阅读 OptaPlanner 的 DEBUGTRACE 日志,如稍后所示。

  2. 创建 src/main/java/com/example/domain/Room.java 类:

    package com.example.domain;
    
    public class Room {
    
        private String name;
    
        private Room() {
        }
    
        public Room(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return name;
        }
    
        // ********************************
        // Getters and setters
        // ********************************
    
        public String getName() {
            return name;
        }
    
    }
    Copy to Clipboard Toggle word wrap
  3. 创建 src/main/java/com/example/domain/Lesson.java 类:

    package com.example.domain;
    
    import org.optaplanner.core.api.domain.entity.PlanningEntity;
    import org.optaplanner.core.api.domain.variable.PlanningVariable;
    
    @PlanningEntity
    public class Lesson {
    
        private Long id;
    
        private String subject;
        private String teacher;
        private String studentGroup;
    
        @PlanningVariable(valueRangeProviderRefs = "timeslotRange")
        private Timeslot timeslot;
    
        @PlanningVariable(valueRangeProviderRefs = "roomRange")
        private Room room;
    
        private Lesson() {
        }
    
        public Lesson(Long id, String subject, String teacher, String studentGroup) {
            this.id = id;
            this.subject = subject;
            this.teacher = teacher;
            this.studentGroup = studentGroup;
        }
    
        @Override
        public String toString() {
            return subject + "(" + id + ")";
        }
    
        // ********************************
        // Getters and setters
        // ********************************
    
        public Long getId() {
            return id;
        }
    
        public String getSubject() {
            return subject;
        }
    
        public String getTeacher() {
            return teacher;
        }
    
        public String getStudentGroup() {
            return studentGroup;
        }
    
        public Timeslot getTimeslot() {
            return timeslot;
        }
    
        public void setTimeslot(Timeslot timeslot) {
            this.timeslot = timeslot;
        }
    
        public Room getRoom() {
            return room;
        }
    
        public void setRoom(Room room) {
            this.room = room;
        }
    
    }
    Copy to Clipboard Toggle word wrap

    bring on 类有一个 @PlanningEntity 注释,因此 OptaPlanner 知道该类在周五期间发生了变化,因为它包含一个或多个规划变量。

    timeslot 字段有一个 @PlanningVariable 注释,因此 OptaPlanner 知道它可以更改其值。要查找分配给此字段的潜在 Timeslot 实例,OptaPlanner 使用 valueRangeProviderRefs 属性连接到提供 List<Timeslot&gt; 的值范围供应商。有关值范围供应商的详情,请查看 第 14.4 节 “在计划解决方案中收集域对象”

    由于同样原因,room 字段也有一个 @PlanningVariable 注释。

14.3. 定义约束并计算分数

当遇到问题时,分数 代表特定解决方案的质量。分数越大。红帽构建的 OptaPlanner 会寻找最佳解决方案,这是可用时间提供的最高分数的解决方案。它可能 是最佳解决方案

因为 timetable 示例用例有硬和软限制,因此请使用 Hard SoftScore 类来代表分数:

  • 硬限制不能被破坏。例如: 一个房间可以同时有一个小时间。
  • 软限制不应中断。例如: 一个公司更喜欢在单个房间参与。

硬限制会与其他硬限制进行权重。软限制会与其他软限制加权。硬限制始终超过软限制,无论它们对应的权重是什么。

要计算分数,您可以实施 EasyScoreCalculator 类:

public class TimeTableEasyScoreCalculator implements EasyScoreCalculator<TimeTable> {

    @Override
    public HardSoftScore calculateScore(TimeTable timeTable) {
        List<Lesson> lessonList = timeTable.getLessonList();
        int hardScore = 0;
        for (Lesson a : lessonList) {
            for (Lesson b : lessonList) {
                if (a.getTimeslot() != null && a.getTimeslot().equals(b.getTimeslot())
                        && a.getId() < b.getId()) {
                    // A room can accommodate at most one lesson at the same time.
                    if (a.getRoom() != null && a.getRoom().equals(b.getRoom())) {
                        hardScore--;
                    }
                    // A teacher can teach at most one lesson at the same time.
                    if (a.getTeacher().equals(b.getTeacher())) {
                        hardScore--;
                    }
                    // A student can attend at most one lesson at the same time.
                    if (a.getStudentGroup().equals(b.getStudentGroup())) {
                        hardScore--;
                    }
                }
            }
        }
        int softScore = 0;
        // Soft constraints are only implemented in the "complete" implementation
        return HardSoftScore.of(hardScore, softScore);
    }

}
Copy to Clipboard Toggle word wrap

不幸的是,这个解决方案无法很好地扩展,因为它不可缩小:每次将小时间分配给不同的时区或空间时,所有定义都会被重新评估以计算新的分数。

更好的解决方案是创建一个 src/main/java/com/example/solver/TimeTableConstraintProvider.java 类来执行增量分数计算。这个类使用 OptaPlanner 的 ConstraintStream API,该 API 被 Java 8 Streams 和 SQL 增加。ConstraintProvider 扩展比 EasyScoreCalculator: O (n)而不是O(n)而不是 O(n)更好的 magnitude 顺序。

流程

创建以下 src/main/java/com/example/solver/TimeTableConstraintProvider.java 类:

package com.example.solver;

import com.example.domain.Lesson;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.api.score.stream.Constraint;
import org.optaplanner.core.api.score.stream.ConstraintFactory;
import org.optaplanner.core.api.score.stream.ConstraintProvider;
import org.optaplanner.core.api.score.stream.Joiners;

public class TimeTableConstraintProvider implements ConstraintProvider {

    @Override
    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
        return new Constraint[] {
                // Hard constraints
                roomConflict(constraintFactory),
                teacherConflict(constraintFactory),
                studentGroupConflict(constraintFactory),
                // Soft constraints are only implemented in the "complete" implementation
        };
    }

    private Constraint roomConflict(ConstraintFactory constraintFactory) {
        // A room can accommodate at most one lesson at the same time.

        // Select a lesson ...
        return constraintFactory.forEach(Lesson.class)
                // ... and pair it with another lesson ...
                .join(Lesson.class,
                        // ... in the same timeslot ...
                        Joiners.equal(Lesson::getTimeslot),
                        // ... in the same room ...
                        Joiners.equal(Lesson::getRoom),
                        // ... and the pair is unique (different id, no reverse pairs)
                        Joiners.lessThan(Lesson::getId))
                // then penalize each pair with a hard weight.
                .penalize(HardSoftScore.ONE_HARD)
                .asConstraint("Room conflict");
    }

    private Constraint teacherConflict(ConstraintFactory constraintFactory) {
        // A teacher can teach at most one lesson at the same time.
        return constraintFactory.forEach(Lesson.class)
                .join(Lesson.class,
                        Joiners.equal(Lesson::getTimeslot),
                        Joiners.equal(Lesson::getTeacher),
                        Joiners.lessThan(Lesson::getId))
                        .penalize(HardSoftScore.ONE_HARD)
                        .asConstraint("Teacher conflict");
    }

    private Constraint studentGroupConflict(ConstraintFactory constraintFactory) {
        // A student can attend at most one lesson at the same time.
        return constraintFactory.forEach(Lesson.class)
                .join(Lesson.class,
                        Joiners.equal(Lesson::getTimeslot),
                        Joiners.equal(Lesson::getStudentGroup),
                        Joiners.lessThan(Lesson::getId))
                        .penalize(HardSoftScore.ONE_HARD)
                        .asConstraint("Student group conflict");
    }

}
Copy to Clipboard Toggle word wrap

14.4. 在计划解决方案中收集域对象

TimeTable 实例将单个数据集的所有 TimeslotRoom 和 Appleon 实例嵌套。另外,由于它包含所有 lessons,每个都具有特定的计划变量状态,所以它是一个 规划解决方案,它具有分数:

  • 如果仅未被分配,则它是一个 未初始化的 解决方案,例如,分数为 -4init/0hard/0soft 的解决方案。
  • 如果它破坏硬限制,则是一个不可行的解决方案,例如,分数为 -2hard/-3soft 的解决方案。
  • 如果它遵循所有硬限制,那么它是一种可行的解决方案,例如,分数为 0hard/-7soft 的解决方案。

TimeTable 类有一个 @PlanningSolution 注释,因此 Red Hat Build of OptaPlanner 知道这个类包含所有输入和输出数据。

具体来说,这个类是问题的输入:

  • 带有所有时间插槽的 timeslotList 字段

    • 这是问题事实列表,因为它们在竞争过程中不会改变。
  • 包含所有空间的 roomList 字段

    • 这是问题事实列表,因为它们在竞争过程中不会改变。
  • 带有所有 lessons 的 lessonList 字段

    • 这是规划实体的列表,因为它们在竞争期间发生了变化。
    • 每个 周一

      • timeslotroom 字段的值通常仍然为空,因此未分配。它们正在规划变量。
      • 其他字段(如 subject、MrrslirpGroup )已填写。这些字段是问题属性。

但是,此类也是解决方案的输出:

  • 一个 lessonList 字段,其中每个 每天 实例在周五后都有非空 timeslotroom 字段
  • 代表输出解决方案的质量的 score 字段,例如 0hard/-5soft

流程

创建 src/main/java/com/example/domain/TimeTable.java 类:

package com.example.domain;

import java.util.List;

import org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty;
import org.optaplanner.core.api.domain.solution.PlanningScore;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;

@PlanningSolution
public class TimeTable {

    @ValueRangeProvider(id = "timeslotRange")
    @ProblemFactCollectionProperty
    private List<Timeslot> timeslotList;

    @ValueRangeProvider(id = "roomRange")
    @ProblemFactCollectionProperty
    private List<Room> roomList;

    @PlanningEntityCollectionProperty
    private List<Lesson> lessonList;

    @PlanningScore
    private HardSoftScore score;

    private TimeTable() {
    }

    public TimeTable(List<Timeslot> timeslotList, List<Room> roomList,
            List<Lesson> lessonList) {
        this.timeslotList = timeslotList;
        this.roomList = roomList;
        this.lessonList = lessonList;
    }

    // ********************************
    // Getters and setters
    // ********************************

    public List<Timeslot> getTimeslotList() {
        return timeslotList;
    }

    public List<Room> getRoomList() {
        return roomList;
    }

    public List<Lesson> getLessonList() {
        return lessonList;
    }

    public HardSoftScore getScore() {
        return score;
    }

}
Copy to Clipboard Toggle word wrap

值范围供应商

timeslotList 字段是一个值范围供应商。它包含 OptaPlanner 可以从中分配 to to the timeslot 字段的 Timeslot 实例。timeslotList 字段有一个 @ValueRangeProvider 注释来连接这两个注解,方法是将 id 与大括号中的 @PlanningVariablevalueRangeProviderRefs 匹配。

遵循同样的逻辑,roomList 字段也具有 @ValueRangeProvider 注释。

问题事实和规划实体属性

此外,OptaPlanner 需要知道它可以更改哪些附件,以及如何检索用于 TimeTableConstraintProviderTimeslotRoom 实例。

timeslotListroomList 字段有一个 @ProblemFactCollectionProperty 注解,因此您的 TimeTableConstraintProvider 可以从这些实例中选择。

lessonList 有一个 @PlanningEntityCollectionProperty 注解,因此 OptaPlanner 可以在将来更改它们,而您的 TimeTableConstraintProvider 也可以从这些中选择。

14.5. TimeTableApp.java 类

创建 IANA 时间应用程序的所有组件后,会将它们放在 TimeTableApp.java 类中。

main () 方法执行以下任务:

  1. 创建 SolverFactory 以为每个 数据集 构建 Solver。
  2. 加载数据集。
  3. 使用 Solver .solve ()来解决 它。
  4. 视觉化该数据集的解决方案。

通常,应用程序只有一个 Solver Factory 来为每个问题数据集构建一个新的 Solver 实例。SolverFactory 是 thread-safe,但 Solver 不是。对于 IANA 时间可时间化应用程序,只有一个数据集,因此只有一个 Solver 实例。

以下是完成的 TimeTableApp.java 类:

package org.acme.schooltimetabling;

import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.acme.schooltimetabling.domain.Lesson;
import org.acme.schooltimetabling.domain.Room;
import org.acme.schooltimetabling.domain.TimeTable;
import org.acme.schooltimetabling.domain.Timeslot;
import org.acme.schooltimetabling.solver.TimeTableConstraintProvider;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.config.solver.SolverConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TimeTableApp {

    private static final Logger LOGGER = LoggerFactory.getLogger(TimeTableApp.class);

    public static void main(String[] args) {
        SolverFactory<TimeTable> solverFactory = SolverFactory.create(new SolverConfig()
                .withSolutionClass(TimeTable.class)
                .withEntityClasses(Lesson.class)
                .withConstraintProviderClass(TimeTableConstraintProvider.class)
                // The solver runs only for 5 seconds on this small data set.
                // It's recommended to run for at least 5 minutes ("5m") otherwise.
                .withTerminationSpentLimit(Duration.ofSeconds(5)));

        // Load the problem
        TimeTable problem = generateDemoData();

        // Solve the problem
        Solver<TimeTable> solver = solverFactory.buildSolver();
        TimeTable solution = solver.solve(problem);

        // Visualize the solution
        printTimetable(solution);
    }

    public static TimeTable generateDemoData() {
        List<Timeslot> timeslotList = new ArrayList<>(10);
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(8, 30), LocalTime.of(9, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(9, 30), LocalTime.of(10, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(10, 30), LocalTime.of(11, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(13, 30), LocalTime.of(14, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(14, 30), LocalTime.of(15, 30)));

        timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(8, 30), LocalTime.of(9, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(9, 30), LocalTime.of(10, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(10, 30), LocalTime.of(11, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(13, 30), LocalTime.of(14, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(14, 30), LocalTime.of(15, 30)));

        List<Room> roomList = new ArrayList<>(3);
        roomList.add(new Room("Room A"));
        roomList.add(new Room("Room B"));
        roomList.add(new Room("Room C"));

        List<Lesson> lessonList = new ArrayList<>();
        long id = 0;
        lessonList.add(new Lesson(id++, "Math", "A. Turing", "9th grade"));
        lessonList.add(new Lesson(id++, "Math", "A. Turing", "9th grade"));
        lessonList.add(new Lesson(id++, "Physics", "M. Curie", "9th grade"));
        lessonList.add(new Lesson(id++, "Chemistry", "M. Curie", "9th grade"));
        lessonList.add(new Lesson(id++, "Biology", "C. Darwin", "9th grade"));
        lessonList.add(new Lesson(id++, "History", "I. Jones", "9th grade"));
        lessonList.add(new Lesson(id++, "English", "I. Jones", "9th grade"));
        lessonList.add(new Lesson(id++, "English", "I. Jones", "9th grade"));
        lessonList.add(new Lesson(id++, "Spanish", "P. Cruz", "9th grade"));
        lessonList.add(new Lesson(id++, "Spanish", "P. Cruz", "9th grade"));

        lessonList.add(new Lesson(id++, "Math", "A. Turing", "10th grade"));
        lessonList.add(new Lesson(id++, "Math", "A. Turing", "10th grade"));
        lessonList.add(new Lesson(id++, "Math", "A. Turing", "10th grade"));
        lessonList.add(new Lesson(id++, "Physics", "M. Curie", "10th grade"));
        lessonList.add(new Lesson(id++, "Chemistry", "M. Curie", "10th grade"));
        lessonList.add(new Lesson(id++, "French", "M. Curie", "10th grade"));
        lessonList.add(new Lesson(id++, "Geography", "C. Darwin", "10th grade"));
        lessonList.add(new Lesson(id++, "History", "I. Jones", "10th grade"));
        lessonList.add(new Lesson(id++, "English", "P. Cruz", "10th grade"));
        lessonList.add(new Lesson(id++, "Spanish", "P. Cruz", "10th grade"));

        return new TimeTable(timeslotList, roomList, lessonList);
    }

    private static void printTimetable(TimeTable timeTable) {
        LOGGER.info("");
        List<Room> roomList = timeTable.getRoomList();
        List<Lesson> lessonList = timeTable.getLessonList();
        Map<Timeslot, Map<Room, List<Lesson>>> lessonMap = lessonList.stream()
                .filter(lesson -> lesson.getTimeslot() != null && lesson.getRoom() != null)
                .collect(Collectors.groupingBy(Lesson::getTimeslot, Collectors.groupingBy(Lesson::getRoom)));
        LOGGER.info("|            | " + roomList.stream()
                .map(room -> String.format("%-10s", room.getName())).collect(Collectors.joining(" | ")) + " |");
        LOGGER.info("|" + "------------|".repeat(roomList.size() + 1));
        for (Timeslot timeslot : timeTable.getTimeslotList()) {
            List<List<Lesson>> cellList = roomList.stream()
                    .map(room -> {
                        Map<Room, List<Lesson>> byRoomMap = lessonMap.get(timeslot);
                        if (byRoomMap == null) {
                            return Collections.<Lesson>emptyList();
                        }
                        List<Lesson> cellLessonList = byRoomMap.get(room);
                        if (cellLessonList == null) {
                            return Collections.<Lesson>emptyList();
                        }
                        return cellLessonList;
                    })
                    .collect(Collectors.toList());

            LOGGER.info("| " + String.format("%-10s",
                    timeslot.getDayOfWeek().toString().substring(0, 3) + " " + timeslot.getStartTime()) + " | "
                    + cellList.stream().map(cellLessonList -> String.format("%-10s",
                            cellLessonList.stream().map(Lesson::getSubject).collect(Collectors.joining(", "))))
                            .collect(Collectors.joining(" | "))
                    + " |");
            LOGGER.info("|            | "
                    + cellList.stream().map(cellLessonList -> String.format("%-10s",
                            cellLessonList.stream().map(Lesson::getTeacher).collect(Collectors.joining(", "))))
                            .collect(Collectors.joining(" | "))
                    + " |");
            LOGGER.info("|            | "
                    + cellList.stream().map(cellLessonList -> String.format("%-10s",
                            cellLessonList.stream().map(Lesson::getStudentGroup).collect(Collectors.joining(", "))))
                            .collect(Collectors.joining(" | "))
                    + " |");
            LOGGER.info("|" + "------------|".repeat(roomList.size() + 1));
        }
        List<Lesson> unassignedLessons = lessonList.stream()
                .filter(lesson -> lesson.getTimeslot() == null || lesson.getRoom() == null)
                .collect(Collectors.toList());
        if (!unassignedLessons.isEmpty()) {
            LOGGER.info("");
            LOGGER.info("Unassigned lessons");
            for (Lesson lesson : unassignedLessons) {
                LOGGER.info("  " + lesson.getSubject() + " - " + lesson.getTeacher() + " - " + lesson.getStudentGroup());
            }
        }
    }

}
Copy to Clipboard Toggle word wrap

main () 方法首先创建 SolverFactory

SolverFactory<TimeTable> solverFactory = SolverFactory.create(new SolverConfig()
        .withSolutionClass(TimeTable.class)
        .withEntityClasses(Lesson.class)
        .withConstraintProviderClass(TimeTableConstraintProvider.class)
        // The solver runs only for 5 seconds on this small data set.
        // It's recommended to run for at least 5 minutes ("5m") otherwise.
        .withTerminationSpentLimit(Duration.ofSeconds(5)));
Copy to Clipboard Toggle word wrap

SolverFactory 创建注册 @PlanningSolution 类、@PlanningEntity 类和 ConstraintProvider 类(您之前创建的全部)。

如果没有终止设置或 terminationEarly () 事件,则 Addressr 会永久运行。为避免这种情况,解决方案会将计时限制为 5 秒。

5 秒后,main () 方法加载问题,解决这个问题,并打印解决方案:

        // Load the problem
        TimeTable problem = generateDemoData();

        // Solve the problem
        Solver<TimeTable> solver = solverFactory.buildSolver();
        TimeTable solution = solver.solve(problem);

        // Visualize the solution
        printTimetable(solution);
Copy to Clipboard Toggle word wrap

solve () 方法不会立即返回。在返回最佳解决方案前,它会运行五秒。

OptaPlanner 返回可用终止时间中找到的最佳解决方案。由于 NP 硬问题的性质,最佳解决方案可能不是最佳的,特别是对于较大的数据集。增加终止时间以有可能找到更好的解决方案。

generateDemoData () 方法生成 IANA 时间化问题来解决。

printTimetable () 方法可模拟控制台的可写时间,因此可以轻松地确定是否是良好的调度。

14.6. 创建并运行 这个时间可时间化应用程序

现在,您已完成了 IANA timetable Java 应用程序的所有组件,您可以将它们放在 TimeTableApp.java 类中,并运行它。

先决条件

  • 您已创建了 IANA timetable 应用程序所需的所有组件。

流程

  1. 创建 src/main/java/org/acme/rhacmtimetabling/TimeTableApp.java 类:

    package org.acme.schooltimetabling;
    
    import java.time.DayOfWeek;
    import java.time.Duration;
    import java.time.LocalTime;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    import org.acme.schooltimetabling.domain.Lesson;
    import org.acme.schooltimetabling.domain.Room;
    import org.acme.schooltimetabling.domain.TimeTable;
    import org.acme.schooltimetabling.domain.Timeslot;
    import org.acme.schooltimetabling.solver.TimeTableConstraintProvider;
    import org.optaplanner.core.api.solver.Solver;
    import org.optaplanner.core.api.solver.SolverFactory;
    import org.optaplanner.core.config.solver.SolverConfig;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class TimeTableApp {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(TimeTableApp.class);
    
        public static void main(String[] args) {
            SolverFactory<TimeTable> solverFactory = SolverFactory.create(new SolverConfig()
                    .withSolutionClass(TimeTable.class)
                    .withEntityClasses(Lesson.class)
                    .withConstraintProviderClass(TimeTableConstraintProvider.class)
                    // The solver runs only for 5 seconds on this small data set.
                    // It's recommended to run for at least 5 minutes ("5m") otherwise.
                    .withTerminationSpentLimit(Duration.ofSeconds(5)));
    
            // Load the problem
            TimeTable problem = generateDemoData();
    
            // Solve the problem
            Solver<TimeTable> solver = solverFactory.buildSolver();
            TimeTable solution = solver.solve(problem);
    
            // Visualize the solution
            printTimetable(solution);
        }
    
        public static TimeTable generateDemoData() {
            List<Timeslot> timeslotList = new ArrayList<>(10);
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(8, 30), LocalTime.of(9, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(9, 30), LocalTime.of(10, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(10, 30), LocalTime.of(11, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(13, 30), LocalTime.of(14, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(14, 30), LocalTime.of(15, 30)));
    
            timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(8, 30), LocalTime.of(9, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(9, 30), LocalTime.of(10, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(10, 30), LocalTime.of(11, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(13, 30), LocalTime.of(14, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(14, 30), LocalTime.of(15, 30)));
    
            List<Room> roomList = new ArrayList<>(3);
            roomList.add(new Room("Room A"));
            roomList.add(new Room("Room B"));
            roomList.add(new Room("Room C"));
    
            List<Lesson> lessonList = new ArrayList<>();
            long id = 0;
            lessonList.add(new Lesson(id++, "Math", "A. Turing", "9th grade"));
            lessonList.add(new Lesson(id++, "Math", "A. Turing", "9th grade"));
            lessonList.add(new Lesson(id++, "Physics", "M. Curie", "9th grade"));
            lessonList.add(new Lesson(id++, "Chemistry", "M. Curie", "9th grade"));
            lessonList.add(new Lesson(id++, "Biology", "C. Darwin", "9th grade"));
            lessonList.add(new Lesson(id++, "History", "I. Jones", "9th grade"));
            lessonList.add(new Lesson(id++, "English", "I. Jones", "9th grade"));
            lessonList.add(new Lesson(id++, "English", "I. Jones", "9th grade"));
            lessonList.add(new Lesson(id++, "Spanish", "P. Cruz", "9th grade"));
            lessonList.add(new Lesson(id++, "Spanish", "P. Cruz", "9th grade"));
    
            lessonList.add(new Lesson(id++, "Math", "A. Turing", "10th grade"));
            lessonList.add(new Lesson(id++, "Math", "A. Turing", "10th grade"));
            lessonList.add(new Lesson(id++, "Math", "A. Turing", "10th grade"));
            lessonList.add(new Lesson(id++, "Physics", "M. Curie", "10th grade"));
            lessonList.add(new Lesson(id++, "Chemistry", "M. Curie", "10th grade"));
            lessonList.add(new Lesson(id++, "French", "M. Curie", "10th grade"));
            lessonList.add(new Lesson(id++, "Geography", "C. Darwin", "10th grade"));
            lessonList.add(new Lesson(id++, "History", "I. Jones", "10th grade"));
            lessonList.add(new Lesson(id++, "English", "P. Cruz", "10th grade"));
            lessonList.add(new Lesson(id++, "Spanish", "P. Cruz", "10th grade"));
    
            return new TimeTable(timeslotList, roomList, lessonList);
        }
    
        private static void printTimetable(TimeTable timeTable) {
            LOGGER.info("");
            List<Room> roomList = timeTable.getRoomList();
            List<Lesson> lessonList = timeTable.getLessonList();
            Map<Timeslot, Map<Room, List<Lesson>>> lessonMap = lessonList.stream()
                    .filter(lesson -> lesson.getTimeslot() != null && lesson.getRoom() != null)
                    .collect(Collectors.groupingBy(Lesson::getTimeslot, Collectors.groupingBy(Lesson::getRoom)));
            LOGGER.info("|            | " + roomList.stream()
                    .map(room -> String.format("%-10s", room.getName())).collect(Collectors.joining(" | ")) + " |");
            LOGGER.info("|" + "------------|".repeat(roomList.size() + 1));
            for (Timeslot timeslot : timeTable.getTimeslotList()) {
                List<List<Lesson>> cellList = roomList.stream()
                        .map(room -> {
                            Map<Room, List<Lesson>> byRoomMap = lessonMap.get(timeslot);
                            if (byRoomMap == null) {
                                return Collections.<Lesson>emptyList();
                            }
                            List<Lesson> cellLessonList = byRoomMap.get(room);
                            if (cellLessonList == null) {
                                return Collections.<Lesson>emptyList();
                            }
                            return cellLessonList;
                        })
                        .collect(Collectors.toList());
    
                LOGGER.info("| " + String.format("%-10s",
                        timeslot.getDayOfWeek().toString().substring(0, 3) + " " + timeslot.getStartTime()) + " | "
                        + cellList.stream().map(cellLessonList -> String.format("%-10s",
                                cellLessonList.stream().map(Lesson::getSubject).collect(Collectors.joining(", "))))
                                .collect(Collectors.joining(" | "))
                        + " |");
                LOGGER.info("|            | "
                        + cellList.stream().map(cellLessonList -> String.format("%-10s",
                                cellLessonList.stream().map(Lesson::getTeacher).collect(Collectors.joining(", "))))
                                .collect(Collectors.joining(" | "))
                        + " |");
                LOGGER.info("|            | "
                        + cellList.stream().map(cellLessonList -> String.format("%-10s",
                                cellLessonList.stream().map(Lesson::getStudentGroup).collect(Collectors.joining(", "))))
                                .collect(Collectors.joining(" | "))
                        + " |");
                LOGGER.info("|" + "------------|".repeat(roomList.size() + 1));
            }
            List<Lesson> unassignedLessons = lessonList.stream()
                    .filter(lesson -> lesson.getTimeslot() == null || lesson.getRoom() == null)
                    .collect(Collectors.toList());
            if (!unassignedLessons.isEmpty()) {
                LOGGER.info("");
                LOGGER.info("Unassigned lessons");
                for (Lesson lesson : unassignedLessons) {
                    LOGGER.info("  " + lesson.getSubject() + " - " + lesson.getTeacher() + " - " + lesson.getStudentGroup());
                }
            }
        }
    
    }
    Copy to Clipboard Toggle word wrap
  2. 运行 TimeTableApp 类作为普通 Java 应用程序的主类。以下输出应该:

    ...
    INFO  |            | Room A     | Room B     | Room C     |
    INFO  |------------|------------|------------|------------|
    INFO  | MON 08:30  | English    | Math       |            |
    INFO  |            | I. Jones   | A. Turing  |            |
    INFO  |            | 9th grade  | 10th grade |            |
    INFO  |------------|------------|------------|------------|
    INFO  | MON 09:30  | History    | Physics    |            |
    INFO  |            | I. Jones   | M. Curie   |            |
    INFO  |            | 9th grade  | 10th grade |            |
    ...
    Copy to Clipboard Toggle word wrap
  3. 验证控制台输出。它是否符合所有硬限制?如果您在 TimeTableConstraintProvider 中注释掉了 roomConflict 约束,会出现什么情况?

info 日志显示 OptaPlanner 在 5 秒内执行的操作:

... Solving started: time spent (33), best score (-8init/0hard/0soft), environment mode (REPRODUCIBLE), random (JDK with seed 0).
... Construction Heuristic phase (0) ended: time spent (73), best score (0hard/0soft), score calculation speed (459/sec), step total (4).
... Local Search phase (1) ended: time spent (5000), best score (0hard/0soft), score calculation speed (28949/sec), step total (28398).
... Solving ended: time spent (5000), best score (0hard/0soft), score calculation speed (28524/sec), phase total (2), environment mode (REPRODUCIBLE).
Copy to Clipboard Toggle word wrap

14.7. 测试应用程序

良好的应用程序包括测试覆盖。测试限制以及您的时间表项目中的 solver。

14.7.1. 测试 prompt 时间可写入限制

要在隔离中测试 timetable 项目的约束,请在单元测试中使用 ConstraintVerifier。这会测试每个约束的发生与其他测试隔离的情况,这可在添加正确测试覆盖的新约束时降低维护。

当给定同一空间的三个较少时,此测试会验证约束 TimeTableConstraintProvider:: roomConflict,其中两个较少项具有相同的 timeslot,以匹配权重为 1。因此,如果约束权重为 10hard,它会按 -10hard 减少分数。

流程

创建 src/test/java/org/acme/optaplanner/solver/TimeTableConstraintProviderTest.java 类:

package org.acme.optaplanner.solver;

import java.time.DayOfWeek;
import java.time.LocalTime;

import javax.inject.Inject;

import io.quarkus.test.junit.QuarkusTest;
import org.acme.optaplanner.domain.Lesson;
import org.acme.optaplanner.domain.Room;
import org.acme.optaplanner.domain.TimeTable;
import org.acme.optaplanner.domain.Timeslot;
import org.junit.jupiter.api.Test;
import org.optaplanner.test.api.score.stream.ConstraintVerifier;

@QuarkusTest
class TimeTableConstraintProviderTest {

    private static final Room ROOM = new Room("Room1");
    private static final Timeslot TIMESLOT1 = new Timeslot(DayOfWeek.MONDAY, LocalTime.of(9,0), LocalTime.NOON);
    private static final Timeslot TIMESLOT2 = new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(9,0), LocalTime.NOON);

    @Inject
    ConstraintVerifier<TimeTableConstraintProvider, TimeTable> constraintVerifier;

    @Test
    void roomConflict() {
        Lesson firstLesson = new Lesson(1, "Subject1", "Teacher1", "Group1");
        Lesson conflictingLesson = new Lesson(2, "Subject2", "Teacher2", "Group2");
        Lesson nonConflictingLesson = new Lesson(3, "Subject3", "Teacher3", "Group3");

        firstLesson.setRoom(ROOM);
        firstLesson.setTimeslot(TIMESLOT1);

        conflictingLesson.setRoom(ROOM);
        conflictingLesson.setTimeslot(TIMESLOT1);

        nonConflictingLesson.setRoom(ROOM);
        nonConflictingLesson.setTimeslot(TIMESLOT2);

        constraintVerifier.verifyThat(TimeTableConstraintProvider::roomConflict)
                .given(firstLesson, conflictingLesson, nonConflictingLesson)
                .penalizesBy(1);
    }

}
Copy to Clipboard Toggle word wrap

请注意,ConstraintVerifier 在测试过程中如何忽略约束权重,即使这些约束权重在 ConstraintProvider 中硬编码。这是因为在进入生产环境前定期更改约束权重。这样,约束权重调整不会影响单元测试。

14.7.2. 测试 7000 timetable resolver

这个示例在 Red Hat build of Quarkus 平台上测试 Red Hat Build of OptaPlanner eXecut timetable 项目。它使用 JUnit 测试来生成测试数据集,并将其发送到 TimeTableController 来解决这个问题。

流程

  1. 使用以下内容创建 src/test/java/com/example/rest/TimeTableResourceTest.java 类:

    package com.exmaple.optaplanner.rest;
    
    import java.time.DayOfWeek;
    import java.time.LocalTime;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.inject.Inject;
    
    import io.quarkus.test.junit.QuarkusTest;
    import com.exmaple.optaplanner.domain.Room;
    import com.exmaple.optaplanner.domain.Timeslot;
    import com.exmaple.optaplanner.domain.Lesson;
    import com.exmaple.optaplanner.domain.TimeTable;
    import com.exmaple.optaplanner.rest.TimeTableResource;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.Timeout;
    
    import static org.junit.jupiter.api.Assertions.assertFalse;
    import static org.junit.jupiter.api.Assertions.assertNotNull;
    import static org.junit.jupiter.api.Assertions.assertTrue;
    
    @QuarkusTest
    public class TimeTableResourceTest {
    
        @Inject
        TimeTableResource timeTableResource;
    
        @Test
        @Timeout(600_000)
        public void solve() {
            TimeTable problem = generateProblem();
            TimeTable solution = timeTableResource.solve(problem);
            assertFalse(solution.getLessonList().isEmpty());
            for (Lesson lesson : solution.getLessonList()) {
                assertNotNull(lesson.getTimeslot());
                assertNotNull(lesson.getRoom());
            }
            assertTrue(solution.getScore().isFeasible());
        }
    
        private TimeTable generateProblem() {
            List<Timeslot> timeslotList = new ArrayList<>();
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(8, 30), LocalTime.of(9, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(9, 30), LocalTime.of(10, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(10, 30), LocalTime.of(11, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(13, 30), LocalTime.of(14, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(14, 30), LocalTime.of(15, 30)));
    
            List<Room> roomList = new ArrayList<>();
            roomList.add(new Room("Room A"));
            roomList.add(new Room("Room B"));
            roomList.add(new Room("Room C"));
    
            List<Lesson> lessonList = new ArrayList<>();
            lessonList.add(new Lesson(101L, "Math", "B. May", "9th grade"));
            lessonList.add(new Lesson(102L, "Physics", "M. Curie", "9th grade"));
            lessonList.add(new Lesson(103L, "Geography", "M. Polo", "9th grade"));
            lessonList.add(new Lesson(104L, "English", "I. Jones", "9th grade"));
            lessonList.add(new Lesson(105L, "Spanish", "P. Cruz", "9th grade"));
    
            lessonList.add(new Lesson(201L, "Math", "B. May", "10th grade"));
            lessonList.add(new Lesson(202L, "Chemistry", "M. Curie", "10th grade"));
            lessonList.add(new Lesson(203L, "History", "I. Jones", "10th grade"));
            lessonList.add(new Lesson(204L, "English", "P. Cruz", "10th grade"));
            lessonList.add(new Lesson(205L, "French", "M. Curie", "10th grade"));
            return new TimeTable(timeslotList, roomList, lessonList);
        }
    
    }
    Copy to Clipboard Toggle word wrap

    此测试会验证之后,所有较少记录都会被分配给一个时间插槽和房间。它还会验证它发现了一个可行的解决方案(没有硬约束中断)。

  2. src/main/resources/application.properties 文件中添加 test 属性:

    # The solver runs only for 5 seconds to avoid a HTTP timeout in this simple implementation.
    # It's recommended to run for at least 5 minutes ("5m") otherwise.
    quarkus.optaplanner.solver.termination.spent-limit=5s
    
    # Effectively disable this termination in favor of the best-score-limit
    %test.quarkus.optaplanner.solver.termination.spent-limit=1h
    %test.quarkus.optaplanner.solver.termination.best-score-limit=0hard/*soft
    Copy to Clipboard Toggle word wrap

通常,解析器会在 200 毫秒内找到可行的解决方案。请注意,application.properties 文件在测试期间如何覆盖 solver 终止,以便在找到可行的解决方案 (0hard thesoft) 时立即终止。这可避免硬编码一个固定时间,因为单元测试可能会在任意硬件上运行。这种方法可确保测试运行足够长,以找到可行的解决方案,即使在较慢的系统上也是如此。但是,即使在快速的系统上,它不会运行毫秒的时间超过其严格要求。

14.8. 日志记录

完成 Red Hat Build of OptaPlanner 旧时间后,您可以使用日志信息来帮助微调 ConstraintProvider 中的限制。查看 info 日志文件中的分数计算速度,以评估对您的限制的影响。以调试模式运行应用程序,以显示应用程序采取的每个步骤,或使用 trace 日志记录来记录每个步骤和每次移动。

流程

  1. 在固定时间内运行 7000 timetable 应用程序,例如五分钟。
  2. 查看日志文件中的分数计算速度,如下例所示:

    ... Solving ended: ..., score calculation speed (29455/sec), ...
    Copy to Clipboard Toggle word wrap
  3. 更改约束,在相同时间内再次运行 planning 应用程序,并查看日志文件中记录的分数计算速度。
  4. 以 debug 模式运行应用程序,以记录应用程序所做的每个步骤:

    • 要从命令行运行调试模式,请使用 -D 系统属性。
    • 要永久启用调试模式,请在 application.properties 文件中添加以下行:

      quarkus.log.category."org.optaplanner".level=debug
      Copy to Clipboard Toggle word wrap

      以下示例以 debug 模式显示 日志文件中 的输出:

      ... Solving started: time spent (67), best score (-20init/0hard/0soft), environment mode (REPRODUCIBLE), random (JDK with seed 0).
      ...     CH step (0), time spent (128), score (-18init/0hard/0soft), selected move count (15), picked move ([Math(101) {null -> Room A}, Math(101) {null -> MONDAY 08:30}]).
      ...     CH step (1), time spent (145), score (-16init/0hard/0soft), selected move count (15), picked move ([Physics(102) {null -> Room A}, Physics(102) {null -> MONDAY 09:30}]).
      ...
      Copy to Clipboard Toggle word wrap
  5. 使用 trace 日志记录来显示每个步骤,以及每个步骤的每次移动。

OptaPlanner 通过 Micrometer (一个 Java 应用程序的指标检测库)公开指标。您可以将 Micrometer 与 Prometheus 搭配使用,来监控 IANA 时间应用程序中的 OptaPlanner solver。

先决条件

  • 您已使用 Java 创建了 OptaPlanner Demo timetable 应用程序。
  • 已安装 Prometheus。有关安装 Prometheus 的详情,请查看 Prometheus 网站。

流程

  1. 将 Micrometer Prometheus 依赖项添加到 IANA timetable pom.xml 文件中,其中 & lt;MICROMETER_VERSION > 是您安装的 Micrometer 版本:

    <dependency>
     <groupId>io.micrometer</groupId>
     <artifactId>micrometer-registry-prometheus</artifactId>
     <version><MICROMETER_VERSION></version>
    </dependency>
    Copy to Clipboard Toggle word wrap
    注意

    还需要 micrometer-core 依赖项。但是,这个依赖项包含在 optaplanner-core 依赖项中,因此您不需要将其添加到 pom.xml 文件中。

  2. 将以下导入语句添加到 TimeTableApp.java 类。

    import io.micrometer.core.instrument.Metrics;
    import io.micrometer.prometheus.PrometheusConfig;
    import io.micrometer.prometheus.PrometheusMeterRegistry;
    Copy to Clipboard Toggle word wrap
  3. TimeTableApp.java 类的主方法的顶部添加以下行,以便 Prometheus 可以在解决方案启动前从 com.sun.net.httpserver.HttpServer 中的数据:

    PrometheusMeterRegistry prometheusRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
    
            try {
                HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
                server.createContext("/prometheus", httpExchange -> {
                    String response = prometheusRegistry.scrape();
                    httpExchange.sendResponseHeaders(200, response.getBytes().length);
                    try (OutputStream os = httpExchange.getResponseBody()) {
                        os.write(response.getBytes());
                    }
                });
    
                new Thread(server::start).start();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
    
            Metrics.addRegistry(prometheusRegistry);
    
            solve();
        }
    Copy to Clipboard Toggle word wrap
  4. 添加以下行来控制计时。通过调整计时,您可以了解指标如何根据时间变化。

    withTerminationSpentLimit(Duration.ofMinutes(5)));
    Copy to Clipboard Toggle word wrap
  5. 启动 7000 timetable 应用程序。
  6. 在 Web 浏览器中打开 http://localhost:8080/prometheus,以在 Prometheus 中查看时间化应用程序。
  7. 打开您的监控系统,以查看您的 OptaPlanner 项目的指标。

    公开以下指标:

    • optaplanner_solver_errors_total :自测量开始以来发生的错误总数。
    • optaplanner_solver_solve_duration_seconds_active_count: 当前地址器的数量。
    • optaplanner_solver_solve_duration_seconds_max: 运行 longest-running 当前活跃解析程序的时间。
    • optaplanner_solver_solve_duration_seconds_duration_sum: 每个活跃解析器解析持续时间的总和。例如,如果有两个活跃的解析器,一个会运行一个三分钟,另一个一分钟,总地址时间为 4 分钟。

作为开发人员,您可以使用 OptaWeb Vehicle Routing starter 应用程序来优化您的电池团队。

先决条件

  • 已安装 OpenJDK (JDK) 11。Red Hat build of Open JDK 位于红帽客户门户网站中的 Software Downloads 页面中(需要登录)。
  • 已安装 Apache Maven 3.6 或更高版本。Maven 可从 Apache Maven 项目网站 获得。

第 15 章 什么是 OptaWeb Vehicle Routing?

许多行业的主要目的是传输各种类型的 cargo。这些企业的目标是将加载点从加载点发送到目的地,并以最有效的方式使用其电点。主要目标之一是最大程度降低以时间或距离来测量的批量成本。

这种类型的优化问题被称为 vehicle 路由问题(VRP),并且有很多变化。

Red Hat build of OptaPlanner 可以解决许多这些 vehicle 路由变体并提供解决方案示例。OptaPlanner 允许开发人员专注于对业务规则和要求建模,而不是学习 约束编程。optaweb Vehicle Routing 通过提供一个回答问题的入门应用程序来扩展 OptaPlanner 的 vehicle 路由功能,例如:

  • 我在哪里获取距离和旋转时间?
  • 如何在映射中视觉化解决方案?
  • 如何构建在云中运行的应用程序?

optaweb Vehicle Routing 使用 OpenStreetMap (OSM)数据文件。有关 OpenStreetMap 的详情,请查看 OpenStreetMap 网站。

在使用 OptaWeb Vehicle 路由时使用以下定义:

区域 :由 OSM 文件表示的 Earth 映射的任意区域。区域可以是国家、城市、连续或一组通常一起使用的国家/地区。例如,DACH 区域包括西班牙语(DE)、Austria (AT)和 Sphone (CH)。

国家/ 地区代码 :由 ISO-3166 标准分配给国家/地区的双字母代码。您可以使用国家代码来过滤 geosearch 结果。由于您可以处理跨越多个国家/地区(如 DACH 区域)的区域,OptaWeb Vehicle Routing 接受一个国家代码列表,以便 geosearch 过滤可与这些区域一起使用。有关国家代码列表,请参阅 ISO 3166 国家代码

Geosearch :一个查询类型,您可以提供一个地址或区域的位置名称作为 search 关键字,并接收多个 GPS 位置。返回的位置数量取决于 search 关键字的唯一位置数量。由于大多数位置名称不是唯一的,因此仅过滤出非相关的结果,方法是仅包含位于工作地区或国家/国家/地区。

您必须在构建和部署 OptaWeb Vehicle 路由前下载并准备部署文件。

流程

  1. 导航到红帽客户门户网站中的 Software Downloads 页面(需要登录),然后从下拉菜单中选择产品和版本:

    • 产品: 红帽构建的 OptaPlanner
    • Version: 8.33
  2. 下载 Red Hat build of OptaPlanner 8.33 Quick Starts
  3. 提取 rhbop-8.33.0-optaplanner-quickstarts-sources.zip 文件。
  4. 导航到 org.optaweb.optaweb-vehicle-routing-8.33.0.Final-redhat-00004 目录。
  5. 下载 Red Hat Build of OptaPlanner 8.33.0 Maven Repositroy (rhbop-8.33.0-optaplanner-maven-repository.zip)。
  6. 提取 rhbop-8.33.0-optaplanner-maven-repository.zip 文件。
  7. rhbop-8.33.0-optaplanner/maven-repository 子目录的内容复制到 ~/.m2/repository 目录中。
  8. 输入以下命令来构建 OptaWeb Vehicle Routing:

    mvn clean package -DskipTests
    Copy to Clipboard Toggle word wrap

Linux 用户可以使用 runLocally.sh Bash 脚本来运行 OptaWeb Vehicle Routing。

注意

runLocally.sh 脚本不会在 macOS 中运行。如果您无法使用 runLocally.sh 脚本,请参阅 第 18 章 手动配置和运行 OptaWeb Vehicle Routing

runLocally.sh 脚本自动执行以下设置步骤,否则必须手动执行:

  • 创建数据目录。
  • 从 Geofabrik 下载所选的 OpenStreetMap (OSM)文件。
  • 尝试自动将国家代码与每个下载的 OSM 文件关联。
  • 如果独立 JAR 文件不存在,则构建项目。
  • 通过使用一个 region 参数或以交互方式选择区域来启动 OptaWeb Vehicle 路由。

有关执行 local Locally.sh 脚本的说明,请参见以下小节:

使用 OptaWeb Vehicle Routing 启动的最简单方法是在没有任何参数的情况下运行 runLocally.sh 脚本。

先决条件

流程

  1. org.optaweb.optaweb-vehicle-routing-8.33.0.Final-redhat-00004 目录中输入以下命令。

     ./runLocally.sh
    Copy to Clipboard Toggle word wrap
  2. 如果提示创建 .optaweb-vehicle-routing 目录,请输入 y。首次运行脚本时,系统会提示您创建该目录。
  3. 如果提示下载 OSM 文件,请输入 y。您第一次运行脚本时,OptaWeb Vehicle Routing 会下载 Belgium OSM 文件。

    应用程序在下载 OSM 文件后启动。

  4. 要打开 OptaWeb Vehicle Routing 用户界面,请在网页浏览器中输入以下 URL:

    http://localhost:8080
    Copy to Clipboard Toggle word wrap
注意

您第一次运行脚本时,需要几分钟才能启动,因为 OSM 文件必须由 GraphHopper 导入,并存储为 road 网络图。下一次运行 run ly.sh 脚本时,负载时间会非常快。

使用交互模式查看下载的 OSM 文件和分配给每个区域的国家代码的列表。您可以使用交互模式从 Geofabrik 下载额外的 OSM 文件,而无需访问网站并选择下载目的地。

先决条件

流程

  1. 将目录更改为 org.optaweb.optaweb-vehicle-routing-8.33.0.Final-redhat-00004
  2. 输入以下命令以互动模式运行脚本:

    ./runLocally.sh -i
    Copy to Clipboard Toggle word wrap
  3. 在您选择的提示符处,输入 d 以显示下载菜单。之前下载的区域列表会显示,后跟您可以下载的区域列表。
  4. 可选:从之前下载的区域列表中选择区域:

    1. 在下载的区域列表中,输入与区域关联的数字。
    2. 按 Enter 键。
  5. 可选:下载区域:

    1. 输入与您要下载的区域关联的数字。例如,若要选择 Europe 映射,请输入 5
    2. 要下载映射,请输入 d,然后按 Enter 键。
    3. 要在映射中下载特定区域,请输入 e,然后输入与您要下载的区域关联的数字,然后按 Enter 键。

      使用大型 OSM 文件

      为获得最佳用户体验,请使用较小的区域,如个人美国或美国状态。使用大于 1 GB 的 OSM 文件需要大量 RAM 大小,并且需要大量时间(最多几小时)进行初始处理。

      应用程序在下载 OSM 文件后启动。

  6. 要打开 OptaWeb Vehicle Routing 用户界面,请在网页浏览器中输入以下 URL:

    http://localhost:8080
    Copy to Clipboard Toggle word wrap

在非互动模式中使用 OptaWeb Vehicle Routing 启动 OptaWeb Vehicle Routing,并带有之前下载的 OSM 文件的单一命令。当您想快速切换区域或进行演示时,这非常有用。

先决条件

流程

  1. 将目录更改为 org.optaweb.optaweb-vehicle-routing-8.33.0.Final-redhat-00004
  2. 执行以下命令,其中 &lt ;OSM_FILE_NAME > 是之前下载的 OSM 文件:

    ./runLocally.sh <OSM_FILE_NAME>
    Copy to Clipboard Toggle word wrap

17.4. 更新数据目录

如果要使用不同的数据目录,您可以更新 OptaWeb Vehicle Routing 使用的数据目录。默认数据目录为 $HOME/.optaweb-vehicle-routing

先决条件

流程

  • 要使用不同的数据目录,请将目录的绝对路径添加到当前数据目录中的 .DATA_DIR_LAST 文件。
  • 要更改与地区关联的国家代码,请在当前数据目录中编辑 national_codes 目录中的对应文件。

    例如,如果您为 Scotland 下载 OSM 文件,且脚本无法猜测国家代码,请将 national _codes/scotland-latest 的内容设置为 GB。

  • 要删除区域,请从 data 目录中的 openstreetmap 目录中删除对应的 OSM 文件,并删除 graphhopper 目录中的区域目录。

第 18 章 手动配置和运行 OptaWeb Vehicle Routing

运行 OptaWeb Vehicle Routing 的最简单方法是使用 runlocally.sh 脚本。但是,如果系统上没有 Bash,您可以手动完成 runlocally.sh 脚本执行的步骤。

先决条件

流程

  1. 下载路由数据。

    路由引擎需要地理数据来计算在位置间传输所需时间。在运行 OptaWeb Vehicle Routing 前,您必须将 OpenStreetMap (OSM)数据文件下载到本地文件系统中。

    注意

    OSM 数据文件通常介于 100 MB 到 1 GB 之间,需要时间来下载文件,以便在构建或启动 OptaWeb Vehicle Routing 应用程序前下载文件。

    1. 在网页浏览器中打开 http://download.geofabrik.de/
    2. 单击 Sub Region 列表中的区域,如 Europe。此时会打开 subregion 页面。
    3. Sub Regions 表中,下载国家的 OSM 文件(.osm.pbf),如 Belgium。
  2. 创建数据结构。

    optaweb Vehicle Routing 读取和写入文件系统中多种数据。它从 openstreetmap 目录中读取 OSM (OpenStreetMap)文件,将 road 网络图写入 图形 跃点目录,并在名为 db 的目录中保留用户数据。创建一个用于存储所有数据的新目录,以便更轻松地升级到 OptaWeb Vehicle Routing 的较新版本,并继续使用之前创建的数据。

    1. 创建 $HOME/.optaweb-vehicle-routing 目录。
    2. $HOME/.optaweb-vehicle-routing 目录中创建 openstreetmap 目录:

      $HOME/.optaweb-vehicle-routing
      └── openstreetmap
      Copy to Clipboard Toggle word wrap
    3. 将所有下载的 OSM 文件(扩展为 .osm.pbf的文件)移到 openstreetmap 目录中。

      目录结构的其余部分由 OptaWeb Vehicle Routing 应用程序首次运行时创建。之后,您的目录结构类似以下示例:

      $HOME/.optaweb-vehicle-routing
      
      ├── db
      │   └── vrp.mv.db
      ├── graphhopper
      │   └── belgium-latest
      └── openstreetmap
          └── belgium-latest.osm.pbf
      Copy to Clipboard Toggle word wrap
  3. 将目录改为 rhbop-8.33.0-kogito-and-optaplanner-quickstarts/optaweb-8.33.0.Final-redhat-00004/optaweb-vehicle-routing/optaweb-vehicle-routing-standalone/target
  4. 要运行 OptaWeb Vehicle Routing,请输入以下命令:

    java \
    -Dapp.demo.data-set-dir=$HOME/.optaweb-vehicle-routing/dataset \
    -Dapp.persistence.h2-dir=$HOME/.optaweb-vehicle-routing/db \
    -Dapp.routing.gh-dir=$HOME/.optaweb-vehicle-routing/graphhopper \
    -Dapp.routing.osm-dir=$HOME/.optaweb-vehicle-routing/openstreetmap \
    -Dapp.routing.osm-file=<OSM_FILE_NAME> \
    -Dapp.region.country-codes=<COUNTRY_CODE_LIST> \
    -jar quarkus-app/quarkus-run.jar
    Copy to Clipboard Toggle word wrap

    在这个命令中,替换以下变量:

    • <OSM_FILE_NAME > :您要使用的区域的 OSM 文件以及之前下载的区域
    • <COUNTRY_CODE_LIST > :用于过滤 geosearch 查询的国家代码的逗号分隔列表。有关国家代码列表,请参阅 ISO 3166 国家代码

      应用程序在下载 OSM 文件后启动。

      在以下示例中,OptaWeb Vehicle Routing 下载 Central 美国的 OSM 映射(central-america-latest.osm.pbf),并在国家 Belize (BZ)和 Guatemala (GT)中搜索。

      java \
      -Dapp.demo.data-set-dir=$HOME/.optaweb-vehicle-routing/dataset \
      -Dapp.persistence.h2-dir=$HOME/.optaweb-vehicle-routing/db \
      -Dapp.routing.gh-dir=$HOME/.optaweb-vehicle-routing/graphhopper \
      -Dapp.routing.osm-dir=$HOME/.optaweb-vehicle-routing/openstreetmap \
      -Dapp.routing.osm-file=entral-america-latest.osm.pbf \
      -Dapp.region.country-codes=BZ,GT \
      -jar quarkus-app/quarkus-run.jar
      Copy to Clipboard Toggle word wrap
  5. 要打开 OptaWeb Vehicle Routing 用户界面,请在网页浏览器中输入以下 URL:

    http://localhost:8080
    Copy to Clipboard Toggle word wrap

Linux 用户可以使用 runOnOpenShift.sh Bash 脚本在 Red Hat OpenShift 上安装 OptaWeb Vehicle Routing。

注意

runOnOpenShift.sh 脚本不会在 macOS 上运行。

先决条件

流程

  1. 登录或启动 Red Hat OpenShift 集群。
  1. 输入以下命令,其中 & lt;PROJECT_NAME > 是新项目的名称:

    oc new-project <PROJECT_NAME>
    Copy to Clipboard Toggle word wrap
  2. 如有必要,将目录改为 org.optaweb.optaweb-vehicle-routing-8.33.0.Final-redhat-00004
  3. 输入以下命令来执行 runOnOpenShift.sh 脚本并下载 OpenStreetMap (OSM)文件:

    ./runOnOpenShift.sh <OSM_FILE_NAME> <COUNTRY_CODE_LIST> <OSM_FILE_DOWNLOAD_URL>
    Copy to Clipboard Toggle word wrap

    在这个命令中,替换以下变量:

    • <OSM_FILE_NAME > :从 < OSM_FILE_DOWNLOAD_URL> 下载的文件的名称
    • <COUNTRY_CODE_LIST > :用于过滤 geosearch 查询的国家代码的逗号分隔列表。有关国家代码列表,请参阅 ISO 3166 国家代码
    • <OSM_FILE_DOWNLOAD_ URL> :可从 OpenShift 访问的 PBF 格式的 OSM 数据文件的 URL。该文件将在后端启动过程中下载,并保存为 /deployments/local/<OSM_FILE_NAME>

      在以下示例中,OptaWeb Vehicle Routing 下载 Central 美国的 OSM 映射(central-america-latest.osm.pbf),并在国家 Belize (BZ)和 Guatemala (GT)中搜索。

      ./runOnOpenShift.sh central-america-latest.osm.pbf BZ,GT http://download.geofabrik.de/europe/central-america-latest.osm.pbf
      Copy to Clipboard Toggle word wrap
注意

对于 runOnOpenShift.sh 脚本的帮助,请输入 ./runOnOpenShift.sh --help

在 Red Hat OpenShift 上部署 OptaWeb Vehicle Routing 应用程序后,您可以更新后端和前端。

先决条件

  • optaweb Vehicle Routing 已成功使用 Maven 构建并在 OpenShift 上部署。

流程

  • 要更新后端,请执行以下步骤:

    1. 更改源代码,并使用 Maven 构建后端模块。
    2. 将目录更改为 org.optaweb.optaweb-vehicle-routing-8.33.0.Final-redhat-00004
    3. 输入以下命令启动 OpenShift 构建:

      oc start-build backend --from-dir=. --follow
      Copy to Clipboard Toggle word wrap
  • 要更新前端,请执行以下步骤:

    1. 更改源代码,并使用 npm 工具构建前端模块。
    2. 将目录更改为 sources/optaweb-vehicle-routing-frontend
    3. 输入以下命令启动 OpenShift 构建:

      oc start-build frontend --from-dir=docker --follow
      Copy to Clipboard Toggle word wrap

第 20 章 使用 OptaWeb Vehicle Routing

在 OptaWeb Vehicle Routing 应用程序中,您可以标记映射中的多个位置。假设第一个位置为 depot。vehicles 必须从这一投票到您标记的所有其他位置提供很好的选择。

您可以设置 vehicle 的数量,以及每个载体的获取容量。但是,路由无法保证使用所有 vehicles。该应用程序根据需要对最佳路由使用尽可能多的 vehicle。

当前版本有一些限制:

  • 每次发送到某个位置的工作都应该使用一个点载载容量。例如,一个容量为 10 的 vehicle 可以访问最多 10 个位置,然后再返回 depot。
  • 不支持设置 vehicles 和 location 的自定义名称。

20.1. 创建路由

要创建最佳路由,请使用 OptaWeb Vehicle Routing 用户界面的 Demo 选项卡。

先决条件

  • optaweb Vehicle Routing 正在运行,您可以访问用户界面。

流程

  1. 在 OptaWeb Vehicle Routing 中,单击 Demo 以打开 Demo 选项卡。
  2. 使用映射上方的蓝色减号和加号按钮来设置载号。每个 vehicle 都有默认的容量 10。
  3. 根据需要,使用映射上的加号按钮到缩放。

    注意

    不要双击 缩放。双击 也会创建一个位置。

  4. 点 depot 的位置。
  5. 点映射上的其他位置进行发送点。
  6. 如果要删除位置:

    1. 将鼠标光标悬停在位置上,以查看位置名称。
    2. 在屏幕左侧的列表中找到位置名称。
    3. 点名称旁边的 X 图标。

每次添加或删除位置或更改 vehicle 数量时,应用程序会创建并显示新的最佳路由。如果解决方案使用多个 vehicles,则应用程序以不同的颜色显示每个载体的路由。

20.2. 查看和设置其他详情

您可以使用 OptaWeb Vehicle Routing 用户界面中的其他标签页来查看和设置其他详情。

先决条件

  • optaweb Vehicle Routing 正在运行,您可以访问用户界面。

流程

  • Vehicles 选项卡查看、添加和删除 vehicles,以及设置每个 vehicle 的容量。
  • Visits 选项卡查看和删除位置。
  • Route 选项卡选择每个载体并查看所选载体的路由。

20.3. 使用 OptaWeb Vehicle 路由创建自定义数据集

有由几个大型 Belgian 城市组成的内置演示数据集。如果要在 Load demo 菜单中有更多演示,您可以准备自己的数据集。

流程

  1. 在 OptaWeb Vehicle Routing 中,通过单击映射或使用 geosearch 添加 depot 和一个或多个访问。
  2. Export,并将文件保存到数据集目录中。

    注意

    data set 目录是 app.demo.data-set-dir 属性中指定的目录。

    如果应用程序通过 runLocally.sh 脚本运行,数据集目录被设置为 $HOME/.optaweb-vehicle-routing/dataset

    否则,属性会从 application.properties 文件中获取,默认为 rhbop-8.33.0-kogito-and-optaplanner-quickstarts/optaweb-8.33.0.Final-redhat-00004/optaweb-vehicle-routing/optaweb-vehicle-routing-standalone/target/local/dataset

    您可以编辑 app.demo.data-set-dir 属性来指定 diffent 数据目录。

  3. 编辑 YAML 文件并为数据集选择唯一名称。
  4. 重启后端。

重启后端后,数据集目录中的文件会出现在 Load demo 菜单中。

20.4. 对 OptaWeb Vehicle 路由进行故障排除

如果 OptaWeb Vehicle 路由行为意外,请按照以下步骤出现问题。

先决条件

  • optaweb Vehicle Routing 正在运行,并意外地执行。

流程

  1. 要识别问题,请查看后端终端输出日志。
  2. 要解决这个问题,删除后端数据库:

    1. 通过在后端终端窗口中按 Ctrl+C 来停止后端。
    2. 删除 optaweb-vehicle-routing/optaweb-vehicle-routing-backend/local/db 目录。
    3. 重启 OptaWeb Vehicle Routing。

第 21 章 optaweb Vehicle Routing 开发指南

本节论述了如何在开发模式中配置和运行后端模块。

21.1. optaweb Vehicle Routing 项目结构

OptaWeb Vehicle Routing 项目是一个多模块 Maven 项目。

图 21.1. 模块依赖关系树图

后端和前端模块位于模块树的底部。这些模块包含应用程序源代码。

standalone 模块是一种编译模块,用于将后端和前端合并到单个可执行 JAR 文件中。

distribution 模块代表最终装配步骤。它采用独立应用程序和文档,并将其嵌套在容易分发的存档中。

后端和前端是单独的项目,您可以单独构建和部署。实际上,它们以完全不同的语言编写,并使用不同的工具构建。这两个项目都可以提供现代开发人员体验,并在代码更改和正在运行的应用程序之间快速切换。

下一小节介绍了如何在开发模式下运行后端和前端项目。

21.2. OptaWeb Vehicle Routing 后端模块

后端模块包含一个服务器端应用程序,它使用红帽构建的 OptaPlanner 来优化载体路由。优化是一个 CPU 密集型计算,必须避免任何 I/O 操作才能对其完整潜在的执行。由于其中一个目标是最大程度降低往返成本,无论是时间还是距离,OptaWeb Vehicle 路由会使传输成本信息保留在 RAM 内存中。OptaPlanner 需要了解用户输入的每一个位置之间的旋转成本。此信息存储在名为 距离列表的结构中

当您输入新位置时,OptaWeb Vehicle Routing 会计算新位置和目前输入的所有其他位置之间的旋转成本,并将传输成本存储在距离列表中。旋转成本计算由 GraphHopper 路由引擎执行。

后端模块实现以下额外功能:

  • Persistence
  • 前端的 websocket 连接
  • 数据集加载、导出和导入

要了解更多有关后端代码架构的信息,请参阅 第 22 章 optaweb Vehicle Routing 后端架构

下一节介绍了如何在开发模式下配置和运行后端。

21.2.1. 运行 OptaWeb Vehicle Routing 后端模块

您可以在 Quarkus 开发模式下运行后端模块。

先决条件

流程

  1. 将目录更改为 rhbop-8.33.0-kogito-and-optaplanner-quickstarts/optaweb-8.33.0.Final-redhat-00004/optaweb-vehicle-routing/optaweb-vehicle-routing-backend
  2. 要在开发模式下运行后端,请输入以下命令:

    mvn compile quarkus:dev
    Copy to Clipboard Toggle word wrap

您可以使用 IntelliJ IDEA Ulitmate 运行 OptaWeb Vehicle Routing 后端模块,以便更轻松地开发您的项目。IntelliJ IDEA Ultimate 包含一个 Quarkus 插件,它为使用 Quarkus 框架的模块自动创建运行配置。

流程

使用 optaweb-vehicle-routing-backend 运行配置来运行后端。

其他资源

如需更多信息,请参阅 运行 Quarkus 应用程序

21.2.3. Quarkus 开发模式

在开发模式中,如果更改了后端源代码或配置,并且您刷新前端运行的浏览器标签页,后端会自动重启。

了解有关 Quarkus 开发模式 的更多信息

您可以临时或永久覆盖 OptaWeb Vehicle Routing 后端模块的默认系统属性值。

OptaWeb Vehicle Routing 后端模块系统属性存储在 /src/main/resources/application.properties 文件中。此文件在版本控制下。使用它永久存储默认配置属性值,并定义 Quarkus 配置集。

先决条件

流程

  • 要临时覆盖默认系统属性值,在运行 mvnjava 命令时包括 -D<PROPERTY>=<VALUE &gt ; 参数,其中 <PROPERTY > 是您要更改的属性值,<VALUE > 是您要临时分配给该属性的值。以下示例演示了如何在使用 Maven 在 dev 模式中编译 Quarkus 项目时,如何将 quarkus.http.port 系统属性的值临时改为 8181

    mvn compile quarkus:dev -Dquarkus.http.port=8181
    Copy to Clipboard Toggle word wrap

    这会临时更改存储在 /src/main/resources/application.properties 文件中的属性值。

  • 要永久更改配置值,例如存储特定于您的开发环境的配置,请将 env-example 文件的内容复制到 optaweb-vehicle-routing-backend/.env 文件中。

    此文件不包括在版本控制中,因此在克隆存储库时不存在该文件。您可以在 .env 文件中进行更改,而不影响 Git 工作树。

其他资源

有关 OptaWeb Vehicle Routing 配置属性的完整列表,请参阅 第 23 章 optaweb Vehicle Routing 后端配置属性

21.2.5. optaweb Vehicle Routing 后端日志记录

optaweb Vehicle Routing 使用 SLF4J API 和 Logback 作为日志记录框架。如需更多信息,请参阅 Quarkus - 配置日志记录

21.3. 使用 OptaWeb Vehicle Routing 前端模块

前端项目使用 Create React App 启动。创建 React App 提供了很多脚本和依赖项,可帮助开发并为生产环境构建应用程序。

先决条件

流程

  1. 在 Fedora 中,输入以下命令设置开发环境:

    sudo dnf install npm
    Copy to Clipboard Toggle word wrap

    有关安装 npm 的更多信息,请参阅下载并安装 Node.js 和 npm

  2. 将目录更改为 rhbop-8.33.0-kogito-and-optaplanner-quickstarts/optaweb-8.33.0.Final-redhat-00004/optaweb-vehicle-routing/optaweb-vehicle-routing-frontend
  3. 安装 npm 依赖项:

    npm install
    Copy to Clipboard Toggle word wrap

    与 Maven 不同,npm 软件包管理器会在项目目录下在 node_modules 中安装依赖项,且仅在执行 npm 安装 时才执行。每当 package.json 中列出的依赖项更改时,例如,当您拉取到 master 分支更改时,您必须在运行开发服务器前执行 npm 安装

  4. 输入以下命令来运行开发服务器:

    npm start
    Copy to Clipboard Toggle word wrap
  5. 如果没有自动打开,在 Web 浏览器中打开 http://localhost:3000/

    默认情况下,npm start 命令尝试在默认浏览器中打开此 URL。

    注意

    如果您不希望 npm start 命令在每次运行时打开新的浏览器标签页,请导出 BROWSER=none 环境变量。您可以使用 .env.local 文件使这个首选项永久化。要做到这一点,请输入以下命令:

    echo BROWSER=none >> .env.local
    Copy to Clipboard Toggle word wrap

    每当在前端源代码中更改时,浏览器都会刷新页面。在终端中运行的开发服务器进程也会选择更改,并将编译和 lint 错误输出到控制台。

  6. 输入以下命令来运行测试:

    npm test
    Copy to Clipboard Toggle word wrap
  7. 更改 REACT_APP_BACKEND_URL 环境变量的值,以指定在执行 npm start 或 npm run 构建时npm 使用的后端项目的位置,例如:

    REACT_APP_BACKEND_URL=http://10.0.0.123:8081
    Copy to Clipboard Toggle word wrap
    注意

    环境变量在 npm 构建过程中在 JavaScript 捆绑包中硬编码,因此您必须在构建和部署前端前指定后端位置。

    要了解更多有关 React 环境变量的信息,请参阅 添加自定义环境变量

  8. 要构建前端,请输入以下命令之一:

    ./mvnw install
    Copy to Clipboard Toggle word wrap
    mvn install
    Copy to Clipboard Toggle word wrap

第 22 章 optaweb Vehicle Routing 后端架构

域模型和用例对于应用程序至关重要。OptaWeb Vehicle Routing 域模型位于构架的中心,由嵌入用例的应用程序层组成。路由优化、距离计算、持久性和网络通信等功能被视为实施详细信息,并放置在架构的最顶层。

图 22.1. 应用程序层图

22.1. 代码机构

后端代码组织在三个层中,如上图所示。

org.optaweb.vehiclerouting
├── domain
├── plugin          # Infrastructure layer
│   ├── persistence
│   ├── planner
│   ├── routing
│   └── rest
└── service         # Application layer
    ├── demo
    ├── distance
    ├── error
    ├── location
    ├── region
    ├── reload
    ├── route
    └── vehicle
Copy to Clipboard Toggle word wrap

service 软件包包含实施用例的应用程序层。插件 软件包包含基础架构层。

每个层中的代码按照功能进一步组织。这意味着每个服务或插件都有自己的软件包。

22.2. 依赖项规则

编译时依赖项只允许从外部层指向中心。遵循此规则有助于使域模型独立于底层框架和其他实施详情,并更精确建模业务实体的行为。随着正在推送到外文的演示和持久性,测试业务实体和用例的行为更为容易。

域没有依赖项。

服务仅依赖于域。如果服务需要发送结果(例如到数据库或客户端),它会使用输出边界接口。其实现由 上下文和依赖项注入 (CDI)容器注入。

插件以两种方式依赖于服务:首先,它们根据用户输入或来自优化引擎的事件调用服务。服务注入到插件中,该插件会将构建和依赖项解析的负担移到 IoC 容器。其次,插件实施服务输出边界接口来处理用例结果,例如对数据库保留更改或向 Web UI 发送响应。

22.3. 域软件包

domain 软件包包含对此项目的域建模 的业务对象,如 LocationVehicleRoute。这些对象严格于面向业务的,不得受到任何工具和框架的影响,如对象关系映射工具和 Web 服务框架。

22.4. 服务软件包

service 软件包包含实施 用例 的类。使用案例描述了您要做的项,例如添加新位置、更改出口容量或查找地址协调。管理用例的新规则使用域对象表示。

服务通常需要与外部层中的插件交互,如持久性、Web 和优化。为了满足层之间的依赖项规则,服务和插件之间的交互以定义服务依赖项的接口表示。插件可以通过提供实现服务的边界接口的 bean 来满足服务的依赖项。CDI 容器会创建一个插件 bean 实例,并在运行时将其注入该服务。这是控制原则的 inversion 示例。

22.5. 插件软件包

插件 软件包包含基础架构功能,如优化、持久性、路由和网络。

第 23 章 optaweb Vehicle Routing 后端配置属性

您可以设置下表中列出的 OptaWeb Vehicle Routing 应用程序属性。

Expand
属性类型示例描述

app.demo.data-set-dir

相对路径或绝对路径

/home/user/.optaweb-vehicle-routing/dataset

自定义数据集从此目录加载。默认为 local/dataset

app.persistence.h2-dir

相对路径或绝对路径

/home/user/.optaweb-vehicle-routing/db

H2 用来存储数据库文件的目录。默认为 local/db

app.region.country-codes

ISO 3166-1 alpha-2 国家代码列表

US,GB,IE,DE,AT,CH )可能为空

限制 geosearch 结果。

app.routing.engine

Enumeration

air,graphhopper

路由引擎实施。默认为 graphhopper

app.routing.gh-dir

相对路径或绝对路径

/home/user/.optaweb-vehicle-routing/graphhopper

GraphHopper 用来存储 road 网络图形的目录。默认为 local/graphhopper

app.routing.osm-dir

相对路径或绝对路径

/home/user/.optaweb-vehicle-routing/openstreetmap

包含 OSM 文件的目录。默认为 local/openstreetmap

app.routing.osm-file

文件名

belgium-latest.osm.pbf

GraphHopper 要载入的 OSM 文件的名称。该文件必须放在 app.routing.osm-dir 下。

optaplanner.solver.termination.spent-limit

java.time.Duration

  • 1m
  • 150s
  • P2dT21h (PnDTnHnMn.nS)

在位置更改后解析程序应运行的时间。

server.address

IP 地址或主机名

10.0.0.123, my-vrp.geo-1.openshiftapps.com

将服务器绑定到的网络地址。

server.port

端口号

4000, 8081

服务器 HTTP 端口。

附录 A. 版本信息

文档最后在 2023 年 3 月 14 日更新。

法律通告

Copyright © 2023 Red Hat, Inc.
The text of and illustrations in this document are licensed by Red Hat under a Creative Commons Attribution–Share Alike 3.0 Unported license ("CC-BY-SA"). An explanation of CC-BY-SA is available at http://creativecommons.org/licenses/by-sa/3.0/. In accordance with CC-BY-SA, if you distribute this document or an adaptation of it, you must provide the URL for the original version.
Red Hat, as the licensor of this document, waives the right to enforce, and agrees not to assert, Section 4d of CC-BY-SA to the fullest extent permitted by applicable law.
Red Hat, Red Hat Enterprise Linux, the Shadowman logo, the Red Hat logo, JBoss, OpenShift, Fedora, the Infinity logo, and RHCE are trademarks of Red Hat, Inc., registered in the United States and other countries.
Linux® is the registered trademark of Linus Torvalds in the United States and other countries.
Java® is a registered trademark of Oracle and/or its affiliates.
XFS® is a trademark of Silicon Graphics International Corp. or its subsidiaries in the United States and/or other countries.
MySQL® is a registered trademark of MySQL AB in the United States, the European Union and other countries.
Node.js® is an official trademark of Joyent. Red Hat is not formally related to or endorsed by the official Joyent Node.js open source or commercial project.
The OpenStack® Word Mark and OpenStack logo are either registered trademarks/service marks or trademarks/service marks of the OpenStack Foundation, in the United States and other countries and are used with the OpenStack Foundation's permission. We are not affiliated with, endorsed or sponsored by the OpenStack Foundation, or the OpenStack community.
All other trademarks are the property of their respective owners.
返回顶部
Red Hat logoGithubredditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

通过我们的产品和服务,以及可以信赖的内容,帮助红帽用户创新并实现他们的目标。 了解我们当前的更新.

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

Theme

© 2026 Red Hat