什么原因导致ConstraintMatchTotal无法添加constraintMatch,当问题绑定到.drl'或'子句?

问题描述 投票:0回答:1

在扩展OptaPlanner护士排班示例代码中的代码。是什么导致“constraintMatchTotal无法添加constraintMatch”(非法状态?)错误被抛出,这与解析带有'或'子句的.drl规则有关吗?它是在将数据导入基于.drl的规则集时立即发生的......但如果两个'或'子句中的任何一个被注释掉,则不会出错。我相信,因为它们可以单独接受,系统应该在'或'设置中处理它们。

规则如下,后跟错误,以及'或'子句中使用的域对象。我确认:

  • 如果我注释掉'or'和它上面的BoundaryDate子句,程序就会加载并运行。
  • 如果我注释掉它下面的'或'和BoundaryDate子句,程序就会加载并运行。
  • 如果我将两者都留在原位,则会立即抛出错误(在规则下面)。
  • 另外,如果我将此子句插入第二个BoundaryDate条件(在'或'之后),则程序加载并运行:preferredSequenceStart == true,

.drl规则:

rule "Highlight irregular shifts"
when
    EmployeeWorkSameShiftTypeSequence(
        employee != null,        
        $firstDayIndex : firstDayIndex,
        $lastDayIndex : lastDayIndex,
        $employee : employee,
        $dayLength : dayLength)

    (
    BoundaryDate(
        dayIndex == $firstDayIndex,
        preferredSequenceStart == false     // does not start on a boundary start date
        )
    or                                      // or
    BoundaryDate(
        dayIndex == $firstDayIndex,
        $dayLength != preferredCoveringLength   // is incorrect length for exactly one block
        )
    )

    StaffRosterParametrization($lastDayIndex >= planningWindowStartDayIndex)    // ignore if assignment is in (fixed) prior data            

    // non-functional identification drives desired indictment display on ShiftAssignment planning objects
    ShiftAssignment(employee == $employee, shiftDateDayIndex >= $firstDayIndex, shiftDateDayIndex <= $lastDayIndex)

then
    scoreHolder.addSoftConstraintMatch(kcontext, -1);
end

