1. OptaPlanner 소개
이 사용방법(예제)에서는 OptaPlanner 라는 Java 제약 조건 만족 솔버를 살펴 봅니다 .
OptaPlanner는 최소한의 설정으로 일련의 알고리즘을 사용하여 계획 문제를 해결합니다.
알고리즘에 대한 이해가 유용한 세부 정보를 제공할 수 있지만 프레임워크가 우리를 위해 힘든 작업을 수행합니다.
2. 메이븐 의존성
먼저 OptaPlanner에 대한 Maven 의존성을 추가합니다.
<dependency>
<groupId>org.optaplanner</groupId>
<artifactId>optaplanner-core</artifactId>
<version>8.24.0.Final</version>
</dependency>
Maven Central 저장소 에서 최신 버전의 OptaPlanner를 찾습니다 .
3. 문제/솔루션 수업
문제를 해결하려면 확실히 예를 들어 특정 문제가 필요합니다.
강의 시간표는 방, 시간, 교사와 같은 자원의 균형을 맞추는 것이 어렵기 때문에 적합한 예입니다.
3.1. 코스 일정
CourseSchedule 에는 문제 변수와 계획 엔터티의 조합이 포함되어 있으므로 솔루션 클래스입니다. 결과적으로 여러 어노테이션을 사용하여 구성합니다.
각각을 개별적으로 자세히 살펴보겠습니다.
@PlanningSolution
public class CourseSchedule {
@ValueRangeProvider(id = "availableRooms")
@ProblemFactCollectionProperty
private List<Integer> roomList;
@ValueRangeProvider(id = "availablePeriods")
@ProblemFactCollectionProperty
private List<Integer> periodList;
@ProblemFactCollectionProperty
private List<Lecture> lectureList;
@PlanningScore
private HardSoftScore score;
PlanningSolution 어노테이션은 이 클래스에 솔루션을 포함하는 데이터가 포함되어 있음을 OptaPlanner에 알립니다.
OptaPlanner는 계획 엔터티, 문제 사실 및 점수와 같은 최소 구성 요소를 기대합니다.
3.2. 강의
POJO인 강의 는 다음과 같습니다.
@PlanningEntity
public class Lecture {
@PlaningId
private Long id;
public Integer roomNumber;
public Integer period;
public String teacher;
@PlanningVariable(
valueRangeProviderRefs = {"availablePeriods"})
public Integer getPeriod() {
return period;
}
@PlanningVariable(
valueRangeProviderRefs = {"availableRooms"})
public Integer getRoomNumber() {
return roomNumber;
}
}
Lecture 클래스를 계획 엔터티로 사용 하므로 CourseSchedule 의 getter에 다른 어노테이션을 추가합니다 .
@PlanningEntityCollectionProperty
public List<Lecture> getLectureList() {
return lectureList;
}
계획 엔터티에는 설정 중인 제약이 포함되어 있습니다.
PlanningVariable 어노테이션 및 valueRangeProviderRef 어노테이션은 제약 조건을 문제 사실에 연결합니다.
이러한 제약 조건 값은 나중에 모든 계획 엔터티에서 점수가 매겨집니다.
3.3. 문제 사실
roomNumber 및 period 변수 는 서로 유사하게 제약 조건으로 작동합니다.
OptaPlanner는 이러한 변수를 사용하여 논리의 결과로 솔루션의 점수를 매깁니다. 두 getter 메서드 에 어노테이션을 추가 합니다.
@ValueRangeProvider(id = "availableRooms")
@ProblemFactCollectionProperty
public List<Integer> getRoomList() {
return roomList;
}
@ValueRangeProvider(id = "availablePeriods")
@ProblemFactCollectionProperty
public List<Integer> getPeriodList() {
return periodList;
}
이러한 List은 강의 필드에서 사용되는 모든 가능한 값입니다.
OptaPlanner는 검색 공간 전체의 모든 솔루션에 이를 채웁니다.
마지막으로 각 솔루션에 점수를 설정하므로 점수를 저장할 필드가 필요합니다.
@PlanningScore
public HardSoftScore getScore() {
return score;
}
점수가 없으면 OptaPlanner는 최적의 솔루션을 찾을 수 없으므로 중요성이 더 일찍 강조됩니다.
4. 채점
지금까지 살펴본 것과 달리 채점 클래스에는 더 많은 사용자 지정 코드가 필요합니다.
이는 점수 계산기가 문제 및 도메인 모델에 따라 다르기 때문입니다.
4.1. 커스텀 자바
우리는 이 문제를 해결하기 위해 간단한 점수 계산을 사용합니다(그렇게 보이지 않을 수도 있음).
public class ScoreCalculator
implements EasyScoreCalculator<CourseSchedule, HardSoftScore> {
@Override
public HardSoftScore calculateScore(CourseSchedule courseSchedule) {
int hardScore = 0;
int softScore = 0;
Set<String> occupiedRooms = new HashSet<>();
for(Lecture lecture : courseSchedule.getLectureList()) {
String roomInUse = lecture.getPeriod()
.toString() + ":" + lecture.getRoomNumber().toString();
if(occupiedRooms.contains(roomInUse)){
hardScore += -1;
} else {
occupiedRooms.add(roomInUse);
}
}
return HardSoftScore.Of(hardScore, softScore);
}
}
위의 코드를 자세히 살펴보면 중요한 부분이 더 명확해집니다. List<Lecture> 에는 방과 기간의 고유하지 않은 특정 조합이 포함되어 있기 때문에 루프에서 점수를 계산합니다 .
HashSet 은 같은 강의실, 같은 기간에 중복 강의를 벌점 처리할 수 있도록 고유한 키(문자열)를 저장하는 데 사용됩니다.
결과적으로 우리는 고유한 방과 기간 세트를 받습니다.
5. 테스트
솔루션, 솔버 및 문제 클래스를 구성했습니다. 그것을 테스트하자!
5.1. 테스트 설정
먼저 몇 가지 설정을 수행합니다.
SolverFactory<CourseSchedule> solverFactory = SolverFactory.create(new SolverConfig()
.withSolutionClass(CourseSchedule.class)
.withEntityClasses(Lecture.class)
.withEasyScoreCalculatorClass(ScoreCalculator.class)
.withTerminationSpentLimit(Duration.ofSeconds(1)));
solver = solverFactory.buildSolver();
unsolvedCourseSchedule = new CourseSchedule();
둘째, 계획 엔티티 컬렉션 및 문제 사실 List 개체에 데이터를 채웁니다.
5.2. 테스트 실행 및 검증
마지막으로 solve 를 호출하여 테스트합니다 .
CourseSchedule solvedCourseSchedule = solver.solve(unsolvedCourseSchedule);
assertNotNull(solvedCourseSchedule.getScore());
assertEquals(-4, solvedCourseSchedule.getScore().getHardScore());
우리는 solveCourseSchedule 에 "최적" 솔루션이 있음을 알려주는 점수가 있는지 확인합니다.
보너스로 최적화된 솔루션을 표시하는 인쇄 방법을 만듭니다.
public void printCourseSchedule() {
lectureList.stream()
.map(c -> "Lecture in Room "
+ c.getRoomNumber().toString()
+ " during Period " + c.getPeriod().toString())
.forEach(k -> logger.info(k));
}
이 방법은 다음을 표시합니다.
Lecture in Room 1 during Period 1
Lecture in Room 2 during Period 1
Lecture in Room 1 during Period 2
Lecture in Room 2 during Period 2
Lecture in Room 1 during Period 3
Lecture in Room 2 during Period 3
Lecture in Room 1 during Period 1
Lecture in Room 1 during Period 1
Lecture in Room 1 during Period 1
Lecture in Room 1 during Period 1
마지막 세 항목이 어떻게 반복되는지 확인합니다. 이것은 문제에 대한 최적의 솔루션이 없기 때문에 발생합니다. 우리는 3개의 기간, 2개의 강의실 및 10개의 강의를 선택했습니다.
이러한 고정된 리소스로 인해 가능한 강의는 6개뿐입니다. 최소한 이 답변은 사용자에게 모든 강의를 담을 공간이나 기간이 충분하지 않다는 것을 보여줍니다.
6. 추가 기능
우리가 만든 OptaPlanner에 대한 예제는 간단한 것이지만 프레임워크에는 더 다양한 사용 사례를 위한 기능이 추가되었습니다. 최적화를 위해 알고리즘을 구현하거나 변경한 다음 이를 사용할 프레임워크를 지정할 수 있습니다.
최근 Java의 멀티스레딩 기능이 개선됨에 따라 OptaPlanner는 개발자에게 포크 및 조인, 증분 해결 및 멀티테넌시와 같은 멀티스레딩의 여러 구현을 사용할 수 있는 기능도 제공합니다.
자세한 내용은 설명서 를 참조하십시오.
7. 결론
OptaPlanner 프레임워크는 개발자에게 스케줄링 및 리소스 할당과 같은 제약 조건 충족 문제를 해결할 수 있는 강력한 도구를 제공합니다.
OptaPlanner는 최소한의 JVM 리소스 사용과 Jakarta EE와의 통합을 제공합니다. 작성자는 프레임워크를 계속 지원하고 있으며 Red Hat은 이를 Business Rules Management Suite의 일부로 추가했습니다.
항상 그렇듯이 코드는 Github 에서 찾을 수 있습니다 .