Ein Leitfaden für OptaPlanner

Ein Leitfaden für OptaPlanner

1. Einführung in OptaPlanner

In diesem Tutorial sehen wir uns einen Java-Constraint-Zufriedenheitslöser namensOptaPlanner an.

OptaPlanner löst Planungsprobleme mithilfe einer Reihe von Algorithmen mit minimalem Setup.

Obwohl ein Verständnis der Algorithmen hilfreich sein kann, leistet das Framework die harte Arbeit für uns.

2. Maven-Abhängigkeit

Zunächst fügen wir eine Maven-Abhängigkeit für OptaPlanner hinzu:


    org.optaplanner
    optaplanner-core
    7.9.0.Final

Wir finden die neueste Version von OptaPlanner ausMaven Central repository.

3. Problem/Solution Class

Um ein Problem zu lösen, brauchen wir sicherlich ein konkretes Beispiel.

Der Stundenplan für Vorlesungen ist ein geeignetes Beispiel, da es schwierig ist, Ressourcen wie Räume, Zeit und Lehrer unter einen Hut zu bringen.

3.1. CourseSchedule

CourseSchedule enthält eine Kombination unserer Problemvariablen und Planungsentitäten, daher ist es die Lösungsklasse. Aus diesem Grund verwenden wir mehrere Anmerkungen, um sie zu konfigurieren.

Schauen wir uns jedes einzeln genauer an:

@PlanningSolution
public class CourseSchedule {

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

DiePlanningSolution -Sannotation teilt OptaPlanner mit, dass diese Klasse die Daten enthält, die eine Lösung umfassen sollen.

OptaPlanner erwartet diese Mindestkomponenten: die Planungseinheit, Problemfakten und eine Bewertung.

3.2. Lecture

Lecture,ist ein POJO, sieht aus wie:

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

Wir verwenden die KlasseLectureals Planungsentität, daher fügen wir dem Getter eine weitere Anmerkung inCourseSchedule hinzu:

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

Unsere Planungseinheit enthält die Einschränkungen, die festgelegt werden.

Die AnnotationPlanningVariable und die AnnotationvalueRangeProviderRefverknüpfen die Einschränkungen mit den Problemfakten.

Diese Einschränkungswerte werden später für alle Planungseinheiten bewertet.

3.3. Problem Fakten

Die VariablenroomNumber andperiodwirken ähnlich wie Einschränkungen.

OptaPlanner bewertet die Lösungen als Ergebnis der Logik unter Verwendung dieser Variablen. Wir fügen beidengetter-Methoden Anmerkungen hinzu:

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

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

Diese Listen sind alle möglichen Werte, die in den FeldernLecture verwendet werden.

OptaPlanner fügt sie in alle Lösungen im gesamten Suchbereich ein.

Schließlich wird für jede der Lösungen eine Punktzahl festgelegt. Daher benötigen wir ein Feld, um die Punktzahl zu speichern:

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

Ohne eine Bewertung kann OptaPlanner nicht die optimale Lösung finden, daher die betonte Bedeutung früher.

4. Wertung

Im Gegensatz zu dem, was wir bisher betrachtet haben, erfordert die Scoring-Klasse mehr benutzerdefinierten Code.

Dies liegt daran, dass der Score-Rechner spezifisch für das Problem und das Domain-Modell ist.

4.1. Benutzerdefiniertes Java

Wir verwenden eine einfache Punktzahlberechnung, um dieses Problem zu lösen (obwohl es nicht so scheint):

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

Wenn wir uns den obigen Code genauer ansehen, werden die wichtigen Teile klarer. We calculate a score in the loop because the List<Lecture> contains specific non-unique combinations of rooms and periods.

DieHashSet -Sis wird zum Speichern eines eindeutigen Schlüssels (einer Zeichenfolge) verwendet, damit doppelte Vorlesungen im selben Raum und in derselben Periode bestraft werden können.

Als Ergebnis erhalten wir einzigartige Sätze von Räumen und Zeiträumen.

4.2. Sabber

Mit Drools-Dateien können Sie schnell die Regeln für das Anwenden auf Dateien ändern. Während die Syntax manchmal verwirrend sein kann, kann die Drools-Datei eine Möglichkeit sein, Logik außerhalb der kompilierten Klassen zu verwalten.

Unsere Regel zum Verhindern von NULL-Einträgen sieht folgendermaßen aus:

global HardSoftScoreHolder scoreHolder;

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

5. Solver-Konfiguration

Als weitere notwendige Konfigurationsdatei benötigen wir eine XML-Datei, um den Solver zu konfigurieren.

5.1. XML-Konfigurationsdatei


    

    
        
