Permalink
Browse files

migrated to optaplanner 7.0.0.Final

  • Loading branch information...
merovingienne committed Jul 2, 2017
1 parent 9e1693b commit 51cf300454829491f3d898d5d0ca102b94f4d424
View
@@ -187,6 +187,13 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>4.7</version>
<scope>compile</scope>
</dependency>
<!--&lt;!&ndash; https://mvnrepository.com/artifact/org.hsqldb/hsqldb &ndash;&gt;-->
<!--<dependency>-->
@@ -23,7 +23,6 @@
import org.openmrs.module.operationtheater.scheduler.solver.TimetableEntryComparator;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.config.solver.XmlSolverFactory;
import java.time.*;
import java.time.temporal.ChronoUnit;
@@ -42,20 +41,20 @@
private Time time = new Time();
private SolverFactory solverFactory;
private SolverFactory<Timetable> solverFactory;
private OperationTheaterService otService;
private LocationService locationService;
private Solver solver;
private Solver<Timetable> solver;
private Thread solverThread;
private Status status = Status.PRISTINE;
private Scheduler() {
solverFactory = new XmlSolverFactory("/scheduler/solverConfig.xml");
solverFactory = SolverFactory.createFromXmlResource("scheduler/solverConfig.xml");
}
/**
@@ -65,7 +64,7 @@ private Scheduler() {
* @param
*/
public void reset() {
solverFactory = new XmlSolverFactory("/scheduler/solverConfig.xml");
solverFactory = SolverFactory.createFromXmlResource("scheduler/solverConfig.xml");
solver = null;
solverThread = null;
status = Status.PRISTINE;
@@ -119,9 +118,8 @@ public void run() {
final Timetable unsolvedTimetable = setupInitialSolution(planningWindow);
// Solve problem
solver.setPlanningProblem(unsolvedTimetable);
solver.solve();
Timetable solvedTimetable = (Timetable) solver.getBestSolution();
// returns Solution class.
Timetable solvedTimetable = solver.solve(unsolvedTimetable);
//set surgeries planned begin and finished attributes
solvedTimetable.persistSolution(otService);
@@ -20,6 +20,7 @@
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import org.optaplanner.core.api.domain.variable.PlanningVariableGraphType;
import org.threeten.extra.Interval;
/**
@@ -92,15 +93,18 @@ public boolean isOutsideAvailableTimes() {
}
public Surgery getSurgery() {
return surgery;
}
public void setSurgery(Surgery surgery) {
this.surgery = surgery;
}
@PlanningVariable(chained = true, valueRangeProviderRefs = { "anchorRange", "plannedSurgeryRange" })
@PlanningVariable(graphType = PlanningVariableGraphType.CHAINED, valueRangeProviderRefs = { "anchorRange", "plannedSurgeryRange" })
public TimetableEntry getPreviousTimetableEntry() {
return previousTimetableEntry;
}
@@ -4,10 +4,12 @@
import org.openmrs.module.operationtheater.api.OperationTheaterService;
import org.openmrs.module.operationtheater.scheduler.solver.SurgeryConflict;
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.value.ValueRangeProvider;
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.optaplanner.core.impl.solution.Solution;
import org.optaplanner.core.api.domain.solution.Solution;
import java.util.ArrayList;
import java.util.Collection;
@@ -20,7 +22,7 @@
* Also used to act as a range provider for the planning variables
*/
@PlanningSolution
public class Timetable implements Solution<HardSoftScore> {
public class Timetable {
//problem facts (don't change value during planning)
private List<Anchor> anchors;
@@ -30,28 +32,14 @@
private HardSoftScore score;
@Override
@PlanningScore
public HardSoftScore getScore() {
return score;
}
@Override
public void setScore(HardSoftScore hardSoftScore) {
score = hardSoftScore;
}
/**
* @return a collection with all problem facts
* @should return all problem facts without the planning entities
*/
@Override
public Collection<?> getProblemFacts() {
//planning entities are added automatically -> don't add them here
List<Object> facts = new ArrayList<Object>();
facts.addAll(anchors);
facts.addAll(calculatePlannedSurgeryConflictList());
return facts;
}
@Override
public String toString() {
@@ -61,6 +49,16 @@ public String toString() {
'}';
}
/**
* getProblemFacts() replaced with
* ProblemFactCollectionProperty annotation directly on
* every problem fact getter.
* @see{https://www.optaplanner.org/download/upgradeRecipe/upgradeRecipe7.0.html}
*/
@ProblemFactCollectionProperty
private Collection<?> calculatePlannedSurgeryConflictList() {
List<SurgeryConflict> conflicts = new ArrayList<SurgeryConflict>();
for (PlannedSurgery left : plannedSurgeries) {
@@ -90,6 +88,7 @@ public String toString() {
return conflicts;
}
@ProblemFactCollectionProperty
@ValueRangeProvider(id = "anchorRange")
public List<Anchor> getAnchors() {
return anchors;
@@ -2,7 +2,7 @@
//import org.joda.time.DateTime;
import org.openmrs.Location;
import org.optaplanner.core.api.domain.variable.PlanningVariable;
import org.optaplanner.core.api.domain.variable.InverseRelationShadowVariable;
import java.time.ZonedDateTime;
@@ -23,7 +23,7 @@
*/
int getChainLengthInMinutes();
@PlanningVariable(mappedBy = "previousTimetableEntry")
@InverseRelationShadowVariable(sourceVariableName = "previousTimetableEntry")
TimetableEntry getNextTimetableEntry();
void setNextTimetableEntry(TimetableEntry entry);
@@ -2,10 +2,11 @@
import org.openmrs.module.operationtheater.SchedulingData;
import org.openmrs.module.operationtheater.scheduler.domain.PlannedSurgery;
import org.openmrs.module.operationtheater.scheduler.domain.Timetable;
import org.optaplanner.core.impl.heuristic.selector.common.decorator.SelectionFilter;
import org.optaplanner.core.impl.score.director.ScoreDirector;
public class MovablePlannedSurgerySelectionFilter implements SelectionFilter<PlannedSurgery> {
public class MovablePlannedSurgerySelectionFilter implements SelectionFilter<Timetable, PlannedSurgery> {
/**
* returns false if surgery has been started or locked<br />
@@ -19,7 +20,7 @@
* @should return false if surgery has been locked
*/
@Override
public boolean accept(ScoreDirector scoreDirector, PlannedSurgery surgery) {
public boolean accept(ScoreDirector<Timetable> scoreDirector, PlannedSurgery surgery) {
SchedulingData schedulingData = surgery.getSurgery().getSchedulingData();
boolean started = surgery.getSurgery().getDateStarted() != null;
boolean locked = schedulingData == null ? false : schedulingData.getDateLocked();
@@ -3,15 +3,11 @@ package scheduler;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScoreHolder;
// import org.joda.time.Interval;
import org.openmrs.module.operationtheater.scheduler.domain.PlannedSurgery;
import org.openmrs.module.operationtheater.scheduler.domain.Anchor;
import org.openmrs.module.operationtheater.scheduler.solver.SurgeryConflict;
// import org.joda.time.DateTime;
import java.util.Date;
// import org.joda.time.Period
// import org.joda.time.Hours;
import java.time.ZonedDateTime;
import java.time.Duration;
@@ -36,13 +32,15 @@ rule "overlappingSurgeriesInSameOperationTheater"
when
$left: PlannedSurgery($location: location, location != null)
$right: PlannedSurgery(this != $left, location == $location, isOverlapping($left))
//prevent the double execution of this rule (AB, BA)
eval( System.identityHashCode($left) < System.identityHashCode($right))
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
////It is not allowed to have surgeries outside available times
rule "surgeriesOutsideAvailableTimes"
when
PlannedSurgery(isOutsideAvailableTimes(), location != null)
@@ -51,6 +49,7 @@ rule "surgeriesOutsideAvailableTimes"
end
//It is not allowed to have unscheduled surgeries if there are OTs within the planning window that are not fully utilized
rule "preventUnscheduledSurgeriesIfTimeLeft"
when
$surgery: PlannedSurgery(location == null, $procedure: surgery.procedure)
@@ -59,7 +58,9 @@ rule "preventUnscheduledSurgeriesIfTimeLeft"
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
// Two overlapping PlannedSurgeries that share the same persons.
rule "conflictingAndOverlappingPlannedSurgeries"
when
$conflict : SurgeryConflict($leftSurgery : left, $rightSurgery : right)
@@ -1,21 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<solver>
<environmentMode>PRODUCTION</environmentMode>
<environmentMode>NON_REPRODUCIBLE</environmentMode>
<!-- model definition -->
<solutionClass>org.openmrs.module.operationtheater.scheduler.domain.Timetable</solutionClass>
<planningEntityClass>org.openmrs.module.operationtheater.scheduler.domain.PlannedSurgery</planningEntityClass>
<entityClass>org.openmrs.module.operationtheater.scheduler.domain.PlannedSurgery</entityClass>
<!-- score function definition -->
<scoreDirectorFactory>
<scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
<scoreDrl>/scheduler/scoreRules.drl</scoreDrl>
<!--<scoreDefinitionType>HARD_SOFT</scoreDefinitionType> Removed as of OP 7.0.0.Beta2 -->
<scoreDrl>scheduler/scoreRules.drl</scoreDrl>
</scoreDirectorFactory>
<!-- configuration of the optimization algorithm(s) -->
<termination>
<maximumSecondsSpend>10</maximumSecondsSpend>
<scoreAttained>0hard/0soft</scoreAttained>
<secondsSpentLimit>10</secondsSpentLimit>
<bestScoreLimit>0hard/0soft</bestScoreLimit>
</termination>
<constructionHeuristic>
<constructionHeuristicType>FIRST_FIT</constructionHeuristicType>
@@ -4,9 +4,9 @@
import org.openmrs.module.operationtheater.scheduler.domain.PlannedSurgery;
import org.openmrs.module.operationtheater.scheduler.domain.Timetable;
import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.config.score.definition.ScoreDefinitionType;
import org.optaplanner.core.config.score.director.ScoreDirectorFactoryConfig;
import org.optaplanner.core.config.solver.SolverConfig;
import org.optaplanner.core.config.solver.XmlSolverFactory;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
@@ -21,21 +21,21 @@
@Test
public void shouldSetUpSolver() throws Exception {
SolverFactory solverFactory = new XmlSolverFactory("/scheduler/solverConfig.xml");
SolverFactory solverFactory = SolverFactory.createFromXmlResource("scheduler/solverConfig.xml");
SolverConfig config = solverFactory.getSolverConfig();
//model definition
// assertThat(config.getSolutionClass(), is(Timetable.class));
assertEquals(Timetable.class, config.getSolutionClass());
assertThat(config.getPlanningEntityClassList(), hasSize(1));
assertThat(config.getEntityClassList(), hasSize(1));
// assertThat(config.getPlanningEntityClassList().get(0), is(PlannedSurgery.class)); //Todo find out how to use assertThat with generics correctly
assertEquals(PlannedSurgery.class, config.getPlanningEntityClassList().get(0));
assertEquals(PlannedSurgery.class, config.getEntityClassList().get(0));
//score function definition
ScoreDirectorFactoryConfig scoreConfig = config.getScoreDirectorFactoryConfig();
assertThat(scoreConfig.getScoreDefinitionType(), is(ScoreDirectorFactoryConfig.ScoreDefinitionType.HARD_SOFT));
assertThat(scoreConfig.getScoreDefinitionType(), is(ScoreDefinitionType.HARD_SOFT));
assertThat(scoreConfig.getScoreDrlList(), hasSize(1));
assertThat(scoreConfig.getScoreDrlList(), contains("/scheduler/scoreRules.drl"));
assertThat(scoreConfig.getScoreDrlList(), contains("scheduler/scoreRules.drl"));
//optimization algorithm definition
//TODO
@@ -26,7 +26,7 @@
/**
* @verifies return all problem facts without the planning entities
* @see Timetable#getProblemFacts()
*
*/
@Test
public void getProblemFacts_shouldReturnAllProblemFactsWithoutThePlanningEntities() throws Exception {
@@ -62,17 +62,19 @@ public void getProblemFacts_shouldReturnAllProblemFactsWithoutThePlanningEntitie
timetable.setPlannedSurgeries(plannedSurgeries);
//call method under test
Collection<?> problemFacts = timetable.getProblemFacts();
//verify contains all problem facts
assertThat(problemFacts, hasSize(3));
assertTrue(problemFacts.containsAll(anchors));
assertTrue(problemFacts.contains(new SurgeryConflict(surgery1, surgery2, 2)));
//verify contains NO planning entities
assertFalse(problemFacts.contains(ps1));
assertFalse(problemFacts.contains(ps2));
//Problem fact check removed due to getProblemFacts() method removal
// //call method under test
// Collection<?> problemFacts = timetable.getProblemFacts();
//
// //verify contains all problem facts
// assertThat(problemFacts, hasSize(3));
// assertTrue(problemFacts.containsAll(anchors));
// assertTrue(problemFacts.contains(new SurgeryConflict(surgery1, surgery2, 2)));
//
// //verify contains NO planning entities
// assertFalse(problemFacts.contains(ps1));
// assertFalse(problemFacts.contains(ps2));
}
/**
View
@@ -13,10 +13,7 @@
<developers>
<developer>
<name>Lukas Breitwieser</name>
</developer>
<developer>
<name>Chanuka Wijayakoon</name>
<name>Lukas Breitwieser, Chanuka Wijayakoon</name>
</developer>
</developers>
@@ -58,7 +55,7 @@
<!--<serializationxstreamVersion>0.2.12</serializationxstreamVersion>-->
<optaplannerVersion>6.0.1.Final</optaplannerVersion>
<optaplannerVersion>7.0.0.Final</optaplannerVersion>
<droolsCompilerVersion>6.0.1.Final</droolsCompilerVersion>
<jodatimeVersion>2.3</jodatimeVersion>

0 comments on commit 51cf300

Please sign in to comment.