/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.optaplanner.examples.curriculumcourse.persistence;
import java.io.File;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
import org.optaplanner.examples.common.app.LoggingMain;
import org.optaplanner.examples.common.persistence.SolutionDao;
import org.optaplanner.examples.common.persistence.StringDataGenerator;
import org.optaplanner.examples.curriculumcourse.domain.Course;
import org.optaplanner.examples.curriculumcourse.domain.CourseSchedule;
import org.optaplanner.examples.curriculumcourse.domain.Curriculum;
import org.optaplanner.examples.curriculumcourse.domain.Day;
import org.optaplanner.examples.curriculumcourse.domain.Lecture;
import org.optaplanner.examples.curriculumcourse.domain.Period;
import org.optaplanner.examples.curriculumcourse.domain.Room;
import org.optaplanner.examples.curriculumcourse.domain.Teacher;
import org.optaplanner.examples.curriculumcourse.domain.Timeslot;
import org.optaplanner.examples.curriculumcourse.domain.UnavailablePeriodPenalty;
import static org.optaplanner.examples.common.persistence.AbstractSolutionImporter.*;
public class CurriculumCourseGenerator extends LoggingMain {
private static final int DAY_LIST_SIZE = 5;
private static final int TIMESLOT_LIST_SIZE = 7;
private static final int PERIOD_LIST_SIZE = DAY_LIST_SIZE * TIMESLOT_LIST_SIZE - TIMESLOT_LIST_SIZE + 4;
public static void main(String[] args) {
CurriculumCourseGenerator generator = new CurriculumCourseGenerator();
generator.writeCourseSchedule(200, 8);
generator.writeCourseSchedule(400, 16);
}
private final int[] roomCapacityOptions = {
20,
25,
30,
40,
50
};
private final String[] courseCodes = new String[]{
"Math",
"Chemistry",
"Physics",
"Geography",
"Biology",
"History",
"English",
"Spanish",
"French",
"German",
"ICT",
"Economics",
"Psychology",
"Art",
"Music"};
private final StringDataGenerator teacherNameGenerator = StringDataGenerator.buildFullNames();
protected final SolutionDao solutionDao;
protected final File outputDir;
protected Random random;
public CurriculumCourseGenerator() {
solutionDao = new CurriculumCourseDao();
outputDir = new File(solutionDao.getDataDir(), "unsolved");
}
private void writeCourseSchedule(int lectureListSize, int curriculumListSize) {
int courseListSize = lectureListSize * 2 / 9 + 1;
int teacherListSize = courseListSize / 3 + 1;
int roomListSize = lectureListSize * 2 / PERIOD_LIST_SIZE;
String fileName = determineFileName(lectureListSize, PERIOD_LIST_SIZE, roomListSize);
File outputFile = new File(outputDir, fileName + ".xml");
CourseSchedule schedule = createCourseSchedule(fileName, teacherListSize, curriculumListSize, courseListSize, lectureListSize, roomListSize);
solutionDao.writeSolution(schedule, outputFile);
}
private String determineFileName(int lectureListSize, int periodListSize, int roomListSize) {
return lectureListSize + "lectures-" + periodListSize + "periods-" + roomListSize + "rooms";
}
public CourseSchedule createCourseSchedule(String fileName, int teacherListSize, int curriculumListSize, int courseListSize, int lectureListSize, int roomListSize) {
random = new Random(37);
CourseSchedule schedule = new CourseSchedule();
schedule.setId(0L);
createDayList(schedule);
createTimeslotList(schedule);
createPeriodList(schedule);
createTeacherList(schedule, teacherListSize);
createCourseList(schedule, courseListSize);
createLectureList(schedule, lectureListSize);
createRoomList(schedule, roomListSize);
createCurriculumList(schedule, curriculumListSize);
createUnavailablePeriodPenaltyList(schedule);
int possibleForOneLectureSize = schedule.getPeriodList().size() * schedule.getRoomList().size();
BigInteger possibleSolutionSize = BigInteger.valueOf(possibleForOneLectureSize).pow(
schedule.getLectureList().size());
logger.info("CourseSchedule {} has {} teachers, {} curricula, {} courses, {} lectures," +
" {} periods, {} rooms and {} unavailable period constraints with a search space of {}.",
fileName,
schedule.getTeacherList().size(),
schedule.getCurriculumList().size(),
schedule.getCourseList().size(),
schedule.getLectureList().size(),
schedule.getPeriodList().size(),
schedule.getRoomList().size(),
schedule.getUnavailablePeriodPenaltyList().size(),
getFlooredPossibleSolutionSize(possibleSolutionSize));
return schedule;
}
private void createDayList(CourseSchedule schedule) {
List<Day> dayList = new ArrayList<>(DAY_LIST_SIZE);
for (int i = 0; i < DAY_LIST_SIZE; i++) {
Day day = new Day();
day.setId((long) i);
day.setDayIndex(i);
day.setPeriodList(new ArrayList<>(TIMESLOT_LIST_SIZE));
dayList.add(day);
}
schedule.setDayList(dayList);
}
private void createTimeslotList(CourseSchedule schedule) {
List<Timeslot> timeslotList = new ArrayList<>(TIMESLOT_LIST_SIZE);
for (int i = 0; i < TIMESLOT_LIST_SIZE; i++) {
Timeslot timeslot = new Timeslot();
timeslot.setId((long) i);
timeslot.setTimeslotIndex(i);
timeslotList.add(timeslot);
}
schedule.setTimeslotList(timeslotList);
}
private void createPeriodList(CourseSchedule schedule) {
List<Period> periodList = new ArrayList<>(schedule.getDayList().size() * schedule.getTimeslotList().size());
long periodId = 0L;
for (Day day : schedule.getDayList()) {
for (Timeslot timeslot : schedule.getTimeslotList()) {
if (day.getDayIndex() == 2 && timeslot.getTimeslotIndex() >= 4) {
// No lectures Wednesday afternoon
continue;
}
Period period = new Period();
period.setId(periodId);
periodId++;
period.setDay(day);
day.getPeriodList().add(period);
period.setTimeslot(timeslot);
periodList.add(period);
}
}
schedule.setPeriodList(periodList);
}
private void createTeacherList(CourseSchedule schedule, int teacherListSize) {
List<Teacher> teacherList = new ArrayList<>(teacherListSize);
teacherNameGenerator.predictMaximumSizeAndReset(teacherListSize);
for (int i = 0; i < teacherListSize; i++) {
Teacher teacher = new Teacher();
teacher.setId((long) i);
teacher.setCode(teacherNameGenerator.generateNextValue());
teacherList.add(teacher);
}
schedule.setTeacherList(teacherList);
}
private void createCourseList(CourseSchedule schedule, int courseListSize) {
List<Teacher> teacherList = schedule.getTeacherList();
List<Course> courseList = new ArrayList<>(courseListSize);
Set<String> codeSet = new HashSet<>();
for (int i = 0; i < courseListSize; i++) {
Course course = new Course();
course.setId((long) i);
String code = (i < courseCodes.length * 2)
? courseCodes[i % courseCodes.length]
: courseCodes[random.nextInt(courseCodes.length)];
StringDataGenerator codeSuffixGenerator = new StringDataGenerator("")
.addAToZPart(true, 0);
if (courseListSize >= courseCodes.length) {
String codeSuffix = codeSuffixGenerator.generateNextValue();
while (codeSet.contains(code + codeSuffix)) {
codeSuffix = codeSuffixGenerator.generateNextValue();
}
code = code + codeSuffix;
codeSet.add(code);
}
course.setCode(code);
Teacher teacher = (i < teacherList.size() * 2)
? teacherList.get(i % teacherList.size())
: teacherList.get(random.nextInt(teacherList.size()));
course.setTeacher(teacher);
course.setLectureSize(0);
course.setMinWorkingDaySize(1);
course.setCurriculumList(new ArrayList<>());
course.setStudentSize(0);
courseList.add(course);
}
schedule.setCourseList(courseList);
}
private void createLectureList(CourseSchedule schedule, int lectureListSize) {
List<Course> courseList = schedule.getCourseList();
List<Lecture> lectureList = new ArrayList<>(lectureListSize);
for (int i = 0; i < lectureListSize; i++) {
Lecture lecture = new Lecture();
lecture.setId((long) i);
Course course = (i < courseList.size() * 2)
? courseList.get(i % courseList.size())
: courseList.get(random.nextInt(courseList.size()));
lecture.setCourse(course);
lecture.setLectureIndexInCourse(course.getLectureSize());
course.setLectureSize(course.getLectureSize() + 1);
lecture.setLocked(false);
lectureList.add(lecture);
}
schedule.setLectureList(lectureList);
}
private void createRoomList(CourseSchedule schedule, int roomListSize) {
List<Room> roomList = new ArrayList<>(roomListSize);
for (int i = 0; i < roomListSize; i++) {
Room room = new Room();
room.setId((long) i);
room.setCode("R" + ((i / 50 * 100) + 1 + i));
room.setCapacity(roomCapacityOptions[random.nextInt(roomCapacityOptions.length)]);
roomList.add(room);
}
schedule.setRoomList(roomList);
}
private void createCurriculumList(CourseSchedule schedule, int curriculumListSize) {
int maximumCapacity = schedule.getRoomList().stream().mapToInt(Room::getCapacity).max().getAsInt();
List<Course> courseList = schedule.getCourseList();
List<Curriculum> curriculumList = new ArrayList<>(curriculumListSize);
StringDataGenerator codeGenerator = new StringDataGenerator("")
.addAToZPart(true, 0).addAToZPart(false, 1).addAToZPart(false, 1).addAToZPart(false, 1);
codeGenerator.predictMaximumSizeAndReset(curriculumListSize);
for (int i = 0; i < curriculumListSize; i++) {
Curriculum curriculum = new Curriculum();
curriculum.setId((long) i);
curriculum.setCode("Group " + codeGenerator.generateNextValue());
// The studentSize is more likely to be 15 than 5 or 25
int studentSize = 5 + random.nextInt(10) + random.nextInt(10);
List<Course> courseSubList = courseList.stream()
.filter(course -> course.getStudentSize() + studentSize < maximumCapacity)
.collect(Collectors.toList());
Collections.shuffle(courseSubList, random);
int lectureCount = 0;
for (Course course : courseSubList) {
lectureCount += course.getLectureSize();
if (lectureCount > PERIOD_LIST_SIZE) {
break;
}
course.getCurriculumList().add(curriculum);
course.setStudentSize(course.getStudentSize() + studentSize);
}
curriculumList.add(curriculum);
}
schedule.setCurriculumList(curriculumList);
}
private void createUnavailablePeriodPenaltyList(CourseSchedule schedule) {
List<Course> courseList = schedule.getCourseList();
List<Period> periodList = schedule.getPeriodList();
List<UnavailablePeriodPenalty> unavailablePeriodPenaltyList = new ArrayList<>(courseList.size());
long penaltyId = 0L;
for (Course course : courseList) {
UnavailablePeriodPenalty penalty = new UnavailablePeriodPenalty();
penalty.setId(penaltyId);
penaltyId++;
penalty.setCourse(course);
penalty.setPeriod(periodList.get(random.nextInt(periodList.size())));
unavailablePeriodPenaltyList.add(penalty);
}
schedule.setUnavailablePeriodPenaltyList(unavailablePeriodPenaltyList);
}
}