optaplanner更喜欢解决方案

时间:2016-03-16 10:13:32

标签: java drools optaplanner

我遇到了一个奇怪的案例,最终解决方案不是最好的解决方案,让我解释一下:

我有两个计划实体,PlannerTask和PlannerTaskResourceAllocation。 PlannerTasks的计划变量是一个起始索引(转换为日历中的日期),PlannerTaskResourceAllocation的计划变量是一个TaskResource。我设置了规则,以便每次启动索引更改和每次资源更改都会扣除一定数量的软分数,其中更改索引的惩罚大于更改资源的惩罚

//this rule deducts 10 soft score for every resource allocation change
rule "taskResourceChangeRule"
    when
        PlannerTaskResourceAllocation(!resource.equals(originalResource))
    then
        scoreHolder.addSoftConstraintMatch(kcontext, -1);
end

//this rule deducts a 1000 soft score times the different in index when chnaging a task index
rule "taskDateChangeRule"
    when
        PlannerTask($startIndex : startIndex, $originalStartIndex : originalStartIndex, $startIndex != $originalStartIndex )
    then
        scoreHolder.addSoftConstraintMatch(kcontext, Math.abs($originalStartIndex - $startIndex) * -1000);
end

我设置了一个测试用例,其中有足够的资源让optaplanner将它们分配给任务而不需要移动它们但是当我运行它时最终还是移动任务而不分配资源。现在我知道它比这复杂得多,你需要更多的信息来理解为什么会发生这种情况,但为了证明这确实很奇怪,我只是从配置中删除了PlannerTask实体,这导致了预期行为得分更高!

所以:

  • 使用PlannerTask实体和相应的移动包含在配置中,我得到了意外的任务移动结果和不良分数:0hard / -28000soft
  • 从配置中删除了PlannerTask实体,我得到任务索引的预期行为保持不变,并且分配了正确的资源,得分更高:0hard / -2000soft(差不多15倍)

因此,为了简化,当包含PlannerTask实体时,optaplanner显然可以达到更好的解决方案,那么为什么它不能到达呢?

为了让事情更容易,我添加了一个

<fixedProbabilityWeight>100.0</fixedProbabilityWeight> 

关于资源分配变更的举动,以便产生更多这些,这没有帮助。

我认为可能导致此问题的唯一原因是我在资源分配移动中使用的过滤器,它检查分配的资源是否具有资源分配实体所需的正确类型和类,并启用跟踪日志显示它拒绝了多少移动:(这只是一个片段,控制台中可能有10000条类似的行)

2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.d.a.tabu.ValueTabuAcceptor     :         Proposed move (replacing resource tractor2 for task with id 100 with tractor2) is tabu and is therefore not accepted.
2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.decider.LocalSearchDecider     :         Move index (46980), score (0hard/-28002soft), accepted (false), move (replacing resource tractor1 for task with id 100 with tractor2).
2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.decider.LocalSearchDecider     :         Move index (46981) not doable, ignoring move (replacing resource tractor1 for task with id 98 with dave daveson Working as ATV Driver).
2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.decider.LocalSearchDecider     :         Move index (46982) not doable, ignoring move (replacing resource tractor2 for task with id 102 with tractor2).
2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.decider.LocalSearchDecider     :         Move index (46983) not doable, ignoring move (replacing resource tractor1 for task with id 98 with oil1).
2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.decider.LocalSearchDecider     :         Move index (46984) not doable, ignoring move (replacing resource joe joeson Working as Tractor Driver for task with id 101 with oil2).
2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.decider.LocalSearchDecider     :         Move index (46985) not doable, ignoring move (replacing resource joe joeson Working as Tractor Driver for task with id 97 with dave daveson Working as ATV Driver).
2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.decider.LocalSearchDecider     :         Move index (46986) not doable, ignoring move (replacing resource joe joeson Working as Tractor Driver for task with id 97 with fertilizer1).
2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.decider.LocalSearchDecider     :         Move index (46987) not doable, ignoring move (replacing resource joe joeson Working as Tractor Driver for task with id 101 with joe joeson Working as ATV Driver).
2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.decider.LocalSearchDecider     :         Move index (46988) not doable, ignoring move (replacing resource joe joeson Working as Tractor Driver for task with id 99 with oil2).
2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.decider.LocalSearchDecider     :         Move index (46989) not doable, ignoring move (replacing resource tractor1 for task with id 100 with joe joeson Working as ATV Driver).
2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.decider.LocalSearchDecider     :         Move index (46990) not doable, ignoring move (replacing resource tractor1 for task with id 100 with oil2).
2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.decider.LocalSearchDecider     :         Move index (46991) not doable, ignoring move (replacing resource tractor1 for task with id 104 with joe joeson Working as ATV Driver).
2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.decider.LocalSearchDecider     :         Move index (46992) not doable, ignoring move (replacing resource joe joeson Working as Tractor Driver for task with id 103 with dave daveson Working as Spreader Driver).
2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.decider.LocalSearchDecider     :         Move index (46993) not doable, ignoring move (replacing resource joe joeson Working as Tractor Driver for task with id 99 with oil2).
2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.decider.LocalSearchDecider     :         Move index (46994) not doable, ignoring move (replacing resource joe joeson Working as Tractor Driver for task with id 101 with joe joeson Working as Tractor Driver).
2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.decider.LocalSearchDecider     :         Move index (46995) not doable, ignoring move (replacing resource tractor1 for task with id 104 with fungicide2).
2016-03-16 09:57:12.187 TRACE   --- [           main] o.o.c.i.l.decider.LocalSearchDecider     :         Move index (46996) not doable, ignoring move (replacing resource joe joeson Working as Tractor Driver for task with id 101 with joe joeson Working as ATV Driver).

