OptaPlanner Spring Boot HelloworldJava快速入门
文章目录
本指南将引导您创建一个使用OptaPlanner的约束求解人工智能(AI)的Spring Boot应用程序。
1. 构建什么
您将构建一个优化学生和教师的学校课程表的REST应用程序:
您的服务将使用AI自动将Lesson实例分配给Timeslot和Room实例,以遵守硬性和软性调度约束,例如以下示例:
- 一个房间在同一时间内最多只能有一节课。
- 一位教师在同一时间内最多只能上一节课。
- 一个学生在同一时间内最多只能上一节课。
- 一位教师更喜欢在同一个房间上所有课程。
- 一位教师更喜欢连续的课程,并且不喜欢课程之间的空隙。
- 一个学生不喜欢连续的相同科目的课程。
从数学角度来说,学校课程安排是一个NP困难问题。这意味着很难进行扩展。即使在超级计算机上,简单地通过迭代所有可能的组合也需要数百万年才能处理一个非平凡的数据集。幸运的是,像OptaPlanner这样的AI约束求解器具有先进的算法,可以在合理的时间内提供近乎最优的解决方案。
2. 解决方案源代码
按照下一节中的说明逐步创建应用程序(推荐)。
或者,您也可以直接跳转到已完成的示例:
克隆Git仓库:
$ git clone https://github.com/kiegroup/optaplanner-quickstarts
或下载一个归档文件。
在technology目录中找到解决方案并运行它(参见其README文件)。
3. 先决条件
要完成本指南,您需要:
- 配置了JAVA_HOME的JDK 11+
- Apache Maven 3.8.1+或Gradle 4+
- 一个IDE,例如IntelliJ IDEA、VSCode或Eclipse
4. 构建文件和依赖项
使用以下依赖项创建一个Spring Boot应用程序:
- Spring Web(spring-boot-starter-web)
- OptaPlanner(optaplanner-spring-boot-starter)
如果选择Maven,则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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.7</version>
</parent>
<groupId>org.acme</groupId>
<artifactId>optaplanner-spring-boot-school-timetabling-quickstart</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>11</java.version>
<version.org.optaplanner>9.44.0.Final</version.org.optaplanner>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.optaplanner</groupId>
<artifactId>optaplanner-bom</artifactId>
<version>${version.org.optaplanner}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.optaplanner</groupId>
<artifactId>optaplanner-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.optaplanner</groupId>
<artifactId>optaplanner-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
另一方面,在Gradle中,您的build.gradle文件如下所示:
plugins {
id "org.springframework.boot" version "3.0.7"
id "io.spring.dependency-management" version "1.0.11.RELEASE"
id "java"
}
def optaplannerVersion = "9.44.0.Final"
group = "org.acme"
version = "1.0-SNAPSHOT"
sourceCompatibility = "11"
repositories {
mavenCentral()
}
dependencies {
implementation "org.springframework.boot:spring-boot-starter-web"
implementation "org.springframework.boot:spring-boot-starter-data-rest"
testImplementation("org.springframework.boot:spring-boot-starter-test")
implementation platform("org.optaplanner:optaplanner-bom:${
optaplannerVersion}")
implementation "org.optaplanner:optaplanner-spring-boot-starter"
testImplementation("org.optaplanner:optaplanner-test")
}
test {
useJUnitPlatform()
}
5. 建模领域对象
您的目标是将每节课分配给一个时间段和一个教室。您将创建以下类:
5.1 Timeslot
Timeslot类表示教学课程的时间间隔,例如周一10:30 - 11:30或周二13:30 - 14:30。为简单起见,所有时间段的持续时间相同,午餐时间或其他休息时间没有时间段。
一个时间段没有日期,因为高中课程表每周都会重复。因此,在连续规划中不需要连续规划。
创建src/main/java/org/acme/schooltimetabling/domain/Timeslot.java类:
package org.acme.schooltimetabling.domain;
import java.time.DayOfWeek;
import java.time.LocalTime;
public class Timeslot {
private DayOfWeek dayOfWeek;
private LocalTime startTime;
private LocalTime endTime;
public Timeslot() {
}
public Timeslot(DayOfWeek dayOfWeek, LocalTime startTime, LocalTime endTime) {
this.dayOfWeek = dayOfWeek;
this.startTime = startTime;
this.endTime = endTime;
}
public DayOfWeek getDayOfWeek() {
return dayOfWeek;
}
public LocalTime getStartTime() {
return startTime;
}
public LocalTime getEndTime() {
return endTime;
}
@Override
public String toString() {
return dayOfWeek + " " + startTime;
}
}
由于在求解过程中Timeslot实例不会改变,因此Timeslot被称为问题事实。这样的类不需要任何OptaPlanner特定的注释。
注意toString()方法保持输出简短,以便更容易阅读OptaPlanner的DEBUG或TRACE日志,如后面所示。
5.2 Room
Room类表示教学课程的地点,例如A教室或B教室。为简单起见,所有教室都没有容量限制,可以容纳所有课程。
创建src/main/java/org/acme/schooltimetabling/domain/Room.java类:
package org.acme.schooltimetabling.domain;
public class Room {
private String name;
public Room() {
}
public Room(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
}
Room实例在求解过程中不会改变,因此Room也是一个问题事实。
5.3 Lesson
在由Lesson类表示的课程中,教师向一组学生教授一个科目,例如9年级的A.Turing的数学课或10年级的M.Curie的化学课。如果同一位教师每周多次教授同一科目给同一学生群体,则有多个仅通过id区分的Lesson实例。例如,9年级每周有六节数学课。
在求解过程中,OptaPlanner会更改Lesson类的timeslot和room字段,以将每节课分配给时间段和教室。因为OptaPlanner会更改这些字段,所以Lesson是一个计划实体:
前图中大部分字段包含输入数据,除了橙色字段:输入数据中的课程的timeslot和room字段未分配(null),输出数据中已分配(非null)。OptaPlanner在求解过程中更改这些字段。这样的字段称为计划变量。为了让OptaPlanner识别它们,timeslot和room字段都需要一个@PlanningVariable注释。它们所属的类Lesson需要一个@PlanningEntity注释。
创建src/main/java/org/acme/schooltimetabling/domain/Lesson.java类:
package org.acme.schooltimetabling.domain;
import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.lookup.PlanningId;
import org.optaplanner.core.api.domain.variable.PlanningVariable;
@PlanningEntity
public class Lesson {