我正在尝试向Optaplanner Nurse Roster Solution添加三个新的计划变量。这三个计划变量是shortTask,mainTask和fullTask,它们是ShiftTaskAllocation类:
public class ShiftTaskAllocation extends AbstractPersistable {
private Task task;
private Shift shift;
private ShiftAssignment shiftAssignment;
.... // getter and setter methods and other complex methods
}
这是Task类:
public class Task extends AbstractPersistable {
private String code;
private String description;
.... // getter and setter methods and other complex methods
}
以下是任务技能要求类,该类包含有关完成任务所需技能的信息:
public class TaskSkillRequirement extends AbstractPersistable {
private Task task;
private Skill skill;
.... // getter and setter methods and other complex methods
}
以下是技能水平课程,该课程包含员工具备的技能信息:
public class SkillProficiency extends AbstractPersistable {
private Employee employee;
private List<Skill> skillList;
.... // getter and setter methods and other complex methods
}
这个想法是针对每个班次,我们可以设置一些需要在特定班次中完成的任务。 这是我的计划实体类已经修改以符合此要求:
public class ShiftAssignment extends AbstractPersistable {
private Shift shift;
private int indexInShift;
// Planning variables: changes during planning, between score calculations.
private Employee employee;
private ShiftTaskAllocation shortTask;
private ShiftTaskAllocation mainTask;
private ShiftTaskAllocation fullTask;
.... // getter and setter methods and other complex methods
@PlanningVariable(valueRangeProviderRefs = {"shiftTaskAllocationRange"}, nullable = true, strengthComparatorClass = ShiftTaskAllocationStrengthComparator.class)
public ShiftTaskAllocation getShortTask() {
return shortTask;
}
@PlanningVariable(valueRangeProviderRefs = {"shiftTaskAllocationRange"}, nullable = true, strengthComparatorClass = ShiftTaskAllocationStrengthComparator.class)
public ShiftTaskAllocation getMainTask() {
return mainTask;
}
@PlanningVariable(valueRangeProviderRefs = {"shiftTaskAllocationRange"}, nullable = true, strengthComparatorClass = ShiftTaskAllocationStrengthComparator.class)
public ShiftTaskAllocation getFullTask() {
return fullTask;
}
public int getSumOfTasks() {
int sum = 0;
if (shortTask != null) {
sum += shortTask.getTask().getType().getValue();
}
if (mainTask != null) {
sum += mainTask.getTask().getType().getValue();
}
if (fullTask != null) {
sum += fullTask.getTask().getType().getValue();
}
return sum;
}
}
这是TaskType枚举:
public enum TaskType {
SHORT(1), MAIN(2), FULL(3);
private final int value;
private TaskType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
在一个班次中,员工应该只处理一个简短的任务+主要任务,或者只处理一个完整的任务。 我需要制定哪些规则才能确保optaplanner能够正确地将任务和员工分配给班次? 任何帮助或评论将不胜感激。 感谢致敬。
**
**
我试着写一些规则,指导optaplanner找到可行的解决方案。我认为我需要记下的规则是:
以下是我试图自行写下的规则:
############################################################################
// Hard constraints
############################################################################
// a nurse can only work one shift per day, i.e. no two shift can be assigned to the same nurse on a day.
rule "oneShiftPerDay"
when
$s1 : ShiftAssignment(employee != null,
$leftId : id, $employee : employee, $shiftDate : shiftDate)
$s2 : ShiftAssignment(employee == $employee, shiftDate == $shiftDate, id > $leftId)
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
// a short shift task can only assigned to one shift only.
rule "oneShortShiftTaskPerShiftAssignment"
when
$s1 : ShiftAssignment(shortTask != null,
$leftId : id, $shortTask : shortTask)
$s2 : ShiftAssignment(shortTask == $shortTask, id != $leftId)
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
// a main shift task can only assigned to one shift only.
rule "oneMainShiftTaskPerShiftAssignment"
when
$s1 : ShiftAssignment(mainTask != null,
$leftId : id, $mainTask : mainTask)
$s2 : ShiftAssignment(mainTask == $mainTask, id != $leftId)
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
// a full shift task can only assigned to one shift only.
rule "oneFullShiftTaskPerShiftAssignment"
when
$s1 : ShiftAssignment(fullTask != null,
$leftId : id, $fullTask : fullTask)
$s2 : ShiftAssignment(fullTask == $fullTask, id != $leftId)
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
// a short task can only assigned to one shift assignment
rule "allShiftTaskAllocationMustBeAssigned"
when
ShiftTaskAllocation(shiftAssignment == null)
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
// a short shift task assignment must assigned to appropriate shift.
rule "shortShiftTaskAssignmentMustAssignedToAppropriateShiftAssignment"
when
ShiftAssignment(shortTask != null, shortTask.shift != shift)
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
// a main shift task assignment must assigned to appropriate shift.
rule "mainShiftTaskAssignmentMustAssignedToAppropriateShiftAssignment"
when
ShiftAssignment(mainTask != null, mainTask.shift != shift)
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
// a full shift task assignment must assigned to appropriate shift.
rule "fullShiftTaskAssignmentMustAssignedToAppropriateShiftAssignment"
when
ShiftAssignment(fullTask != null, fullTask.shift != shift)
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
// sum of tasks on a shift is lower than 3.
rule "sumTasksOfShiftLowerThan3"
when
$s1 : ShiftAssignment(sumOfTasks < 3, $sumOfTasks : sumOfTasks)
then
scoreHolder.addHardConstraintMatch(kcontext, $sumOfTasks - 3);
end
// sum of tasks on a shift is higher than 3.
rule "sumTasksOfShiftHigherThan3"
when
$s1 : ShiftAssignment(sumOfTasks > 3, $sumOfTasks : sumOfTasks)
then
scoreHolder.addHardConstraintMatch(kcontext, 3- $sumOfTasks);
end
// a nurse can't be assigned to short and main task that he/she didn't has the skill to do it
rule "noUnmatchedSkillEmployeeAssignmentToShortMainTask"
when
ShiftAssignment( employee != null,
$employee : employee, shortTask != null, mainTask != null, fullTask == null,
$shortTask : shortTask, $mainTask : mainTask )
not ( forall ( TaskSkillRequirement( $shortTask.task == task, $skill : skill ),
SkillProficiency( employee == $employee, skillList contains $skill )
)
)
not ( forall ( TaskSkillRequirement( $mainTask.task == task, $skill : skill ),
SkillProficiency( employee == $employee, skillList contains $skill )
)
)
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
// a nurse can't be assigned to full task that he/she didn't has the skill to do it
rule "noUnmatchedSkillEmployeeAssignmentToFullTask"
when
ShiftAssignment( employee != null,
$employee : employee, shortTask == null, mainTask == null, fullTask != null,
$fullTask : fullTask )
not ( forall ( TaskSkillRequirement( $fullTask.task == task, $skill : skill ),
SkillProficiency( employee == $employee, skillList contains $skill )
)
)
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
// only task with type SHORT that can be assigned to shortTask
rule "noUmatchedShortTaskAssignment"
when
ShiftAssignment(shortTask != null, shortTask.task.type.value != 1, $shortValue : shortTask.task.type.value,
shortTask.shift != shift)
then
scoreHolder.addHardConstraintMatch(kcontext, $shortValue * -1);
end
// only task with type MAIN that can be assigned to mainTask
rule "noUmatchedMainTaskAssignment"
when
ShiftAssignment(mainTask != null, mainTask.task.type.value != 2, $mainValue : mainTask.task.type.value,
mainTask.shift != shift)
then
scoreHolder.addHardConstraintMatch(kcontext, $mainValue * -1);
end
// only task with type FULLL that can be assigned to fullTask
rule "noUmatchedFullTaskAssignment"
when
ShiftAssignment(fullTask != null, fullTask.task.type.value != 3, $fullValue : fullTask.task.type.value,
fullTask.shift != shift)
then
scoreHolder.addHardConstraintMatch(kcontext, $fullValue * -1);
end
但是当我尝试使用简单的样本数据集运行求解阶段时(求解阶段运行2分钟),它只产生一个不可行的解决方案:
2017-04-14 05:52:11,276 [main] INFO KieModule was added: MemoryKieModule[releaseId=org.default:artifact:1.0.0-SNAPSHOT]
2017-04-14 05:52:12,300 [main] DEBUG Starting Engine in PHREAK mode
2017-04-14 05:52:12,923 [main] INFO Solving started: time spent (273), best score (uninitialized/-30hard/-75soft), environment mode (REPRODUCIBLE), random (JDK with seed 0).
2017-04-14 05:52:24,265 [main] DEBUG CH step (0), time spent (11616), score (-28hard/-74soft), selected move count (49130), picked move ([2017-01-01(Sun)_MORN@null => DAI(Daisy), 2017-01-01(Sun)_MORN@null => ShiftTaskAllocation-63, 2017-01-01(Sun)_MORN@null => null, 2017-01-01(Sun)_MORN@null => null]).
2017-04-14 05:52:32,375 [main] DEBUG CH step (1), time spent (19726), score (-26hard/-73soft), selected move count (49130), picked move ([2017-01-01(Sun)_MORN@null => HAN(Hanks), 2017-01-01(Sun)_MORN@null => ShiftTaskAllocation-73, 2017-01-01(Sun)_MORN@null => null, 2017-01-01(Sun)_MORN@null => null]).
2017-04-14 05:52:39,478 [main] DEBUG CH step (2), time spent (26829), score (-24hard/-73soft), selected move count (49130), picked move ([2017-01-01(Sun)_MORN@null => ANT(Anton), 2017-01-01(Sun)_MORN@null => ShiftTaskAllocation-68, 2017-01-01(Sun)_MORN@null => null, 2017-01-01(Sun)_MORN@null => null]).
2017-04-14 05:52:45,838 [main] DEBUG CH step (3), time spent (33189), score (-22hard/-73soft), selected move count (49130), picked move ([2017-01-01(Sun)_AFNO@null => CHR(Christine), 2017-01-01(Sun)_AFNO@null => ShiftTaskAllocation-74, 2017-01-01(Sun)_AFNO@null => null, 2017-01-01(Sun)_AFNO@null => null]).
2017-04-14 05:52:51,926 [main] DEBUG CH step (4), time spent (39277), score (-21hard/-72soft), selected move count (49130), picked move ([2017-01-01(Sun)_AFNO@null => FRA(Frans), 2017-01-01(Sun)_AFNO@null => null, 2017-01-01(Sun)_AFNO@null => ShiftTaskAllocation-64, 2017-01-01(Sun)_AFNO@null => null]).
2017-04-14 05:52:57,902 [main] DEBUG CH step (5), time spent (45253), score (-20hard/-71soft), selected move count (49130), picked move ([2017-01-01(Sun)_AFNO@null => IRE(Irene), 2017-01-01(Sun)_AFNO@null => null, 2017-01-01(Sun)_AFNO@null => ShiftTaskAllocation-65, 2017-01-01(Sun)_AFNO@null => null]).
2017-04-14 05:53:03,956 [main] DEBUG CH step (6), time spent (51307), score (-19hard/-70soft), selected move count (49130), picked move ([2017-01-01(Sun)_AFNO@null => JUS(Justin), 2017-01-01(Sun)_AFNO@null => null, 2017-01-01(Sun)_AFNO@null => ShiftTaskAllocation-69, 2017-01-01(Sun)_AFNO@null => null]).
2017-04-14 05:53:10,393 [main] DEBUG CH step (7), time spent (57744), score (-18hard/-70soft), selected move count (49130), picked move ([2017-01-01(Sun)_NIGH@null => BRA(Brandon), 2017-01-01(Sun)_NIGH@null => null, 2017-01-01(Sun)_NIGH@null => ShiftTaskAllocation-70, 2017-01-01(Sun)_NIGH@null => null]).
2017-04-14 05:53:15,975 [main] DEBUG CH step (8), time spent (63326), score (-17hard/-70soft), selected move count (49130), picked move ([2017-01-01(Sun)_NIGH@null => EMI(Emily), 2017-01-01(Sun)_NIGH@null => null, 2017-01-01(Sun)_NIGH@null => ShiftTaskAllocation-75, 2017-01-01(Sun)_NIGH@null => null]).
2017-04-14 05:53:21,517 [main] DEBUG CH step (9), time spent (68867), score (-16hard/-70soft), selected move count (49130), picked move ([2017-01-01(Sun)_NIGH@null => GEO(George), 2017-01-01(Sun)_NIGH@null => null, 2017-01-01(Sun)_NIGH@null => ShiftTaskAllocation-76, 2017-01-01(Sun)_NIGH@null => null]).
2017-01-01-MORN : DAI(Daisy) : Blood Test(63)
2017-01-01-MORN : HAN(Hanks) : Blood Test(73)
2017-01-01-MORN : ANT(Anton) : Urine Test(68)
2017-01-01-AFNO : CHR(Christine) : Urine Test(74)
2017-01-01-AFNO : FRA(Frans) : Clean Patient(64)
2017-01-01-AFNO : IRE(Irene) : Clean Patient(65)
2017-01-01-AFNO : JUS(Justin) : Clean Patient(69)
2017-01-01-NIGH : BRA(Brandon) : Clean Patient(70)
2017-01-01-NIGH : EMI(Emily) : Clean Patient(75)
2017-01-01-NIGH : GEO(George) : Clean Patient(76)
2017-04-14 05:53:21,519 [main] INFO Construction Heuristic phase (0) ended: step total (10), time spent (68870), best score (-16hard/-70soft).
2017-04-14 05:53:22,327 [main] DEBUG LS step (0), time spent (69678), score (-16hard/-70soft), best score (-16hard/-70soft), accepted/selected move count (800/800), picked move (2017-01-01(Sun)_MORN@DAI(Daisy) <=> 2017-01-01(Sun)_MORN@ANT(Anton)).
2017-04-14 05:53:22,783 [main] DEBUG LS step (1), time spent (70134), score (-16hard/-70soft), best score (-16hard/-70soft), accepted/selected move count (800/993), picked move (2017-01-01(Sun)_AFNO@IRE(Irene) => ShiftTaskAllocation-71).
2017-04-14 05:53:23,176 [main] DEBUG LS step (2), time spent (70527), score (-16hard/-70soft), best score (-16hard/-70soft), accepted/selected move count (800/1152), picked move ([[2017-01-01(Sun)_AFNO@FRA(Frans)] => JUS(Justin), [2017-01-01(Sun)_AFNO@JUS(Justin)] => FRA(Frans)]).
2017-04-14 05:53:23,522 [main] DEBUG LS step (3), time spent (70873), score (-16hard/-70soft), best score (-16hard/-70soft), accepted/selected move count (800/1195), picked move (2017-01-01(Sun)_AFNO@JUS(Justin) => ShiftTaskAllocation-66).
2017-04-14 05:53:23,886 [main] DEBUG LS step (4), time spent (71237), score (-16hard/-70soft), best score (-16hard/-70soft), accepted/selected move count (800/1452), picked move ([[2017-01-01(Sun)_NIGH@BRA(Brandon)] => IRE(Irene), [2017-01-01(Sun)_AFNO@IRE(Irene)] => BRA(Brandon)]).
2017-04-14 05:53:24,326 [main] DEBUG LS step (5), time spent (71677), score (-16hard/-70soft), best score (-16hard/-70soft), accepted/selected move count (800/1508), picked move (2017-01-01(Sun)_AFNO@FRA(Frans) <=> 2017-01-01(Sun)_NIGH@GEO(George)).
..........
2017-04-14 05:54:08,226 [main] DEBUG LS step (69), time spent (115577), score (-16hard/-70soft), best score (-16hard/-70soft), accepted/selected move count (800/5125), picked move (2017-01-01(Sun)_MORN@DAI(Daisy) <=> 2017-01-01(Sun)_AFNO@EMI(Emily)).
2017-04-14 05:54:10,613 [main] DEBUG LS step (70), time spent (117964), score (-16hard/-70soft), best score (-16hard/-70soft), accepted/selected move count (800/13436), picked move ([[2017-01-01(Sun)_AFNO@CHR(Christine)] => IRE(Irene), [2017-01-01(Sun)_NIGH@IRE(Irene)] => CHR(Christine)]).
2017-04-14 05:54:12,649 [main] DEBUG LS step (71), time spent (120000), score (-16hard/-70soft), best score (-16hard/-70soft), accepted/selected move count (591/10978), picked move ([[2017-01-01(Sun)_AFNO@DAI(Daisy)] => EMI(Emily), [2017-01-01(Sun)_MORN@EMI(Emily)] => DAI(Daisy)]).
2017-04-14 05:54:12,649 [main] INFO Local Search phase (1) ended: step total (72), time spent (120000), best score (-16hard/-70soft).
2017-04-14 05:54:12,649 [main] INFO Solving ended: time spent (120000), best score (-16hard/-70soft), average calculate count per second (6544), environment mode (REPRODUCIBLE).
匹配约束:
mainShiftTaskAssignmentMustAssignedToAppropriateShiftAssignment=-6
org.optaplanner.webexamples.nurserostering.rest.solver/mainShiftTaskAssignmentMustAssignedToAppropriateShiftAssignment/level0/[2017-01-01(Sun)_NIGH@GEO(George)]=-1
org.optaplanner.webexamples.nurserostering.rest.solver/mainShiftTaskAssignmentMustAssignedToAppropriateShiftAssignment/level0/[2017-01-01(Sun)_NIGH@EMI(Emily)]=-1
org.optaplanner.webexamples.nurserostering.rest.solver/mainShiftTaskAssignmentMustAssignedToAppropriateShiftAssignment/level0/[2017-01-01(Sun)_NIGH@BRA(Brandon)]=-1
org.optaplanner.webexamples.nurserostering.rest.solver/mainShiftTaskAssignmentMustAssignedToAppropriateShiftAssignment/level0/[2017-01-01(Sun)_AFNO@JUS(Justin)]=-1
org.optaplanner.webexamples.nurserostering.rest.solver/mainShiftTaskAssignmentMustAssignedToAppropriateShiftAssignment/level0/[2017-01-01(Sun)_AFNO@IRE(Irene)]=-1
org.optaplanner.webexamples.nurserostering.rest.solver/mainShiftTaskAssignmentMustAssignedToAppropriateShiftAssignment/level0/[2017-01-01(Sun)_AFNO@FRA(Frans)]=-1
fullShiftTaskAssignmentMustAssignedToAppropriateShiftAssignment=-4
org.optaplanner.webexamples.nurserostering.rest.solver/fullShiftTaskAssignmentMustAssignedToAppropriateShiftAssignment/level0/[2017-01-01(Sun)_AFNO@CHR(Christine)]=-1
org.optaplanner.webexamples.nurserostering.rest.solver/fullShiftTaskAssignmentMustAssignedToAppropriateShiftAssignment/level0/[2017-01-01(Sun)_MORN@ANT(Anton)]=-1
org.optaplanner.webexamples.nurserostering.rest.solver/fullShiftTaskAssignmentMustAssignedToAppropriateShiftAssignment/level0/[2017-01-01(Sun)_MORN@HAN(Hanks)]=-1
org.optaplanner.webexamples.nurserostering.rest.solver/fullShiftTaskAssignmentMustAssignedToAppropriateShiftAssignment/level0/[2017-01-01(Sun)_MORN@DAI(Daisy)]=-1
sumTasksOfShiftLowerThan3=-6
org.optaplanner.webexamples.nurserostering.rest.solver/sumTasksOfShiftLowerThan3/level0/[2017-01-01(Sun)_NIGH@GEO(George)]=-1
org.optaplanner.webexamples.nurserostering.rest.solver/sumTasksOfShiftLowerThan3/level0/[2017-01-01(Sun)_NIGH@EMI(Emily)]=-1
org.optaplanner.webexamples.nurserostering.rest.solver/sumTasksOfShiftLowerThan3/level0/[2017-01-01(Sun)_NIGH@BRA(Brandon)]=-1
org.optaplanner.webexamples.nurserostering.rest.solver/sumTasksOfShiftLowerThan3/level0/[2017-01-01(Sun)_AFNO@JUS(Justin)]=-1
org.optaplanner.webexamples.nurserostering.rest.solver/sumTasksOfShiftLowerThan3/level0/[2017-01-01(Sun)_AFNO@IRE(Irene)]=-1
org.optaplanner.webexamples.nurserostering.rest.solver/sumTasksOfShiftLowerThan3/level0/[2017-01-01(Sun)_AFNO@FRA(Frans)]=-1
我现在很困惑,为什么optaplanner会产生不可行的解决方案,无论我花多长时间,它仍然会产生不可行的解决方案。有谁知道,这个问题的根本原因是什么?谢谢和问候。