有什么想法吗?

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<solver>
<environmentMode>FULL_ASSERT</environmentMode>

<!-- Domain model configuration -->
<solutionClass>com.rdthree.plenty.services.activities.planner.ActivitySolution</solutionClass>
<entityClass>com.rdthree.plenty.services.activities.helpers.dtos.PlannerTask</entityClass>
<entityClass>com.rdthree.plenty.services.activities.helpers.dtos.PlannerTaskResourceAllocation</entityClass>

<!-- Score configuration -->
<scoreDirectorFactory>
    <scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
    <scoreDrl>com/rdthree/plenty/services/activities/planner/activity-scoring.drl</scoreDrl>
    <initializingScoreTrend>ONLY_DOWN</initializingScoreTrend>
</scoreDirectorFactory>

<!-- Optimization algorithms configuration -->
<termination>
    <terminationCompositionStyle>OR</terminationCompositionStyle>
    <bestScoreLimit>0hard/0soft</bestScoreLimit>
    <secondsSpentLimit>60</secondsSpentLimit>
</termination>

<constructionHeuristic>
    <queuedEntityPlacer>
        <entitySelector id="resourceAllocationSelector">
            <entityClass>com.rdthree.plenty.services.activities.helpers.dtos.PlannerTaskResourceAllocation</entityClass>
            <cacheType>PHASE</cacheType>
            <selectionOrder>SORTED</selectionOrder>
            <sorterManner>DECREASING_DIFFICULTY_IF_AVAILABLE</sorterManner>
        </entitySelector>
        <changeMoveSelector>
            <entitySelector mimicSelectorRef="resourceAllocationSelector" />
            <valueSelector>
                <variableName>resource</variableName>
                <cacheType>PHASE</cacheType>
            </valueSelector>
        </changeMoveSelector>
    </queuedEntityPlacer>
</constructionHeuristic>

<constructionHeuristic>
    <queuedEntityPlacer>
        <entitySelector id="taskSelector">
            <entityClass>com.rdthree.plenty.services.activities.helpers.dtos.PlannerTask</entityClass>
            <cacheType>PHASE</cacheType>
            <selectionOrder>SORTED</selectionOrder>
            <sorterManner>DECREASING_DIFFICULTY_IF_AVAILABLE</sorterManner>
        </entitySelector>
        <changeMoveSelector>
            <entitySelector mimicSelectorRef="taskSelector" />
            <filterClass>com.rdthree.plenty.services.activities.planner.filters.TaskLengthChnageFilter</filterClass>
            <valueSelector>
                <variableName>startIndex</variableName>
                <cacheType>PHASE</cacheType>
            </valueSelector>
        </changeMoveSelector>
    </queuedEntityPlacer>
</constructionHeuristic>

<localSearch>
    <unionMoveSelector>
        <moveListFactory>
            <fixedProbabilityWeight>100.0</fixedProbabilityWeight>
            <moveListFactoryClass>com.rdthree.plenty.services.activities.planner.MoveResourceAllocationMoveFactory</moveListFactoryClass>
        </moveListFactory>
        <changeMoveSelector>
            <fixedProbabilityWeight>1.0</fixedProbabilityWeight>
            <filterClass>com.rdthree.plenty.services.activities.planner.filters.TaskLengthChnageFilter</filterClass>
            <entitySelector id="taskMoveSelector">
                <entityClass>com.rdthree.plenty.services.activities.helpers.dtos.PlannerTask</entityClass>
            </entitySelector>
            <valueSelector>
                <variableName>startIndex</variableName>
            </valueSelector>
        </changeMoveSelector>
    </unionMoveSelector>

    <acceptor>
        <valueTabuSize>7</valueTabuSize>
    </acceptor>
    <forager>
        <acceptedCountLimit>2000</acceptedCountLimit>
    </forager>
</localSearch>

Drools文件:

