增量分数计算错误?

时间:2014-07-02 13:57:33

标签: optaplanner

我已经处理了几天没有明显原因的分数损坏错误。该错误仅出现在FULL_ASSERT模式下,并且与drools文件中定义的约束无关。

以下是错误:

014-07-02 14:51:49,037 [SwingWorker-pool-1-thread-4] TRACE         Move index (0), score (-4/-2450/-240/-170), accepted (false) for move (EMP4@START => EMP2).
       java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Score corruption: the workingScore (-3/-1890/-640/-170) is not the uncorruptedScore (-3/-1890/-640/-250) after completedAction (EMP3@EMP4 => EMP4):
      The corrupted scoreDirector has 1 ConstraintMatch(s) which are in excess (and should not be there):
        com.abcdl.be.solver/MinimizeTotalTime/level3/[org.drools.core.reteoo.InitialFactImpl@4dde85f0]=-170
      The corrupted scoreDirector has 1 ConstraintMatch(s) which are missing:
        com.abcdl.be.solver/MinimizeTotalTime/level3/[org.drools.core.reteoo.InitialFactImpl@4dde85f0]=-250
      Check your score constraints.

每次完成几个步骤之后,每次都会出现错误。

我正在开发一个软件来安排考虑时间和资源限制的几项任务。 整个过程由有向树图表示,使得图的节点表示任务和边,即任务之间的依赖关系。

为此,计划程序会更改每个节点的父节点,直到找到最佳解决方案。

节点是计划实体,其父节点是计划变量:

    @PlanningEntity(difficultyComparatorClass = NodeDifficultyComparator.class)
public class Node extends ProcessChain {

    private Node parent; // Planning variable: changes during planning, between score calculations.

    private String delay; // Used to display the delay for nodes of type "And" 

    private int id; // Used as an identifier for each node. Different nodes cannot have the same id

    public Node(String name, String type, int time, int resources, String md, int id)
    {
        super(name, "", time, resources, type, md); 
        this.id = id;
    }

    public Node()
    {
        super();
        this.delay = "";
    }

    public String getDelay() {
        return delay;
    }

    public void setDelay(String delay) {
        this.delay = delay;
    }


    @PlanningVariable(valueRangeProviderRefs = {"parentRange"}, strengthComparatorClass = ParentStrengthComparator.class, nullable = false) 
    public Node getParent() {
        return parent;
    }

    public void setParent(Node parent) {
        this.parent = parent;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    /*public String toString()
    {
        if(this.type.equals("AND"))
            return delay;
        if(!this.md.isEmpty())
            return Tools.excerpt(name+" : "+this.md);

         return Tools.excerpt(name);
    }*/


    public String toString()
    {
        if(parent!= null)
            return Tools.excerpt(name) +"@"+parent;
        else 
            return Tools.excerpt(name);
    }
    public boolean equals( Object o ) {
        if (this == o) {
            return true;
        } else if (o instanceof Node) {
            Node other = (Node) o;
            return new EqualsBuilder()
                    .append(name, other.name)
                    .append(id, other.id)
                    .isEquals();
        } else {
            return false;
        }
    }

    public int hashCode() {
        return new HashCodeBuilder()
                .append(name)
                .append(id)
                .toHashCode();
    }


     // ************************************************************************
    // Complex methods
    // ************************************************************************

     public int getStartTime()
     {
         try{
             return  Graph.getInstance().getNode2times().get(this).getFirst();
            }
         catch(NullPointerException e)
         {
             System.out.println("getStartTime() is null for " + this);
         }
         return 10;
     }

     public int getEndTime()
     {  try{
         return  Graph.getInstance().getNode2times().get(this).getSecond();
        }
     catch(NullPointerException e)
     {
         System.out.println("getEndTime() is null for " + this);
     }
     return 10;
     }

     @ValueRangeProvider(id = "parentRange")
     public Collection<Node> getPossibleParents()
     {  
         Collection<Node> nodes = new ArrayList<Node>(Graph.getInstance().getNodes());

         nodes.remove(this); // We remove this node from the list

         if(Graph.getInstance().getParentsCount(this) > 0) 
             nodes.remove(Graph.getInstance().getParents(this)); // We remove its parents from the list

         if(Graph.getInstance().getChildCount(this) > 0)
             nodes.remove(Graph.getInstance().getChildren(this)); // We remove its children from the list

         if(!nodes.contains(Graph.getInstance().getNt()))
             nodes.add(Graph.getInstance().getNt());

         return nodes;
     }

    /**
     * The normal methods {@link #equals(Object)} and {@link #hashCode()} cannot be used because the rule engine already
     * requires them (for performance in their original state).
     * @see #solutionHashCode()
     */
    public boolean solutionEquals(Object o) {
        if (this == o) {
            return true;
        } else if (o instanceof Node) {
            Node other = (Node) o;
            return new EqualsBuilder()
                    .append(name, other.name)
                    .append(id, other.id)
                    .isEquals();
        } else {
            return false;
        }
    }

    /**
     * The normal methods {@link #equals(Object)} and {@link #hashCode()} cannot be used because the rule engine already
     * requires them (for performance in their original state).
     * @see #solutionEquals(Object)
     */
    public int solutionHashCode() {
        return new HashCodeBuilder()
                .append(name)
                .append(id)
                .toHashCode();
    }


}

每次移动都必须通过删除前一个边缘并将新边缘从节点添加到其父级来更新图形,因此我使用自定义更改移动:

public class ParentChangeMove implements Move{

