15.5. Real-time Planning
To do real-time planning, first combine backup planning and continuous planning with short planning windows to lower the burden of real-time planning. As time passes, the problem itself changes:

In the example above, 3 customers are added at different times (07:56, 08:02 and 08:45), after the original customer set finished solving at 07:55 and in some cases after the vehicles already left. Planner can handle such scenario’s with ProblemFactChange (in combination with immovable planning entities).
15.5.1. ProblemFactChange
While the Solver is solving, an outside event might want to change one of the problem facts, for example an airplane is delayed and needs the runway at a later time. Do not change the problem fact instances used by the Solver while it is solving (from another thread or even in the same thread), as that will corrupt it. Instead, add a ProblemFactChange to the Solver which it will execute in the solver thread as soon as possible.
public interface Solver {
...
boolean addProblemFactChange(ProblemFactChange problemFactChange);
boolean isEveryProblemFactChangeProcessed();
...
}public interface ProblemFactChange {
void doChange(ScoreDirector scoreDirector);
}Here’s an example:
public void deleteComputer(final CloudComputer computer) {
solver.addProblemFactChange(new ProblemFactChange() {
public void doChange(ScoreDirector scoreDirector) {
CloudBalance cloudBalance = (CloudBalance) scoreDirector.getWorkingSolution();
// First remove the problem fact from all planning entities that use it
for (CloudProcess process : cloudBalance.getProcessList()) {
if (ObjectUtils.equals(process.getComputer(), computer)) {
scoreDirector.beforeVariableChanged(process, "computer");
process.setComputer(null);
scoreDirector.afterVariableChanged(process, "computer");
}
}
// A SolutionCloner does not clone problem fact lists (such as computerList)
// Shallow clone the computerList so only workingSolution is affected, not bestSolution or guiSolution
cloudBalance.setComputerList(new ArrayList<CloudComputer>(cloudBalance.getComputerList()));
// Next remove it the problem fact itself
for (Iterator<CloudComputer> it = cloudBalance.getComputerList().iterator(); it.hasNext(); ) {
CloudComputer workingComputer = it.next();
if (ObjectUtils.equals(workingComputer, computer)) {
scoreDirector.beforeProblemFactRemoved(workingComputer);
it.remove(); // remove from list
scoreDirector.afterProblemFactRemoved(workingComputer);
break;
}
}
}
});
}
Any change on the problem facts or planning entities in a ProblemFactChange must be told to the ScoreDirector.
To write a ProblemFactChange correctly, it’s important to understand the behavior of a planning clone:
-
Any change in a
ProblemFactChangemust be done on theSolutioninstance ofscoreDirector.getWorkingSolution(). TheworkingSolutionis a planning clone of theBestSolutionChangedEvent'sbestSolution. So theworkingSolutionin theSolveris never the same instance as theSolutionin the rest of your application. -
A planning clone also clones the planning entities and planning entity collections. So any change on the planning entities must happen on the instances held by
scoreDirector.getWorkingSolution(). A planning clone does not clone the problem facts, nor the problem fact collections. Therefore the
workingSolutionand thebestSolutionshare the same problem fact instances and the same problem fact list instances.Any problem fact or problem fact list changed by a
ProblemFactChangemust be problem cloned first (which can imply rerouting references in other problem facts and planning entities). Otherwise, if theworkingSolutionandbestSolutionare used in different threads (for example a solver thread and a GUI event thread), a race condition can occur.
Many types of changes can leave a planning entity uninitialized, resulting in a partially initialized solution. That’s fine, as long as the first solver phase can handle it. All construction heuristics solver phases can handle that, so it’s recommended to configure such a solver phase as the first phase.
In essence, the Solver stops, runs the ProblemFactChange and restarts. This is a warm start because its initial solution is the adjusted best solution of the previous run. Each solver phase runs again. This implies the construction heuristic runs again, but because little or no planning variables are uninitialized (unless you have a nullable planning variable), it finishes much quicker than in a cold start.
Each configured Termination resets (both in solver and phase configuration), but a previous call to terminateEarly() is not undone. Normally however, you won’t configure any Termination (except in daemon mode), just call Solver.terminateEarly() when the results are needed. Alternatively, do configure a Termination and use the daemon mode in combination with BestSolutionChangedEvent as described below.
15.5.2. Daemon: solve() Does Not Return
In real-time planning, it’s often useful to have a solver thread wait when it runs out of work, and immediately resume solving a problem once new problem fact changes are added. Putting the Solver in daemon mode has these effects:
If the
Solver'sTerminationterminates, it does not return fromsolve()but blocks its thread instead (which frees up CPU power).-
Except for
terminateEarly(), which does make it return fromsolve(), freeing up system resources and allowing an application to shutdown gracefully. -
If a
Solverstarts with an empty planning entity collection, it waits in the blocked state immediately.
-
Except for
-
If a
ProblemFactChangeis added, it goes into the running state, applies theProblemFactChangeand runs theSolveragain.
To configure the daemon mode:
<solver> <daemon>true</daemon> ... </solver>
Don’t forget to call Solver.terminateEarly() when your application needs to shutdown to avoid killing the solver thread unnaturally.
Subscribe to the BestSolutionChangedEvent to process new best solutions found by the solver thread. A BestSolutionChangedEvent doesn’t guarantee that every ProblemFactChange has been processed already, nor that the solution is initialized and feasible. To ignore BestSolutionChangedEvents with such invalid solutions, do this:
public void bestSolutionChanged(BestSolutionChangedEvent<CloudBalance> event) {
// Ignore invalid solutions
if (event.isEveryProblemFactChangeProcessed()
&& event.isNewBestSolutionInitialized()
&& event.getNewBestSolution().getScore().isFeasible()) {
...
}
}
Where did the comment section go?
Red Hat's documentation publication system recently went through an upgrade to enable speedier, more mobile-friendly content. We decided to re-evaluate our commenting platform to ensure that it meets your expectations and serves as an optimal feedback mechanism. During this redesign, we invite your input on providing feedback on Red Hat documentation via the discussion platform.