From c0b78ee35e5ad9351252eb023173008aedce31dd Mon Sep 17 00:00:00 2001 From: smokeyrobot Date: Sun, 2 Sep 2018 05:32:52 -0400 Subject: [PATCH] Bael-801 A Guide to OptaPlanner Pull Request (#4986) * BAEL-801 A Guide to OptaPlanner * BAEL-801 Optaplanner per Editor Review * BAEL-801 Fixed parent pom merge issue * BAEL-801 Fixed parent pom merge issue * BAEL-801 Added logback to suppress debug logging causing build failures * BAEL-801 Updated per editor review * BAEL-801 Removed static variables --- optaplanner/pom.xml | 27 ++++++++ .../baeldung/optaplanner/CourseSchedule.java | 63 +++++++++++++++++++ .../com/baeldung/optaplanner/Lecture.java | 30 +++++++++ .../baeldung/optaplanner/ScoreCalculator.java | 32 ++++++++++ .../src/main/resources/courseSchedule.drl | 14 +++++ .../courseScheduleSolverConfigDrools.xml | 12 ++++ .../courseScheduleSolverConfiguration.xml | 12 ++++ optaplanner/src/main/resources/logback.xml | 3 + .../optaplanner/test/OptaPlannerUnitTest.java | 50 +++++++++++++++ pom.xml | 3 +- 10 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 optaplanner/pom.xml create mode 100644 optaplanner/src/main/java/com/baeldung/optaplanner/CourseSchedule.java create mode 100644 optaplanner/src/main/java/com/baeldung/optaplanner/Lecture.java create mode 100644 optaplanner/src/main/java/com/baeldung/optaplanner/ScoreCalculator.java create mode 100644 optaplanner/src/main/resources/courseSchedule.drl create mode 100644 optaplanner/src/main/resources/courseScheduleSolverConfigDrools.xml create mode 100644 optaplanner/src/main/resources/courseScheduleSolverConfiguration.xml create mode 100644 optaplanner/src/main/resources/logback.xml create mode 100644 optaplanner/src/test/java/com/baeldung/optaplanner/test/OptaPlannerUnitTest.java diff --git a/optaplanner/pom.xml b/optaplanner/pom.xml new file mode 100644 index 0000000000..22abbedc8c --- /dev/null +++ b/optaplanner/pom.xml @@ -0,0 +1,27 @@ + + + + parent-modules + com.baeldung + 1.0.0-SNAPSHOT + + 4.0.0 + + optaplanner + + + + org.optaplanner + optaplanner-core + 7.9.0.Final + + + + + + + + + \ No newline at end of file diff --git a/optaplanner/src/main/java/com/baeldung/optaplanner/CourseSchedule.java b/optaplanner/src/main/java/com/baeldung/optaplanner/CourseSchedule.java new file mode 100644 index 0000000000..8a820ab56e --- /dev/null +++ b/optaplanner/src/main/java/com/baeldung/optaplanner/CourseSchedule.java @@ -0,0 +1,63 @@ +package com.baeldung.optaplanner; + +import org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty; +import org.optaplanner.core.api.domain.solution.PlanningScore; +import org.optaplanner.core.api.domain.solution.PlanningSolution; +import org.optaplanner.core.api.domain.solution.drools.ProblemFactCollectionProperty; +import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider; +import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +@PlanningSolution +public class CourseSchedule { + + Logger logger = LoggerFactory.getLogger("CourseSchedule"); + + private List roomList; + private List periodList; + private List lectureList; + private HardSoftScore score; + + public CourseSchedule(){ + roomList = new ArrayList<>(); + periodList = new ArrayList<>(); + lectureList = new ArrayList<>(); + } + + @ValueRangeProvider(id = "availableRooms") + @ProblemFactCollectionProperty + public List getRoomList() { + return roomList; + } + + @ValueRangeProvider(id = "availablePeriods") + @ProblemFactCollectionProperty + public List getPeriodList() { + return periodList; + } + + @PlanningEntityCollectionProperty + public List getLectureList() { + return lectureList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void printCourseSchedule() { + lectureList.stream() + .map(c -> "Lecture in Room " + c.getRoomNumber().toString() + " during Period " + c.getPeriod().toString()) + .forEach(k -> logger.info(k)); + } + +} diff --git a/optaplanner/src/main/java/com/baeldung/optaplanner/Lecture.java b/optaplanner/src/main/java/com/baeldung/optaplanner/Lecture.java new file mode 100644 index 0000000000..48af4e89d1 --- /dev/null +++ b/optaplanner/src/main/java/com/baeldung/optaplanner/Lecture.java @@ -0,0 +1,30 @@ +package com.baeldung.optaplanner; + +import org.optaplanner.core.api.domain.entity.PlanningEntity; +import org.optaplanner.core.api.domain.variable.PlanningVariable; + +@PlanningEntity +public class Lecture { + + private Integer roomNumber; + private Integer period; + + @PlanningVariable(valueRangeProviderRefs = {"availablePeriods"}) + public Integer getPeriod() { + return period; + } + + @PlanningVariable(valueRangeProviderRefs = {"availableRooms"}) + public Integer getRoomNumber() { + return roomNumber; + } + + public void setPeriod(Integer period) { + this.period = period; + } + + public void setRoomNumber(Integer roomNumber) { + this.roomNumber = roomNumber; + } + +} diff --git a/optaplanner/src/main/java/com/baeldung/optaplanner/ScoreCalculator.java b/optaplanner/src/main/java/com/baeldung/optaplanner/ScoreCalculator.java new file mode 100644 index 0000000000..11e5e4a5be --- /dev/null +++ b/optaplanner/src/main/java/com/baeldung/optaplanner/ScoreCalculator.java @@ -0,0 +1,32 @@ +package com.baeldung.optaplanner; + +import org.optaplanner.core.api.score.Score; +import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore; +import org.optaplanner.core.impl.score.director.easy.EasyScoreCalculator; + +import java.util.HashSet; + +public class ScoreCalculator implements EasyScoreCalculator { + + @Override + public Score calculateScore(CourseSchedule courseSchedule) { + int hardScore = 0; + int softScore = 0; + + HashSet occupiedRooms = new HashSet<>(); + for (Lecture lecture : courseSchedule.getLectureList()) { + if(lecture.getPeriod() != null && lecture.getRoomNumber() != null) { + String roomInUse = lecture.getPeriod().toString() + ":" + lecture.getRoomNumber().toString(); + if (occupiedRooms.contains(roomInUse)) { + hardScore += -1; + } else { + occupiedRooms.add(roomInUse); + } + } else { + hardScore += -1; + } + } + + return HardSoftScore.valueOf(hardScore, softScore); + } +} diff --git a/optaplanner/src/main/resources/courseSchedule.drl b/optaplanner/src/main/resources/courseSchedule.drl new file mode 100644 index 0000000000..9372a24976 --- /dev/null +++ b/optaplanner/src/main/resources/courseSchedule.drl @@ -0,0 +1,14 @@ +package com.baeldung.optaplanner + +import com.baeldung.optaplanner.Lecture; +import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScoreHolder; + +global HardSoftScoreHolder scoreHolder; + +rule "noNullRoomPeriod" + when + Lecture( roomNumber == null ); + Lecture( period == null ); + then + scoreHolder.addHardConstraintMatch(kcontext, -1); +end \ No newline at end of file diff --git a/optaplanner/src/main/resources/courseScheduleSolverConfigDrools.xml b/optaplanner/src/main/resources/courseScheduleSolverConfigDrools.xml new file mode 100644 index 0000000000..7cf95fdcd3 --- /dev/null +++ b/optaplanner/src/main/resources/courseScheduleSolverConfigDrools.xml @@ -0,0 +1,12 @@ + + + + + + courseSchedule.drl + + + + 10 + + \ No newline at end of file diff --git a/optaplanner/src/main/resources/courseScheduleSolverConfiguration.xml b/optaplanner/src/main/resources/courseScheduleSolverConfiguration.xml new file mode 100644 index 0000000000..0ad2264dcd --- /dev/null +++ b/optaplanner/src/main/resources/courseScheduleSolverConfiguration.xml @@ -0,0 +1,12 @@ + + + + + + com.baeldung.optaplanner.ScoreCalculator + + + + 10 + + \ No newline at end of file diff --git a/optaplanner/src/main/resources/logback.xml b/optaplanner/src/main/resources/logback.xml new file mode 100644 index 0000000000..c109aa19e2 --- /dev/null +++ b/optaplanner/src/main/resources/logback.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/optaplanner/src/test/java/com/baeldung/optaplanner/test/OptaPlannerUnitTest.java b/optaplanner/src/test/java/com/baeldung/optaplanner/test/OptaPlannerUnitTest.java new file mode 100644 index 0000000000..2847a233a5 --- /dev/null +++ b/optaplanner/src/test/java/com/baeldung/optaplanner/test/OptaPlannerUnitTest.java @@ -0,0 +1,50 @@ +package com.baeldung.optaplanner.test; + +import com.baeldung.optaplanner.CourseSchedule; +import com.baeldung.optaplanner.Lecture; +import org.junit.Assert; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.optaplanner.core.api.solver.Solver; +import org.optaplanner.core.api.solver.SolverFactory; +import java.util.Arrays; + +public class OptaPlannerUnitTest { + + static CourseSchedule unsolvedCourseSchedule; + + @BeforeAll + public static void setUp() { + + unsolvedCourseSchedule = new CourseSchedule(); + + for(int i = 0; i < 10; i++){ + unsolvedCourseSchedule.getLectureList().add(new Lecture()); + } + + unsolvedCourseSchedule.getPeriodList().addAll(Arrays.asList(new Integer[] { 1, 2, 3 })); + unsolvedCourseSchedule.getRoomList().addAll(Arrays.asList(new Integer[] { 1, 2 })); + } + + @Test + public void test_whenCustomJavaSolver() { + + SolverFactory solverFactory = SolverFactory.createFromXmlResource("courseScheduleSolverConfiguration.xml"); + Solver solver = solverFactory.buildSolver(); + CourseSchedule solvedCourseSchedule = solver.solve(unsolvedCourseSchedule); + + Assert.assertNotNull(solvedCourseSchedule.getScore()); + Assert.assertEquals(-4, solvedCourseSchedule.getScore().getHardScore()); + } + + @Test + public void test_whenDroolsSolver() { + + SolverFactory solverFactory = SolverFactory.createFromXmlResource("courseScheduleSolverConfigDrools.xml"); + Solver solver = solverFactory.buildSolver(); + CourseSchedule solvedCourseSchedule = solver.solve(unsolvedCourseSchedule); + + Assert.assertNotNull(solvedCourseSchedule.getScore()); + Assert.assertEquals(0, solvedCourseSchedule.getScore().getHardScore()); + } +} diff --git a/pom.xml b/pom.xml index 7bed9c5cc0..583ab8a957 100644 --- a/pom.xml +++ b/pom.xml @@ -592,6 +592,7 @@ spring-webflux-amqp antlr maven-archetype + optaplanner apache-meecrowave spring-reactive-kotlin jnosql @@ -1295,4 +1296,4 @@ 3.8 - + \ No newline at end of file