//this rule deducts a 1000 soft score times the different in index when chnaging a task index
rule "taskDateChangeRule"
    when
        PlannerTask($startIndex : startIndex, $originalStartIndex : originalStartIndex, $startIndex != $originalStartIndex )
    then
        scoreHolder.addSoftConstraintMatch(kcontext, Math.abs($originalStartIndex - $startIndex) * -1000);
end

//this rule deducts 1000 soft score for every time unit of space between consecutive tasks of the same activity
rule "taskNonConsecutivPlacement"
    when
        PlannerTask($task1Activity : activity, $task1Id : id, $task1endIndex : endIndex, $task1IndexInActivity : indexInActivity)
        PlannerTask(activity == $task1Activity, $task1Id != id, indexInActivity == $task1IndexInActivity+1, $task2startIndex : startIndex)
    then
        scoreHolder.addSoftConstraintMatch(kcontext, Math.abs($task2startIndex - $task1endIndex) * -1000);
end

//this rule deducts 10 soft score for every resource allocation change
rule "taskResourceChangeRule"
    when
        PlannerTaskResourceAllocation(!resource.equals(originalResource))
    then
        scoreHolder.addSoftConstraintMatch(kcontext, -1);
end

// This rule creates a constraint based on overlap of two tasks which use the 
// same labourer at the same time. The constraint is multipled by the size of the overlap in days
rule "TaskLabourerTimeOverlap"
    when
        $task1 : PlannerTask()
        $task1ResourceAllocation : PlannerTaskResourceAllocation(taskId == $task1.id, resource instanceof SkillsAndRates, $task1UserId : resource.getUser().getId())

        $task2 : PlannerTask($task1.id != id)
        eval($task1.overlaps($task2))
        $task2ResourceAllocation : PlannerTaskResourceAllocation(taskId == $task2.id, resource instanceof SkillsAndRates, resource.getUser().getId().equals($task1UserId))
    then
        scoreHolder.addHardConstraintMatch(kcontext, (int)$task1.getOverlapLegth($task2) * -1);
        $task1ResourceAllocation.setState(ScheduleState.CONFLICT.toString());
        $task2ResourceAllocation.setState(ScheduleState.CONFLICT.toString());
        $task1.setState(ScheduleState.CONFLICT.toString());
        $task2.setState(ScheduleState.CONFLICT.toString());
end

// This rule creates a constraint based on overlap of a task and it's labour days off
// The constraint is multipled by the size of the overlap in days
rule "TaskLabourerDayOffOverlap"
    when
        $task1 : PlannerTask()
        $task1ResourceAllocation : PlannerTaskResourceAllocation(taskId == $task1.id, resource instanceof SkillsAndRates, $task1UserId : resource.getUser().getId())
        $labourDayOff : PlannerLabourDayOff(user.getId() == $task1UserId, $task1.contains(dateIndex))
    then
        scoreHolder.addHardConstraintMatch(kcontext, -1);
        $task1ResourceAllocation.setState(ScheduleState.CONFLICT.toString());
        $task1.setState(ScheduleState.CONFLICT.toString());
end

// This rule creates a constraint based on overlap of two tasks which use the 
// same equipment at the same time. The constraint is multipled by the size of the overlap in days
rule "TaskEquipmentTimeOverlap"
    when
        $task1 : PlannerTask()
        $task1ResourceAllocation : PlannerTaskResourceAllocation(taskId == $task1.id, resource instanceof EquipmentEquipmentTypes, $task1EquipmentId : resource.getEquipment().getId())

        $task2 : PlannerTask($task1.id != id)
        eval($task1.overlaps($task2))
        $task2ResourceAllocation : PlannerTaskResourceAllocation(taskId == $task2.id, resource instanceof EquipmentEquipmentTypes, resource.getEquipment().getId() == $task1EquipmentId)
    then
        scoreHolder.addHardConstraintMatch(kcontext, (int)$task1.getOverlapLegth($task2) * -1);
        $task1ResourceAllocation.setState(ScheduleState.CONFLICT.toString());
        $task2ResourceAllocation.setState(ScheduleState.CONFLICT.toString());
        $task1.setState(ScheduleState.CONFLICT.toString());
        $task2.setState(ScheduleState.CONFLICT.toString());
end

// This rule create a constraint based on product defficiency for a product at a taks's start date
rule "TaskProductShortage"
    when
        $task : PlannerTask( $taskId : id, $taskNeedsProductBy : startIndex)
        $allocation : PlannerTaskResourceAllocation( taskId == $taskId, resource != null, resource instanceof Product)
        OnHandForProduct(dateIndex == $taskNeedsProductBy, productId == $allocation.resource.getId(), $onHandAmount : amount, amount - $allocation.amount  <= 0)
    then
        scoreHolder.addHardConstraintMatch(kcontext, $allocation.getAmount() * -1);
        $allocation.setState(ScheduleState.CONFLICT.toString());
        $task.setState(ScheduleState.CONFLICT.toString());
end

0 个答案:

没有答案