OptaPlanner:卡在局部最优中。域模型有问题吗?需要更好的举动吗?分数陷阱?等等

时间:2019-08-22 19:38:50

标签: optaplanner

预警:这是一个非常详细且冗长的问题。除非我真的被困住,否则我通常不会发布这样的帖子。我已经在这个问题上工作了几周了,我已经达到了一个临界点。我找不到求解器甚至解决我的硬约束的方法,更不用说优化。我是应届毕业生,我正在免费做这个自由职业项目。我正在为妈妈工作的医院做的。优化他们的提供者时间表将使她的生活更加轻松。她是负责人员配备的RN(这真是地狱),她58岁时每天工作11-12个小时,因为她还定期履行RN职责,我真的很想帮助她!另外,这种经历不会受到伤害,我希望在履历表中有类似的内容。

我将尽可能简明扼要地介绍问题所在,以及到目前为止所做的事情,但这将很困难。

问题

我正在为医疗保健提供者编写一个自动计划程序。这家医院部门。有一个静态的调度系统,大约有23家提供商。他们希望负载均衡部门中提供程序所需的人员。每个班次。提供者还必须履行其他部门的义务。

每个提供者平均每个月必须在特定位置执行一定数量的轮班。

这是他们如何安排日程安排的方式,我对此很感兴趣。可以按时间,日期和时间安排班次,每个班次都是离散的。

广告位:1、2、3、4

日期:M,T,W,Th,F

时间:上午,下午

因此(1st,M,AM)指的是当月早晨的第一个星期一。让我们将其称为一个时间段。因此,所有可用的时隙就是这三个集合的笛卡尔积。请注意,可能会有第5个广告位,因为有时一个月中有5次出现在几天中,但是为了简化起见,第5个广告位的时间表可以简单地模仿第3个广告位。

某些班次必须整天。那就是他们需要跨越一个时段,一天和两次。某些轮班必须整天,并且必须在每周的同一天发生。这意味着它们跨越一天,每个时段和每次。最后一个班次类型是半天,发生在每周的同一天。这些跨越一天,一个时间和每个时段。

有两种不同的位置类型:部门内和部门外。部门位置对时隙中的房间数有限制。外部位置限制了可以在一个时隙中安排的提供者的数量。

硬约束:

  • 提供者不能同时进行两个不同的班次。
  • 在某个时间段的某个部门安排的所有班次。位置不能超过房间的可用性。
  • 在外部位置的时间段中安排的所有班次不能超过提供商的限制。

中等约束条件:

  • 即使每个部门中的RN数也没有。每个时隙中的位置。
  • 即使每个部门中的ST数量也没有。每个时隙中的位置。
  • 甚至每个部门中的MA数量也是如此。每个时隙中的位置。

软约束:

  • 为简单起见,暂时保留

我做了什么

我为每种班次类型有4个计划实体。我将在下面概述它们的计划变量和类型:

HalfDayShift:广告位,日期,时间

HalfDayNoSplitShift:日期,时间

FullDayShift:广告位,日期

FullDayNoSplitShift:天

每一个都扩展了一个抽象的Shift类,因此我可以轻松地与它们进行交互。我可以只用一个计划实体作为“最小”班次(HalfDayShift)来完成此任务,而是将“较大”班次定义为这些较小班次的组。但是,我发现用这种方法定义自定义动作更容易,因为这些动作是此问题中最奇怪的部分。我会尽力解释移动机制。

考虑到此进度表的移动,第一种类型仅适用于构造。对于每种班次类型,唯一的变动是每种类型的计划变量的变动变动的笛卡尔积。很简单。

对于本地搜索,我认为最简单的方法是选择一个班次,然后根据该类型的可能值为其分配一个新值,即FullDayShift将选择一个新的广告位/天,而FullDayNoSplitShift将仅选择一个新日,并且与同一提供商在该新值上发生的任何变动都将被交换。但是,这仅在移位移动到所有移位都小于或等于其大小的值时才有效。即将HalfDayShift移到FullDayNosplitShift填充的日期是没有意义的,因为原始Day的所有班次也应交换。因此,一旦我们找到一组有效的班次以进行下一步,班次就可以交换所有计划变量。即FullDayNoSplitShift只是在前一天与同一提供者交换任何班次的Day。

这显示了不同的班次如何在彼此之上移动

