Show Table of Contents

15.5.1.
15.5.2. Daemon:
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.beforeProblemFactRemoved(workingComputer);
break;
}
}
}
});
}Warning
Any change on the problem facts or planning entities in a
ProblemFactChange must be told to the ScoreDirector.
Important
To write a
ProblemFactChange correctly, it's important to understand the behaviour 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 hold 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 aProblemFactChangemust 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.
Note
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 from
solve(), 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.
- If a
ProblemFactChangeis added, it goes into the running state, applies theProblemFactChangeand runs theSolveragain.
To configure the daemon mode:
<solver> <daemon>true</daemon> ... </solver>
Warning
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.