From 6c06d313afc93e17a4f1d3c5e859f975ad3a0436 Mon Sep 17 00:00:00 2001 From: RandomCyberCoder Date: Fri, 15 May 2026 12:39:59 -0700 Subject: [PATCH 01/10] prescheduling done, but needs more checks --- .../acme/schooltimetabling/domain/Room.java | 16 ++ .../helperClasses/BitSetHelper.java | 15 ++ .../Generators/LessonGenerator.java | 8 +- .../Generators/RoomGenerator.java | 25 ++- .../Generators/TeacherGenerator.java | 21 +-- .../helperClasses/ParseInput.java | 14 +- .../helperClasses/PrescheduleObject.java | 149 ++++++++++++++++++ .../helperClasses/ResultSaver.java | 11 +- 8 files changed, 236 insertions(+), 23 deletions(-) create mode 100644 java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/PrescheduleObject.java diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Room.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Room.java index 85c96d39..28708c91 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Room.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Room.java @@ -2,6 +2,8 @@ import ai.timefold.solver.core.api.domain.lookup.PlanningId; +import java.util.BitSet; + public class Room { @PlanningId @@ -12,12 +14,18 @@ public class Room { * of what is needed for scheduling. Anything else is for debugging * but even that could be found in the hashmaps that have been created*/ private int ID; + + private BitSet prescheduled; + + public static boolean hasPrescheduled = false; + public Room() { } public Room(String id, String name) { this.id = id; this.name = name; + this.prescheduled = new BitSet(); } public Room(String id, String name, int ID){ @@ -46,4 +54,12 @@ public String getName() { public int getID() { return ID; } + + public BitSet getPrescheduled(){ + return (BitSet) prescheduled.clone(); + } + + public void prescheduleUpdate(BitSet addBitset){ + if(addBitset != null) this.prescheduled.or(addBitset); + } } diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/BitSetHelper.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/BitSetHelper.java index 32d28ccc..ebdff419 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/BitSetHelper.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/BitSetHelper.java @@ -1,6 +1,7 @@ package org.acme.schooltimetabling.helperClasses; import org.acme.schooltimetabling.constants.Constants; import org.acme.schooltimetabling.constants.Days; +import org.acme.schooltimetabling.helperClasses.PrescheduleObject.PrescheduledWindow; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -212,4 +213,18 @@ public static BitSet timeJsonToBs(Map time) { bitSet.set(dayOffset + startBlock, dayOffset + endBlock); return bitSet; } + + public static BitSet timeJsonToBs(PrescheduledWindow time) { + if (time == null) { + throw new IllegalArgumentException("Time window must not be null"); + } + + Map mappedTime = Map.of( + "day", time.getDay(), + "start", time.getStart(), + "end", time.getEnd() + ); + + return timeJsonToBs(mappedTime); + } } diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/LessonGenerator.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/LessonGenerator.java index b2ef77c1..6e8d923f 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/LessonGenerator.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/LessonGenerator.java @@ -7,6 +7,7 @@ import org.acme.schooltimetabling.domain.lesson.Lesson; import org.acme.schooltimetabling.domain.teacher.Faculty; import org.acme.schooltimetabling.helperClasses.ParseInput; +import org.acme.schooltimetabling.helperClasses.PrescheduleObject; import org.acme.schooltimetabling.helperClasses.ScheduleConfig; import org.acme.schooltimetabling.helperClasses.ScheduleFormat; import org.acme.schooltimetabling.domain.teacher.Teacher; @@ -21,7 +22,8 @@ public class LessonGenerator extends Generator{ public static boolean OLD_studio_detected = false; public static boolean proper_studio_detected = false; private static final Logger LOGGER = LoggerFactory.getLogger(LessonGenerator.class); - private static final Map>> PRESCHED_TIMES = ParseInput.readPrescheduledFile(); + private static final PrescheduleObject PRESCHED_TIMES = ScheduleConfig.getPrescheduledFileName() != null ? + ParseInput.readPrescheduledFile() : null; /** * Keeps track of the next available section number available for a course */ @@ -195,12 +197,12 @@ private static Teacher getTeacher(Map teacherHashMap, String te teacher = noSurveyTeacher(teacherName, defaultTime); //add prescheduled times if possible - if(PRESCHED_TIMES.containsKey(teacherName)){ + if(PRESCHED_TIMES != null && PRESCHED_TIMES.getTeachers().containsKey(teacherName)){ LOGGER.info(String.format("Found a prescheduled time for '%s'. Adding the time to their conflict bitset.", teacherName)); try { - BitSet addConflict = TeacherGenerator.createPreschedBs(PRESCHED_TIMES.get(teacherName)); + BitSet addConflict = TeacherGenerator.createPreschedBs(PRESCHED_TIMES.getTeachers().get(teacherName)); teacher.getAcceptable().andNot(addConflict); teacher.getPreferences().andNot(addConflict); teacher.getConflict().or(addConflict); diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/RoomGenerator.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/RoomGenerator.java index 36ba8251..472914ea 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/RoomGenerator.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/RoomGenerator.java @@ -2,10 +2,22 @@ import org.acme.schooltimetabling.constants.Constants; import org.acme.schooltimetabling.domain.Room; +import org.acme.schooltimetabling.helperClasses.BitSetHelper; +import org.acme.schooltimetabling.helperClasses.ParseInput; +import org.acme.schooltimetabling.helperClasses.PrescheduleObject; +import org.acme.schooltimetabling.helperClasses.ScheduleConfig; +import org.apache.commons.math3.random.BitsStreamGenerator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.BitSet; public class RoomGenerator extends Generator{ + private static final PrescheduleObject PRESCHED_TIMES = ScheduleConfig.getPrescheduledFileName() != null ? + ParseInput.readPrescheduledFile() : null; + private static final Logger LOGGER = LoggerFactory.getLogger(RoomGenerator.class); + /** * Generates a list of rooms that are specified in the {@link Constants#POSSIBLE_ROOMS Constants.POSSIBLE_ROOMS} * constant. This includes all lab rooms and a special room used for lecture only courses @@ -32,7 +44,18 @@ public static ArrayList generateRooms(){ */ private static Room generateRoom(String roomName){ int roomID = Constants.ROOM_TO_ID_BIMAP.get(roomName); - return new Room(String.valueOf(roomID), roomName, roomID); + Room room = new Room(String.valueOf(roomID), roomName, roomID); + + //check for prescheduling + if(PRESCHED_TIMES != null && PRESCHED_TIMES.getRooms().containsKey(roomName)){ + LOGGER.info("Found prescheduling times for room '{}'. Using prescheduling times.", roomName); + for(PrescheduleObject.PrescheduledWindow prescheduledWindow: PRESCHED_TIMES.getRooms().get(roomName)){ + BitSet preBs = BitSetHelper.timeJsonToBs(prescheduledWindow); + room.prescheduleUpdate(preBs); + } + } + + return room; } } diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/TeacherGenerator.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/TeacherGenerator.java index aaaf7be0..20c3ec7f 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/TeacherGenerator.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/TeacherGenerator.java @@ -4,18 +4,16 @@ import org.acme.schooltimetabling.apiCalls.surveyEndpoint.SurveyRecord; import org.acme.schooltimetabling.apiCalls.teacherEndpoint.TeacherRecord; import org.acme.schooltimetabling.constants.Constants; -import org.acme.schooltimetabling.constants.Days; import org.acme.schooltimetabling.constants.Preference; import org.acme.schooltimetabling.domain.teacher.Faculty; import org.acme.schooltimetabling.helperClasses.BitSetHelper; +import org.acme.schooltimetabling.helperClasses.PrescheduleObject; import org.acme.schooltimetabling.domain.teacher.Teacher; import org.acme.schooltimetabling.helperClasses.ParseInput; import org.acme.schooltimetabling.helperClasses.ScheduleConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; import java.util.*; public class TeacherGenerator extends Generator{ @@ -97,8 +95,10 @@ public static Map teacherGenDriver(){ if(ScheduleConfig.getPrescheduledFileName() != null){ LOGGER.info("Reading in prescheduled time file"); - Map>> preschedTimes = ParseInput.readPrescheduledFile(); - if(!preschedTimes.isEmpty()) prescheduleUpdate(teacherMap, preschedTimes); + PrescheduleObject preschedTimes = ParseInput.readPrescheduledFile(); + if(preschedTimes.hasTeachers()) { + prescheduleUpdate(teacherMap, preschedTimes.getTeachers()); + } } return teacherMap; @@ -268,10 +268,11 @@ private static Teacher generateTeacher(HashMap surveyEntry){ } - private static void prescheduleUpdate(Map teacherMap, Map>> presched){ - Iterator>>> iterator = presched.entrySet().iterator(); + private static void prescheduleUpdate(Map teacherMap, + Map> presched){ + Iterator>> iterator = presched.entrySet().iterator(); while (iterator.hasNext()) { - Map.Entry>> entry = iterator.next(); + Map.Entry> entry = iterator.next(); String name = entry.getKey(); Teacher teacher = teacherMap.get(name); if (teacher == null){ @@ -302,9 +303,9 @@ private static void prescheduleUpdate(Map teacherMap, Map> timeJson){ + public static BitSet createPreschedBs(List timeJson){ BitSet bs = new BitSet(); - for(Map time: timeJson){ + for(PrescheduleObject.PrescheduledWindow time: timeJson){ bs.or(BitSetHelper.timeJsonToBs(time)); } diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/ParseInput.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/ParseInput.java index acd4700e..52ef8539 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/ParseInput.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/ParseInput.java @@ -218,18 +218,20 @@ public static Set getFaculty(String file) { return faculty; } - public static Map>> readPrescheduledFile(){ + public static PrescheduleObject readPrescheduledFile(){ String filePath = "input/" + ScheduleConfig.getPrescheduledFileName(); try(InputStream inputStream = getResourceAsStream(filePath)){ ObjectMapper mapper = new ObjectMapper(); - - return mapper.readValue(inputStream, new TypeReference<>() {}); + PrescheduleObject prescheduleObject = mapper.readValue(inputStream, PrescheduleObject.class); + prescheduleObject.validate(); + return prescheduleObject; } catch (Exception e) { ParseInput.LOGGER.error("ERROR reading file containing teachers prescheduled times. Skipping inclusion of " + - "these times."); - return new HashMap<>(); + "these times. System existing..."); + e.printStackTrace(); + System.exit(1); + throw new IllegalStateException("Failed to read prescheduled file", e); } } - } diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/PrescheduleObject.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/PrescheduleObject.java new file mode 100644 index 00000000..7c7d86ca --- /dev/null +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/PrescheduleObject.java @@ -0,0 +1,149 @@ +package org.acme.schooltimetabling.helperClasses; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class PrescheduleObject { + + private Map> teachers = new LinkedHashMap<>(); + private Map> rooms = new LinkedHashMap<>(); + + public PrescheduleObject() { + } + + @JsonProperty("teachers") + public Map> getTeachers() { + return teachers; + } + + public void setTeachers(Map> teachers) { + this.teachers = teachers; + } + + @JsonProperty("rooms") + public Map> getRooms() { + return rooms; + } + + public void setRooms(Map> rooms) { + this.rooms = rooms; + } + + public boolean hasTeachers() { + return teachers != null; + } + + public boolean hasRooms() { + return rooms != null; + } + + /** + * Checks to make sure that the JSON object has all the fields that are necessary + */ + public void validate() { + if (!hasTeachers()) { + throw new IllegalStateException("PrescheduleObject must contain at least one teacher entry."); + } + if (!hasRooms()) { + throw new IllegalStateException("PrescheduleObject must contain at least one room entry."); + } + + validateWindows("teacher", teachers); + validateWindows("room", rooms); + } + + private void validateWindows(String category, Map> entries) { + for (Map.Entry> entry : entries.entrySet()) { + if (entry.getValue() == null || entry.getValue().isEmpty()) { + throw new IllegalStateException("PrescheduleObject contains no windows for " + category + " '" + + entry.getKey() + "'."); + } + for (PrescheduledWindow window : entry.getValue()) { + if (window == null) { + throw new IllegalStateException("PrescheduleObject contains a null window for " + category + " '" + + entry.getKey() + "'."); + } + window.validate(category, entry.getKey()); + } + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class PrescheduledWindow { + @JsonProperty("start") + private String start; + @JsonProperty("day") + private String day; + @JsonProperty("end") + private String end; + + public PrescheduledWindow() { + } + + public PrescheduledWindow(String start, String day, String end) { + this.start = start; + this.day = day; + this.end = end; + } + + public void setStart(String start) { + this.start = start; + } + + public void setDay(String day) { + this.day = day; + } + + public void setEnd(String end) { + this.end = end; + } + + public String getStart() { + return start; + } + + public String getDay() { + return day; + } + + public String getEnd() { + return end; + } + + public void validate(String category, String ownerName) { + if (start == null || start.isBlank()) { + throw new IllegalStateException("Missing start for " + category + " '" + ownerName + "'."); + } + if (day == null || day.isBlank()) { + throw new IllegalStateException("Missing day for " + category + " '" + ownerName + "'."); + } + if (end == null || end.isBlank()) { + throw new IllegalStateException("Missing end for " + category + " '" + ownerName + "'."); + } + } + } + + public static PrescheduleObject empty() { + return new PrescheduleObject(); + } + + /** + * Add a new Prescheduled Window for a teacher + */ + public void addTeacherWindow(String teacherName, PrescheduledWindow window) { + teachers.computeIfAbsent(teacherName, ignored -> new ArrayList<>()).add(window); + } + + /** + * Add a new Prescheduled Window for a room + */ + public void addRoomWindow(String roomName, PrescheduledWindow window) { + rooms.computeIfAbsent(roomName, ignored -> new ArrayList<>()).add(window); + } +} diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/ResultSaver.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/ResultSaver.java index 659946a6..ccf57811 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/ResultSaver.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/ResultSaver.java @@ -527,14 +527,19 @@ public void teacherTimesToJson() throws IOException { //we only consider lessons that don't violate any hard constraints as actually being scheduled List lsWithNoHard = extracted.getKey(); - Map>> res = new HashMap<>(); + Map>> teachers = new LinkedHashMap<>(); + Map>> rooms = new LinkedHashMap<>(); for(Lesson lesson: lsWithNoHard){ final String teacherName = lesson.getTeacherObj().getName(); - if(!res.containsKey(teacherName)) res.put(teacherName, new ArrayList<>()); - List> teacherMap = res.get(teacherName); + if(!teachers.containsKey(teacherName)) teachers.put(teacherName, new ArrayList<>()); + List> teacherMap = teachers.get(teacherName); teacherMap.addAll(lesson.toJson()); } + Map res = new LinkedHashMap<>(); + res.put("teachers", teachers); + res.put("rooms", rooms); + Scanner retryScanner = new Scanner(System.in); boolean retryAllowed = true; From 800af340dc83cda63db4a6357ef2bb1ccae5a0d8 Mon Sep 17 00:00:00 2001 From: RandomCyberCoder Date: Fri, 15 May 2026 12:45:54 -0700 Subject: [PATCH 02/10] updated preschedule example file --- generated/scheduled_times_dump.example.json | 151 +++++++++++--------- 1 file changed, 81 insertions(+), 70 deletions(-) diff --git a/generated/scheduled_times_dump.example.json b/generated/scheduled_times_dump.example.json index c8a6180d..0e757824 100644 --- a/generated/scheduled_times_dump.example.json +++ b/generated/scheduled_times_dump.example.json @@ -1,71 +1,82 @@ { - "LastName, FirstName": [ - { - "start": "10:00AM", - "day": "MONDAY", - "end": "12:00PM" - }, - { - "start": "10:00AM", - "day": "WEDNESDAY", - "end": "12:00PM" - }, - { - "start": "10:00AM", - "day": "FRIDAY", - "end": "12:00PM" - }, - { - "start": "4:00PM", - "day": "MONDAY", - "end": "6:00PM" - }, - { - "start": "4:00PM", - "day": "WEDNESDAY", - "end": "6:00PM" - }, - { - "start": "4:00PM", - "day": "FRIDAY", - "end": "6:00PM" - }, - { - "start": "12:00PM", - "day": "MONDAY", - "end": "2:00PM" - }, - { - "start": "12:00PM", - "day": "WEDNESDAY", - "end": "2:00PM" - }, - { - "start": "12:00PM", - "day": "FRIDAY", - "end": "2:00PM" - } - ], - "LastName2, FirstName2": [ - { - "start": "9:00AM", - "day": "MONDAY", - "end": "10:30AM" - }, - { - "start": "9:00AM", - "day": "WEDNESDAY", - "end": "10:30AM" - }, - { - "start": "11:00AM", - "day": "MONDAY", - "end": "1:00PM" - }, - { - "start": "11:00AM", - "day": "WEDNESDAY", - "end": "1:00PM" - } - ] -} \ No newline at end of file + "teachers": { + "LastName, FirstName": [ + { + "start": "10:00AM", + "day": "MONDAY", + "end": "12:00PM" + }, + { + "start": "10:00AM", + "day": "WEDNESDAY", + "end": "12:00PM" + }, + { + "start": "10:00AM", + "day": "FRIDAY", + "end": "12:00PM" + }, + { + "start": "4:00PM", + "day": "MONDAY", + "end": "6:00PM" + }, + { + "start": "4:00PM", + "day": "WEDNESDAY", + "end": "6:00PM" + }, + { + "start": "4:00PM", + "day": "FRIDAY", + "end": "6:00PM" + }, + { + "start": "12:00PM", + "day": "MONDAY", + "end": "2:00PM" + }, + { + "start": "12:00PM", + "day": "WEDNESDAY", + "end": "2:00PM" + }, + { + "start": "12:00PM", + "day": "FRIDAY", + "end": "2:00PM" + } + ], + "LastName2, FirstName2": [ + { + "start": "9:00AM", + "day": "MONDAY", + "end": "10:30AM" + }, + { + "start": "9:00AM", + "day": "WEDNESDAY", + "end": "10:30AM" + }, + { + "start": "11:00AM", + "day": "MONDAY", + "end": "1:00PM" + }, + { + "start": "11:00AM", + "day": "WEDNESDAY", + "end": "1:00PM" + } + ] + }, + "rooms": { + "Room 101": [ + { + "start": "8:00AM", + "day": "TUESDAY", + "end": "9:30AM" + } + ] + } +} From a810b39f895c22ee8e3832cd3c9337a797b23884 Mon Sep 17 00:00:00 2001 From: RandomCyberCoder Date: Fri, 15 May 2026 12:51:32 -0700 Subject: [PATCH 03/10] updated package name --- .../org/acme/schooltimetabling/DefaultTimes/DefaultTime.java | 2 +- .../acme/schooltimetabling/DefaultTimes/DefaultTimeAll.java | 2 +- .../schooltimetabling/DefaultTimes/DefaultTimeEvening.java | 2 +- .../schooltimetabling/DefaultTimes/DefaultTimeMorning.java | 2 +- .../schooltimetabling/DefaultTimes/DefaultTimeRegistry.java | 2 +- .../helperClasses/Generators/LessonGenerator.java | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTime.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTime.java index b3613892..0db7e690 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTime.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTime.java @@ -1,4 +1,4 @@ -package org.acme.schooltimetabling.DefaultTimes; +package org.acme.schooltimetabling.defaultTimes; import java.util.BitSet; diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeAll.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeAll.java index b31324b3..d4d02d93 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeAll.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeAll.java @@ -1,4 +1,4 @@ -package org.acme.schooltimetabling.DefaultTimes; +package org.acme.schooltimetabling.defaultTimes; import org.acme.schooltimetabling.constants.Constants; import org.acme.schooltimetabling.constants.Days; diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeEvening.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeEvening.java index 32722318..dd0867f6 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeEvening.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeEvening.java @@ -1,4 +1,4 @@ -package org.acme.schooltimetabling.DefaultTimes; +package org.acme.schooltimetabling.defaultTimes; import org.acme.schooltimetabling.constants.Constants; import org.acme.schooltimetabling.constants.Days; diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeMorning.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeMorning.java index db0c3bbc..2f6230c0 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeMorning.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeMorning.java @@ -1,4 +1,4 @@ -package org.acme.schooltimetabling.DefaultTimes; +package org.acme.schooltimetabling.defaultTimes; import org.acme.schooltimetabling.constants.Constants; import org.acme.schooltimetabling.constants.Days; diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeRegistry.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeRegistry.java index 37d3b729..6c3b3bd2 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeRegistry.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeRegistry.java @@ -1,4 +1,4 @@ -package org.acme.schooltimetabling.DefaultTimes; +package org.acme.schooltimetabling.defaultTimes; import java.util.List; import java.util.Random; diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/LessonGenerator.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/LessonGenerator.java index 6e8d923f..5bea7712 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/LessonGenerator.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/LessonGenerator.java @@ -1,7 +1,7 @@ package org.acme.schooltimetabling.helperClasses.Generators; -import org.acme.schooltimetabling.DefaultTimes.DefaultTime; -import org.acme.schooltimetabling.DefaultTimes.DefaultTimeRegistry; +import org.acme.schooltimetabling.defaultTimes.DefaultTime; +import org.acme.schooltimetabling.defaultTimes.DefaultTimeRegistry; import org.acme.schooltimetabling.constants.Constants; import org.acme.schooltimetabling.constants.Preference; import org.acme.schooltimetabling.domain.lesson.Lesson; From 96acf5d0474681954626aa68596113ec68aab0c8 Mon Sep 17 00:00:00 2001 From: RandomCyberCoder Date: Fri, 15 May 2026 13:21:05 -0700 Subject: [PATCH 04/10] added in the room prescheduling constraint --- .../Generators/RoomGenerator.java | 2 ++ .../solver/TimetableConstraintProvider.java | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/RoomGenerator.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/RoomGenerator.java index 472914ea..34a706c0 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/RoomGenerator.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/RoomGenerator.java @@ -48,6 +48,8 @@ private static Room generateRoom(String roomName){ //check for prescheduling if(PRESCHED_TIMES != null && PRESCHED_TIMES.getRooms().containsKey(roomName)){ + //mark this var so that we know to include the prescheduling room constraint + Room.hasPrescheduled = true; LOGGER.info("Found prescheduling times for room '{}'. Using prescheduling times.", roomName); for(PrescheduleObject.PrescheduledWindow prescheduledWindow: PRESCHED_TIMES.getRooms().get(roomName)){ BitSet preBs = BitSetHelper.timeJsonToBs(prescheduledWindow); diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/solver/TimetableConstraintProvider.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/solver/TimetableConstraintProvider.java index cc69c2c6..b82ff2a6 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/solver/TimetableConstraintProvider.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/solver/TimetableConstraintProvider.java @@ -90,6 +90,12 @@ else if(AGGRESSIVE_CHOICE.equals("NONE")){ System.exit(1); } + //add in the room prescheduling conflict constraint only if we prescheduled rooms detected + if(Room.hasPrescheduled){ + LOGGER.info("At least on room has been found to be prescheduled. Including the room prescheduling constraint."); + solver_constraints.add(roomPreschedule(constraintFactory)); + } + return solver_constraints.toArray(Constraint[]::new); } @@ -599,4 +605,23 @@ Constraint outBestTime(ConstraintFactory constraintFactory){ .asConstraint("Penalize time out of preferred time interval"); } + //--------------------- NEW COSNTRAINT FOR THE ROOM PRESCHEDULING ---------------// + Constraint roomPreschedule(ConstraintFactory constraintFactory){ + return constraintFactory.forEach(Lesson.class) + .filter(lesson -> { + //skip lessons that are in the lecture only room + //take into account only lessons that have a room that has prescheduling times + if(Objects.equals(lesson.getRoom().getName(), Constants.LEC_ONLY) + || !lesson.getRoom().getPrescheduled().isEmpty() ) return false; + + //penalize lessons that are scheduled in a rooms prescheduled time + BitSet prescheduled = lesson.getRoom().getPrescheduled(); + + return prescheduled.intersects(lesson.getTimeslot().getLectureBitSet()) || + prescheduled.intersects(lesson.getTimeslot().getLabActBitSet()); + }) + .penalize(HardMediumSoftScore.ONE_SOFT) + .asConstraint("Penalize a lesson "); + } + } From 80293303184e1e9be1bc090217bf16a26adc1ee6 Mon Sep 17 00:00:00 2001 From: RandomCyberCoder Date: Mon, 18 May 2026 12:54:16 -0700 Subject: [PATCH 05/10] updating default times for better scheduling --- .../DefaultTimes/DefaultTimeAll.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeAll.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeAll.java index d4d02d93..c2ddaf64 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeAll.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/DefaultTimes/DefaultTimeAll.java @@ -26,18 +26,28 @@ public class DefaultTimeAll implements DefaultTime{ final DateTimeFormatter FORMATTER = Constants.TIME_FORMATTER; EnumSet defaultDays = EnumSet.allOf(Days.class); - List acceptableTimes = List.of( + //I'm trying to set preferred times outside of prime time to help solver push more lectures into this time. + List preferredTimes = List.of( LocalTime.parse("7:00AM", FORMATTER), LocalTime.parse("8:00AM", FORMATTER), + LocalTime.parse("3:00PM", FORMATTER), + LocalTime.parse("4:00PM", FORMATTER), + LocalTime.parse("5:00PM", FORMATTER) + + ); + + for(LocalTime localTime : preferredTimes){ + INSTANCE.preference.or(BitSetHelper.timeSlotBitSet(localTime, NUM_BLOCKS_FULL_HOUR, defaultDays)); + } + + + List acceptableTimes = List.of( LocalTime.parse("9:00AM", FORMATTER), LocalTime.parse("10:00AM", FORMATTER), LocalTime.parse("11:00AM", FORMATTER), LocalTime.parse("12:00PM", FORMATTER), LocalTime.parse("1:00PM", FORMATTER), - LocalTime.parse("2:00PM", FORMATTER), - LocalTime.parse("3:00PM", FORMATTER), - LocalTime.parse("4:00PM", FORMATTER), - LocalTime.parse("5:00PM", FORMATTER) + LocalTime.parse("2:00PM", FORMATTER) ); for(LocalTime localTime : acceptableTimes) { INSTANCE.acceptable.or(BitSetHelper.timeSlotBitSet(localTime, NUM_BLOCKS_FULL_HOUR, defaultDays)); From 2fec938a3e2d091e0da34d63bde2d4514cd9a618 Mon Sep 17 00:00:00 2001 From: RandomCyberCoder Date: Mon, 18 May 2026 12:58:59 -0700 Subject: [PATCH 06/10] faculty meeting times updated --- .../org/acme/schooltimetabling/domain/teacher/Faculty.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/teacher/Faculty.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/teacher/Faculty.java index 0cee4327..8e8fe107 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/teacher/Faculty.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/teacher/Faculty.java @@ -35,14 +35,14 @@ public class Faculty extends Teacher{ final int NUM_BLOCKS_FULL_HOUR = 2; List facultyTimes; DateTimeFormatter formatter = DateTimeFormatter.ofPattern("h:mma"); - EnumSet facultyDays = EnumSet.of(Days.MONDAY, Days.WEDNESDAY, Days.FRIDAY); + EnumSet facultyDays; try{ //set special bits for testing if(Constants.TESTING){ LocalTime localTime = LocalTime.parse("9:00PM", formatter); BitSet temp = BitSetHelper.timeSlotBitSet(localTime, NUM_BLOCKS_FULL_HOUR, - EnumSet.of(Days.MONDAY, Days.WEDNESDAY, Days.FRIDAY)); + EnumSet.of(Days.MONDAY, Days.TUESDAY, Days.WEDNESDAY, Days.THURSDAY)); FACULTY_CONFLICT.or(temp); } /*CSC faculty times*/ From 0616413663161db9ee013fe3efb9ae720c940d93 Mon Sep 17 00:00:00 2001 From: RandomCyberCoder Date: Mon, 18 May 2026 13:05:53 -0700 Subject: [PATCH 07/10] fixed facutly default survey --- .../Generators/LessonGenerator.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/LessonGenerator.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/LessonGenerator.java index 5bea7712..d967f415 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/LessonGenerator.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/LessonGenerator.java @@ -329,19 +329,24 @@ private static boolean skipCourse(String modifier, String name, String config){ private static Teacher noSurveyTeacher(String name, DefaultTime defaultTime){ final int LAST_NAME_POS = 0; String[] nameFragments = name.split(","); + boolean isFaculty = Constants.FACULTY_LAST_NAMES.contains(nameFragments[LAST_NAME_POS]); - if(Constants.FACULTY_LAST_NAMES.contains(nameFragments[LAST_NAME_POS])){ - LOGGER.info(String.format("Found teacher '%s' to be a faculty member. Promoting Teacher obj to Faculty" + if(isFaculty){ + LOGGER.info(String.format("Found teacher '%s' to be a faculty member. Promoting to Faculty" , name)); - return new Faculty(TeacherGenerator.getNextTeacherID(), name, new BitSet(), new BitSet(), new BitSet(), + return new Faculty(TeacherGenerator.getNextTeacherID(), name, + defaultTime.getPreference(), + defaultTime.getAcceptable(), + defaultTime.getConflict(), + Preference.NEUTRAL); + } + else{ + return new Teacher(TeacherGenerator.getNextTeacherID(), name, + defaultTime.getPreference(), + defaultTime.getAcceptable(), + defaultTime.getConflict(), Preference.NEUTRAL); } - - return new Teacher(TeacherGenerator.getNextTeacherID(), name, - defaultTime.getPreference(), - defaultTime.getAcceptable(), - defaultTime.getConflict(), - Preference.NEUTRAL); } From 98e0dd3bf04eb1d5bfbae1cbe83f2cbed9747587 Mon Sep 17 00:00:00 2001 From: RandomCyberCoder Date: Mon, 18 May 2026 13:06:33 -0700 Subject: [PATCH 08/10] updated Room constructor --- .../src/main/java/org/acme/schooltimetabling/domain/Room.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Room.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Room.java index 28708c91..21a08374 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Room.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Room.java @@ -32,6 +32,7 @@ public Room(String id, String name, int ID){ this.id = id; this.name = name; this.ID = ID; + this.prescheduled = new BitSet(); } @Override From 0f04ac6a9fe0d272210e518fd6afa668939cc9c5 Mon Sep 17 00:00:00 2001 From: RandomCyberCoder Date: Mon, 18 May 2026 13:39:14 -0700 Subject: [PATCH 09/10] fixed the usage of old surveys as a fall back --- .../Generators/TeacherGenerator.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/TeacherGenerator.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/TeacherGenerator.java index 20c3ec7f..0dfa4cb6 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/TeacherGenerator.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/TeacherGenerator.java @@ -108,7 +108,9 @@ public static Map teacherGenDriver(){ /** * Returns a hashmap of the teacher's canon name mapped to their teacher's object. If the person is a faculty * member the teacher object will actually be a Faculty object. The function will try to bleed an old survey - * if the professor chooses. If a professor bleeds forward in the old survey, the teacher is skipped + * if the professor chooses. If a professor bleeds forward in the old survey, the teacher is skipped. + * + * If a teacher has no survey for the current quarter, then we will try to use the survey from the previous quarter * * @param curQuarterSurvey current quarter survey file path assuming it's in src directory * @param prevQuarterSurvey prev quarter survey file path assuming it's in src directory @@ -117,9 +119,10 @@ public static Map teacherGenDriver(){ * */ public static HashMap generateTeachers(List> curQuarterSurvey, List> prevQuarterSurvey){ - /* This Hash map will map the teacher's name to the teacher's object */ + /* This Hash map will map the teacher's CANON name to their teacher object */ HashMap teacherHashMap = new HashMap<> (); final String BLEED_FORWARD_STRING = "Yes, use the same as last term"; + final String BLEED_FORWARD_KEY = "use_old"; /*we will use a hashmap to keep track of who want to bleed forward for * easy lookup*/ @@ -130,7 +133,7 @@ public static HashMap generateTeachers(List generateTeachers(List surveyEntry : prevQuarterSurvey){ String instructorName = surveyEntry.get("name").strip(); + final String canonName = Constants.TEACHER_NAME_TO_CANON.get(instructorName); + /*Check if the instructor wanted to bleed forward*/ if(teacherBleed.containsKey(instructorName)){ LOGGER.info(String.format("Trying to use %s's old survey", instructorName)); /*If the instructor choose to bleed forward in the previous survey * we will be forced to skip them :( */ - if(BLEED_FORWARD_STRING.equals(surveyEntry.get("use_old"))){ + if(BLEED_FORWARD_STRING.equals(surveyEntry.get(BLEED_FORWARD_KEY))){ LOGGER.warn(String.format("Previous survey also bleeds forward. SKIPPING %s", instructorName)); continue; } @@ -171,16 +176,19 @@ public static HashMap generateTeachers(List Date: Mon, 18 May 2026 15:21:23 -0700 Subject: [PATCH 10/10] updated room prescheduling, and update a random log --- .../Generators/TeacherGenerator.java | 2 +- .../solver/TimetableConstraintProvider.java | 45 ++++++++------- .../solver/ConstraintTestHelper.java | 48 ++++++++++++---- .../solver/TestConstraints.java | 57 +++++++++++++++++-- 4 files changed, 114 insertions(+), 38 deletions(-) diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/TeacherGenerator.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/TeacherGenerator.java index 0dfa4cb6..42378a84 100644 --- a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/TeacherGenerator.java +++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/TeacherGenerator.java @@ -161,7 +161,7 @@ public static HashMap generateTeachers(List { + //skip lessons that are in the lecture only room + //take into account only lessons that have a room that has prescheduling times + if(Constants.LEC_ONLY.equals(lesson.getRoom().getName()) || + lesson.getRoom().getPrescheduled().isEmpty()) return false; + + //penalize lessons that are scheduled in a rooms prescheduled time + BitSet prescheduled = lesson.getRoom().getPrescheduled(); + + //studio course both// non studio only lab + return prescheduled.intersects(lesson.getTimeslot().getLabActBitSet()) || + (lesson.isStudio() && prescheduled.intersects(lesson.getTimeslot().getLectureBitSet())); + }) + .penalize(HardMediumSoftScore.ONE_HARD) + .asConstraint("Penalize a lesson "); + } + //-------------------------------------- Medium Constraints -------------------------------------- Constraint prefTime(ConstraintFactory constraintFactory){ @@ -604,24 +627,4 @@ Constraint outBestTime(ConstraintFactory constraintFactory){ .penalize(HardMediumSoftScore.ONE_SOFT) .asConstraint("Penalize time out of preferred time interval"); } - - //--------------------- NEW COSNTRAINT FOR THE ROOM PRESCHEDULING ---------------// - Constraint roomPreschedule(ConstraintFactory constraintFactory){ - return constraintFactory.forEach(Lesson.class) - .filter(lesson -> { - //skip lessons that are in the lecture only room - //take into account only lessons that have a room that has prescheduling times - if(Objects.equals(lesson.getRoom().getName(), Constants.LEC_ONLY) - || !lesson.getRoom().getPrescheduled().isEmpty() ) return false; - - //penalize lessons that are scheduled in a rooms prescheduled time - BitSet prescheduled = lesson.getRoom().getPrescheduled(); - - return prescheduled.intersects(lesson.getTimeslot().getLectureBitSet()) || - prescheduled.intersects(lesson.getTimeslot().getLabActBitSet()); - }) - .penalize(HardMediumSoftScore.ONE_SOFT) - .asConstraint("Penalize a lesson "); - } - } diff --git a/java/hello-world/src/test/java/org/acme/schooltimetabling/solver/ConstraintTestHelper.java b/java/hello-world/src/test/java/org/acme/schooltimetabling/solver/ConstraintTestHelper.java index 9486384e..774db5f6 100644 --- a/java/hello-world/src/test/java/org/acme/schooltimetabling/solver/ConstraintTestHelper.java +++ b/java/hello-world/src/test/java/org/acme/schooltimetabling/solver/ConstraintTestHelper.java @@ -5,8 +5,10 @@ import org.acme.schooltimetabling.domain.Room; import org.acme.schooltimetabling.domain.Timeslot; import org.acme.schooltimetabling.domain.teacher.Teacher; +import org.acme.schooltimetabling.helperClasses.BitSetHelper; import org.glassfish.jaxb.runtime.v2.runtime.reflect.opt.Const; +import java.time.LocalTime; import java.util.BitSet; import java.util.EnumSet; import java.util.Set; @@ -22,12 +24,16 @@ public class ConstraintTestHelper { /** * Test: room name; will be used by a specific course {@link #TEST_L_W_LAB_SPECIFIC} */ - public final static String TEST_LAB_ROOM_SPECIFIC = "LabRoom"; + public final static String TEST_NAME_LAB_ROOM_SPECIFIC = "LabRoom"; /** * Test: name of lab room not assigned to an course specifically */ - public final static String TEST_RANDOM_LAB_ROOM = "Random lab room"; - public final static Set TEST_SET_LAB_ROOMS = Set.of(TEST_LAB_ROOM_SPECIFIC); + public final static String TEST_NAME_RANDOM_LAB_ROOM = "Random lab room"; + /** + * Test: name of prescheduled ROOM + */ + public final static String TEST_NAME_PRESCHEDULED_ROOM = "Room Prescheduled"; + public final static Set TEST_SET_LAB_ROOMS = Set.of(TEST_NAME_LAB_ROOM_SPECIFIC); /** * Test: name of a course that has a specific lab*/ public final static String TEST_L_W_LAB_SPECIFIC = "Lab/Act course"; @@ -39,28 +45,48 @@ public class ConstraintTestHelper { * Room that is used for a course specifically and can also be used by an lab */ public static Room TEST_ROOM_SPECIFIC; + /** + * Room prescheduled + */ + public static Room TEST_ROOM_PRESCHEDULED; public static String NON_STUDIO_SPECIFIC = "non studio course with a specific room"; static{ - Constants.POSSIBLE_ROOMS.add(TEST_LAB_ROOM_SPECIFIC); - Constants.ROOM_TO_ID_BIMAP.put(TEST_LAB_ROOM_SPECIFIC, 1000); + //add rooms to those that are possible + Constants.POSSIBLE_ROOMS.add(TEST_NAME_LAB_ROOM_SPECIFIC); + Constants.ROOM_TO_ID_BIMAP.put(TEST_NAME_LAB_ROOM_SPECIFIC, 1000); - Constants.POSSIBLE_ROOMS.add(TEST_RANDOM_LAB_ROOM); - Constants.ROOM_TO_ID_BIMAP.put(TEST_RANDOM_LAB_ROOM, 1001); + Constants.POSSIBLE_ROOMS.add(TEST_NAME_RANDOM_LAB_ROOM); + Constants.ROOM_TO_ID_BIMAP.put(TEST_NAME_RANDOM_LAB_ROOM, 1001); + Constants.POSSIBLE_ROOMS.add(TEST_NAME_PRESCHEDULED_ROOM); + Constants.ROOM_TO_ID_BIMAP.put(TEST_NAME_PRESCHEDULED_ROOM, 1002); + + //add course names to their ID Constants.COURSE_ID_BIMAP.put(DUMMY_STUDIO, 2000); Constants.COURSE_ID_BIMAP.put(NON_STUDIO_SPECIFIC, 2001); + //add which lessons are studios Constants.STUDIO_STYLE_COURSES.add(DUMMY_STUDIO); Constants.STUDIO_STYLE_COURSES.add(TEST_L_W_LAB_SPECIFIC); + //add mapping for which courses can be in certain rooms Constants.COURSE_TO_ROOMS.put(TEST_L_W_LAB_SPECIFIC, TEST_SET_LAB_ROOMS); Constants.COURSE_TO_ROOMS.put(NON_STUDIO_SPECIFIC, TEST_SET_LAB_ROOMS); - TEST_ROOM_RANDO_LAB = new Room(Integer.toString(Constants.ROOM_TO_ID_BIMAP.get(TEST_RANDOM_LAB_ROOM)), - TEST_RANDOM_LAB_ROOM, Constants.ROOM_TO_ID_BIMAP.get(TEST_RANDOM_LAB_ROOM)); - TEST_ROOM_SPECIFIC = new Room(Integer.toString(Constants.ROOM_TO_ID_BIMAP.get(TEST_LAB_ROOM_SPECIFIC)), - TEST_LAB_ROOM_SPECIFIC, Constants.ROOM_TO_ID_BIMAP.get(TEST_LAB_ROOM_SPECIFIC)); + //create the room objects + TEST_ROOM_RANDO_LAB = new Room(Integer.toString(Constants.ROOM_TO_ID_BIMAP.get(TEST_NAME_RANDOM_LAB_ROOM)), + TEST_NAME_RANDOM_LAB_ROOM, Constants.ROOM_TO_ID_BIMAP.get(TEST_NAME_RANDOM_LAB_ROOM)); + TEST_ROOM_SPECIFIC = new Room(Integer.toString(Constants.ROOM_TO_ID_BIMAP.get(TEST_NAME_LAB_ROOM_SPECIFIC)), + TEST_NAME_LAB_ROOM_SPECIFIC, Constants.ROOM_TO_ID_BIMAP.get(TEST_NAME_LAB_ROOM_SPECIFIC)); + TEST_ROOM_PRESCHEDULED = new Room(Integer.toString(Constants.ROOM_TO_ID_BIMAP.get(TEST_NAME_LAB_ROOM_SPECIFIC)), + TEST_NAME_LAB_ROOM_SPECIFIC, Constants.ROOM_TO_ID_BIMAP.get(TEST_NAME_LAB_ROOM_SPECIFIC)); + + //let's preschedule a room + EnumSet MWF = EnumSet.of(Days.MONDAY, Days.WEDNESDAY, Days.FRIDAY); + BitSet roomBlocked = BitSetHelper.timeSlotBitSet(LocalTime.parse("9:00AM", Constants.TIME_FORMATTER), 2, MWF); + TEST_ROOM_PRESCHEDULED.prescheduleUpdate(roomBlocked); + Constants.TESTING = true; } diff --git a/java/hello-world/src/test/java/org/acme/schooltimetabling/solver/TestConstraints.java b/java/hello-world/src/test/java/org/acme/schooltimetabling/solver/TestConstraints.java index c4c310ad..129ec181 100644 --- a/java/hello-world/src/test/java/org/acme/schooltimetabling/solver/TestConstraints.java +++ b/java/hello-world/src/test/java/org/acme/schooltimetabling/solver/TestConstraints.java @@ -12,14 +12,11 @@ import org.acme.schooltimetabling.helperClasses.BitSetHelper; import org.acme.schooltimetabling.helperClasses.Generators.LessonGenerator; import org.acme.schooltimetabling.helperClasses.ScheduleConfig; -import org.glassfish.jaxb.runtime.v2.runtime.reflect.opt.Const; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import ai.timefold.solver.test.api.score.stream.ConstraintVerifier; -import java.sql.Time; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.BitSet; @@ -342,7 +339,7 @@ void noPenLessonRoomCheck(){ Lesson lesson2 = Lesson.test_buildLesson("1", 1, "some only lec course", "", "", "1-0-0", 1111, ConstraintTestHelper.DUMMY_TEACHER, ConstraintTestHelper.DUMMY_TS, LEC_ONLY_ROOM); - Lesson lsLabOnly = Lesson.test_buildLesson("2", 1, ConstraintTestHelper.TEST_RANDOM_LAB_ROOM, + Lesson lsLabOnly = Lesson.test_buildLesson("2", 1, ConstraintTestHelper.TEST_NAME_RANDOM_LAB_ROOM, "noName", "", "0-0-1", 1, ConstraintTestHelper.DUMMY_TEACHER, ConstraintTestHelper.DUMMY_TS, ConstraintTestHelper.DUMMY_ROOM); @@ -710,6 +707,56 @@ void penalizeDislikedHourGap() { likedGap1, likedGap2) .penalizesBy(2); } -} + @Test + @DisplayName("Penalty: room prescheduling blocks lab and studio lecture") + void roomPrescheduleConflict() { + EnumSet MWF = EnumSet.of(Days.MONDAY, Days.WEDNESDAY, Days.FRIDAY); + + BitSet blockedLessonBits = BitSetHelper.timeSlotBitSet(LocalTime.parse("9:00AM", formatter), 2, MWF); + BitSet blockedLessonBits2 = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", formatter), 2, MWF); + Timeslot blockedTimeslot = Timeslot.test_lecLabBitAndDays(1, blockedLessonBits, ConstraintTestHelper.EMPTY_BS, + MWF, ConstraintTestHelper.NO_DAYS); + Timeslot blockedTimeslot_LecLab = Timeslot.test_lecLabBitAndDays(2, blockedLessonBits2, blockedLessonBits, + MWF, MWF); + + Lesson blockedLessonLab = Lesson.test_buildLesson("room-blocked-lab", 2, "", "", "", + "3-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER,blockedTimeslot_LecLab, + ConstraintTestHelper.TEST_ROOM_PRESCHEDULED); + Lesson blockedStudio = Lesson.test_buildLesson("room-blocked-studio", 3, ConstraintTestHelper.DUMMY_STUDIO, + "", "", "1-0-0", Constants.COURSE_ID_BIMAP.get(ConstraintTestHelper.DUMMY_STUDIO), + ConstraintTestHelper.DUMMY_TEACHER, blockedTimeslot, ConstraintTestHelper.TEST_ROOM_PRESCHEDULED); + + constraintVerifier.verifyThat(TimetableConstraintProvider::roomPreschedule) + .given(blockedLessonLab, blockedStudio) + .penalizesBy(2); + } + + @Test + @DisplayName("No Penalty: room prescheduling skip branches") + void noRoomPrescheduleConflict() { + EnumSet MWF = EnumSet.of(Days.MONDAY, Days.WEDNESDAY, Days.FRIDAY); + + BitSet roomBlocked = BitSetHelper.timeSlotBitSet(LocalTime.parse("9:00AM", formatter), 2, MWF); + BitSet bs_10AM = BitSetHelper.timeSlotBitSet(LocalTime.parse("10:00AM", formatter), 2, MWF); + + Timeslot blockedTimeslot = Timeslot.test_lecLabBitAndDays(2, roomBlocked, ConstraintTestHelper.EMPTY_BS, + MWF, ConstraintTestHelper.NO_DAYS); + Timeslot ts_9AM_10AM_MWF = Timeslot.test_lecLabBitAndDays(2, roomBlocked, bs_10AM, MWF, MWF); + Timeslot ts_10AM_MWF = Timeslot.test_lecLabBitAndDays(2, bs_10AM, ConstraintTestHelper.EMPTY_BS, MWF, + ConstraintTestHelper.NO_DAYS); + Lesson openLessonLec = Lesson.test_buildLesson("room-open-lec", 1, "", "", "", + "3-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER, ts_9AM_10AM_MWF, + ConstraintTestHelper.TEST_ROOM_PRESCHEDULED); + Lesson lecOnlyRoomLesson = Lesson.test_buildLesson("room-lec-only", 3, "", "", "", + "1-0-0", 1, ConstraintTestHelper.DUMMY_TEACHER, blockedTimeslot, + ConstraintTestHelper.TEST_ROOM_PRESCHEDULED); + Lesson emptyRoomLesson = Lesson.test_buildLesson("room-empty", 4, "", "", "", + "1-0-0", 1, ConstraintTestHelper.DUMMY_TEACHER, blockedTimeslot, ConstraintTestHelper.DUMMY_ROOM); + + constraintVerifier.verifyThat(TimetableConstraintProvider::roomPreschedule) + .given(openLessonLec, lecOnlyRoomLesson, emptyRoomLesson) + .penalizesBy(0); + } +}