@GeoffreyDeSmet
Tips:
by Geoffrey De Smet
OptaPlanner lead
Press down to show
Press right to skip
https://www.washingtonpost.com/news/wonk/wp/2015/03/10/a-data-genius-computes-the-ultimate-american-road-trip/
Road trip for 50 landmarks, monuments, etc.
Traditional algorithm: 271h 35m 16s
Is it optimal?
Olson's trip: 232h 43m 10s
⇒ 38h 52m 6s
faster (14%
)
Is it optimal?
Olson's trip: 232h 43m 10s
Not optimal
OptaPlanner's trip: 230h 17m 54s
⇒ Another 2h 25m 16s
faster (1%
⇒ 15%
in total)
Optimal, also 33km 710m
(= 20.95 miles
) shorter
Warm starts to solve in milliseconds
Assumption: An optimal VRP route uses only 1 vehicle.
Assumption: An optimal VRP route uses only 1 vehicle. (false)
Assumption: An optimal VRP route has no crossing lines.
Assumption: An optimal VRP route has no crossing lines. (false)
Assumption: An optimal, feasible VRP route with n vehicles is still optimal for n+1 vehicles.
Assumption: An optimal, feasible VRP route with n vehicles is still optimal for n+1 vehicles. (false)
Assumption: An optimal VRP route has no crossing lines of the same color.
Assumption: An optimal VRP route has no crossing lines of the same color. (false)
Assumption: We can focus on time windows before focusing on capacity (or vice versa).
Assumption: We can focus on time windows before focusing on capacity (or vice versa). (false)
Assumption: Humans optimize VRP optimally.
Assumption: Humans optimize VRP optimally. (false)
Can a manager reasonable judge if this is optimal?
Find better solutions in time and scale out
Press down to show
Press right to skip
// My domain specific class as input
Roster problem = ...;
SolverFactory<Roster> factory = SolverFactory
.createFromXmlResource(".../mySolverConfig.xml");
Solver<Roster> solver = factory.buildSolver();
// My domain specific class as output
Roster solution = solver.solve(problem);
for (Shift shift : solution.getShiftList()) {
// Each shift is now assigned to an employee
System.out.println(shift + ": " + shift.getEmployee());
}
public class Spot {
private String name;
private Skill requiredSkill;
...
}
Plain old java class
public class Employee {
private String name;
private List<Skill> skillList;
...
public boolean hasSkill(Skill skill) {
return skillList.contains(skill);
}
}
Plain old java class
@PlanningEntity
public class Shift {
private Spot spot;
private TimeSlot timeSlot;
@PlanningVariable(...)
private Employee employee; // Changes during solve()
...
}
Plain old java class with OptaPlanner annotations
public class MyScoreCalculator
implements EasyScoreCalculator<Roster> {
public Score calculateScore(Roster roster) {
int hardScore = 0;
for (Shift shift : roster.getShiftList()) {
Skill requiredSkill = shift.getSpot().getRequiredSkill();
if (shift.getEmployee() != null
// Employee lacks required skill
&& !shift.getEmployee().hasSkill(requiredSkill)) {
// Lower hard score
hardScore--;
}
}
...
return HardSoftScore.valueOf(hardScore, softScore);
}
}
rule "Required skill"
when
Shift(
getEmployee() != null,
// Employee lacks required skill
!getEmployee().hasSkill(getSpot().getRequiredSkill()))
then
// Lower hard score
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
public class Availability {
private Employee employee;
private TimeSlot timeSlot;
// UNAVAILABLE, UNDESIRED or DESIRED
private AvailabilityState state;
...
}
Plain old java class
All instances handled by only one constraint
public Score calculateScore(Roster roster) {
...
int softScore = 0;
for (Availability av : roster.getAvailabilityList()) {
for (Shift shift : roster.getShiftList()) {
if (shift.getEmployee() == av.getEmployee()
&& shift.getTimeSlot() == av.getTimeSlot()) {
if (av.getState() == AvailabilityState.UNDESIRED {
// Lower soft score
softScore--;
} else {
...
}
}
}
}
return HardSoftScore.valueOf(hardScore, softScore);
}
rule "Time off request"
when
Availability(
state == AvailabilityState.UNDESIRED,
$e : employee,
$t : timeSlot)
Shift(
employee == $e,
timeSlot == $t)
then
// Lower soft score
scoreHolder.addSoftConstraintMatch(kcontext, -1);
end
The user
Press down to show
Press right to skip
Introducing
the
template
OptaWeb Employee Rostering
DEMO
DEMO
DEMO
DEMO
OptaWeb Employee Rostering
OptaWeb
Employee Rostering
OptaPlanner
DEMO
The user is in control
DEMO
OptaWeb Employee Rostering
OptaWeb Employee Rostering
Ann is sick, Beth needs to go funeral, ...
What if ...
We can now measure the impact.
DEMO
$ git clone https://github.com/kiegroup/optaweb-employee-rostering.git
...
$ cd optaweb-employee-rostering
$ oc login https://api.YOUR_SERVER.openshift.com
...
$ ./provision.sh setup
...
$ git clone https://github.com/kiegroup/optaweb-employee-rostering.git
...
$ cd optaweb-employee-rostering
$ mvn clean install -DskipTests
...
// Deploy optaweb-employee-rostering-webapp-*.war
Press down to show
Press right to skip
Press down to show
Press right to skip
Expected: -1% driving time
Result: -25% driving time
⇒ -10 million kg CO² emission per year
⇒ -100 million $ cost per year
Generate
pom.xml
or build.gradle
mvn quarkus:dev
OK...
Not in public
int hardScore = 0;
for (Lesson a : lessonList) {
for (Lesson b : lessonList) {
if (a.getTimeslot() == b.getTimeslot()
&& a.getRoom() == b.getRoom()) {
hardScore--;
}
}
}
...
return HardSoftScore.ofHard(hardScore);
Not incremental!
When the room of the Math lesson changes,
it checks again
if French and Chemistry use the same room.
Search space for n lessons: nn
Search space for 400 lessons: 101040
Press down to show
Press right to skip
protected Constraint roomCounterStream(ConstraintFactory f) {
return f
.forEach(Lesson.class)
.join(Lesson.class,
Joiners.equal(Lesson::getStudentGroup),
Joiners.equal(
(lesson) -> lesson.getTimeslot().getDayOfWeek()),
Joiners.equal(
(lesson1) -> lesson1.getTimeslot().getEndTime(),
(lesson2) -> lesson2.getTimeslot().getStartTime()),
// Students transfer counter stream.
// For example from room C to room B.
Joiners.greaterThan(
(lesson) -> lesson.getRoom().getName()))
.penalize("Room counter stream",
HardSoftScore.ONE_HARD);
}
Press down to show
Press right to skip
https://code.quarkus.io
mvn quarkus:dev
Press down to show
Press right to skip
public class Timeslot {
private LocalDateTime startDateTime;
private LocalDateTime endDateTime;
...
}
Plain old java class
public class Room {
private String name;
private int capacity;
...
}
Plain old java class
public class Speaker {
private String name;
...
}
Plain old java class
public class Talk {
private String code;
private String title;
private TalkType talkType;
private List<Speaker> speakerList;
...
@PlanningVariable(valueRangeProviderRefs = "timeslotRange")
private Timeslot timeslot;
@PlanningVariable(valueRangeProviderRefs = "roomRange")
private Room room;
...
}
Plain old java class with OptaPlanner annotations
public class ConferenceSchedulingEasyScoreCalculator
implements EasyScoreCalculator<ConferenceSchedule> {
public HardMediumSoft calculateScore(ConferenceSchedule cs) {
int hardScore = 0;
for (Talk talk : cs.getTalkList()) {
if (talk.getTimeslot() != null
&& talk.hasUnavailableRoom()) {
hardScore--;
}
}
...
return HardMediumSoft.valueOf(
hardScore, mediumScore, softScore);
}
}
rule "Room unavailable timeslot"
when
Talk(hasUnavailableRoom())
then
scoreHolder.penalize(kcontext);
end
Press down to show
Press right to skip
Press down to show
Press right to skip
⇔ Is P = NP?
And human aren't good at it
But they don't realize it
(nor does their manager)
public class Computer {
private int cpuPower;
private int memory;
private int networkBandwidth;
private int cost;
// getters
}
@PlanningEntity
public class Process {
private int requiredCpuPower;
private int requiredMemory;
private int requiredNetworkBandwidth;
// getters
...
}
@PlanningEntity
public class Process {
...
private Computer computer;
@PlanningVariable(valueRangeProviderRefs = {"computerRange"})
public Computer getComputer() {
return computer;
}
public void setComputer(Computer computer) {
this.computer = computer;
}
}
@PlanningSolution
public class CloudBalance {
private List<Computer> computerList;
private List<Process> processList;
@ValueRangeProvider(id = "computerRange")
@ProblemFactCollectionProperty
public List<Computer> getComputerList() {
return computerList;
}
@PlanningEntityCollectionProperty
public List<Process> getProcessList() {
return processList;
}
...
}
@PlanningSolution
public class CloudBalance {
...
private HardSoftScore score;
@PlanningScore
public HardSoftScore getScore() {
return score;
}
public void setScore(HardSoftScore score) {
this.score = score;
}
}
public class CloudBalancingEasyScoreCalculator
implements EasyScoreCalculator<CloudBalance> {
public HardSoftScore calculateScore(CloudBalance cb) {
...
return HardSoftScore.valueOf(hardScore, softScore);
}
}
rule "computerCost"
when
// there is a computer
$s : Computer($c : cost)
// there is a processes on that computer
exists Process(computer == $s)
then
// lower soft score by the maintenance cost
scoreHolder.addSoftConstraintMatch(kcontext, - $c);
end
rule "requiredCpuPowerTotal"
when
// there is a computer
$s : Computer($cpu : cpuPower)
// with too little cpu for its processes
accumulate(
Process(computer == $s, $requiredCpu : requiredCpuPower);
$total : sum($requiredCpu);
$total > $cpu
)
then
// lower hard score by the excessive CPU usage
scoreHolder.addHardConstraintMatch(kcontext,
$cpu - $total);
end
<solver>
<scanAnnotatedClasses/>
<scoreDirectorFactory>
<scoreDrl>...Constraints.drl</scoreDrl>
</scoreDirectorFactory>
</solver>
SolverFactory<CloudBalance> factory
= SolverFactory.createFromXmlResource("...SolverConfig.xml");
Solver<CloudBalance> solver = factory.buildSolver();
CloudBalance problem = ... // Load problem
CloudBalance solution = solver.solve(problem);
Press down to show
Press right to skip
<solver>
...
<exhaustiveSearch>
<exhaustiveSearchType>BRUTE_FORCE</exhaustiveSearchType>
</exhaustiveSearch>
</solver>
<solver>
...
<constructionHeuristic>
<constructionHeuristicType>FIRST_FIT</constructionHeuristicType>
</constructionHeuristic>
</solver>
<solver>
...
<constructionHeuristic>
<constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType>
</constructionHeuristic>
</solver>
public class ProcessDifficultyComparator
implements Comparator<Process> {
public int compare(Process a, Process b) {
// Compare on requiredCpuPower * requiredMemory
// * requiredNetworkBandwidth
}
}
@PlanningEntity(difficultyComparatorClass
= ProcessDifficultyComparator.class)
public class Process {
...
}
<solver>
...
<constructionHeuristic>
<constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType>
</constructionHeuristic>
<localSearch>
...
<localSearch>
</solver>
<localSearch>
<forager>
<!-- Untweaked standard value -->
<acceptedCountLimit>1000</acceptedCountLimit>
</forager>
</localSearch>
<localSearch>
<acceptor>
<!-- Typical standard value -->
<entityTabuSize>7</entityTabuSize>
</acceptor>
<forager>
<!-- Typical value -->
<acceptedCountLimit>1000</acceptedCountLimit>
</forager>
</localSearch>
<localSearch>
<acceptor>
<!-- Tweaked value -->
<simulatedAnnealingStartingTemperature>
0hard/400soft
</simulatedAnnealingStartingTemperature>
</acceptor>
<forager>
<!-- Typical value -->
<acceptedCountLimit>4</acceptedCountLimit>
</forager>
</localSearch>
<localSearch>
<acceptor>
<!-- Typical standard value -->
<lateAcceptanceSize>400</lateAcceptanceSize>
</acceptor>
<forager>
<!-- Typical value -->
<acceptedCountLimit>4</acceptedCountLimit>
</forager>
</localSearch>
Use case | Gain type | Avg | Min | Max |
---|---|---|---|---|
Cloud Balancing | Maintenance cost 1 | -18% | -16% | -21% |
Machine Reassignment | Unbalanced load 2 | -63% | -25% | -97% |
Vehicle Routing (Belgium datasets) |
Distance 1 | -20% | -7% | -27% |
Nurse rostering | Happiness 1 | +53% | -19% | -85% |
Course scheduling | Unhappiness 1 | -66% | -26% | -100% |
OptaPlanner in a 5 minute run
1 Compared to traditional algorithms with domain knowledge.
2 Compared to initial assignments.
Press down to show
Press right to skip
> humans?
7 000 000 000
> minimum atoms
in the observable universe?
1080
100100 = 10200
1 0000000000 0000000000 0000000000 0000000000 0000000000
0000000000 0000000000 0000000000 0000000000 0000000000
0000000000 0000000000 0000000000 0000000000 0000000000
0000000000 0000000000 0000000000 0000000000 0000000000
nn
|valueSet||variableSet|
Presume 109 scores/ms ⇒ 1020 scores/year
Queens | Combinations | Calculation time |
---|---|---|
100 | 100100 = 10200 | 10180 years |
1000 | 10001000 = 103000 | 102980 years |
10000 | 1000010000 = 1040000 | 1039980 years |
n | # moves | # solutions |
---|---|---|
4 | 16 | 256 |
8 | 64 | 16777216 |
64 | 4096 | 10116 |
n | n2 | nn |
For example:
$ git clone https://github.com/kiegroup/optaplanner-quickstarts.git
...
$ cd optaplanner-quickstarts
$ cd use-cases/school-timetabling
$ mvn quarkus:dev
...
Homepage | www.optaplanner.org |
---|---|
Slides | www.optaplanner.org/learn/slides.html |
User guide | www.optaplanner.org/learn/documentation.html |
Feedback | @GeoffreyDeSmet |