This shows how the different shifts can move on top of eachother

我仅实施了硬约束,而无法生成可行的解决方案。一些注意事项:

  • 某些转变是同时发生的转变。这意味着它们会同时在两个位置(通常在附近的位置)发生。这一切的真正含义是,转移已经并将第二个位置计入资源。
  • 我相信这里没有任何得分陷阱,我的举动足够粗略,可以根据需要将事情交换在一起。但是,请让我知道我是否缺少任何东西。
    rule "Provider cannot work two shifts in same timeslot"
        when
            $leftShift : Shift (
                isInitialized(),
                $leftProv : provider
            )
            Shift (
                this != $leftShift,
                provider == $leftProv,
                isInitialized(),
                conflictsWith($leftShift)
            )
        then
            scoreHolder.addHardConstraintMatch(kcontext, -100);
    end

    rule "Cannot exceed available slots resource constraint"
        when 
            $loc : Location(rooms == false, $avail: availMap)
            $s : Slot()
            $d : Day()
            $t : Time()       
            accumulate (
                Shift (
                    isInitialized(),
                    occursOn($s,$d,$t),
                    isConsumingResc(),
                    primaryAt($loc) || concAt($loc)
                );
               $c : count();
               ((Integer)$avail.get($s,$d,$t)) != null &&
               ((Integer)$avail.get($s,$d,$t)) < $c
            )
        then
            scoreHolder.addHardConstraintMatch(kcontext, (int)(((Integer)$avail.get($s,$d,$t)) - ($c)));
    end
    rule "Cannot exceed room resource constraint"
        when 
            $loc : Location(rooms == true, $avail: availMap)
            $s : Slot()
            $d : Day()
            $t : Time() 
            accumulate (
                Shift (
                    isInitialized(),
                    occursOn($s,$d,$t),
                    isConsumingResc(),
                    primaryAt($loc),
                    $primRI : primaryResc
                );
                $s1 : sum($primRI.getNumRMs())
            )
            accumulate (
                Shift (
                    isInitialized(),
                    occursOn($s,$d,$t),
                    isConsumingResc(),
                    concAt($loc),
                    $concRI : concurrentResc
                );
                $s2 : sum($concRI.getNumRMs())
            )
            eval(
               ((Integer)$avail.get($s,$d,$t)) != null &&
               ((Integer)$avail.get($s,$d,$t)) < ($s1 + $s2)
            )  
        then
            scoreHolder.addHardConstraintMatch(kcontext,(int)(((Integer)$avail.get($s,$d,$t)) - ($s1 + $s2)) );
    end

这也是我的配置。

