@GeoffreyDeSmet
Tips:
by Geoffrey De Smet
OptaPlanner lead
Is manually planning difficult?
Bad
Good
Traveling Salesman
Import
, directory tutorial
, file st68.tsp
TSP demo
VRP demo
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?
Cloud Balancing demo
⇔ Is P = NP?
And humans aren't good at it
But they don't realize it
(nor does their manager)
Find better solutions in time and scale out
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);
}
}
@ProblemFact(Collection)Property
@PlanningEntity(Collection)Property
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>
<solutionClass>...CloudBalance</solutionClass>
<entityClass>...Process</entityClass>
<scoreDirectorFactory>
<scoreDrl>...Constraints.drl</scoreDrl>
</scoreDirectorFactory>
<!-- optimization algorithms are optional -->
</solver>
SolverFactory<CloudBalance> factory
= SolverFactory.createFromXmlResource("...SolverConfig.xml");
Solver<CloudBalance> solver = factory.buildSolver();
CloudBalance problem = ... // Load problem
CloudBalance solution = solver.solve(problem);
In IDE
demo
Score = -2
Conflicts: A-B, B-D
Score = 0
No conflicts
Source: NASA (wikipedia)
> humans?
7 000 000 000
Source: NASA and ESA (wikipedia)
> 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 |
Moore's law == a drop in the ocean
8 queens = 15.7 seconds
9 queens = 2.5 minutes (times 10)
10 queens = 0.83 hours (times 20)
<solver>
...
<exhaustiveSearch>
<exhaustiveSearchType>BRUTE_FORCE</exhaustiveSearchType>
</exhaustiveSearch>
</solver>
nodeExplorationType
entitySorterManner
valueSorterManner
<solver>
...
<exhaustiveSearch>
<exhaustiveSearchType>BRANCH_AND_BOUND</exhaustiveSearchType>
</exhaustiveSearch>
</solver>
nodeExplorationType
SolverEventListener
class Teacher {
...
}
class Course {
Teacher teacher;
...
}
class Period {
...
}
class Room {
...
}
@PlanningEntity
class Lecture {
Course course;
int lectureIndexInCourse;
@PlanningVariable(...) Period period;
@PlanningVariable(...) Room room;
...
}
class UnavailablePeriodPenalty {
Course course;
Period period;
...
}
public HardSoftScore calculateScore(Schedule schedule);
int hardScore = 0;
for (UnavailablePeriodPenalty penalty : schedule.getPenaltyList()) {
for (Lecture lecture : schedule.getLectureList()) {
if (penalty.getCourse() == lecture.getCourse()
&& penalty.getPeriod() == lecture.getPeriod()) {
hardScore--;
}
}
}
...
return HardSoftScore.valueOf(hardScore, softScore);
}
class UnavailablePeriodPenalty {
Course course;
Period period;
...
}
rule "unavailablePeriodPenalty"
when
// When a course is unavailable for a certain period ...
UnavailablePeriodPenalty($c : course, $p : period)
// ... and a lecture of that course is scheduled in that period ...
Lecture(course == $c, period == $p)
then
// ... then we lose 1 hard score point
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
Course scheduling demo
initializingScoreTrend
public class Nurse {
private Country country;
public boolean isHoliday(Date date) {
if (country == Country.BE) {
// true if date is 1-JAN, easter monday, 21-JUL, ...
} else if (country == Country.FR) {
// true if date is 1-JAN, easter monday, 14-JUL, ...
} else if (...) {
...
}
...
}
}
Demo
Demo
Demo
CloudBalancingApp.createSolver()
String s = JOptionPane.showInputDialog(
null, "How many seconds?", "");
Demo
Demo
Demo
Random
calls that don't use the seeded Random
HashMap
in Solution => Use LinkedHashMap
HashSet
in Solution => Use LinkedHashSet
EnvironmentMode
)What is the solver doing?
logback.xml
info
, debug
and trace
Good answer: First Fit Decreasing with Late Acceptance
Better answer: try them all and use the best one
Demo and statistics overview
Demo
Demo
<solver>
...
<constructionHeuristic>
<constructionHeuristicType>FIRST_FIT</constructionHeuristicType>
</constructionHeuristic>
</solver>
All hard constraints satisfied: maintenance cost shown
<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 {
...
}
Not yet supported
If the Construction Heuristic takes too long, Local Search has too little time
$ git clone https://github.com/kiegroup/optaplanner.git
...
$ cd optaplanner
$ mvn clean install -DskipTests
...
$ cd optaplanner-examples
$ mvn exec:java
...
Add it on optaplanner.org
How? By sending a Pull Request to add it in the yml
file (or just mail me).
800*600
Anyone want to convert to odp/ppt and share?
<solver>
...
<constructionHeuristic>
<constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType>
</constructionHeuristic>
<localSearch>
...
<localSearch>
</solver>
n | # moves | # solutions |
---|---|---|
4 | 16 | 256 |
8 | 64 | 16777216 |
64 | 4096 | 10116 |
n | n2 | nn |
Multiple moves can reach any solution
<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>
Are there bugs in my code?
Scaling out with TSP and VRP
Winston Churchill
(Prime Minister UK 1940-1945)
Mike Tyson
(Boxer, heavyweight champion 1987-1990)
"We can not automate planning
because the plans will change."
⇒
"We need to automate (re)planning
because the plans will change."
But how?
Each planning stage feeds into the next.
Especially for trains, airplanes, etc
"Any organization that designs a (software) system
will produce a design whose structure
is a copy of the organization's communication structure."
⇒
Planning problems solved by different groups
should use different Solver instances
(during the first year in production).
@Path("/trainWagon")
public class TrainWagonDeciderResource {
@Inject
SolverManager<TrainWagonSolution, ...> wagonSolverManager;
... wagonSolverManager.solve(...)
}
@Path("/trainPlatform")
public class TrainPlatformDeciderResource {
@Inject
SolverManager<TrainCrewSolution, ...> platformSolverManager;
// Uses TrainWagonSolution's output as problem facts:
// Length of train impacts eligble platforms
... platformSolverManager.solve(...)
}
No java.time
import
import java.time.LocalDate;
@PlanningEntity
public class Patient {
private String name;
private LocalDate arrivalDate;
private LocalDate departureDate;
...
@PlanningVariable(...)
private Bed bed;
...
}
Hospital bed planning does not change
a patient's arrival or departure date.
Don't use java.util.Date
for time manipulation.
It's like asking your bartender how to treat cancer.
Don't use java.util.Calender
either,
It's like asking your dog how to treat cancer.
Use java.time.LocalDate
or java.time.LocalDateTime
instead!
Pick a good domain model.
@PlanningEntity
public class Lesson {
private String subject;
...
@PlanningVariable(...)
private Timeslot timeslot;
...
}
import java.time.DayOfWeek;
import java.time.LocalTime;
public class Timeslot {
private DayOfWeek dayOfWeek;
private LocalTime startTime;
private LocalTime endTime;
...
}
@Entity
public class Shift {
private LocalDateTime start;
private LocalDateTime end;
@PlanningPin
private boolean history;
...
@PlanningVariable(...)
private Employee employee;
...
}
@PlanningEntity
public class Shift {
private LocalDateTime start;
private LocalDateTime end;
@PlanningPin
// every historic shift is published
private boolean published;
...
@PlanningVariable(...)
private Employee employee;
...
}
Maybe
Assign all respiratory specialists
to the last shift of your planning window
(even in other wards as normal nurses).
⇒
draft length >= 2 * publish length
import java.time
@PlanningEntity
public class Talk {
private Timeslot publishedTimeslot;
...
@PlanningVariable(...)
private Timeslot timeslot;
}
Constraint publishedTimeslot(ConstraintFactory f) {
return f.forEach(Talk.class)
.filter(talk -> talk.getPublishedTimeslot() != null
&& talk.getTimeslot()
!= talk.getPublishedTimeslot())
.penalize(HardMediumSoft.ONE_MEDIUM)
.asConstraint(PUBLISHED_TIMESLOT);
}
DEMO
solver.addProblemFactChange(...)
@PlanningVariable(nullable = true)
class Employee {
boolean virtual;
}
Combine them as needed.
Not really repeated planning, but score calculation
Plan until the end of time?
Nurse rostering demo
Filter
that is always appliedAKA semi-movable entities
When you need an answer in milliseconds
solve()
does not return ...terminateEarly()
is calledThe easiest ways to fail (or win) a project...
OptaPlanner enhances the human planner
Course scheduling demo
<solver>
<moveThreadCount>4</moveThreadCount>
...
</solver>
Also supported: AUTO, NONE
See OptaPlanner HUB
CPLEX
IloNumVar[] expense = cplex.numVarArray(2, lb, ub);
cplex.addLe(cplex.sum(
cplex.prod(1.06, expense[0]), // Euro
cplex.prod(1.24, expense[1])), // British Pound
50.0);
OptaPlanner
rule "50 Dollar limit"
when
Expense(1.06 * euroCost + 1.24 * britishPoundCost > 50 )
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
SolverFactory<CloudSolution> solverFactory
= SolverFactory.createFromKieContainerXmlResource(
"opta/optacloud/cloudSolverConfig.solver.xml");
// For every dataset
Solver<CloudSolution> solver = solverFactory.buildSolver();
CloudBalance problem = ... // Load problem
CloudSolution solution = solver.solve(problem);
optacloud-1.0.0.jar
is in the classpath
KieServices kieServices = KieServices.Factory.get();
SolverFactory<Object> solverFactory
= SolverFactory.createFromKieContainerXmlResource(
kieServices.newReleaseId("opta", "optacloud", "1.0.0"),
"opta/optacloud/cloudSolverConfig.solver.xml");
// For every dataset
Solver<Object> solver = solverFactory.buildSolver();
CloudBalance problem = ... // Load problem
Object solution = solver.solve(problem);
optacloud-1.0.0.jar
is not in the classpath
Downloaded from a Maven repository
kmodule.xml
<kmodule xmlns="https://www.drools.org/xsd/kmodule">
<kbase packages="opta.optacloud">
<ksession name="cloudKsession"/>
</kbase>
</kmodule>
cloudSolverConfig.solver.xml
<solver>
<scanAnnotatedClasses/>
<scoreDirectorFactory>
<ksessionName>cloudKsession</ksessionName>
</scoreDirectorFactory>
</solver>
kmodule.xml
<kmodule xmlns="https://www.drools.org/xsd/kmodule"/>
cloudSolverConfig.solver.xml
<solver>
<scanAnnotatedClasses/>
</solver>
Homepage | www.optaplanner.org |
---|---|
Slides | www.optaplanner.org/learn/slides.html |
User guide | www.optaplanner.org/learn/documentation.html |
Feedback | ![]() |