I am using Optaplanner to solve an installer booking assignment problem, which is a chaining problem, similar to vehicle routing. An installer(vehicle) may have multiple bookings(customer) assigned to it. I need to implement the chain because I need to evaluate one booking in relation to another booking an a particular order.
So, I declared Booking and Installer as #PlanningEntityCollectionProperty in my Solution. Both Booking and Installer implements Standstill. But only in Booking I declared #PlanningVariable for method getPreviousStandstill().
My config:
<solver>
<!--<environmentMode>FAST_ASSERT</environmentMode>-->
<!-- Domain model configuration -->
<solutionClass>InstallationSolution</solutionClass>
<entityClass>Standstill</entityClass>
<entityClass>Booking</entityClass>
<!-- Score configuration -->
<scoreDirectorFactory>
<scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
<!--<easyScoreCalculatorClass>org.optaplanner.examples.cloudbalancing.solver.score.CloudBalancingEasyScoreCalculator</easyScoreCalculatorClass>-->
<!--<easyScoreCalculatorClass>org.optaplanner.examples.cloudbalancing.solver.score.CloudBalancingMapBasedEasyScoreCalculator</easyScoreCalculatorClass>-->
<!--<incrementalScoreCalculatorClass>org.optaplanner.examples.cloudbalancing.solver.score.CloudBalancingIncrementalScoreCalculator</incrementalScoreCalculatorClass>-->
<!--<scoreDrl>com/tmrnd/pejal/opta/solver/fulfillmentScoreRules.drl</scoreDrl>-->
<initializingScoreTrend>ONLY_DOWN</initializingScoreTrend>
<!--<assertionScoreDirectorFactory>-->
<!--<easyScoreCalculatorClass>org.optaplanner.examples.cloudbalancing.solver.score.CloudBalancingMapBasedEasyScoreCalculator</easyScoreCalculatorClass>-->
<!--</assertionScoreDirectorFactory>-->
</scoreDirectorFactory>
<!-- Optimization algorithms configuration -->
<termination>
<!-- <secondsSpentLimit>20</secondsSpentLimit>-->
<unimprovedSecondsSpentLimit>15</unimprovedSecondsSpentLimit>
</termination>
<constructionHeuristic>
<constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType>
</constructionHeuristic>
<localSearch>
<unionMoveSelector>
<changeMoveSelector/>
<swapMoveSelector/>
<subChainChangeMoveSelector>
<selectReversingMoveToo>true</selectReversingMoveToo>
</subChainChangeMoveSelector>
<subChainSwapMoveSelector>
<selectReversingMoveToo>true</selectReversingMoveToo>
</subChainSwapMoveSelector>
</unionMoveSelector>
<acceptor>
<entityTabuSize>20</entityTabuSize>
</acceptor>
<forager>
<acceptedCountLimit>1000</acceptedCountLimit>
</forager>
</localSearch>
</solver>
When I try to solve(), I got one installer with all bookings asssigned to him. What do I need to do to improve the situation?
My initial guess would be you have only 1 anchor (1 installer) known to OptaPlanner. Check if the Solution's List's size is bigger than 1 in size. Then check if getter has #ValueRangeProvider and if the id of that ValueRangeProvider is added in the #PlanningEntity's valueRangeProviderRefs.
Also be aware of a common pitfall that might apply here too (but shouldn't if you use value range providers properly): list.add(list) instead of list.addAll(list).
Related
I want to experiment with different local search configurations. They all use the same neighborhood, so I defined the unionMoveSelector in the inheritedSolverBenchmark.
<inheritedSolverBenchmark>
<solver>
...
<localSearch>
<termination>
<unimprovedSecondsSpentLimit>30</unimprovedSecondsSpentLimit>
</termination>
<unionMoveSelector>
<changeMoveSelector />
<swapMoveSelector />
<pillarChangeMoveSelector>
<subPillarType>ALL</subPillarType>
</pillarChangeMoveSelector>
<pillarSwapMoveSelector>
<subPillarType>ALL</subPillarType>
</pillarSwapMoveSelector>
</unionMoveSelector>
</localSearch>
</solver>
</inheritedSolverBenchmark>
Then I created various benchmarks to compare:
<solverBenchmark>
<name>Tabu</name>
<solver>
<localSearch>
<localSearchType>TABU_SEARCH</localSearchType>
</localSearch>
</solver>
</solverBenchmark>
<solverBenchmark>
<name>LateAcceptance</name>
<solver>
<localSearch>
<localSearchType>LATE_ACCEPTANCE</localSearchType>
</localSearch>
</solver>
</solverBenchmark>
<solverBenchmark>
<name>GreatDeluge</name>
<solver>
<localSearch>
<localSearchType>GREAT_DELUGE</localSearchType>
</localSearch>
</solver>
</solverBenchmark>
<solverBenchmark>
<name>Tabu_LA</name>
<solver>
<localSearch>
<acceptor>
<entityTabuSize>7</entityTabuSize>
<lateAcceptanceSize>400</lateAcceptanceSize>
</acceptor>
<forager>
<acceptedCountLimit>1000</acceptedCountLimit>
</forager>
</localSearch>
</solver>
</solverBenchmark>
But now it seems that OptaPlanner executes three phases (construction + 2 x local search). I had expected the localSearch configuration from inheritedSolverBenchmark and plannerBenchmark to be merged. Is that not the case?
No, I am afraid it's not.
When inherited, the SolverConfig's phaseList gets list-merged, not overwritten, nor list-element-merged:
Nobody want's overwritten (as that would just ignore what's in the inherited config), so that's not it.
list-merged: some cases want this. I 've used it a lot to define a CH in the inherited config and the LS variants in the single benchmarks.
list-element-merged: what you're asking. Some cases want this.
We can't detect automatically if it's a case that needs list-merged or list-element-merged, so we had to make a choice and we went with list-merged.
Fix: Either declare it more verbosely or look at the Freemarker template support.
The OptaPlanner user guide (http://docs.jboss.org/optaplanner/release/6.4.0.Final/optaplanner-docs/html_single/index.html#whichOptimizationAlgorithmsShouldIUse) mentions "combining multiple algorithms together".
How do you specifiy that in the configuration file? The XML does not allow more than one localSearchType element. I tried a run with
<acceptor>
<entityTabuSize>7</entityTabuSize>
<lateAcceptanceSize>200</lateAcceptanceSize>
<simulatedAnnealingStartingTemperature>0hard/500soft</simulatedAnnealingStartingTemperature>
</acceptor>
and it ran, but I'm not sure what algorithms were used.
You can combine LS's and also sequence them.
Combine:
<acceptor>
<entityTabuSize>7</entityTabuSize>
<lateAcceptanceSize>200</lateAcceptanceSize>
</acceptor>
// with acceptedCounLimit 1 => LA with a bit of tabu
Sequence
<localSearch>
<termination>...</termination>
<acceptor>
<lateAcceptanceSize>200</lateAcceptanceSize>
</acceptor>
...
</localSearch>
<localSearch>
<acceptor>
<entityTabuSize>7</entityTabuSize>
</acceptor>
...
</localSearch>
// First LA, then TS
Apparently, you can use a sequence of localSearch sections.
I'm new into OptaPlanner and I'm trying to create an as simple as possible app that assigns few employees to some shifts. The only rule is that one employee can be assigned to one shift per day. I wonder if following solver configuration is not enough:
<solver>
<solutionClass>com.test.shiftplanner.ShiftPlanningSolution</solutionClass>
<entityClass>com.test.shiftplanner.ShiftAssignment</entityClass>
<scoreDirectorFactory>
<scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
<scoreDrl>rules.drl</scoreDrl>
</scoreDirectorFactory>
<!-- Solver termination -->
<termination>
<secondsSpentLimit>60</secondsSpentLimit>
</termination>
<constructionHeuristic>
<constructionHeuristicType>FIRST_FIT</constructionHeuristicType>
</constructionHeuristic>
</solver>
because the collection of ShiftAssignment at ShiftPlanningSolution class remains EMPTY even though the Solver.solve() finishes and getBestSolution() returns something. What's more it seems that my rules at rules.drl are not fired at all. I even added a dummy rule just to see if it is triggered:
rule "test"
when
shiftAssignment : ShiftAssignment()
then
System.out.println(shiftAssignment);
end
and it's not fired at all.
So what are my mistakes here? Thanks in advance!
the rule should be doing something with scoreHolder, see docs chapter 5. But despite that, you should see that rule being fired once for every ShiftAssignement instance in your dataset - check if you have any in there.
I'm trying to sort the planning entities on decreasing difficulty in the local search phase.
I tried to add the "entitySelector" in the config file like the following but it results in a ConversionException:
<localSearch>
<termination>
<maximumUnimprovedStepCount>500</maximumUnimprovedStepCount>
</termination>
<moveListFactory>
<cacheType>PHASE</cacheType> <!-- STEP, PHASE -->
<selectionOrder>RANDOM</selectionOrder>
<moveListFactoryClass>com.abcdl.be.solver.move.factory.ParentChangeMoveFactory</moveListFactoryClass>
<entitySelector>
<cacheType>PHASE</cacheType>
<selectionOrder>SORTED</selectionOrder>
<sorterManner>DECREASING_DIFFICULTY</sorterManner>
</entitySelector>
</moveListFactory>
<acceptor>
<lateAcceptanceSize>400</lateAcceptanceSize>
<entityTabuSize>5</entityTabuSize>
</acceptor>
<forager>
<pickEarlyType>NEVER</pickEarlyType> <!-- FIRST_BEST_SCORE_IMPROVING -->
<acceptedCountLimit>3</acceptedCountLimit>
</forager>
</localSearch>
The following Comparator class in annotated on the domain model :
public class NodeDifficultyComparator implements Comparator<Node>{
public int compare(Node a, Node b) {
return new CompareToBuilder()
.append(a.getResources(), b.getResources()) // the most difficult nodes are the ones who use the most resources
.append(a.getId(), b.getId())
.toComparison();
}
}
Did I choose a wrong placement for the "entitySelector" tag ? Should I do this in an another way ??
Thanks for your help.
Implementation detail: the element <moveListFactory> is defined by the class MoveListFactoryConfig. If you look at the source of that class, it does not have a field called entitySelector, so you cannot nest an <entitySelector> element in it.
There are 2 ways to solve your problem:
Instead of using <moveListFactory>, use <changeMoveSelector>, which does support an <entitySelector> element. You can then delete your custom ParentChangeMoveFactory class. The <changeMoveSelector> has many advantages over a custom MoveListFactory, such as JIT support and much more.
If you have a good reason to have a custom MoveListFactory (for example due to some complex reasons which move filtering can't cover), then adjust your ParentChangeMoveFactory to sort the entity list before generating the moves. Remember to shallow clone that list first, as you don't want your sorting to affect the entity list references by the workingSolution instance. The point is: if you write a custom MoveListFactory you're in total control on how to generate the moves, but you do need to do everything yourself...
Sitecore CMS+DMS 6.6.0 rev.130404 => 7.0 rev.130424
In our project we have been using AdvancedDatabaseCrawler (ADC) for our indexes (specially because of it's dynamic fields feature). Here's a sample index configuration:
<index id="GeoIndex" type="Sitecore.Search.Index, Sitecore.Kernel">
<param desc="name">$(id)</param>
<param desc="folder">$(id)</param>
<analyzer ref="search/analyzer" />
<locations hint="list:AddCrawler">
<web type="scSearchContrib.Crawler.Crawlers.AdvancedDatabaseCrawler, scSearchContrib.Crawler">
<database>web</database>
<root>/sitecore/content/Globals/Locations</root>
<IndexAllFields>true</IndexAllFields>
<include hint="list:IncludeTemplate">
<!--Suburb Template-->
<suburb>{FF0D64AA-DCB4-467A-A310-FF905F9393C0}</suburb>
</include>
<dynamicFields hint="raw:AddDynamicFields">
<dynamicField type="OurApp.CustomSearchFields.SearchTextField,OurApp" name="search text" storageType="NO" indexType="TOKENIZED" vectorType="NO" />
<dynamicField type="OurApp.CustomSearchFields.LongNameField,OurApp" name="display name" storageType="YES" indexType="UN_TOKENIZED" vectorType="NO" />
</dynamicFields>
</web>
</locations>
</index>
As you can see, we use scSearchContrib.Crawler.Crawlers.AdvancedDatabaseCrawler as the crawler and it uses the fields defined inside <dynamicFields hint="raw:AddDynamicFields"> section to inject custom fields into the index.
Now we are upgrading our project to sitecore 7. In Sitecore 7, they have ported the DynamicFields functionality from ADC into sitecore. I found out some articles on this and converted our custom search field classes to implement sitecore 7 IComputedIndexField interface instead of inheriting from BaseDynamicField class in ADC. Now my problem is how to change the index configuration to match with new sitecore 7 APIs. There were bits and pieces on the web but couldn't find all the examples I needed to convert my configuration. Can anybody help me on this?
While I'm doing this I'm under the impression that we won't have to rebuild our indexes since it still uses Lucene internally. I don't want to change the index structure. Just want to upgrade the code and configuration from AdvancedDatabaseCrawler to Sitecore 7. Should I be worried about breaking our existing indexes? Please shed some light on this as well.
Thanks
A few quick clarifications :)
We have not merged ADC into Sitecore 7, the ContentSearch layer is a complete rewrite of the old search layer for Sitecore. We have taken some of the core concepts from ADC, such as dynamic fields, and put them in the new implementation (as ComputedFields). They are not 1:1 compatible and you will have to do some work on your indexes.
The version of Lucene has also been upgraded from 2.* to 3.0.3 so all indexes will need to be re-created anyway as they are a very different version of Lucene.
There are two options here, the old Lucene search (Sitecore.Search namespace) (which ADC was built upon) has not been touched and will still work in the same way, although I am not sure about ADC compatibility with SItecore 7 as in theory this has now been superseded.
The next option is to update your index to take advantage of the new search features of Sitecore 7. The configuration you have will not be directly compatible but, while you will need to rework your index into the new configuration, the basic concepts should be familiar to you. The dynamic fields you have should map logically to ComputedFields (fields that are calculated when an item is indexed) and everything else is straightforward.
While it looks like a lot of extra config for ContentSearch a lot of it is default config that you will not need to touch, you will just need to override the configuration parts for the computed fields you want to add and the template you want to include.
An example of creating your own configuration override can be found here : http://www.mikkelhm.dk/post/2013/10/12/Defining-a-custom-index-in-Sitecore-7-and-utilizing-it.aspx
I would also recommend making sure you upgrade to 7.0 rev. 131127 (7.0 Update-3) as this fixes a bug in the IncludeTemplates logic in the version you currently have.
I managed to convert the index configuration for sitecore ContentSearch API. Looking at Sitecore default index configurations was a great help for this.
Note: As also mentioned by Stephen, <include hint="list:IncludeTemplate"> does not work in Sitecore 7.0 initial release. It's fixed in Sitecore 7.0 rev. 131127 (7.0 Update-3) and I'm planning to upgrade to it.
Here's a good article on sitecore 7 index update strategies by John West. It'll help you in configuration your indexes the way you want.
Converted configuration:
<sitecore>
<contentSearch>
<configuration type="Sitecore.ContentSearch.LuceneProvider.LuceneSearchConfiguration, Sitecore.ContentSearch.LuceneProvider">
<DefaultIndexConfiguration type="Sitecore.ContentSearch.LuceneProvider.LuceneIndexConfiguration, Sitecore.ContentSearch.LuceneProvider">
<IndexAllFields>true</IndexAllFields>
<include hint="list:IncludeTemplate">
<!--Suburb Template-->
<suburb>{FF0D64AA-DCB4-467A-A310-FF905F9393C0}</suburb>
</include>
<fields hint="raw:AddComputedIndexField">
<field fieldName="search text" storageType="NO" indexType="TOKENIZED" vectorType="NO">OurApp.CustomSearchFields.SearchTextField,OurApp</field>
<field fieldName="display name" storageType="YES" indexType="UN_TOKENIZED" vectorType="NO">OurApp.CustomSearchFields.LongNameField,OurApp</field>
</fields>
</DefaultIndexConfiguration>
<indexes hint="list:AddIndex">
<index id="GeoIndex" type="Sitecore.ContentSearch.LuceneProvider.LuceneIndex, Sitecore.ContentSearch.LuceneProvider">
<param desc="name">$(id)</param>
<param desc="folder">$(id)</param>
<!-- This initializes index property store. Id has to be set to the index id -->
<param desc="propertyStore" ref="contentSearch/databasePropertyStore" param1="$(id)" />
<strategies hint="list:AddStrategy">
<!-- NOTE: order of these is controls the execution order -->
<strategy ref="contentSearch/indexUpdateStrategies/onPublishEndAsync" />
</strategies>
<commitPolicy hint="raw:SetCommitPolicy">
<policy type="Sitecore.ContentSearch.TimeIntervalCommitPolicy, Sitecore.ContentSearch" />
</commitPolicy>
<commitPolicyExecutor hint="raw:SetCommitPolicyExecutor">
<policyExecutor type="Sitecore.ContentSearch.CommitPolicyExecutor, Sitecore.ContentSearch" />
</commitPolicyExecutor>
<locations hint="list:AddCrawler">
<crawler type="Sitecore.ContentSearch.LuceneProvider.Crawlers.DefaultCrawler, Sitecore.ContentSearch.LuceneProvider">
<Database>web</Database>
<Root>/sitecore/content/Globals/Countries</Root>
</crawler>
</locations>
</index>
</indexes>
</configuration>
</contentSearch>
</sitecore>