Is there a way to use sorterClass during construction phase? - optaplanner

Problem overview:
We are assigning Tasks to Users
Only one Task can be assigned to User
The Tasks can be variable length: 1-16 hours
Users can only do one Task at a time Users have 8 hours per day
We are using the Time Grain pattern - 1 grain = 1 hour.
As Tasks are variable length not all TimeGrains are used for a given User
Using the default sort does not return with a solution within 10min.
I've had some success (result returning within seconds) with the following configuration
<constructionHeuristic>
<changeMoveSelector>
<selectionOrder>RANDOM</selectionOrder>
<selectedCountLimit>300</selectedCountLimit>
<valueSelector variableName="user"/>
</changeMoveSelector>
<changeMoveSelector>
<valueSelector variableName="startDate"/>
</changeMoveSelector>
</constructionHeuristic>
However this seems to lead to uneven distribution of tasks. I've tried to implement a custom sort i.e.
<sorterClass>com.example.UserSelectionSorter</sorterClass>
But this gives the following error:
Exception in thread "main" java.lang.IllegalStateException: Replay must occur after record. The recordingEntitySelector (Recording(FromSolutionEntitySelector(SubTask)))'s hasNext() has not been called yet.
at org.optaplanner.core.impl.heuristic.selector.entity.mimic.MimicReplayingEntitySelector$ReplayingEntityIterator.hasNext(MimicReplayingEntitySelector.java:117)
at org.optaplanner.core.impl.heuristic.selector.common.iterator.AbstractOriginalChangeIterator.createUpcomingSelection(AbstractOriginalChangeIterator.java:47)
at org.optaplanner.core.impl.heuristic.selector.common.iterator.AbstractOriginalChangeIterator.createUpcomingSelection(AbstractOriginalChangeIterator.java:26)
at org.optaplanner.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator.hasNext(UpcomingSelectionIterator.java:43)
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:132)
at org.optaplanner.core.impl.heuristic.selector.move.decorator.AbstractCachingMoveSelector.constructCache(AbstractCachingMoveSelector.java:77)
at org.optaplanner.core.impl.heuristic.selector.move.decorator.SortingMoveSelector.constructCache(SortingMoveSelector.java:43)
at org.optaplanner.core.impl.heuristic.selector.common.SelectionCacheLifecycleBridge.phaseStarted(SelectionCacheLifecycleBridge.java:51)
at org.optaplanner.core.impl.phase.event.PhaseLifecycleSupport.firePhaseStarted(PhaseLifecycleSupport.java:37)
at org.optaplanner.core.impl.heuristic.selector.AbstractSelector.phaseStarted(AbstractSelector.java:50)
at org.optaplanner.core.impl.phase.event.PhaseLifecycleSupport.firePhaseStarted(PhaseLifecycleSupport.java:37)
at org.optaplanner.core.impl.heuristic.selector.AbstractSelector.phaseStarted(AbstractSelector.java:50)
at org.optaplanner.core.impl.phase.event.PhaseLifecycleSupport.firePhaseStarted(PhaseLifecycleSupport.java:37)
at org.optaplanner.core.impl.constructionheuristic.placer.AbstractEntityPlacer.phaseStarted(AbstractEntityPlacer.java:44)
at org.optaplanner.core.impl.constructionheuristic.DefaultConstructionHeuristicPhase.phaseStarted(DefaultConstructionHeuristicPhase.java:118)
at org.optaplanner.core.impl.constructionheuristic.DefaultConstructionHeuristicPhase.solve(DefaultConstructionHeuristicPhase.java:65)
at org.optaplanner.core.impl.solver.AbstractSolver.runPhases(AbstractSolver.java:99)
at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:192)
at com.example.App.main(App.java:50)

See the advanced configuration of a Construction heuristic. That writes the mimic recording/replay explicitly. That allows you to go into the first entitySelector (so not the ref one) and use the sorterClass there.

Related

Persist detailed information about failed Item processing

