[英]Is it possible to verify already existing solution by OptaPlanner to check how may rules get broken?
[英]How is it possible to spread exams of students in optaplanner?
我正在學習使用 optaplanner。 我需要傳播一個學生的考試。 一個學生的兩次考試之間的時間越短,我給予的懲罰就越多。
我需要我的 Student 類的整數列表 ExamIds,因為有那個學生的所有考試。
然后我需要檢查所有這些考試計划的時間段,給他們更多的時間。
我嘗試的是以下代碼:
`
Constraint spaceBetweenExams(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Student.class)
.join(Exam.class)
.join(Exam.class)
.filter((student, exam1, exam2) ->{
if(student.getExamIds().contains(exam1.getID()) && student.getExamIds().contains(exam2.getID())){
if(exam1.getID() < exam2.getID()){
int timeDifference = Math.abs(exam1.getTimeslot().getID() - exam2.getTimeslot().getID());
if (timeDifference == 1) {
penalty = 16;
} else if (timeDifference == 2) {
penalty = 8;
} else if (timeDifference == 3) {
penalty = 4;
} else if (timeDifference == 4) {
penalty = 2;
} else if (timeDifference == 5) {
penalty = 1;
}
return true;
}
}
return false;
})
.penalize("Max time between exams", HardSoftScore.ofSoft(penalty));
`
我得到的結果是 24645 個軟懲罰,但 optaplanner 甚至沒有嘗試修復它們。 我認為我在上面的代碼中檢查考試的方式並不完全正確。
這是我的約束類:
public class ExamTableConstraintProvider implements ConstraintProvider {
int penalty = 0;
List<Integer> studentExamIds;
@Override
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[]{
// Hard constraints
twoExamForStudentConflict(constraintFactory),
// Soft constraints
spaceBetweenExams(constraintFactory)
};
}
private Constraint twoExamForStudentConflict(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Exam.class)
.join(Exam.class,
Joiners.equal(Exam::getTimeslot),
Joiners.lessThan(Exam::getID))
.filter((exam1, exam2) -> {
List<Integer> result = new ArrayList<>(exam1.getSID());
result.retainAll(exam2.getSID());
return result.size() > 0;
})
.penalize("Student conflict", HardSoftScore.ONE_HARD);
}
Constraint spaceBetweenExams(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Student.class)
.join(Exam.class)
.join(Exam.class)
.filter((student, exam1, exam2) ->{
if(student.getExamIds().contains(exam1.getID()) && student.getExamIds().contains(exam2.getID())){
if(exam1.getID() < exam2.getID()){
int timeDifference = Math.abs(exam1.getTimeslot().getID() - exam2.getTimeslot().getID());
if (timeDifference == 1) {
penalty = 16;
} else if (timeDifference == 2) {
penalty = 8;
} else if (timeDifference == 3) {
penalty = 4;
} else if (timeDifference == 4) {
penalty = 2;
} else if (timeDifference == 5) {
penalty = 1;
}
return true;
}
}
return false;
})
.penalize("Max time between exams", HardSoftScore.ofSoft(penalty));
}
/*Constraint spaceBetweenExams(ConstraintFactory constraintFactory) {
penalty = 0;
return constraintFactory.forEach(Student.class)
.join(Exam.class,
equal(Student::getExamIds, Exam::getID),
filtering((student, exam1) -> exam1.getTimeslot() != null))
.join(Exam.class,
equal((student, exam1) -> student.getExamIds(), Exam::getID),
equal((student, exam1) -> exam1.getTimeslot(), Exam::getTimeslot),
filtering((student, exam1, exam2) -> {
int timeDifference = getPeriodBetweenExams(exam1, exam2);
if (timeDifference == 1) {
penalty += 16;
} else if (timeDifference == 2) {
penalty += 8;
} else if (timeDifference == 3) {
penalty += 4;
} else if (timeDifference == 4) {
penalty += 2;
} else if (timeDifference == 5) {
penalty += 1;
}
if(penalty == 0){
return false;
}
return true;
}))
.penalize("Max time between exams", HardSoftScore.ONE_SOFT);
}*/
}
這是我啟動程序的課程:
public class ExamTableApp {
private static final Logger LOGGER = LoggerFactory.getLogger(ExamTableApp.class);
public static void main(String[] args) {
SolverFactory<ExamTable> solverFactory = SolverFactory.create(new SolverConfig()
.withSolutionClass(ExamTable.class)
.withEntityClasses(Exam.class)
.withConstraintProviderClass(ExamTableConstraintProvider.class)
// The solver runs only for 5 seconds on this small dataset.
// It's recommended to run for at least 5 minutes ("5m") otherwise.
.withTerminationSpentLimit(Duration.ofSeconds(30)));
// Load the problem
ExamTable problem = getData();
// Solve the problem
Solver<ExamTable> solver = solverFactory.buildSolver();
ExamTable solution = solver.solve(problem);
// Visualize the solution
printTimetable(solution);
}
public static ExamTable getData(){
DataReader parser = new DataReader("benchmarks/sta-f-83.crs", "benchmarks/sta-f-83.stu");
List<Room> roomList = new ArrayList<>(1);
roomList.add(new Room(1,"Room A"));
// roomList.add(new Room(2,"Room B"));
// roomList.add(new Room(3,"Room C"));
List<Exam> examList = new ArrayList<>();
HashMap<Integer, Exam> exams = parser.getExams();
Set<Integer> keys = exams.keySet();
for (Integer i : keys) {
Exam exam = exams.get(i);
examList.add(new Exam(exam.getID(), exam.getSID()));
}
List<Student> studentList = new ArrayList<>();
HashMap<Integer, Student> students = parser.getStudents();
Set<Integer> keys2 = students.keySet();
for (Integer i : keys2) {
Student student = students.get(i);
studentList.add(new Student(student.getID(), student.getExamIds()));
}
return new ExamTable(parser.getTimeslots(), roomList, examList, studentList);
}
private static void printTimetable(ExamTable examTable) {
LOGGER.info("");
List<Room> roomList = examTable.getRoomList();
List<Exam> examList = examTable.getExamList();
Map<TimeSlot, Map<Room, List<Exam>>> examMap = examList.stream()
.filter(exam -> exam.getTimeslot() != null && exam.getRoom() != null)
.collect(Collectors.groupingBy(Exam::getTimeslot, Collectors.groupingBy(Exam::getRoom)));
LOGGER.info("| | " + roomList.stream()
.map(room -> String.format("%-10s", room.getName())).collect(Collectors.joining(" | ")) + " |");
LOGGER.info("|" + "------------|".repeat(roomList.size() + 1));
for (TimeSlot timeslot : examTable.getTimeslotList()) {
List<List<Exam>> cellList = roomList.stream()
.map(room -> {
Map<Room, List<Exam>> byRoomMap = examMap.get(timeslot);
if (byRoomMap == null) {
return Collections.<Exam>emptyList();
}
List<Exam> cellLessonList = byRoomMap.get(room);
if (cellLessonList == null) {
return Collections.<Exam>emptyList();
}
return cellLessonList;
})
.collect(Collectors.toList());
LOGGER.info("| " + String.format("%-10s",
timeslot.getID() + " " + " | "
+ cellList.stream().map(cellLessonList -> String.format("%-10s",
cellLessonList.stream().map(Exam::getName).collect(Collectors.joining(", "))))
.collect(Collectors.joining(" | "))
+ " |"));
LOGGER.info("|" + "------------|".repeat(roomList.size() + 1));
}
List<Exam> unassignedExams = examList.stream()
.filter(exam -> exam.getTimeslot() == null || exam.getRoom() == null)
.collect(Collectors.toList());
if (!unassignedExams.isEmpty()) {
LOGGER.info("");
LOGGER.info("Unassigned lessons");
for (Exam exam : unassignedExams) {
LOGGER.info(" " + exam.getName() + " - " + exam.getNumberOfStudents() + " - " + exam.getSID());
}
}
}
}
有人可以幫我解決這個問題嗎?
提前致謝。
您提供的代碼顯示了對 Constraint Streams 如何工作的一個主要誤解,這反過來又從根本上破壞了它。
ConstraintProvider
的任何實例都必須是無狀態的。 即使您在技術上可以在該類中擁有字段,但它沒有用處,因為約束 - 在運行時 - 需要沒有副作用。 這樣,您引入了分數損壞並且可能甚至沒有注意到。
此外,約束權重HardScore.ofSoft(...)
僅在約束創建期間計算一次,而不是在求解器運行時計算一次,因此無論如何定義它都毫無意義。 (它的值將是實例化時的任何penalty
,因此0
。)您需要使用的是匹配權重,它是在運行時計算的。 僅進行該修改,所討論的約束將如下所示:
Constraint spaceBetweenExams(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Student.class)
.join(Exam.class)
.join(Exam.class)
.filter((student, exam1, exam2) -> {
if(student.getExamIds().contains(exam1.getID()) && student.getExamIds().contains(exam2.getID())){
if(exam1.getID() < exam2.getID()){
return true;
}
}
return false;
})
.penalize("Max time between exams", HardSoftScore.ONE_SOFT,
(student, exam1, exam2) ->{
int timeDifference = Math.abs(exam1.getTimeslot().getID() - exam2.getTimeslot().getID());
if (timeDifference == 1) {
return 16;
} else if (timeDifference == 2) {
return 8;
} else if (timeDifference == 3) {
return 4;
} else if (timeDifference == 4) {
return 2;
} else {
return 1;
}
}
});
}
上面的代碼至少會讓這段代碼正常運行,但很可能會很慢。 有關如何更改域模型以提高約束性能的靈感,請參閱我最近的其他答案。 它可能不完全適合,但這個想法似乎也適用於您的用例。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.