            org.example.optaplanner.ScoreCalculator
        
    

    
        10
    

Aufgrund unserer Anmerkungen in der KlasseCourseSchedule verwenden wir hier das ElementscanAnnotatedClasses, um Dateien im Klassenpfad zu scannen.

Der Elementinhalt vonscoreDirectorFactoryetzt unsereScoreCalculator-Klasse so, dass sie unsere Bewertungslogik enthält.

Wenn wir eine Drools-Datei verwenden möchten, ersetzen wir den Elementinhalt durch:

courseScheduleScoreRules.drl

Unsere endgültige Einstellung ist das Abschlusselement. Anstatt endlos nach einer optimierten Lösung zu suchen, die möglicherweise nie existiert, wird die Suche mit dieser Einstellung nach einer bestimmten Zeit abgebrochen.

Zehn Sekunden sind mehr als genug für die meisten Probleme.

6. Testen

Wir haben unsere Lösungs-, Lösungs- und Problemklassen konfiguriert. Lass es uns testen!

6.1. Aufbau unseres Tests

Zuerst machen wir einige Einstellungen:

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

unsolvedCourseSchedule = new CourseSchedule();

Zweitens füllen wir Daten in die Objekte der Planungsentitätssammlung und des ProblemfaktsList.

6.2. Testdurchführung und Verifikation

Schließlich testen wir es, indem wirsolve aufrufen.

CourseSchedule solvedCourseSchedule = solver.solve(unsolvedCourseSchedule);

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

Wir überprüfen, obsolvedCourseSchedule eine Punktzahl hat, die uns sagt, dass wir die „optimale“ Lösung haben.

Als Bonus erstellen wir eine Druckmethode, die unsere optimierte Lösung anzeigt:

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

Diese Methode zeigt Folgendes an:

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

Beachten Sie, wie sich die letzten drei Einträge wiederholen. Dies geschieht, weil es keine optimale Lösung für unser Problem gibt. Wir haben drei Stunden, zwei Klassenräume und zehn Vorlesungen gewählt.

Aufgrund dieser festen Ressourcen gibt es nur sechs mögliche Vorträge. Zumindest zeigt diese Antwort dem Benutzer, dass es nicht genügend Räume oder Zeiträume gibt, um alle Vorträge aufzunehmen.

7. Zusätzliche Funktionen

Unser Beispiel für OptaPlanner, das wir erstellt haben, ist einfach. Das Framework hat jedoch Funktionen für vielfältigere Anwendungsfälle hinzugefügt. Möglicherweise möchten wir unseren Algorithmus für die Optimierung implementieren oder ändern und dann das Framework für die Verwendung festlegen.

Aufgrund der jüngsten Verbesserungen der Multithreading-Funktionen von Java bietet OptaPlanner Entwicklern auch die Möglichkeit, mehrere Multithreading-Implementierungen wie Fork and Join, inkrementelles Lösen und Mandantenfähigkeit zu verwenden.

Weitere Informationen finden Sie indocumentation.

8. Fazit

Das OptaPlanner-Framework bietet Entwicklern ein leistungsstarkes Tool zur Lösung von Problemen bei der Erfüllung von Einschränkungen, wie z. B. Zeitplanung und Ressourcenzuweisung.

OptaPlanner bietet eine minimale Nutzung von JVM-Ressourcen sowie die Integration in Java EE. Der Autor unterstützt das Framework weiterhin und Red Hat hat es als Teil seiner Business Rules Management Suite hinzugefügt.

Wie immer kann der Codeover on Github gefunden werden.