I´ve got a Job that runs a TaskletStep, then a chunk-based step and then another TaskletStep.
In each of these steps, errors (in the form of Exceptions) can occur.
The chunk-based step looks like this:
stepBuilderFactory
.get("step2")
.chunk<SomeItem, SomeItem>(1)
.reader(flatFileItemReader)
.processor(itemProcessor)
.writer {}
.faultTolerant()
.skipPolicy { _ , _ -> true } // skip all Exceptions and continue
.taskExecutor(taskExecutor)
.throttleLimit(taskExecutor.corePoolSize)
.build()
The whole job definition:
jobBuilderFactory.get("job1")
.validator(validator())
.preventRestart()
.start(taskletStep1)
.next(step2)
.next(taskletStep2)
.build()
I expected that Spring Batch somehow picks up the Exceptions that occur along the way, so I can then create a Report including them after the Job has finished processing. Looking at the different contexts, there´s also fields that should contain failureExceptions. However, it seems there´s no such information (especially for the chunked step).
What would be a good approach if I need information about:
what Exceptions did occur in which Job execution
which Item was the one that triggered it
The JobExecution provides a method to get all failure exceptions that happened during the job. You can use that in a JobExecutionListener#afterJob(JobExecution jobExecution) to generate your report.
In regards to which items caused the issue, this will depend on where the exception happens (during the read, process or write operation). For this requirement, you can use one of the ItemReadListener, ItemProcessListener or ItemWriteListener to keep record of the those items (For example, by adding them to the job execution context to be able to get access to them in the JobExecutionListener#afterJob method for your report).

Optaplanner : dynamic termination config

I want to configure my termination strategy dynamically. My intention is that the solver should terminate when either it has tried 10k steps or when the score has not improved for 4k steps. For that I am setting the phases in config as follows:
<constructionHeuristic></constructionHeuristic>
<localSearch></localSearch>
And then before starting the solver, I set
TerminationConfig terminationConfig = new TerminationConfig();
terminationConfig.setTerminationCompositionStyle(TerminationCompositionStyle.OR);
terminationConfig.setUnimprovedStepCountLimit(4000);
terminationConfig.setStepCountLimit(10000);
LocalSearchPhaseConfig localSearchPhaseConfig = (LocalSearchPhaseConfig) solverFactory.getSolverConfig().getPhaseConfigList().get(1);
localSearchPhaseConfig.setTerminationConfig(terminationConfig);
I tried keeping the value of unimprovedStepCount as 1 but it still does not terminate after 30-40 minutes. How can I define this config?
Also, do I need to define the constructionHeuristic and constructionHeuristic in the xml config explicitly or are they inferred by default?
Isn't it this ?
<localSearch>
<termination>
<terminationCompositionStyle>OR</terminationCompositionStyle>
<unimprovedStepCountLimit>4000</unimprovedStepCountLimit>
<stepCountLimit>10000</stepCountLimit>
</termination>
</localSearch>
If you don't define a CH the default is FIRST_FIT I believe.
The problem was in my SelectionFilter. There was a bug such that the accept method of SelectionFilter was always returning false and hence the local search phase was not progressing at all.
After resolving it, the dynamic termination config works as it is.

OptaPlanner vehicle routing course grained moves

I am working on a problem almost identical to the coachshuttlegathering example. I have wastebins and vehicles that should collect the wastebins. Each wastebin has a payload and each vehicle has a capacity.
I setup my solution as shown in the coachshuttlegathering example and this basically works but I end up in local optimas because the default moves are not coarse grained enough. Specifically, I need to allow 2 wastebin chains from 2 distinct vehicles to be assigned to a different vehicle in a single move.
I tried to reuse the existing subChainChangeMoveSelector in combination with a cartesianProductMoveSelector like this:
<cartesianProductMoveSelector>
<subChainChangeMoveSelector>
<entityClass>...Wastebin</entityClass>
<subChainSelector>
<valueSelector>
<variableName>previousRouteComponent</variableName>
</valueSelector>
</subChainSelector>
<valueSelector>
<variableName>previousRouteComponent</variableName>
</valueSelector>
<selectReversingMoveToo>false</selectReversingMoveToo>
</subChainChangeMoveSelector>
<subChainChangeMoveSelector>
<entityClass>...Wastebin</entityClass>
<subChainSelector>
<valueSelector>
<variableName>previousRouteComponent</variableName>
</valueSelector>
</subChainSelector>
<valueSelector>
<variableName>previousRouteComponent</variableName>
</valueSelector>
<selectReversingMoveToo>false</selectReversingMoveToo>
</subChainChangeMoveSelector>
<fixedProbabilityWeight>1.5</fixedProbabilityWeight>
</cartesianProductMoveSelector>
When I run the solver with this configuration, I get the following exception:
The entity (Wastebin{id=3}) has a variable (previousRouteComponent) with value (Wastebin{id=3}) which has a sourceVariableName variable (nextWastebin) with a value (Wastebin{id=1}) which is not null.
Verify the consistency of your input problem for that sourceVariableName variable.
Any idea what is going wrong here? One odd thing is that the planning entity apparently points to itself. I was thinking maybe the move selects 2 overlapping subChains which causes inconsistencies at some point?
EDIT
Turning on FULL_ASSERT yields more details:
Caused by: java.lang.IllegalStateException: UndoMove corruption: the beforeMoveScore (0/0/23/-2245/-12) is not the undoScore (0/0/23/-1358/-2) which is the uncorruptedScore (0/0/23/-1358/-2) of the workingSolution.
1) Enable EnvironmentMode FULL_ASSERT (if you haven't already) to fail-faster in case there's a score corruption.
2) Check the Move.createUndoMove(...) method of the moveClass (class org.optaplanner.core.impl.heuristic.move.CompositeMove). The move ([[Wastebin{id=3}..Wastebin{id=3}] {Wastebin{id=3} -> Vehicle{id=2}}, [Wastebin{id=3}..Wastebin{id=3}] {Wastebin{id=3} -> Wastebin{id=1}}]) might have a corrupted undoMove (Undo([[Wastebin{id=3}..Wastebin{id=3}] {Wastebin{id=3} -> Vehicle{id=2}}, [Wastebin{id=3}..Wastebin{id=3}] {Wastebin{id=3} -> Wastebin{id=1}}])).
3) Check your custom VariableListeners (if you have any) for shadow variables that are used by the score constraints with a different score weight between the beforeMoveScore (0/0/23/-2245/-12) and the undoScore (0/0/23/-1358/-2).
at org.optaplanner.core.impl.phase.scope.AbstractPhaseScope.assertExpectedUndoMoveScore(AbstractPhaseScope.java:145)
at org.optaplanner.core.impl.localsearch.decider.LocalSearchDecider.doMove(LocalSearchDecider.java:153)
at org.optaplanner.core.impl.localsearch.decider.LocalSearchDecider.decideNextStep(LocalSearchDecider.java:121)
at org.optaplanner.core.impl.localsearch.DefaultLocalSearchPhase.solve(DefaultLocalSearchPhase.java:72)
at org.optaplanner.core.impl.solver.DefaultSolver.runPhases(DefaultSolver.java:215)
at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:176)
Unfortunately, the output contains move twice, I created PLANNER-599 for this issue. I got the correct undo move from the debugger:
[Wastebin{id=3}..Wastebin{id=3}] {Wastebin{id=3} -> Vehicle{id=1}}
[Wastebin{id=3}..Wastebin{id=3}] {Wastebin{id=3} -> Vehicle{id=1}}
I do not have any custom VariableListeners
This is a known issue in OptaPlanner 6.4.0.Final. It happens with complex CompositeMoves and it's due to the design of the Move interface. Fixing it is not trivial, see PLANNER-611.
Update: This is fixed for 7.0.0.CR1.
"The entity (Wastebin{id=3}) has a variable (previousRouteComponent) with value (Wastebin{id=3})"
That mean Wastebin-3 is pointing to itself? That would violate the chained variables principles. In theory, the cartesian production move shouldn't be able to cause that - but if your input problems is valid, that must be happening anyway...
Turn on environmentMode FULL_ASSERT and see if it fails earlier.

