Рубрики
Без рубрики

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

Введение в библиотеку OptaPlanner с базовым примером оптимизации планирования ресурсов

Автор оригинала: baeldung.

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

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

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

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

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

Во – первых, мы добавим зависимость Maven для OptaPlanner:


    org.optaplanner
    optaplanner-core
    7.9.0.Final

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

3. Класс Проблем/Решений

Чтобы решить проблему, нам, конечно, нужен конкретный пример.

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

3.1. Расписание курсов

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

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

@PlanningSolution
public class CourseSchedule {

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

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

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

3.2. Лекция

Лекция, a 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;
    }
}

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

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

Наша планирующая сущность содержит ограничения, которые устанавливаются.

Переменная Planning annotation и valuerangeproviderrefs annotations связывают ограничения с фактами проблемы.

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

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

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

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. Подсчет очков

В отличие от того, что мы рассматривали до сих пор, класс scoring требует больше пользовательского кода.

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

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 может быть способом управления логикой вне скомпилированных классов.

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

global HardSoftScoreHolder scoreHolder;

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

5. Конфигурация решателя

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

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


    

    
        
            org.baeldung.optaplanner.ScoreCalculator
        
    

    
        10
    

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

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

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

courseScheduleScoreRules.drl

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

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

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

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

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

Во-первых, мы делаем некоторые настройки:

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

unsolvedCourseSchedule = new CourseSchedule();

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

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

Наконец, мы тестируем его, вызывая solve .

CourseSchedule solvedCourseSchedule = solver.solve(unsolvedCourseSchedule);

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

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

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

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 также дает разработчикам возможность использовать несколько реализаций многопоточности, таких как fork и join, incremental solving и multitenancy.

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

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

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

OptaPlanner предлагает минимальное использование ресурсов JVM, а также интеграцию с Jakarta EE. Автор продолжает поддерживать фреймворк, и Red Hat добавила его как часть своего пакета управления бизнес-правилами.

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