Ръководство за OptaPlanner

1. Въведение в OptaPlanner

В този урок разглеждаме решение за удовлетворяване на ограничения за Java, наречено OptaPlanner.

OptaPlanner решава проблеми с планирането, като използва набор от алгоритми с минимална настройка.

Въпреки че разбирането на алгоритмите може да предостави полезни подробности, като рамката изпълнява упоритата работа за нас.

2. Зависимост на Maven

Първо ще добавим зависимост на Maven за OptaPlanner:

 org.optaplanner optaplanner-core 7.9.0.Final  

Намираме най-новата версия на OptaPlanner от хранилището на Maven Central.

3. Клас на проблем / решение

За да разрешим даден проблем, ние със сигурност се нуждаем от конкретен като пример.

Графикът на лекциите е подходящ пример поради трудностите при балансиране на ресурси като стаи, време и учители.

3.1. График на курса

CourseSchedule съдържа комбинация от нашите проблемни променливи и обекти за планиране, следователно това е класът на решенията. В резултат на това използваме множество анотации, за да го конфигурираме.

Нека разгледаме по-отблизо всеки поотделно:

@PlanningSolution public class CourseSchedule { private List roomList; private List periodList; private List lectureList; private HardSoftScore score;

В PlanningSolution анотацията казва OptaPlanner, че този клас съдържа данните, за да обхване решение.

OptaPlanner очаква тези минимални компоненти: субектът за планиране, фактите за проблема и резултат.

3.2. Лекция

Лекция, POJO, изглежда така:

@PlanningEntity public class Lecture { 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; } }

Използваме лекционен клас като субект за планиране, така че добавяме още една анотация върху гетера в CourseSchedule :

@PlanningEntityCollectionProperty public List getLectureList() { return lectureList; }

Нашият обект за планиране съдържа ограниченията, които се задават.

В PlanningVariable анотация и valueRangeProviderRef анотациите сочат ограниченията на проблема фактите.

Тези стойности на ограничения ще бъдат отбелязани по-късно за всички обекти за планиране.

3.3. Проблемни факти

На roomNumber и период променливите действат като ограничения по същия начин един към друг.

OptaPlanner оценява решенията в резултат на логиката, използвайки тези променливи. Ние добавяме пояснения към двете Газопоглъщащите методи:

@ValueRangeProvider(id = "availableRooms") @ProblemFactCollectionProperty public List getRoomList() { return roomList; } @ValueRangeProvider(id = "availablePeriods") @ProblemFactCollectionProperty public List getPeriodList() { return periodList; } 

Тези списъци са всички възможни стойности, използвани в полетата Лекция .

OptaPlanner ги попълва във всички решения в пространството за търсене.

И накрая, след това задава оценка за всяко от решенията, така че се нуждаем от поле за съхраняване на резултата:

@PlanningScore public HardSoftScore getScore() { return score; }

Без резултат OptaPlanner не може да намери оптималното решение, поради което подчертаното значение е по-рано.

4. Точкуване

За разлика от това, което разгледахме до момента, класът за оценяване изисква повече персонализиран код.

Това е така, защото точният калкулатор е специфичен за проблема и модела на домейна.

4.1. Персонализирана Java

Използваме просто изчисление на резултатите, за да решим този проблем (въпреки че може да не изглежда така):

public class ScoreCalculator implements EasyScoreCalculator { @Override public Score calculateScore(CourseSchedule courseSchedule) { int hardScore = 0; int softScore = 0; Set 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.valueOf(hardScore, softScore); } }

Ако разгледаме по-отблизо горния код, важните части стават по-ясни. Изчисляваме резултат в цикъла, защото Списъкът съдържа специфични не уникални комбинации от стаи и периоди.

В HashSet се използва, за да запазите уникален ключ (низ), така че да можем да санкционира дублиращи лекции в една и съща стая и период.

В резултат на това получаваме уникални комплекти от стаи и периоди.

4.2. Drools

Drools files give us a quick way to alter rules for applying to files. While the syntax can sometimes be confusing the Drools file can be a way to manage logic outside of the compiled classes.

Our rule to prevent null entries looks like this:

global HardSoftScoreHolder scoreHolder; rule "noNullRoomPeriod" when Lecture( roomNumber == null ); Lecture( period == null ); then scoreHolder.addHardConstraintMatch(kcontext, -1); end

5. Solver Configuration

Another necessary configuration file, we need an XML file to configure the solver.

5.1. XML Configuration File

    org.baeldung.optaplanner.ScoreCalculator    10   

Due to our annotations in the CourseSchedule class, we use the scanAnnotatedClasses element here to scan files on the classpath.

The scoreDirectorFactory element contents set our ScoreCalculator class to contain our scoring logic.

When we want to use a Drools file, we replace the element contents with:

courseScheduleScoreRules.drl

Our final setting is the termination element. Rather than search endlessly for an optimized solution that may never exist, this setting will stop the search after a time limit.

Ten seconds is more than enough for most problems.

6. Testing

We configured our solution, solver and problem classes. Let's test it!

6.1. Setting up Our Test

First, we do some setup:

SolverFactory solverFactory = SolverFactory .createFromXmlResource("courseScheduleSolverConfiguration.xml"); solver = solverFactory.buildSolver(); unsolvedCourseSchedule = new CourseSchedule();

Second, we populate data into the planning entity collection and problem fact List objects.

6.2. Test Execution and Verification

Finally, we test it by calling solve.

CourseSchedule solvedCourseSchedule = solver.solve(unsolvedCourseSchedule); assertNotNull(solvedCourseSchedule.getScore()); assertEquals(-4, solvedCourseSchedule.getScore().getHardScore());

We check that the solvedCourseSchedule has a score which tells us that we have the “optimal” solution.

For a bonus, we create a print method that will display our optimized solution:

public void printCourseSchedule() { lectureList.stream() .map(c -> "Lecture in Room " + c.getRoomNumber().toString() + " during Period " + c.getPeriod().toString()) .forEach(k -> logger.info(k)); }

This method displays:

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

Notice how the last three entries are repeating. This happens because there is no optimal solution to our problem. We chose three periods, two classrooms and ten lectures.

There are only six possible lectures due to these fixed resources. At the very least this answer shows the user that there are not enough rooms or periods to contain all the lectures.

7. Extra Features

Our example for OptaPlanner we created was a simple one, however, the framework has added features for more diverse use cases. We may want to implement or alter our algorithm for optimization and then specify the framework to use it.

Due to recent improvements in Java's multi-threading capabilities, OptaPlanner also gives developers the ability to use multiple implementations of multi-threading such as fork and join, incremental solving and multitenancy.

Обърнете се към документацията за повече информация.

8. Заключение

Рамката OptaPlanner предоставя на разработчиците мощен инструмент за решаване на проблеми със задоволяването на ограничения, като планиране и разпределение на ресурси.

OptaPlanner предлага минимално използване на JVM ресурси, както и интегриране с Джакарта EE. Авторът продължава да поддържа рамката и Red Hat я добави като част от своя пакет за управление на бизнес правила.

Както винаги кодът може да бъде намерен в Github.