Exception executing consequence for rule "Highlight irregular shifts" in westgranite.staffrostering.solver: java.lang.IllegalStateException: The constraintMatchTotal (westgranite.staffrostering.solver/Highlight irregular shifts=0hard/-274soft) could not add constraintMatch (westgranite.staffrostering.solver/Highlight irregular shifts/[2020-01-02/D/6, 2018-12-25 - 2020-01-06, 2020-01-02, ... [继续列出约束匹配]

BoundaryData.java位于下方,因此从规则调用的方法是可见的:

package westgranite.staffrostering.domain;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import com.thoughtworks.xstream.annotations.XStreamAlias;

import westgranite.common.domain.AbstractPersistable;

@XStreamAlias("BoundaryDate")
public class BoundaryDate extends AbstractPersistable {
    /**
     * 
     */
    private static final long serialVersionUID = -7393276689810490427L;

    private static final DateTimeFormatter LABEL_FORMATTER = DateTimeFormatter.ofPattern("E d MMM");

    private int dayIndex; 
    private LocalDate date;

    private boolean preferredSequenceStart; // true means "this date is a preferred start to assignment sequences"
    private boolean preferredSequenceEnd;   // true means "this date is a preferred end for assignment sequences"
    private int nextPreferredStartDayIndex; // MAX_VALUE means "none"; if preferredSequenceStart is true, then this ref is still to the FUTURE next pref start date
    private int prevPreferredStartDayIndex; // MIN_VALUE means "none"; if preferredSequenceStart is true, then this ref is still to the PREVIOUS next pref start date

    // magic value that is beyond reasonable dayIndex range and still allows delta of indices to be an Integer
    public static final int noNextPreferredDayIndex = Integer.MAX_VALUE/3;
    public static final int noPrevPreferredDayIndex = Integer.MIN_VALUE/3;

    public int getDayIndex() {
        return dayIndex;
    }

    public void setDayIndex(int dayIndex) {
        this.dayIndex = dayIndex;
    }

    public LocalDate getDate() {
        return date;
    }

    public void setDate(LocalDate date) {
        this.date = date;
    }

    public boolean isPreferredSequenceStart() {
        return preferredSequenceStart;
    }

    public void setPreferredSequenceStart(boolean preferredSequenceStart) {
        this.preferredSequenceStart = preferredSequenceStart;
    }

    public boolean isPreferredSequenceEnd() {
        return preferredSequenceEnd;
    }

    public void setPreferredSequenceEnd(boolean preferredSequenceEnd) {
        this.preferredSequenceEnd = preferredSequenceEnd;
    }

    public int getNextPreferredStartDayIndex() {
        return nextPreferredStartDayIndex;
    }

    public void setNextPreferredStartDayIndex(int nextPreferredStartDayIndex) {
        this.nextPreferredStartDayIndex = nextPreferredStartDayIndex;
    }

    public int getPrevPreferredStartDayIndex() {
        return prevPreferredStartDayIndex;
    }

    public void setPrevPreferredStartDayIndex(int prevPreferredStartDayIndex) {
        this.prevPreferredStartDayIndex = prevPreferredStartDayIndex;
    }

    // ===================== COMPLEX METHODS ===============================
    public int getCurrOrPrevPreferredStartDayIndex() {
        return (isPreferredSequenceStart() ? dayIndex : prevPreferredStartDayIndex);
    }

    public int getCurrOrNextPreferredStartDayIndex() {
        return (isPreferredSequenceStart() ? dayIndex : nextPreferredStartDayIndex);
    }

    public int getCurrOrPrevPreferredEndDayIndex() {
        return (isPreferredSequenceEnd() ? dayIndex : (isPreferredSequenceStart() ? dayIndex-1 : prevPreferredStartDayIndex-1));
    }

    public int getCurrOrNextPreferredEndDayIndex() {
        return (isPreferredSequenceEnd() ? dayIndex : nextPreferredStartDayIndex-1);
    }

    public boolean isNoNextPreferred() {
        return getNextPreferredStartDayIndex() == noNextPreferredDayIndex;
    }

    public boolean isNoPrevPreferred() {
        return getPrevPreferredStartDayIndex() == noPrevPreferredDayIndex;
    }

    /**
     * @return if this is a preferred start date, then the sequence length that will fill from this date through the next end date; otherwise the days filling the past preferred start date through next end date
     */
    public int getPreferredCoveringLength() {
        if (isPreferredSequenceStart()) {
            return nextPreferredStartDayIndex - dayIndex;
        }
        return nextPreferredStartDayIndex - prevPreferredStartDayIndex;
    }

    /**
     * @return if this is a preferred start boundary, then "today", else day of most recent start boundary
     */
    public DayOfWeek getPreferredStartDayOfWeek() {
        if (isPreferredSequenceStart()) {
            return getDayOfWeek();
        }
        if (isNoPrevPreferred()) {
            throw new IllegalStateException("No prev preferred day of week available for " + toString());
        }
        return date.minusDays(dayIndex - getPrevPreferredStartDayIndex()).getDayOfWeek();
    }

    public DayOfWeek getPreferredEndDayOfWeek() {
        if (isPreferredSequenceEnd()) {
            return getDayOfWeek();
        }
        if (isNoNextPreferred()) {
            throw new IllegalStateException("No next preferred day of week available for " + toString());
        }
        return date.plusDays((getNextPreferredStartDayIndex()-1) - dayIndex).getDayOfWeek();
    }

    public DayOfWeek getDayOfWeek() {
        return date.getDayOfWeek();
    }

    public int getMostRecentDayIndexOf(DayOfWeek targetDayOfWeek) {
        return dayIndex - getBackwardDaysToReach(targetDayOfWeek);
    }

    public int getUpcomingDayIndexOf(DayOfWeek targetDayOfWeek) {
        return dayIndex + getForwardDaysToReach(targetDayOfWeek);
    }

    public LocalDate getMostRecentDateOf(DayOfWeek targetDayOfWeek) {
        return date.minusDays(getBackwardDaysToReach(targetDayOfWeek));
    }

    public LocalDate getUpcomingDateOf(DayOfWeek targetDayOfWeek) {
        return date.plusDays(getForwardDaysToReach(targetDayOfWeek));
    }

    public int getForwardDaysToReach(DayOfWeek targetDayOfWeek) {
        return getForwardDaysToReach(this.getDayOfWeek(), targetDayOfWeek);
    }

    public static int getForwardDaysToReach(DayOfWeek startDayOfWeek, DayOfWeek targetDayOfWeek) {
        if (startDayOfWeek == targetDayOfWeek) {
            return 0;
        }
        int forwardDayCount = 1;
        while (startDayOfWeek.plus(forwardDayCount) != targetDayOfWeek) {
            forwardDayCount++;

            if (forwardDayCount > 10) {
                throw new IllegalStateException("counting forward in days from " + startDayOfWeek + " never found target day of week: " + targetDayOfWeek);
            }
        }
        return forwardDayCount;
    }

    public int getBackwardDaysToReach(DayOfWeek targetDayOfWeek) {
        return getBackwardDaysToReach(this.getDayOfWeek(), targetDayOfWeek);
    }

    public static int getBackwardDaysToReach(DayOfWeek startDayOfWeek, DayOfWeek targetDayOfWeek) {
        if (startDayOfWeek == targetDayOfWeek) {
            return 0;
        }
        int backwardDayCount = 1;
        while (startDayOfWeek.minus(backwardDayCount) != targetDayOfWeek) {
            backwardDayCount++;

            if (backwardDayCount > 10) {
                throw new IllegalStateException("counting backward in days from " + startDayOfWeek + " never found target day of week: " + targetDayOfWeek);
            }
        }
        return backwardDayCount;
    }

    public String getLabel() {
        return date.format(LABEL_FORMATTER);
    }

    @Override
    public String toString() {
        return date.format(DateTimeFormatter.ISO_DATE);
    }

}
drools optaplanner drools-planner
1个回答
0
投票

如果在规则中测试的同一对象可以在'或'条件的多个部分匹配,则Optaplanner抛出此IllegalStateException,至少通过7.15.0。查看optaplanner jira 1433中探索的详细信息。

解决方法是始终将术语添加到“或”表达式的后续术语中,以确保匹配对象不能与匹配“或”的先前部分的匹配对象相同。对于上面的原始帖子,'preferredSequenceStart == true'实现了此排除。

请注意,在'或'的条款中使用'exists'关键字可能会导致此解决方法出现问题;尽量避免在这种情况下使用“存在”。

© www.soinside.com 2019 - 2024. All rights reserved.