Setting an execution's local variable according to the flow led to the sub-process

I have a BPMN process with a sub-process within it. There are different flows leading to the sub-process. And I want to know, once inside the sub-process' execution, which flow has led to the current execution.
To this end I think variables could be handy. So I conducted a test in which I wrote a couple of scripts for flow's listener leading to the sup-process.
execution.setVariableLocal("V", "Expecting it to be local to the sub-process' execution");
But it turned out that execution points to the outer/parent process and thus the variable was set in parent scope.
So is there anyway to set an execution local variable from outside?
The simplest approach (basically use getVariable instead of getVariableLocal):
Add an execution listener to the take event of the sequence flows of interest
In the execution listener, perform
execution.setVariableLocal("flowTaken", execution.getCurrentTransitionId());
Access it in the sub process via
execution.getVariable("flowTaken");
If it has to be a local variable in the sub process:
Add an execution listener to the take event of the sequence flows of interest
In the execution listener, perform
execution.setVariableLocal("flowTaken", execution.getCurrentTransitionId());
In the BPMN 2.0 XML, define a variable input mapping for the subprocess:
<subProcess ...>
<extensionElements>
<camunda:inputOutput>
<camunda:inputParameter name="flowTakenAsSubprocessLocalVariable">${flowTaken}</camunda:inputParameter>
</camunda:inputOutput>
</extensionElements>
...
</subProcess>

I am getting 'Local Search phase started with an uninitialized Solution' when I run on a larger dataset

I am developing a solver using Optaplanner 6.1.0, similar to the Vehicle Routing Problem. When I run my solver on 700 installers and 200 bookings, it will successfully solve the planning problem. But, when I used against a larger dataset (700 installers and 1220 bookings), I get
Caused by: java.lang.IllegalStateException: Local Search phase started with an uninitialized Solution. First initialize the Solution. For example, run a Construction Heuristic phase first.
but right before the exception,
16:10:40,378 INFO [DefaultConstructionHeuristicPhase] [http-listener-1(4)] Construction Heuristic phase (0) ended: step total (194), time spent (30693), best score (-1hard/-688803soft).
I am using <constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType>
in my config.
Am I using it wrong?
Maybe the value range for a planning variable is empty. Especially with value range provider from entity, this is more likely. Feel free to file a jira that the error message should improve in such a case.
Diagnostic todo: Comment out the local solver phase, run the solver (so it only does the construction heuristic) and then iterate through the planning entities and print out the value for each planning value. Check if there are any nulls in there.
The fact that you have 194 steps, instead 200 steps in your CH indicates this. (If those other 6 planning entities are immovable, this won't trigger this exception (more info), so that's not the problem.)