当问题与 .drl 'or' 子句相关时,是什么原因导致 ConstraintMatchTotal 无法添加 constraintMatch?
what causes ConstraintMatchTotal could not add constraintMatch, when issue is tied to a .drl 'or' clause?
在扩展 OptaPlanner 护士排班示例代码中的代码。是什么导致抛出 "constraintMatchTotal could not add constraintMatch"(非法状态?)错误,这与使用 'or' 子句解析 .drl 规则有关吗?它在将数据导入基于 .drl 的规则集时立即发生...但如果注释掉两个 'or' 子句中的任何一个,则不会出错。我相信,因为它们单独是可以接受的,所以系统应该在 'or' 设置中处理它们。
下面是规则,后面是错误,以及 'or' 子句中使用的域对象。我确认:
- 如果我注释掉 'or' 和上面的 BoundaryDate 子句,
程序加载并运行。
- 如果我注释掉 'or' 及其下方的 BoundaryDate 子句,程序将加载并运行。
- 如果我将两者都留在原地,则会立即抛出错误(低于规则)。
- 此外,如果我将此子句插入第二个 BoundaryDate 条件(在 'or' 之后),则程序加载并运行:
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);
}
}
如果规则中测试的同一个对象可以在 'or' 条件的多个部分匹配,则 Optaplanner 将抛出此 IllegalStateException,至少在 7.15.0 之前是这样。查看 optaplanner jira 1433 中探索的详细信息。
解决方法是始终将术语添加到 'or' 表达式的后面的术语,以确保匹配的对象不能与匹配 'or' 的前面部分的对象相同。对于上面的原始帖子,'preferredSequenceStart == true' 实现了此排除。
请注意,在 'or' 的条款中使用 'exists' 关键字可能会导致此解决方法出现问题;尽量避免在这种情况下使用 'exists'。
在扩展 OptaPlanner 护士排班示例代码中的代码。是什么导致抛出 "constraintMatchTotal could not add constraintMatch"(非法状态?)错误,这与使用 'or' 子句解析 .drl 规则有关吗?它在将数据导入基于 .drl 的规则集时立即发生...但如果注释掉两个 'or' 子句中的任何一个,则不会出错。我相信,因为它们单独是可以接受的,所以系统应该在 'or' 设置中处理它们。
下面是规则,后面是错误,以及 'or' 子句中使用的域对象。我确认:
- 如果我注释掉 'or' 和上面的 BoundaryDate 子句, 程序加载并运行。
- 如果我注释掉 'or' 及其下方的 BoundaryDate 子句,程序将加载并运行。
- 如果我将两者都留在原地,则会立即抛出错误(低于规则)。
- 此外,如果我将此子句插入第二个 BoundaryDate 条件(在 'or' 之后),则程序加载并运行:
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);
}
}
如果规则中测试的同一个对象可以在 'or' 条件的多个部分匹配,则 Optaplanner 将抛出此 IllegalStateException,至少在 7.15.0 之前是这样。查看 optaplanner jira 1433 中探索的详细信息。
解决方法是始终将术语添加到 'or' 表达式的后面的术语,以确保匹配的对象不能与匹配 'or' 的前面部分的对象相同。对于上面的原始帖子,'preferredSequenceStart == true' 实现了此排除。
请注意,在 'or' 的条款中使用 'exists' 关键字可能会导致此解决方法出现问题;尽量避免在这种情况下使用 'exists'。