<?xml version="1.0" encoding="UTF-8"?>
<solver>
    <!--<environmentMode>FAST_ASSERT</environmentMode>-->
    <!--<environmentMode>FULL_ASSERT</environmentMode>-->
    <scanAnnotatedClasses>
        <packageInclude>com.labrador.providerplanner.domain</packageInclude>
    </scanAnnotatedClasses>

    <scoreDirectorFactory>
        <scoreDrl>constraints.drl</scoreDrl>
    </scoreDirectorFactory>

    <!--<constructionHeuristic>
        <constructionHeuristicType>CHEAPEST_INSERTION</constructionHeuristicType>
    </constructionHeuristic>-->

    <constructionHeuristic>
        <queuedEntityPlacer>
            <entitySelector id="placerEntitySelector">
                <cacheType>PHASE</cacheType>
                <entityClass>com.labrador.providerplanner.domain.FullDayNoSplitShift</entityClass>
            </entitySelector>
            <changeMoveSelector>
                <entitySelector mimicSelectorRef="placerEntitySelector"/>
            </changeMoveSelector>
        </queuedEntityPlacer>
    </constructionHeuristic>
    <constructionHeuristic>
        <queuedEntityPlacer>
            <entitySelector id="placerEntitySelector">
                <cacheType>PHASE</cacheType>
                <entityClass>com.labrador.providerplanner.domain.HalfDayNoSplitShift</entityClass>
            </entitySelector>
            <cartesianProductMoveSelector>
                <changeMoveSelector>
                    <entitySelector mimicSelectorRef="placerEntitySelector"/>
                    <valueSelector variableName="time"/>
                </changeMoveSelector>
                <changeMoveSelector>
                    <entitySelector mimicSelectorRef="placerEntitySelector"/>
                    <valueSelector variableName="day"/>
                </changeMoveSelector>
            </cartesianProductMoveSelector>
        </queuedEntityPlacer>
    </constructionHeuristic>
    <constructionHeuristic>
        <queuedEntityPlacer>
            <entitySelector id="placerEntitySelector">
                <cacheType>PHASE</cacheType>
                <entityClass>com.labrador.providerplanner.domain.FullDayShift</entityClass>
            </entitySelector>
            <cartesianProductMoveSelector>
                <changeMoveSelector>
                    <entitySelector mimicSelectorRef="placerEntitySelector"/>
                    <valueSelector variableName="slot"/>
                </changeMoveSelector>
                <changeMoveSelector>
                    <entitySelector mimicSelectorRef="placerEntitySelector"/>
                    <valueSelector variableName="day"/>
                </changeMoveSelector>
            </cartesianProductMoveSelector>
        </queuedEntityPlacer>
    </constructionHeuristic>
    <constructionHeuristic>
        <queuedEntityPlacer>
            <entitySelector id="placerEntitySelector">
                <cacheType>PHASE</cacheType>
                <entityClass>com.labrador.providerplanner.domain.HalfDayShift</entityClass>
            </entitySelector>
            <cartesianProductMoveSelector>
                <changeMoveSelector>
                    <entitySelector mimicSelectorRef="placerEntitySelector"/>
                    <valueSelector variableName="slot"/>
                </changeMoveSelector>
                <changeMoveSelector>
                    <entitySelector mimicSelectorRef="placerEntitySelector"/>
                    <valueSelector variableName="day"/>
                </changeMoveSelector>
                <changeMoveSelector>
                    <entitySelector mimicSelectorRef="placerEntitySelector"/>
                    <valueSelector variableName="time"/>
                </changeMoveSelector>
            </cartesianProductMoveSelector>
        </queuedEntityPlacer>
    </constructionHeuristic>

    <localSearch>
        <localSearchType>LATE_ACCEPTANCE</localSearchType>
        <moveIteratorFactory>
            <moveIteratorFactoryClass>com.labrador.providerplanner.solver.SingleProviderShiftSwapMoveIteratorFactory</moveIteratorFactoryClass>
        </moveIteratorFactory>
        <termination>
            <secondsSpentLimit>60</secondsSpentLimit>
        </termination>
    </localSearch>
</solver>

问题所在

这是主要问题。以下是求解器吐出的内容。它陷入了这个局部最优中。例如,让我们看一下Main OR Robot位置,它是一个外部位置,只有三个提供程序在其中进行轮班。P2和P3在Main OR Robot中每个都有4个FullDayShift。 P1有4个FullDayShift和4个HalfDay Shift。 Main OR Robot在每个星期一的时间段只能容纳1个提供商,每个星期二容纳1个,每个星期四容纳1个,每个星期五上午容纳1个提供商。

Here is the output Roster

主或机器人的移位被圈出。 P1在星期二违反了资源限制。 P2在星期五违反了另一个资源限制。箭头显示了一组动作,这些动作将不会再成功打破Main OR Robot的那些约束。但是,问题是这些斑点在FH癌中含有转移。这是另一个充满边缘的外部位置。因此,移动这些约束会破坏其他约束,甚至更多。因此,必须重新整理这些位置,否则可能会导致其他位置以及其他位置……等等……这确实是优化问题的症结所在,似乎元启发式算法根本不足打破这种局部最优。

这就是我的位置。我真的不知道该如何进行。我可以采取一些更粗略的措施来帮助求解器从这种最优状态中退出吗?即两个提供商之间的交易地点时间。我已经尝试过类似的方法,但并没有帮助。我需要重新考虑我的域模型吗?即位置是否应根据其可用性在特定时间设置房间或空位,应将提供商分配给这些房间或空位吗?那可能行得通,但是如何在此域中封装较大的班次类型?我如何确保每个提供商在每个位置工作所需的轮班次数?

这是一个非常复杂的问题,也是一堵信息墙。因此,如果您确实阅读了所有这些内容,那么谢谢!非常感谢任何帮助或指示,因为我现在很困在这里。如果有任何令人困惑的地方或需要更多信息,请告诉我,我会尽力清除它。

0 个答案:

没有答案