    private Node node;
    private Node parent;

    private Graph g  = Graph.getInstance();

    public ParentChangeMove(Node node, Node parent) {
        this.node = node;
        this.parent = parent;
    }

    public boolean isMoveDoable(ScoreDirector scoreDirector) {  
        List<Dependency> dep = new ArrayList<Dependency>(g.getDependencies());
        dep.add(new Dependency(parent.getName(), node.getName()));

        return !ObjectUtils.equals(node.getParent(), parent) && !g.detectCycles(dep) && !g.getParents(node).contains(parent);
    }

    public Move createUndoMove(ScoreDirector scoreDirector) {
        return new ParentChangeMove(node, node.getParent());
    }


    public void doMove(ScoreDirector scoreDirector) {

        scoreDirector.beforeVariableChanged(node, "parent"); // before changes are made

        //The previous edge is removed from the graph
        if(node.getParent() != null)
        {
            Dependency d = new Dependency(node.getParent().getName(), node.getName());
            g.removeEdge(g.getDep2link().get(d)); 
            g.getDependencies().remove(d);
            g.getDep2link().remove(d);
        }

        node.setParent(parent); // the move

        //The new edge is added on the graph (parent ==> node)
        Link link = new Link();
        Dependency d = new Dependency(parent.getName(), node.getName());
        g.addEdge(link, parent, node); 
        g.getDependencies().add(d);
        g.getDep2link().put(d, link);

        g.setStepTimes();

        scoreDirector.afterVariableChanged(node, "parent"); // after changes are made
    }


    public Collection<? extends Object> getPlanningEntities() {
        return Collections.singletonList(node);
    }


    public Collection<? extends Object> getPlanningValues() {
        return Collections.singletonList(parent);
    }

     public boolean equals(Object o) {
            if (this == o) {
                return true;
            } else if (o instanceof ParentChangeMove) {
                ParentChangeMove other = (ParentChangeMove) o;
                return new EqualsBuilder()
                        .append(node, other.node)
                        .append(parent, other.parent)
                        .isEquals();
            } else {
                return false;
            }
        }

        public int hashCode() {
            return new HashCodeBuilder()
                    .append(node)
                    .append(parent)
                    .toHashCode();
        }

        public String toString() {
            return node + " => " + parent;
        }

}

该图确实定义了约束用于计算每个解决方案得分的多种方法,如下所示:

    rule "MinimizeTotalTime" // Minimize the total process time
    when
        eval(true)
    then
        scoreHolder.addSoftConstraintMatch(kcontext, 1, -Graph.getInstance().totalTime());
end

在其他环境模式下,错误不会出现,但计算出的最佳分数不等于实际分数。

我不知道问题可能来自哪里。请注意,我已经检查了所有的equals和hashcode方法。


编辑:遵循ge0ffrey的提议,我在“MinimizeTotalTime”规则中使用了收集CE以检查错误是否再次出现:

rule "MinimizeTotalTime" // Minimize the total process time
    when
        ArrayList() from  collect(Node())
    then
        scoreHolder.addSoftConstraintMatch(kcontext, 0, -Graph.getInstance().totalTime());
end

此时,没有错误出现,一切似乎都没问题。但是当我使用“提前终止”时,我收到以下错误:

java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Score corruption: the solution's score (-9133) is not the uncorruptedScore (-9765).

此外,我有一个规则,不使用Graph类中的任何方法,似乎尊重增量分数计算,但返回另一个分数损坏错误。

规则的目的是确保我们不使用更多可用资源:

   rule "addMarks" //insert a Mark each time a task starts or ends

    when
        Node($startTime : getStartTime(), $endTime : getEndTime())

    then
        insertLogical(new Mark($startTime));
        insertLogical(new Mark($endTime));

end 

rule "resourcesLimit" // At any time, The number of resources used must not exceed the total number of resources available

    when
        Mark($startTime: time)
        Mark(time > $startTime, $endTime : time)
        not Mark(time > $startTime, time < $endTime)
        $total : Number(intValue > Global.getInstance().getAvailableResources() ) from  
             accumulate(Node(getEndTime() >=$endTime, getStartTime()<= $startTime, $res : resources), sum($res))
    then
            scoreHolder.addHardConstraintMatch(kcontext, 0, (Global.getInstance().getAvailableResources() - $total.intValue()) * ($endTime - $startTime) );             
end

以下是错误:

    java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Score corruption: the workingScore (-193595) is not the uncorruptedScore (-193574) after completedAction (DWL_CM_XX_101@DWL_PA_XX_180 => DWL_PA_XX_180):
  The corrupted scoreDirector has 4 ConstraintMatch(s) which are in excess (and should not be there):
    com.abcdl.be.solver/resourcesLimit/level0/[43.0, 2012, 1891]=-2783
    com.abcdl.be.solver/resourcesLimit/level0/[45.0, 1870, 1805]=-1625
    com.abcdl.be.solver/resourcesLimit/level0/[46.0, 1805, 1774]=-806
    com.abcdl.be.solver/resourcesLimit/level0/[45.0, 1774, 1762]=-300
  The corrupted scoreDirector has 3 ConstraintMatch(s) which are missing:
    com.abcdl.be.solver/resourcesLimit/level0/[43.0, 2012, 1901]=-2553
    com.abcdl.be.solver/resourcesLimit/level0/[45.0, 1870, 1762]=-2700
    com.abcdl.be.solver/resourcesLimit/level0/[44.0, 1901, 1891]=-240
  Check your score constraints.

1 个答案:

答案 0 :(得分:3)

LHS仅为&#34; eval(true)&#34;的分数规则本质上是破碎的。对于完全相同的权重,该约束总是被打破,并且没有理由对其进行评估。或者它有时会被破坏(或者总是被破坏但是对于不同的权重)然后规则需要相应地重新启动。

问题:随着计划变量更改值,Graph.getInstance().totalTime()的返回值会发生变化。但是Drools只是看着LHS,因为规划变量发生了变化,并且它发现LHS中没有任何变化,所以当计划变量发生变化时,不需要重新评估该分数规则。注意:这称为增量分数计算(参见文档),这是一个巨大的性能加速。

子问题:方法Graph.getInstance().totalTime()本质上不是增量的。

修复:根据totalTime()选项将Node函数转换为DRL函数。您可能需要使用accumulate。如果这太难了(因为它是关键路径的复杂计算左右),无论如何都要尝试(为了增量分数计算)或尝试执行{{1}的LHS }覆盖所有collect s(类似于Node,但每次都会被重新启动。