Руководство по OptaPlanner

Руководство по OptaPlanner

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

В этом руководстве мы рассмотрим средство решения ограничений Java под названиемOptaPlanner.

OptaPlanner решает задачи планирования, используя набор алгоритмов с минимальными настройками.

Хотя понимание алгоритмов может предоставить полезные детали, фреймворк выполняет за нас тяжелую работу.

2. Maven Dependency

Сначала мы добавим зависимость Maven для OptaPlanner:


    org.optaplanner
    optaplanner-core
    7.9.0.Final

Мы находим самую последнюю версию OptaPlanner изMaven Central repository.

3. Problem/Solution Class

Для решения проблемы нам, безусловно, нужен конкретный пример.

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

3.1. CourseScheduleс

CourseSchedule содержит комбинацию переменных нашей проблемы и объектов планирования, следовательно, это класс решения. В результате мы используем несколько аннотаций для его настройки.

Давайте рассмотрим каждый отдельно:

@PlanningSolution
public class CourseSchedule {

    private List roomList;
    private List periodList;
    private List lectureList;
    private HardSoftScore score;

СаннотацияPlanningSolution сообщает OptaPlanner, что этот класс содержит данные для включения решения.

OptaPlanner ожидает следующих минимальных компонентов: объект планирования, факты проблемы и оценка.

3.2. Lectureс

Lecture, 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;
    }
}

Мы используем классLecture в качестве объекта планирования, поэтому мы добавляем еще одну аннотацию к получателю вCourseSchedule:

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

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

АннотацииPlanningVariable иvalueRangeProviderRef связывают ограничения с фактами проблемы.

Эти значения ограничений будут оцениваться позже по всем объектам планирования.

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

ПеременныеroomNumber andperiod действуют как ограничения аналогично друг другу.

OptaPlanner оценивает решения в результате логики с использованием этих переменных. Мы добавляем аннотации к обоим методамgetter:

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

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

В этих списках представлены все возможные значения, используемые в поляхLecture .

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);
    }
}

Если мы поближе рассмотрим приведенный выше код, важные части станут более понятными. We calculate a score in the loop because the List<Lecture> contains specific non-unique combinations of rooms and periods.с

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

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

4.2. Drools

Файлы Drools дают нам быстрый способ изменить правила применения к файлам. Хотя синтаксис может иногда сбивать с толку, файл Drools может быть способом управления логикой вне скомпилированных классов.

Наше правило для предотвращения пустых записей выглядит так:

global HardSoftScoreHolder scoreHolder;

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

5. Конфигурация Солвера

Еще один необходимый файл конфигурации, нам нужен файл XML для настройки решателя.

5.1. XML-файл конфигурации


    

    
        
            org.example.optaplanner.ScoreCalculator
        
    

    
        10
    

Из-за наших аннотаций в классеCourseSchedule мы используем здесь элементscanAnnotatedClasses для сканирования файлов в пути к классам.

Содержимое элементаscoreDirectorFactory устанавливает наш классScoreCalculator для хранения нашей логики оценки.

Когда мы хотим использовать файл Drools, мы заменяем содержимое элемента на:

courseScheduleScoreRules.drl

Наша последняя настройка - это элемент завершения. Вместо бесконечного поиска оптимизированного решения, которое может никогда не существовать, этот параметр остановит поиск по истечении определенного времени.

Десять секунд более чем достаточно для большинства проблем.

6. тестирование

Мы настроили наше решение, решатель и классы задач. Давай проверим!

6.1. Настройка нашего теста

Сначала мы сделаем некоторые настройки:

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

unsolvedCourseSchedule = new CourseSchedule();

Во-вторых, мы заполняем данные в коллекцию плановых объектов и проблемные объектыList.

6.2. Выполнение теста и проверка

Наконец, мы проверяем это, вызываяsolve.

CourseSchedule solvedCourseSchedule = solver.solve(unsolvedCourseSchedule);

assertNotNull(solvedCourseSchedule.getScore());
assertEquals(-4, solvedCourseSchedule.getScore().getHardScore());

Мы проверяем, чтоsolvedCourseSchedule имеет оценку, которая говорит нам, что у нас есть «оптимальное» решение.

Для бонуса мы создаем метод печати, который будет отображать наше оптимизированное решение:

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

Обратите внимание, как последние три записи повторяются. Это происходит потому, что не существует оптимального решения нашей проблемы. Мы выбрали три периода, две аудитории и десять лекций.

Есть только шесть возможных лекций благодаря этим фиксированным ресурсам. По крайней мере, этот ответ показывает пользователю, что не хватает комнат или периодов для размещения всех лекций.

7. Дополнительные возможности

Наш пример для OptaPlanner, который мы создали, был простым, однако, фреймворк добавил функции для более разнообразных вариантов использования. Мы можем захотеть реализовать или изменить наш алгоритм оптимизации, а затем указать структуру для его использования.

Благодаря недавним улучшениям в многопоточности Java, OptaPlanner также дает разработчикам возможность использовать несколько реализаций многопоточности, таких как разветвление и соединение, инкрементное решение и многопоточность.

Обратитесь кdocumentation для получения дополнительной информации.

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

Платформа OptaPlanner предоставляет разработчикам мощный инструмент для решения проблем удовлетворения ограничений, таких как планирование и распределение ресурсов.

OptaPlanner предлагает минимальное использование ресурсов JVM, а также интеграцию с Java EE. Автор продолжает поддерживать фреймворк, и Red Hat добавила его в свой пакет Business Rules Management Suite.

Как всегда можно найти кодover on Github.