From 646ac738a5ead8fba1722968d3ee5849f32e6bb5 Mon Sep 17 00:00:00 2001
From: RandomCyberCoder
Date: Thu, 21 May 2026 11:47:53 -0700
Subject: [PATCH 01/10] moved objects for better file structure
---
.../main/java/org/acme/schooltimetabling/TimetableApp.java | 2 ++
.../apiCalls/surveyEndpoint/SurveyCalls.java | 2 +-
.../builders/teachers/policies/FacultyPolicy.java | 2 +-
.../org/acme/schooltimetabling/constants/Constants.java | 2 +-
.../org/acme/schooltimetabling/domain/lesson/Lesson.java | 2 +-
.../org/acme/schooltimetabling/domain/teacher/Faculty.java | 4 +---
.../{helperClasses => fileObjects}/PrescheduleObject.java | 2 +-
.../{helperClasses => fileObjects}/ScheduleConfig.java | 4 +++-
.../{helperClasses => fileObjects}/ScheduleFormat.java | 2 +-
.../acme/schooltimetabling/helperClasses/BitSetHelper.java | 2 +-
.../helperClasses/Generators/Generator.java | 3 +--
.../helperClasses/Generators/LessonGenerator.java | 6 +++---
.../helperClasses/Generators/RoomGenerator.java | 5 ++---
.../helperClasses/Generators/TeacherGenerator.java | 5 ++---
.../acme/schooltimetabling/helperClasses/ParseInput.java | 4 +++-
.../acme/schooltimetabling/helperClasses/ResultSaver.java | 1 +
.../solver/TimetableConstraintProvider.java | 2 +-
.../org/acme/schooltimetabling/TestClasses/TestBitset.java | 2 +-
.../acme/schooltimetabling/TestClasses/TestLessons.java | 4 ++--
.../org/acme/schooltimetabling/TestClasses/TestRooms.java | 2 +-
.../acme/schooltimetabling/TestClasses/TestTeachers.java | 7 +------
.../acme/schooltimetabling/TestClasses/TestTimeslot.java | 2 +-
.../schooltimetabling/solver/TestConstraintPropStudio.java | 2 +-
.../org/acme/schooltimetabling/solver/TestConstraints.java | 4 +---
24 files changed, 34 insertions(+), 39 deletions(-)
rename java/hello-world/src/main/java/org/acme/schooltimetabling/{helperClasses => fileObjects}/PrescheduleObject.java (98%)
rename java/hello-world/src/main/java/org/acme/schooltimetabling/{helperClasses => fileObjects}/ScheduleConfig.java (97%)
rename java/hello-world/src/main/java/org/acme/schooltimetabling/{helperClasses => fileObjects}/ScheduleFormat.java (96%)
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/TimetableApp.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/TimetableApp.java
index f1c69e31..aee071eb 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/TimetableApp.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/TimetableApp.java
@@ -14,6 +14,8 @@
import org.acme.schooltimetabling.domain.Timeslot;
import org.acme.schooltimetabling.domain.Timetable;
import org.acme.schooltimetabling.domain.teacher.Teacher;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
+import org.acme.schooltimetabling.fileObjects.ScheduleFormat;
import org.acme.schooltimetabling.helperClasses.*;
import org.acme.schooltimetabling.helperClasses.Generators.*;
import org.slf4j.Logger;
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/apiCalls/surveyEndpoint/SurveyCalls.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/apiCalls/surveyEndpoint/SurveyCalls.java
index bdb62ca1..70600e49 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/apiCalls/surveyEndpoint/SurveyCalls.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/apiCalls/surveyEndpoint/SurveyCalls.java
@@ -3,7 +3,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import org.acme.schooltimetabling.apiCalls.ApiConstants;
-import org.acme.schooltimetabling.helperClasses.ScheduleConfig;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import retrofit2.Call;
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/builders/teachers/policies/FacultyPolicy.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/builders/teachers/policies/FacultyPolicy.java
index 25ea16c9..fcd56d5b 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/builders/teachers/policies/FacultyPolicy.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/builders/teachers/policies/FacultyPolicy.java
@@ -3,7 +3,7 @@
import org.acme.schooltimetabling.constants.Constants;
import org.acme.schooltimetabling.constants.Days;
import org.acme.schooltimetabling.helperClasses.BitSetHelper;
-import org.acme.schooltimetabling.helperClasses.ScheduleConfig;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/constants/Constants.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/constants/Constants.java
index bc698a18..cc95fe80 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/constants/Constants.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/constants/Constants.java
@@ -6,7 +6,7 @@
import org.acme.schooltimetabling.apiCalls.teacherEndpoint.TeacherRecord;
import org.acme.schooltimetabling.helperClasses.Generators.Generator;
import org.acme.schooltimetabling.helperClasses.ParseInput;
-import org.acme.schooltimetabling.helperClasses.ScheduleConfig;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/lesson/Lesson.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/lesson/Lesson.java
index 533ab121..34f1efea 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/lesson/Lesson.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/lesson/Lesson.java
@@ -9,7 +9,7 @@
import org.acme.schooltimetabling.domain.Timeslot;
import org.acme.schooltimetabling.domain.teacher.Teacher;
import org.acme.schooltimetabling.helperClasses.BitSetHelper;
-import org.acme.schooltimetabling.helperClasses.ScheduleConfig;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 2e8a0253..b2db29f1 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
@@ -1,14 +1,12 @@
package org.acme.schooltimetabling.domain.teacher;
-import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
-import org.acme.schooltimetabling.TimetableApp;
import org.acme.schooltimetabling.builders.teachers.TeacherBuilder;
import org.acme.schooltimetabling.constants.Constants;
import org.acme.schooltimetabling.constants.Days;
import org.acme.schooltimetabling.constants.Preference;
import org.acme.schooltimetabling.helperClasses.BitSetHelper;
import org.acme.schooltimetabling.helperClasses.ParseInput;
-import org.acme.schooltimetabling.helperClasses.ScheduleConfig;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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/fileObjects/PrescheduleObject.java
similarity index 98%
rename from java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/PrescheduleObject.java
rename to java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/PrescheduleObject.java
index 7c7d86ca..884fa5c6 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/PrescheduleObject.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/PrescheduleObject.java
@@ -1,4 +1,4 @@
-package org.acme.schooltimetabling.helperClasses;
+package org.acme.schooltimetabling.fileObjects;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/ScheduleConfig.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/ScheduleConfig.java
similarity index 97%
rename from java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/ScheduleConfig.java
rename to java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/ScheduleConfig.java
index 2529cefb..cd6a1ead 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/ScheduleConfig.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/ScheduleConfig.java
@@ -1,6 +1,8 @@
-package org.acme.schooltimetabling.helperClasses;
+package org.acme.schooltimetabling.fileObjects;
import org.acme.schooltimetabling.constants.Days;
+import org.acme.schooltimetabling.helperClasses.BitSetHelper;
+import org.acme.schooltimetabling.helperClasses.ParseInput;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.time.Duration;
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/ScheduleFormat.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/ScheduleFormat.java
similarity index 96%
rename from java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/ScheduleFormat.java
rename to java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/ScheduleFormat.java
index 482ce187..db86e06e 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/ScheduleFormat.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/ScheduleFormat.java
@@ -1,4 +1,4 @@
-package org.acme.schooltimetabling.helperClasses;
+package org.acme.schooltimetabling.fileObjects;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.util.List;
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 ebdff419..7c2df511 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,7 +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.acme.schooltimetabling.fileObjects.PrescheduleObject.PrescheduledWindow;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/Generator.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/Generator.java
index b1e158a6..6b7f8cca 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/Generator.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/Generator.java
@@ -1,8 +1,7 @@
package org.acme.schooltimetabling.helperClasses.Generators;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
-import org.acme.schooltimetabling.helperClasses.ParseInput;
-import org.acme.schooltimetabling.helperClasses.ScheduleConfig;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 aa4c436c..1b0b2523 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
@@ -10,9 +10,9 @@
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.fileObjects.PrescheduleObject;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
+import org.acme.schooltimetabling.fileObjects.ScheduleFormat;
import org.acme.schooltimetabling.domain.teacher.Teacher;
import org.apache.commons.math3.util.Pair;
import org.slf4j.Logger;
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 34a706c0..23f7221d 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
@@ -4,9 +4,8 @@
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.acme.schooltimetabling.fileObjects.PrescheduleObject;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 d3ede1cf..914313c6 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
@@ -10,12 +10,11 @@
import org.acme.schooltimetabling.constants.Preference;
import org.acme.schooltimetabling.defaultTimes.DefaultTime;
import org.acme.schooltimetabling.defaultTimes.DefaultTimeRegistry;
-import org.acme.schooltimetabling.domain.teacher.Faculty;
import org.acme.schooltimetabling.helperClasses.BitSetHelper;
-import org.acme.schooltimetabling.helperClasses.PrescheduleObject;
+import org.acme.schooltimetabling.fileObjects.PrescheduleObject;
import org.acme.schooltimetabling.domain.teacher.Teacher;
import org.acme.schooltimetabling.helperClasses.ParseInput;
-import org.acme.schooltimetabling.helperClasses.ScheduleConfig;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 52ef8539..cd7e0219 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
@@ -3,9 +3,11 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.opencsv.CSVReader;
+import org.acme.schooltimetabling.fileObjects.PrescheduleObject;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
+import org.acme.schooltimetabling.fileObjects.ScheduleFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.yaml.snakeyaml.Yaml;
import java.io.*;
import java.nio.charset.StandardCharsets;
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 ccf57811..ea4ea8d1 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
@@ -16,6 +16,7 @@
import org.acme.schooltimetabling.domain.Timeslot;
import org.acme.schooltimetabling.domain.Timetable;
import org.acme.schooltimetabling.domain.teacher.Teacher;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.acme.schooltimetabling.helperClasses.Generators.LessonGenerator;
import org.acme.schooltimetabling.solver.justifications.WrongHoursAmountJustification;
import org.apache.commons.math3.util.Pair;
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 185f5087..980b8d00 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
@@ -11,7 +11,7 @@
import org.acme.schooltimetabling.domain.teacher.Teacher;
import org.acme.schooltimetabling.helperClasses.BitSetHelper;
import org.acme.schooltimetabling.helperClasses.Generators.LessonGenerator;
-import org.acme.schooltimetabling.helperClasses.ScheduleConfig;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.acme.schooltimetabling.solver.justifications.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestBitset.java b/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestBitset.java
index a92467af..4ae6251a 100644
--- a/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestBitset.java
+++ b/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestBitset.java
@@ -1,7 +1,7 @@
package org.acme.schooltimetabling.TestClasses;
import org.acme.schooltimetabling.constants.Days;
import org.acme.schooltimetabling.helperClasses.BitSetHelper;
-import org.acme.schooltimetabling.helperClasses.ScheduleConfig;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
diff --git a/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestLessons.java b/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestLessons.java
index b7ea4c01..f2973751 100644
--- a/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestLessons.java
+++ b/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestLessons.java
@@ -5,8 +5,8 @@
import org.acme.schooltimetabling.domain.lesson.Lesson;
import org.acme.schooltimetabling.helperClasses.Generators.*;
import org.acme.schooltimetabling.helperClasses.ParseInput;
-import org.acme.schooltimetabling.helperClasses.ScheduleConfig;
-import org.acme.schooltimetabling.helperClasses.ScheduleFormat;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
+import org.acme.schooltimetabling.fileObjects.ScheduleFormat;
import org.acme.schooltimetabling.domain.teacher.Teacher;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.function.Executable;
diff --git a/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestRooms.java b/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestRooms.java
index a6121091..f87ba0ad 100644
--- a/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestRooms.java
+++ b/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestRooms.java
@@ -3,7 +3,7 @@
import org.acme.schooltimetabling.constants.Constants;
import org.acme.schooltimetabling.domain.Room;
import org.acme.schooltimetabling.helperClasses.Generators.RoomGenerator;
-import org.acme.schooltimetabling.helperClasses.ScheduleConfig;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.function.Executable;
diff --git a/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestTeachers.java b/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestTeachers.java
index 7ed6be4f..f1ce35f6 100644
--- a/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestTeachers.java
+++ b/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestTeachers.java
@@ -1,20 +1,15 @@
package org.acme.schooltimetabling.TestClasses;
-import org.acme.schooltimetabling.constants.Constants;
import org.acme.schooltimetabling.builders.teachers.TeacherBuilder;
import org.acme.schooltimetabling.builders.teachers.policies.FacultyPolicy;
import org.acme.schooltimetabling.constants.Preference;
import org.acme.schooltimetabling.domain.teacher.Faculty;
-import org.acme.schooltimetabling.helperClasses.BitSetHelper;
-import org.acme.schooltimetabling.helperClasses.Generators.TeacherGenerator;
-import org.acme.schooltimetabling.helperClasses.ParseInput;
import org.acme.schooltimetabling.domain.teacher.Teacher;
-import org.acme.schooltimetabling.helperClasses.ScheduleConfig;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.acme.schooltimetabling.solver.ConstraintTestHelper;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.function.Executable;
import java.util.*;
diff --git a/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestTimeslot.java b/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestTimeslot.java
index 5f0d96c5..81d49146 100644
--- a/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestTimeslot.java
+++ b/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestTimeslot.java
@@ -2,7 +2,7 @@
import org.acme.schooltimetabling.domain.Timeslot;
import org.acme.schooltimetabling.helperClasses.Generators.TimeslotGenerator;
-import org.acme.schooltimetabling.helperClasses.ScheduleConfig;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
diff --git a/java/hello-world/src/test/java/org/acme/schooltimetabling/solver/TestConstraintPropStudio.java b/java/hello-world/src/test/java/org/acme/schooltimetabling/solver/TestConstraintPropStudio.java
index 943b9660..882376f9 100644
--- a/java/hello-world/src/test/java/org/acme/schooltimetabling/solver/TestConstraintPropStudio.java
+++ b/java/hello-world/src/test/java/org/acme/schooltimetabling/solver/TestConstraintPropStudio.java
@@ -7,7 +7,7 @@
import org.acme.schooltimetabling.domain.Timeslot;
import org.acme.schooltimetabling.domain.Timetable;
import org.acme.schooltimetabling.helperClasses.BitSetHelper;
-import org.acme.schooltimetabling.helperClasses.ScheduleConfig;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
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 6b8f4df2..68e8f334 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
@@ -10,12 +10,10 @@
import org.acme.schooltimetabling.domain.Room;
import org.acme.schooltimetabling.domain.Timeslot;
import org.acme.schooltimetabling.domain.Timetable;
-import org.acme.schooltimetabling.domain.teacher.Faculty;
import org.acme.schooltimetabling.domain.teacher.Teacher;
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.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
From 03d0b420ab24b276418272dcb29f8b71d7baf8de Mon Sep 17 00:00:00 2001
From: RandomCyberCoder
Date: Thu, 21 May 2026 13:58:53 -0700
Subject: [PATCH 02/10] done with the json file deserialization
---
.../fileObjects/CoursePatterns.java | 59 +++++++++++++++++++
.../fileObjects/LabPatterns.java | 12 ++++
.../helperClasses/ParseInput.java | 21 +++++++
3 files changed, 92 insertions(+)
create mode 100644 java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/CoursePatterns.java
create mode 100644 java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/LabPatterns.java
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/CoursePatterns.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/CoursePatterns.java
new file mode 100644
index 00000000..aa4a3a2d
--- /dev/null
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/CoursePatterns.java
@@ -0,0 +1,59 @@
+package org.acme.schooltimetabling.fileObjects;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CoursePatterns {
+ private Map defaultPattern = new LinkedHashMap<>();
+ private Map> teacherPreference = new LinkedHashMap<>();
+
+
+ public CoursePatterns() {
+ }
+
+ /**
+ * retrieves the pattern for a lab of the given course
+ * @param course name of the course whose lab pattern we want
+ * @return {@code null} if no course for the pattern was found or
+ */
+ public LabPatterns getCourseDefault(String course) {
+ if(course == null) return null;
+ return this.defaultPattern.get(course);
+ }
+
+ @JsonProperty("defaultPattern")
+ public void setDefaultPattern(Map defaultPattern) {
+ this.defaultPattern = defaultPattern;
+ }
+
+ /**
+ * Returns the preference of a course for an instructor if one can be found.
+ *
+ * @param course course whose preference we want
+ * @param name name of teacher whose preference we want
+ * @return preference of the course for the given instructor. If not preference was found,
+ * {@code null} will be returned.
+ */
+ public LabPatterns getTeacherPref(String course, String name) {
+ Map teacherMap;
+
+ if(course == null || name == null) return null;
+
+ //return null if no instructor preferences found or if no preference for the course
+ //is given; Otherwise, return the preference for the course
+ teacherMap = this.teacherPreference.get(name);
+ if(teacherMap == null) return null;
+ return teacherMap.get(course);
+ }
+
+ @JsonProperty("teacherPreference")
+ public void setTeacherPreference(Map> teacherPreference) {
+ this.teacherPreference = teacherPreference;
+ }
+
+
+}
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/LabPatterns.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/LabPatterns.java
new file mode 100644
index 00000000..7a2e08f3
--- /dev/null
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/LabPatterns.java
@@ -0,0 +1,12 @@
+package org.acme.schooltimetabling.fileObjects;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+
+public enum LabPatterns {
+ ONE, MULTIPLE;
+
+ @JsonCreator
+ public static LabPatterns parseString(String value){
+ return LabPatterns.valueOf(value.toUpperCase());
+ }
+}
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 cd7e0219..a1a959ce 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
@@ -3,6 +3,7 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.opencsv.CSVReader;
+import org.acme.schooltimetabling.fileObjects.CoursePatterns;
import org.acme.schooltimetabling.fileObjects.PrescheduleObject;
import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.acme.schooltimetabling.fileObjects.ScheduleFormat;
@@ -236,4 +237,24 @@ public static PrescheduleObject readPrescheduledFile(){
}
}
+ public static CoursePatterns readCoursePatterns(String filePath) {
+ try (InputStream inputStream = getResourceAsStream(filePath)) {
+ if (inputStream == null) {
+ throw new IllegalStateException("Could not find resource: " + filePath);
+ }
+ return readCoursePatterns(inputStream);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to close stream for: " + filePath, e);
+ }
+ }
+
+ public static CoursePatterns readCoursePatterns(InputStream inputStream) {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readValue(inputStream, CoursePatterns.class);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to read CoursePatterns JSON", e);
+ }
+ }
+
}
From b38dd4ea5a43d96774555c833f461e54f95707c6 Mon Sep 17 00:00:00 2001
From: RandomCyberCoder
Date: Wed, 27 May 2026 11:38:11 -0700
Subject: [PATCH 03/10] LessonBuilder made and Lesson object update made and
prefernces added
---
.../builders/lessons/LessonBuilder.java | 110 ++++++++
.../domain/lesson/Lesson.java | 72 +++--
.../fileObjects/CoursePatterns.java | 6 +-
.../fileObjects/ScheduleConfig.java | 6 +
.../Generators/LessonGenerator.java | 265 ++++++++++++++----
.../helperClasses/ResultSaver.java | 1 -
6 files changed, 384 insertions(+), 76 deletions(-)
create mode 100644 java/hello-world/src/main/java/org/acme/schooltimetabling/builders/lessons/LessonBuilder.java
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/builders/lessons/LessonBuilder.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/builders/lessons/LessonBuilder.java
new file mode 100644
index 00000000..f7d6d421
--- /dev/null
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/builders/lessons/LessonBuilder.java
@@ -0,0 +1,110 @@
+package org.acme.schooltimetabling.builders.lessons;
+
+import org.acme.schooltimetabling.constants.Constants;
+import org.acme.schooltimetabling.domain.lesson.Lesson;
+import org.acme.schooltimetabling.domain.teacher.Teacher;
+import org.acme.schooltimetabling.fileObjects.LabPatterns;
+
+
+/**
+ * Use this class to build {@code Lesson} objects. Using the constructors is getting
+ * kind of messy. The following must be set id, course name, course configuration,
+ * teacher object, and section. The following are optional: modifier ("") and
+ * labPattern (null if only lecture; {@link LabPatterns} if the configuration includes
+ * a lab/act)
+ *
+ * Note about setting id. that the id is for the lesson object.
+ * Don't mistake this with the section number of a courses lecture and/or lab.
+ *
+ * Note about setting the section number. If the course configuration for the lesson you
+ * are building has either a lecture or a lab/act, then set the section number normally. If the
+ * course configuration has both a lecture and a lab/act then just pass the lecture section number.
+ * The lesson will assume the section number after the lecture is reserved for the lab/act
+ */
+public class LessonBuilder {
+ //required build fields
+ private String id = null;
+ private String courseName = null;
+ private String courseConfig = null;
+ private Teacher teacherObj = null;
+ private Integer section = null;
+
+ //optional build fields
+ private String modifier = "";
+ private LabPatterns labPattern = null;
+
+
+ public LessonBuilder id(int id){
+ if(id < 0) throw new IllegalArgumentException("the id must be greater then zero");
+ this.id = courseName;
+ return this;
+ }
+
+ public LessonBuilder section(int lecSection) {
+ this.section = lecSection;
+ return this;
+ }
+
+ public LessonBuilder courseName(String courseName) {
+ this.courseName = courseName;
+ return this;
+ }
+
+ public LessonBuilder modifier(String modifier) {
+ if(Constants.SPECIAL_CODE_CONVERSION.containsKey(modifier)) modifier = Constants.SPECIAL_CODE_CONVERSION.get(modifier);
+ else if(!Constants.SPECIAL_CODE_CONVERSION.containsValue(modifier)) throw new IllegalArgumentException("Modifier is " +
+ "not a valid option. Look in Constants.java for valid modifiers.");
+ this.modifier = modifier;
+ return this;
+ }
+
+ public LessonBuilder courseConfig(String courseConfig) {
+ if(courseConfig == null || !validateConfig(courseConfig)) throw new IllegalArgumentException("A course config must be given in the format " +
+ "d-d-d. Where d is an digit and one of them must be non-zero and digit order is lec-lab-act" );
+ this.courseConfig = courseConfig;
+ return this;
+ }
+
+ private boolean validateConfig(String config){
+ return courseConfig.matches("^(?=.*[1-9])[0-9]-[0-9]-[0-9]$");
+ }
+
+ public LessonBuilder teacherObj(Teacher teacherObj) {
+ this.teacherObj = teacherObj;
+ return this;
+ }
+
+ public LessonBuilder labPattern(LabPatterns labPattern) {
+ this.labPattern = labPattern;
+ return this;
+ }
+
+ public LessonBuilder clear() {
+ id = null;
+ section = null;
+ courseName = null;
+ modifier = "";
+ courseConfig = null;
+ teacherObj = null;
+ labPattern = null;
+ return this;
+ }
+
+ public Lesson build() {
+ if (id == null || section == null || courseName == null || courseConfig == null || teacherObj == null) {
+ throw new IllegalCallerException(
+ "When calling build() you must have values set for id, section, courseName, courseConfig, and teacherObj"
+ );
+ }
+
+ //check if we need a default for the lab
+ String[] units = courseConfig.split("-");
+ int labUnits = Integer.parseInt(units[1]);
+ int actUnits = Integer.parseInt(units[2]);
+ if(labPattern == null && (labUnits != 0 || actUnits != 0)) labPattern = LabPatterns.MULTIPLE;
+
+ if(modifier == null) modifier = "";
+
+ return new Lesson(id, section, courseName, modifier, courseConfig, teacherObj, labPattern);
+ }
+}
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/lesson/Lesson.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/lesson/Lesson.java
index 34f1efea..38c514b5 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/lesson/Lesson.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/lesson/Lesson.java
@@ -8,6 +8,7 @@
import org.acme.schooltimetabling.domain.Room;
import org.acme.schooltimetabling.domain.Timeslot;
import org.acme.schooltimetabling.domain.teacher.Teacher;
+import org.acme.schooltimetabling.fileObjects.LabPatterns;
import org.acme.schooltimetabling.helperClasses.BitSetHelper;
import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.slf4j.Logger;
@@ -37,12 +38,6 @@ public class Lesson {
*/
@PlanningId
private String id;
- public String courseName, teacherName, modifiers;
- public int courseID, lecSection, labActSection;
- public boolean hasLecture, hasLabAct;
- public int lecHours, labActHours;
- public Teacher teacherObj;
-
@PlanningVariable
private Timeslot timeslot;
@@ -50,7 +45,14 @@ public class Lesson {
@PlanningVariable
private Room room;
- /**Don't for default constructor use*/
+ public String courseName, modifiers;
+ public int courseID, sectionNumber;
+ public boolean hasLecture, hasLabAct;
+ public int lecHours, labActHours;
+ public Teacher teacherObj;
+ private LabPatterns labPattern;
+
+ /**Prevent default constructor use*/
private Lesson() {
}
@@ -58,6 +60,8 @@ private Lesson() {
/*TODO change all planning variable IDs to a int/Integer as mentioned in the documentation
* https://docs.timefold.ai/timefold-solver/latest/using-timefold-solver/modeling-planning-problems#planningId*/
+ //TODO update to remove the teacherName field from all constructors. This will simplify the generators funcs
+
/* Test factory methods */
public static Lesson test_buildLesson(String Id, int lecSection, String courseName, String teacherName, String modifiers,
String courseConfig, int courseID, Teacher teacherObj, Timeslot timeslot, Room room){
@@ -65,9 +69,8 @@ public static Lesson test_buildLesson(String Id, int lecSection, String courseNa
, timeslot, room);
}
- /* Test constructor(s)*/
-
- private Lesson(String Id, int lecSection, String courseName, String teacherName, String modifiers,
+ //only used by test_buildLesson
+ private Lesson(String Id, int sectionNumber, String courseName, String teacherName, String modifiers,
String courseConfig, int courseID, Teacher teacherObj, Timeslot timeslot, Room room){
/*the courseConfig stream is assumed to come in the format
* E-L-A where E is the number of lecture units, L is the number of
@@ -85,11 +88,9 @@ private Lesson(String Id, int lecSection, String courseName, String teacherName,
this.hasLecture = lecUnits != NO_HOURS;
this.hasLabAct = labActHours != NO_HOURS;
this.id = Id;
- this.lecSection = lecSection;
- this.labActSection = this.hasLabAct ? lecSection + 1 : NO_SECTION;
+ this.sectionNumber = sectionNumber;
this.courseID = courseID;
this.courseName = courseName;
- this.teacherName = teacherName;
this.teacherObj = teacherObj;
this.modifiers = modifiers;
/*Populate planning variables*/
@@ -106,13 +107,13 @@ private Lesson(String Id, int lecSection, String courseName, String teacherName,
*
*
* @param Id unique ID for the lesson
- * @param lecSection unique section number for the lecture
+ * @param sectionNumber unique section number for the lecture
* @param courseName name of the course that will be taught for this lesson
* @param modifiers any course modifiers. If no modifiers pass a "" string
* @param courseConfig configuration of the courseName for this lesson
* @param teacherObj teacher object associated with the teacherName
*/
- public Lesson(String Id, int lecSection, String courseName, String modifiers,
+ public Lesson(String Id, int sectionNumber, String courseName, String modifiers,
String courseConfig, Teacher teacherObj){
/*the courseConfig stream is assumed to come in the format
* E-L-A where E is the number of lecture units, L is the number of
@@ -130,14 +131,18 @@ public Lesson(String Id, int lecSection, String courseName, String modifiers,
this.hasLecture = lecUnits != NO_HOURS;
this.hasLabAct = labActHours != NO_HOURS;
this.id = Id;
- this.lecSection = lecSection;
- this.labActSection = this.hasLabAct ? lecSection + 1 : NO_SECTION;
+ this.sectionNumber = sectionNumber;
this.courseID = Constants.COURSE_ID_BIMAP.get(courseName);
this.courseName = courseName;
this.teacherObj = teacherObj;
- /*TODO check if we can delete this field*/
- this.teacherName = teacherObj.getName();
this.modifiers = modifiers;
+ this.labPattern = null;
+ }
+
+ public Lesson(String Id, int lecSection, String courseName, String modifiers,
+ String courseConfig, Teacher teacherObj, LabPatterns labPattern){
+ this(Id, lecSection, courseName, modifiers, courseConfig, teacherObj);
+ this.labPattern = labPattern;
}
/**
@@ -150,11 +155,12 @@ public Lesson(String Id, int lecSection, String courseName, String modifiers,
public static Lesson dummyRecord(String courseName, String modifiers, Teacher teacher){
return new Lesson(courseName, modifiers, teacher);
}
+
+ //only used by dummyRecord();
private Lesson(String courseName, String modifiers, Teacher teacher){
this.courseName = courseName;
this.modifiers = modifiers;
this.teacherObj = teacher;
- this.teacherName = teacher.getName();
}
@Override
public String toString() {
@@ -187,12 +193,16 @@ public void setRoom(Room room) {
this.room = room;
}
+ public LabPatterns getLabPattern() {
+ return this.labPattern;
+ }
+
public String getCourseName() {
return courseName;
}
public String getTeacherName() {
- return teacherName;
+ return teacherObj.getName();
}
public String getModifiers() {
@@ -203,18 +213,32 @@ public int getCourseID() {
return courseID;
}
+ /**
+ * Retrieve a lec section if one exists
+ * @return lec section if one exists; otherwise -1
+ */
public int getLecSection() {
- return lecSection;
+ return this.hasLecture ? this.sectionNumber : -1;
}
+ /**
+ * Retrieve a lec/act section if one exists
+ * @return lab/act section if one exists; otherwise -1
+ */
public int getLabActSection() {
- return labActSection;
+ /*Note that if the lesson has both a lecture and a lab/act, the section number stored is assumed to be
+ for the lecture and the next section number is assumed to be reserved for the lab/act portion of the lesson
+ */
+ if(this.hasLabAct && this.hasLecture) return this.sectionNumber + 1;
+ if(this.hasLabAct) return this.sectionNumber;
+ return -1;
}
+ //I guess we technically don't need this with the current setup up of getLecSection()
public boolean isHasLecture() {
return hasLecture;
}
-
+ //I guess we technically don't need this with the current setup up of getLabActSection()
public boolean isHasLabAct() {
return hasLabAct;
}
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/CoursePatterns.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/CoursePatterns.java
index aa4a3a2d..1214ab33 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/CoursePatterns.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/CoursePatterns.java
@@ -35,7 +35,7 @@ public void setDefaultPattern(Map defaultPattern) {
*
* @param course course whose preference we want
* @param name name of teacher whose preference we want
- * @return preference of the course for the given instructor. If not preference was found,
+ * @return preference of the course for the given instructor. If no preference was found,
* {@code null} will be returned.
*/
public LabPatterns getTeacherPref(String course, String name) {
@@ -55,5 +55,7 @@ public void setTeacherPreference(Map> teacherPr
this.teacherPreference = teacherPreference;
}
-
+ static public CoursePatterns empty(){
+ return new CoursePatterns();
+ }
}
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/ScheduleConfig.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/ScheduleConfig.java
index cd6a1ead..f4b5c269 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/ScheduleConfig.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/ScheduleConfig.java
@@ -30,6 +30,7 @@ public class ScheduleConfig {
public String compressStart;
public String compressEnd;
public String prescheduledFileName;
+ public String patternsFileName;
//------------ values calculated ------------
private BitSet cpmrsInBs;
private BitSet cmprsOutBs;
@@ -157,4 +158,9 @@ public static String getPrescheduledFileName(){
if(HOLDER.scheduleConfig == null) throw new IllegalStateException("Configuration must be loaded");
return HOLDER.scheduleConfig.prescheduledFileName;
}
+
+ public static String getPatternsFileName(){
+ if(HOLDER.scheduleConfig == null) throw new IllegalStateException("Configuration must be loaded");
+ return HOLDER.scheduleConfig.patternsFileName;
+ }
}
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 1b0b2523..ef9a16fa 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,5 +1,6 @@
package org.acme.schooltimetabling.helperClasses.Generators;
+import org.acme.schooltimetabling.builders.lessons.LessonBuilder;
import org.acme.schooltimetabling.builders.teachers.TeacherBuilder;
import org.acme.schooltimetabling.builders.teachers.policies.DefaultTeachingPolicy;
import org.acme.schooltimetabling.builders.teachers.policies.FacultyPolicy;
@@ -9,30 +10,32 @@
import org.acme.schooltimetabling.constants.Preference;
import org.acme.schooltimetabling.domain.lesson.Lesson;
import org.acme.schooltimetabling.domain.teacher.Faculty;
+import org.acme.schooltimetabling.fileObjects.*;
import org.acme.schooltimetabling.helperClasses.ParseInput;
-import org.acme.schooltimetabling.fileObjects.PrescheduleObject;
-import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
-import org.acme.schooltimetabling.fileObjects.ScheduleFormat;
import org.acme.schooltimetabling.domain.teacher.Teacher;
import org.apache.commons.math3.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;
+import static org.acme.schooltimetabling.helperClasses.ParseInput.readCoursePatterns;
+
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 PrescheduleObject PRESCHED_TIMES = ScheduleConfig.getPrescheduledFileName() != null ?
ParseInput.readPrescheduledFile() : null;
+ private static final CoursePatterns COURSE_PATTERNS;
/**
* Keeps track of the next available section number available for a course
*/
private static final HashMap COURSE_SECTION_COUNTER;
/**
- * Holds the next available lesson ID. NOTE use the {@link #nxtLessonID()} function to retrieve the next
+ * Holds the next available lesson ID. NOTE use the {@link #nextLessonID()} function to retrieve the next
* available lesson ID rather than using this attribute directly.
*/
private static int lessonID = 1;
@@ -44,13 +47,30 @@ public class LessonGenerator extends Generator{
COURSE_SECTION_COUNTER = new HashMap<>();
for(String course: Constants.COURSE_CONFIGS.keySet()){
- if(!course.contains(DEPARTMENT)){
- continue;
+ if(course.toLowerCase().contains(DEPARTMENT)){
+ COURSE_SECTION_COUNTER.put(course, STARTING_SECTION_NUMBER);
}
- COURSE_SECTION_COUNTER.put(course, STARTING_SECTION_NUMBER);
}
- }
+ //read in course patterns if they exist; make an empty object if no file name was given
+ String patternsFileName = ScheduleConfig.getPatternsFileName();
+
+ if (patternsFileName == null) {
+ COURSE_PATTERNS = CoursePatterns.empty();
+ } else {
+ String filePath = "input/" + patternsFileName.strip();
+
+ try (InputStream inputStream = ParseInput.getResourceAsStream(filePath)) {
+ if (inputStream == null) {
+ COURSE_PATTERNS = CoursePatterns.empty();
+ } else {
+ COURSE_PATTERNS = readCoursePatterns(inputStream);
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to load course patterns from " + filePath, e);
+ }
+ }
+ }
@@ -72,16 +92,6 @@ public static ArrayList generateLessons(List schedules,
Lesson newLesson;
-
- /*create a list of courses to section number and remove courses not in the department we
- * are scheduling*/
- HashMap courseSectionCounter = new HashMap<>();
- for (String course : Constants.COURSE_CONFIGS.keySet()) {
- if (course.contains(DEPARTMENT)) {
- courseSectionCounter.put(course, STARTING_SECTION_NUMBER);
- }
- }
-
/*loop through the schedule (list of courses) a teacher is planned
* to teach*/
for (ScheduleFormat schedule : schedules) {
@@ -104,6 +114,10 @@ public static ArrayList generateLessons(List schedules,
String courseModifier = parsedCourse.getFirst();
String courseName = parsedCourse.getSecond();
String courseConfig = Constants.COURSE_CONFIGS.get(courseName);
+ if(courseConfig == null) throw new RuntimeException(String.format("Unable to find a configuration for " +
+ "the course '%s'", courseName));
+
+ //if we have a remote course we need to set it here
/*check if the course is marked for scheduling*/
if(skipCourse(courseModifier, courseName, courseConfig)){
@@ -113,10 +127,47 @@ public static ArrayList generateLessons(List schedules,
skippedLessons.add(newLesson);
}
else{
- newLesson = generateLesson(teacherHashMap, courseSectionCounter,
- course, teacherName);
- if(newLesson.isStudio()) proper_studio_detected = true;
- lessons.add(newLesson);
+ //TODO here we will have to update when splitting
+ //check if we have to split
+ LabPatterns defaultPattern = getLabPattern(courseName, teacherName);
+ if(LabPatterns.ONE.equals(defaultPattern)){
+ //if we are in this code block, the lesson must have a lab
+ //get the parsed config
+ int[] courseUnits = parseConfig(courseName);
+ final int LEC_POS = 0;
+ final int LAB_POS = 1;
+ final int ACT_POS = 2;
+
+ //check if this course has a lecture portion
+ if(courseUnits[LEC_POS] != 0){
+ lessons.add(
+ generateLesson(teacherHashMap, course, teacherName,
+ String.format("%d-0-0", courseUnits[LEC_POS]),
+ null)
+ );
+ }
+ /*TODO make sure we can make a lab only lesson.
+ checks below:
+ -the lesson geneorator take into account the section numbers well
+ -Does the lesson object take this into account well? done
+ + DO THIS BEFORE THE BELOW: Update the timeslots
+ +how does the above effect print out? It doesn't I already check before printing if the
+ lesson has lecture or lab. ACUTALLY update the timeslots first. This will determine how
+ the print out and the constraints will have to be updated
+ +check over constraints
+ */
+ lessons.add(
+ generateLesson(teacherHashMap, course, teacherName,
+ String.format("0-%d-%d", courseUnits[LAB_POS], courseUnits[ACT_POS]),
+ LabPatterns.ONE)
+ );
+ }
+ //if not we do the below
+ else{
+ newLesson = generateLesson(teacherHashMap, course, teacherName);
+ if(newLesson.isStudio()) proper_studio_detected = true;
+ lessons.add(newLesson);
+ }
}
}
}
@@ -127,47 +178,60 @@ public static ArrayList generateLessons(List schedules,
+ //TODO lets update so we stop passing teacherHashMap and teacherName. No point since they both get combined here; normalize this
/**
* Generates a new lesson for the course that a teacher will teach.
- * If the course has a modifier {@link Constants#SKIP_SCHEDULE Constants.SKIP_SCHEDULE} or configuration
- * {@link Constants#SKIP_CONFIGURATIONS Constants.SKIP_CONFIGURATIONS} contains it will not be scheduled
*
* @param teacherHashMap Hashmap of teacher's canon name mapped to its respective Teacher object
- * @param courseSectionCounter HashMap of a course name mapped to its next available section number
- * @param course name of the course whose lesson will be created for
+ * @param course course slug for the lesson that will be created; expected format is modifier-courseName or
+ * courseName
* @param teacherName name of the teacher who will teach the lesson
* @return returns a new lesson to be scheduled
* @see Constants#COURSE_CONFIGS
* @see Constants#COURSE_ID_BIMAP
*/
- private static Lesson generateLesson(Map teacherHashMap, Map courseSectionCounter,
- String course, String teacherName){
- String courseConfig;
- boolean hasLabOrAct;
- int sectionNumber;
- Teacher teacher;
-
+ private static Lesson generateLesson(Map teacherHashMap, String course, String teacherName){
+ Teacher teacher = getTeacher(teacherHashMap, teacherName);;
//first element = modifier; second element = course name
Pair courseDetails = getCourseDetails(course);
String courseModifier = courseDetails.getFirst();
String courseName = courseDetails.getSecond();
- courseConfig = Constants.COURSE_CONFIGS.get(courseName);
-
-
- //just in case we have some course config like "various"
- hasLabOrAct = determineLabOrAct(courseConfig);
- sectionNumber = courseSectionCounter.get(courseName);
-
- /*We increase the section counter by two if it has a lab because a lesson consists of its lecture
- * and its lab/act and a lab/act section number is separate from its respective lecture section
- * number*/
- courseSectionCounter.replace(courseName, (hasLabOrAct ? sectionNumber + 2 : sectionNumber + 1) );
+ String courseConfig = Constants.COURSE_CONFIGS.get(courseName);
+
+ if(courseConfig == null) throw new IllegalArgumentException(String.format("Couldn't find a section number for the " +
+ "course '%s'. Likely due to it having no configuration. Check the your configuration file.", courseName));
+
+ return new LessonBuilder()
+ .id(nextLessonID())
+ .section(nextSectionNum(courseName, courseConfig))
+ .courseName(courseName)
+ .courseConfig(courseConfig)
+ .modifier(courseModifier)
+ .teacherObj(teacher)
+ .labPattern(getLabPattern(courseName, courseModifier))
+ .build();
+ }
- teacher = getTeacher(teacherHashMap, teacherName);
+ /**
+ * used only for when we split a course into two lesson objects
+ */
+ private static Lesson generateLesson(Map teacherHashMap, String course, String teacherName,
+ String config, LabPatterns labPattern){
+ Teacher teacher = getTeacher(teacherHashMap, teacherName);;
+ //first element = modifier; second element = course name
+ Pair courseDetails = getCourseDetails(course);
+ String courseModifier = courseDetails.getFirst();
+ String courseName = courseDetails.getSecond();
- /*create class*/
- return new Lesson(Integer.toString(nxtLessonID()), sectionNumber, courseName,
- courseModifier, courseConfig, teacher);
+ return new LessonBuilder()
+ .id(nextLessonID())
+ .section(nextSectionNum(courseName, config))
+ .courseName(courseName)
+ .courseConfig(config)
+ .modifier(courseModifier)
+ .teacherObj(teacher)
+ .labPattern(labPattern)
+ .build();
}
@@ -377,10 +441,113 @@ private static boolean determineLabOrAct(String courseConfig){
* helper function to ensure lessonID is always incremented when retrieving the next available lesson ID
* @return next available lesson ID
*/
- private static int nxtLessonID(){
+ public static int nextLessonID(){
return lessonID++;
}
+ /** This method requires the config to determine if the course will need a section for a lecture,
+ * lab/act, or both. This function should be used for pulling section numbers. In the case that the
+ * configuration has both a lecture and lab, the section number returned will be for the lesson and the
+ * section number after the one returned is reserved for the lab/act portion of the course.
+ */
+ public static int nextSectionNum(String course, String courseConfig){
+ if(!COURSE_SECTION_COUNTER.containsKey(course)) throw new IllegalArgumentException(
+ String.format("Couldn't find a section number for the course '%s'. Likely due to it having no " +
+ "configuration. Check the your configuration file.", course));
+
+ int sectionNumber = COURSE_SECTION_COUNTER.get(course);
+
+ //update the section number accordingly
+ String[] units = courseConfig.split("-");
+ int lecUnits = Integer.parseInt(units[0]);
+ int labUnits = Integer.parseInt(units[1]);
+ int actUnits = Integer.parseInt(units[2]);
+ int sectionIncrement = 0;
+ if(lecUnits != 0) sectionIncrement++;
+ if(labUnits != 0 || actUnits != 0) sectionIncrement++;
+ COURSE_SECTION_COUNTER.put(course, sectionNumber + sectionIncrement);
+
+ return sectionNumber;
+ }
+
+ /**
+ * If no pattern for a lab has been found then we will default to the {@link LabPatterns#MULTIPLE} pattern.
+ * Three different possibilities will give considered: no pattern default, global default, teacher preference. With
+ * teacher preference having the highest authority and no pattern default having the lowest authority.
+ *
+ * @param course Name of the course whose labPattern we want
+ * @param name Canon name of the teacher
+ * @return Pattern we want for the lab; null if the course has no lab
+ */
+ public static LabPatterns getLabPattern(String course, String name){
+ final int LEC_POS = 0;
+ final int LAB_POS = 1;
+ final int ACT_POS = 2;
+ int[] parsedConfig = parseConfig(course);
+
+ //if only lecture return null;
+ if(parsedConfig[LAB_POS] == 0 && parsedConfig[ACT_POS] == 0) return null;
+
+ LabPatterns global = COURSE_PATTERNS.getCourseDefault(course);
+ LabPatterns specific = COURSE_PATTERNS.getTeacherPref(course, name);
+
+ //takes precedence
+ if (specific != null) {
+ return specific;
+ }
+ //fall back to global if the teacher has no preference
+ if (global != null) {
+ return global;
+ }
+
+ //if no lab pattern was found we default to the following
+ return LabPatterns.MULTIPLE;
+ }
+
+
+ /**
+ *
+ * @param course name of the course whose configuration will be parsed
+ * @return parsed config with the element order being: LECTURE, LAB, ACTIVITY.
+ */
+ public static int[] parseConfig(String course) {
+ final int PARTITIONS = 3;
+ String courseConfig = Constants.COURSE_CONFIGS.get(course);
+
+ if (courseConfig == null) {
+ throw new IllegalArgumentException(String.format(
+ "For the course '%s', no configuration was found",
+ course
+ ));
+ }
+
+ String[] parts = courseConfig.strip().split("-");
+
+ if (parts.length != PARTITIONS) {
+ throw new IllegalArgumentException(String.format(
+ "For the course '%s', a configuration in the form d-d-d must be present, where d is a nonnegative integer.",
+ course
+ ));
+ }
+
+ int[] parsedConfig = new int[PARTITIONS];
+
+ for (int i = 0; i < PARTITIONS; i++) {
+ try {
+ parsedConfig[i] = Integer.parseInt(parts[i].trim());
+ if (parsedConfig[i] < 0) {
+ throw new NumberFormatException("Negative value");
+ }
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(String.format(
+ "For the course '%s', a configuration in the form d-d-d must be present, where d is a nonnegative integer.",
+ course
+ ), e);
+ }
+ }
+
+ return parsedConfig;
+ }
public static List getSkippedLessons(){ return skippedLessons; }
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 ea4ea8d1..593b04e8 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
@@ -268,7 +268,6 @@ private int listViewPrntHlpr(XSSFSheet listSheet, List lessons, int rowI
for(Lesson lesson: lessons){
final Timeslot lsTs = lesson.getTimeslot();
- Timeslot.test_minSetUp("1");
if(lesson.isHasLecture()){
Row row = listSheet.createRow(rowIdx++);
String roomName = lesson.isStudio() ? lesson.getRoom().getName() : "University Room";
From 52fec7e5573fa7b9d15188b8a62a1065ae0cd429 Mon Sep 17 00:00:00 2001
From: RandomCyberCoder
Date: Sat, 30 May 2026 14:10:03 -0700
Subject: [PATCH 04/10] updated timeslot to the new spec, timeslot tests
updated
---
.../schooltimetabling/domain/Timeslot.java | 456 +++++++++++-------
.../domain/lesson/Lesson.java | 16 +-
.../Generators/TimeslotGenerator.java | 28 +-
.../WrongHoursAmountJustification.java | 4 +-
.../resources/constants/possibleTimes.csv | 2 +-
.../TestClasses/TestTimeslot.java | 317 ++++++++++--
.../src/test/resources/possibletimes.csv | 5 +-
7 files changed, 569 insertions(+), 259 deletions(-)
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Timeslot.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Timeslot.java
index 0c7f2dcb..3e82822e 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Timeslot.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Timeslot.java
@@ -1,43 +1,56 @@
package org.acme.schooltimetabling.domain;
import ai.timefold.solver.core.api.domain.lookup.PlanningId;
+import org.acme.schooltimetabling.constants.Constants;
import org.acme.schooltimetabling.constants.Days;
import org.acme.schooltimetabling.helperClasses.BitSetHelper;
+import java.time.Duration;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.BitSet;
import java.util.EnumSet;
public class Timeslot {
-
+ /*TODO checklist
+ * -convert names having lecture or lab/act to first slot and second slot; maybe rething names
+ * -update callers for getting previous lecture or lab/act bitsets
+ * Note that we shouldn't try to do any smart behavior like returning lab/act time
+ * if we have only one slot and also returning lecture time if we have only one slot this will be messy
+ * This should be handle by callers to decide how they want to handle this info
+ * -we might also want to update the parsers to reflect this; this should just boil down to the header names
+ * +Update the constraints that are accessing the times. We might want to look into adding another constraint
+ * so we can divided up responsibilities*/
@PlanningId
private String id;
private int ID;
- public LocalTime startTimeLec;
- public LocalTime endTimeLec;
- public BitSet lectureBitSet;
- public boolean hasLec;
- public boolean hasLabAct;
- public LocalTime startTimeLabAct;
- public LocalTime endTimeLabAct;
- public BitSet labActBitSet;
- public BitSet allTimesBitSet;
/*I should make these days into a class or something*/
- private EnumSet lecDays;
- private EnumSet nonLecDays;
+ private EnumSet daysSlot1;
+ public LocalTime startTimeSlot1;
+ public LocalTime endTimeSlot1;
+ public BitSet bitSetSlot1;
/**
* Amount of hours per day in portion one of this timeslot;
- * usually for lecture but possibly for studio space
+ * This slot can be either for lecture or lab/act time
*/
- public float lecHours;
+ public float hoursSlot1;
+ public boolean hasSlot2;
+ private EnumSet daysSlot2;
+ public LocalTime startTimeSlot2;
+ public LocalTime endTimeSlot2;
+ public BitSet bitSetSlot2;
/**
* Amount of hours per day in the second portion of this timeslot if any;
* currently used only for lab/act
*/
- private float labActHours;
- private static final float FLOAT_TIME_DELTA = 0.01f;
+ private float hoursSlot2;
+ public BitSet allTimesBitSet;
+
+ //static vars
+ private static final float EPSILON = 0.01f;
private static final int MINUTES_PER_HOUR = 60;
+ private static final LocalTime EARLIEST_TIME = LocalTime.parse("7:00AM", Constants.TIME_FORMATTER);
+ private static final LocalTime LATEST_TIME = LocalTime.parse("10:00PM", Constants.TIME_FORMATTER);
/**
* Default constructor shouldn't be accessed
@@ -46,7 +59,7 @@ private Timeslot() {
}
- /* Test factory method lesson builders */
+ /*----------------------------- Test Stuff ----------------------------- */
/**
* test factory method
@@ -87,157 +100,254 @@ public static Timeslot test_minSetUp(String id){
/**
* test constructor
- * @param ID
- * @param lecDays
- * @param labDays
*/
- private Timeslot(int ID, EnumSet lecDays, EnumSet labDays){
+ private Timeslot(int ID, EnumSet daysSlot1, EnumSet daysSlot2){
+ if(daysSlot1.isEmpty()) throw new IllegalArgumentException("Slot1 must always include a time");
this.id = Integer.toString(ID);
- this.lecDays = lecDays.clone();
- this.nonLecDays = labDays.clone();
- this.hasLec = !this.lecDays.isEmpty();
- this.hasLabAct = !this.nonLecDays.isEmpty();
+ this.daysSlot1 = daysSlot1.clone();
+ this.daysSlot2 = daysSlot2.clone();
+ this.hasSlot2 = !this.daysSlot2.isEmpty();
}
/**
* test constructor
- * @param ID
- * @param lecBitSet
- * @param labActBitSet
- * @param lecDays
- * @param labActDays
*/
- private Timeslot(int ID, BitSet lecBitSet, BitSet labActBitSet, EnumSet lecDays , EnumSet labActDays){
+ private Timeslot(int ID, BitSet lecBitSet, BitSet bitSetSlot2, EnumSet daysSlot1, EnumSet labActDays){
+ if(daysSlot1.isEmpty()) throw new IllegalArgumentException("Slot1 must always include a time");
+
this.id = Integer.toString(ID);
this.ID = ID;
- this.lectureBitSet = (BitSet) lecBitSet.clone();
- this.labActBitSet = (BitSet) labActBitSet.clone();
- this.lecDays = lecDays.clone();
- this.nonLecDays = labActDays.clone();
- this.hasLec = !this.lecDays.isEmpty();
- this.hasLabAct = !this.nonLecDays.isEmpty();
+ this.bitSetSlot1 = (BitSet) lecBitSet.clone();
+ this.bitSetSlot2 = (BitSet) bitSetSlot2.clone();
+ this.daysSlot1 = daysSlot1.clone();
+ this.daysSlot2 = labActDays.clone();
+ this.hasSlot2 = !this.daysSlot2.isEmpty();
BitSet allBitSet = new BitSet();
- allBitSet.or(this.lectureBitSet);
- allBitSet.or(this.labActBitSet);
+ allBitSet.or(this.bitSetSlot1);
+ allBitSet.or(this.bitSetSlot2);
this.allTimesBitSet = allBitSet;
- this.lecHours = lecBitSet.cardinality() /(float)lecDays.size() / 2f;
- this.labActHours = labActBitSet.cardinality() /(float)labActDays.size() /2f;
+ this.hoursSlot1 = lecBitSet.cardinality() /(float) daysSlot1.size() / 2f;
+ this.hoursSlot2 = bitSetSlot2.cardinality() /(float)labActDays.size() /2f;
}
/**
* test constructor
- * @param id
*/
private Timeslot(String id){
this.id = id;
}
+ /*----------------------------- End of Test Stuff ----------------------------- */
- public Timeslot(int ID, String days, String startTime, String endTime, float lecHours, float totalHours,
- String days2, String startTime2, String endTime2, float lab_hours)
- throws Exception{
- final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("h:mma");
- //by default a timeslot has neither
- this.hasLec = false;
- this.hasLabAct = false;
- this.lectureBitSet = new BitSet();
- this.labActBitSet = new BitSet();
- this.allTimesBitSet = new BitSet();
- this.lecDays = EnumSet.noneOf(Days.class);
- this.nonLecDays = EnumSet.noneOf(Days.class);
+ /**
+ * Main constructor for creating timeslots
+ */
+ public Timeslot(int ID, String days, String startTime, String endTime, float startHours, float totalHours,
+ String days2, String startTime2, String endTime2, float totalHours2){
+ if(days.isBlank()) throw new IllegalArgumentException("The first portion of the timeslot config entry must be " +
+ "populated");
+
+ final DateTimeFormatter FORMATTER = Constants.TIME_FORMATTER;
+
+ //parse raw inputs
+ EnumSet daysSlot1 = parseDayString(days.strip());
+ LocalTime startTimeSlot1 = LocalTime.parse(startTime.strip(), FORMATTER);
+ LocalTime endTimeSlot1 = LocalTime.parse(endTime.strip(), FORMATTER);
+
+ EnumSet daysSlot2 = parseDayString(days2.strip());
+ LocalTime startTimeSlot2;
+ LocalTime endTimeSlot2;
+ if(daysSlot2.isEmpty()) startTimeSlot2 = endTimeSlot2 = null;
+ else{
+ startTimeSlot2 = LocalTime.parse(startTime2.strip(), FORMATTER);
+ endTimeSlot2 = LocalTime.parse(endTime2.strip(), FORMATTER);
+ }
+
+ //validate parsed raw inputs
+ validate(daysSlot1, startTimeSlot1, endTimeSlot1, startHours, totalHours,
+ daysSlot2, startTimeSlot2, endTimeSlot2, totalHours2);
this.ID = ID;
this.id = String.valueOf(ID);
- //check the first subslot
- if(!days.isBlank()){
- this.hasLec = true;
- if(days.contains("M")){
- lecDays.add(Days.MONDAY);
- }
- if(days.contains("T")){
- lecDays.add(Days.TUESDAY);
+ //process parsed inputs after we have validated them;
+ if(days2.isEmpty()){
+ long startMinutes = Math.round(startHours * 60f);
+ long totalMinutes = Math.round(totalHours * 60f);
+
+ //scenario where starthours is the whole timeslot
+ if(startMinutes == totalMinutes){
+ this.hasSlot2 = false;
+
+ this.daysSlot1 = daysSlot1;
+ this.startTimeSlot1 = startTimeSlot1;
+ this.endTimeSlot1 = endTimeSlot1;
+ this.hoursSlot1 = totalHours;
+ this.bitSetSlot1 = BitSetHelper.timeSlotBitSet(
+ this.startTimeSlot1, Math.round(this.hoursSlot1 * 2f), this.daysSlot1
+ );
+
+ this.daysSlot2 = EnumSet.noneOf(Days.class);
+ this.startTimeSlot2 = null;
+ this.endTimeSlot2 = null;
+ this.hoursSlot2 = 0;
+ this.bitSetSlot2 = new BitSet();
+
+ this.allTimesBitSet = new BitSet();
+ this.allTimesBitSet.or(this.bitSetSlot1);
}
- if(days.contains("W")){
- lecDays.add(Days.WEDNESDAY);
- }
- if(days.contains("R")){
- lecDays.add(Days.THURSDAY);
- }
- if(days.contains("F")){
- lecDays.add(Days.FRIDAY);
- }
-
- this.lecHours = lecHours;
-
- /*check start and end time for the lab and possibly for the lab/activity */
- this.startTimeLec = LocalTime.parse(startTime.trim(), FORMATTER);
- /*LocalTime is immutable so doing this won't modify startTimeLec*/
- this.endTimeLec = startTimeLec.plusMinutes(Math.round(MINUTES_PER_HOUR * this.lecHours));
- /* initialize the lecture BitSet, multiply lecHours by 2 because we need then number of 30 minute blocks */
- this.lectureBitSet = BitSetHelper.timeSlotBitSet(this.startTimeLec, Math.round(this.lecHours * 2),
- this.lecDays);
-
- //if the total hours is greater than the lecHours then there is extra time for lab in this subslot
- /* We do the following comparison instead of lec_hours == total_hours because of floating point errors */
- if(Math.abs(lecHours - totalHours) > FLOAT_TIME_DELTA){
- this.hasLabAct = true;
- /* end time of the timeslot is when the lab will end */
- this.endTimeLabAct = LocalTime.parse(endTime.trim(), FORMATTER);
- /* When computing the start time of the lab/activity we are assuming that the lab/activity takes equally long.
- * This doesn't necessarily start right after the time the lecture ends. We could have a gap (i.e. like during
- * Tuesday and Thursday)*/
- this.startTimeLabAct = this.endTimeLabAct.minusMinutes(Math.round(MINUTES_PER_HOUR * this.lecHours));
- /* create BitSet for the lab/lec */
- this.nonLecDays = this.lecDays;
- this.labActBitSet = BitSetHelper.timeSlotBitSet(this.startTimeLabAct, Math.round(this.lecHours * 2),
- this.nonLecDays);
- this.labActHours = this.lecHours;
+ //other scenario when it doesn't
+ //this means we have the slot1 and slot2 populated
+ else{
+ this.hasSlot2 = true;
+
+ this.daysSlot1 = daysSlot1;
+ this.hoursSlot1 = startHours;
+ this.startTimeSlot1 = startTimeSlot1;
+ this.endTimeSlot1 = this.startTimeSlot1.plusMinutes(Math.round(this.hoursSlot1 * 60f));
+ this.bitSetSlot1 = BitSetHelper.timeSlotBitSet(
+ this.startTimeSlot1, Math.round(this.hoursSlot1 * 2f), this.daysSlot1
+ );
+
+ this.daysSlot2 = daysSlot1;
+ this.hoursSlot2 = startHours;
+ this.startTimeSlot2 = endTimeSlot1.minusMinutes(Math.round(this.hoursSlot2 * 60f));
+ this.endTimeSlot2 = endTimeSlot1;
+ this.bitSetSlot2 = BitSetHelper.timeSlotBitSet(
+ this.startTimeSlot2, Math.round(this.hoursSlot2 * 2f), this.daysSlot2
+ );
+
+ this.allTimesBitSet = BitSetHelper.timeSlotBitSet(
+ this.startTimeSlot1, Math.round(totalHours * 2f), this.daysSlot1
+ );
}
}
+ else{
+ this.hasSlot2 = true;
+
+ this.daysSlot1 = daysSlot1;
+ this.startTimeSlot1 = startTimeSlot1;
+ this.endTimeSlot1 = endTimeSlot1;
+ this.hoursSlot1 = totalHours;
+ this.bitSetSlot1 = BitSetHelper.timeSlotBitSet(
+ this.startTimeSlot1, Math.round(this.hoursSlot1 * 2), this.daysSlot1
+ );
+
+ this.daysSlot2 = daysSlot2;
+ this.startTimeSlot2 = startTimeSlot2;
+ this.endTimeSlot2 = endTimeSlot2;
+ this.hoursSlot2 = totalHours2;
+ this.bitSetSlot2 = BitSetHelper.timeSlotBitSet(
+ this.startTimeSlot2, Math.round(this.hoursSlot2 * 2), this.daysSlot2
+ );
+
+ this.allTimesBitSet = new BitSet();
+ this.allTimesBitSet.or(this.bitSetSlot1);
+ this.allTimesBitSet.or(this.bitSetSlot2);
+ }
+ }
- if(!days2.isBlank()){
- //sanity check; avoids accidentally having two lab/act timeslots
- if(this.hasLabAct) throw new RuntimeException("When creating a timeslot an error occurred. Timeslot had a " +
- "lab activity set in the first and second sub slot.");
- this.hasLabAct = true;
- if(days2.contains("M")){
- this.nonLecDays.add(Days.MONDAY);
+ private EnumSet parseDayString(String parse){
+ EnumSet res = EnumSet.noneOf(Days.class);
+
+ for(char day : parse.toUpperCase().toCharArray()){
+ if(day == 'M'){
+ res.add(Days.MONDAY);
+ }
+ else if(day == 'T'){
+ res.add(Days.TUESDAY);
+ }
+ else if(day == 'W'){
+ res.add(Days.WEDNESDAY);
}
- if(days2.contains("T")){
- this.nonLecDays.add(Days.TUESDAY);
+ else if(day == 'R'){
+ res.add(Days.THURSDAY);
}
- if(days2.contains("W")){
- this.nonLecDays.add(Days.WEDNESDAY);
+ else if(day == 'F'){
+ res.add(Days.FRIDAY);
}
- if(days2.contains("R")){
- this.nonLecDays.add(Days.THURSDAY);
+ else{
+ throw new RuntimeException("Days can only include (case-insensitive) 'm' [Monday], " +
+ "'t' [Tuesdays], 'W' [Wednesday], 'r' [Thursday], 'f' [Friday].");
}
- if(days2.contains("F")){
- this.nonLecDays.add(Days.FRIDAY);
+ }
+
+ return res;
+ }
+
+ /**
+ * used to validate the parsed values given in the constructor, it will throw errors if an invalid setup
+ * is found
+ */
+ private static void validate(
+ EnumSet days, LocalTime startTime, LocalTime endTime, float startHours, float totalHours,
+ EnumSet days2, LocalTime startTime2, LocalTime endTime2, float totalHours2) {
+
+ //validate individual slots
+ validatePartition(days, startTime, endTime, startHours, totalHours, "first");
+ if(!days2.isEmpty()){
+ validatePartition(days2, startTime2, endTime2, totalHours2, totalHours2, "second");
+
+ //make sure that the bitsets don't overlap if the second portion was given
+ BitSet part1 = BitSetHelper.timeSlotBitSet(startTime, Math.round(startHours * 2f), days);
+ BitSet part2 = BitSetHelper.timeSlotBitSet(startTime2, Math.round(totalHours * 2f), days2);
+ if(part1.intersects(part2)) throw new RuntimeException("Both portions given in the timeslot config overlap");
+ //if we use the second partition then the first time slots total hours should all be used up by startHours
+ try{
+ validatePartition(days, startTime, endTime, startHours, startHours, "first");
+ } catch (Exception e) {
+ throw new RuntimeException("If using both partitions for a timeslot setup, the first partition should " +
+ "have startHours equal totalHours.");
}
+ }
+ }
+
+ private static void validatePartition(
+ EnumSet days, LocalTime startTime, LocalTime endTime, float startHours, float totalHours, String label) {
+ long actualMinutes = Duration.between(startTime, endTime).toMinutes();
+ long expectedMinutes = Math.round(totalHours * 60f);
- /*Hours per day for second timeslot are assumed to be dedicated towards labs/acts */
- this.labActHours = lab_hours;
- this.startTimeLabAct = LocalTime.parse(startTime2.trim(), FORMATTER);
- this.endTimeLabAct = LocalTime.parse(endTime2.trim(), FORMATTER);
- this.labActBitSet = BitSetHelper.timeSlotBitSet(this.startTimeLabAct, Math.round(lab_hours * 2),
- this.nonLecDays);
+ if(days == null || days.isEmpty()) {
+ throw new IllegalArgumentException(label + " days must not be empty");
}
- //sanity check if user overlapped the lec and lab/act sub timeslots
- if(this.labActBitSet.intersects(this.lectureBitSet)) throw new RuntimeException("Error creating timeslot. " +
- "The lecture and lab/act times overlap");
+ if(!startTime.isBefore(endTime)) {
+ throw new IllegalArgumentException("The end time of a timeslot must come after the start time");
+ }
- if(!days.isBlank()){
- /*we assume that the whole block will be occupied by whoever is assigned it*/
- this.allTimesBitSet.or(BitSetHelper.timeSlotBitSet(this.startTimeLec, Math.round(totalHours * 2),
- this.lecDays));
+ if(startTime.isBefore(EARLIEST_TIME) || endTime.isAfter(LATEST_TIME)){
+ throw new IllegalArgumentException("The time specified must be between 7AM and 10PM inclusive");
}
- //OR with lab/act bitset if the lab/act time was in the second subplot instead of the first
- if(!days2.isBlank()){
- this.allTimesBitSet.or(this.labActBitSet);
+
+ if(startHours <= EPSILON || totalHours <= EPSILON || startHours > totalHours + EPSILON){
+ throw new IllegalArgumentException("Start hours and total hours must be positive. " +
+ "Total hours must be at least start hours.");
}
+
+ if(!isMultipleOfHalfHour(startHours) || !isMultipleOfHalfHour(totalHours)) {
+ throw new IllegalArgumentException("Start hours must be in 0.5 hour increments");
+ }
+
+ if(!approximatelyEqual(startHours, totalHours) && startHours * 2f > totalHours + EPSILON) {
+ throw new IllegalArgumentException(
+ "Start hours must either equal total hours or be at most half of total hours. Note everything must" +
+ "be a multiple of .5");
+ }
+
+ if (actualMinutes != expectedMinutes) {
+ throw new IllegalArgumentException(
+ label + " partition duration must match total hours. Expected " + totalHours +
+ " hours but found " + (actualMinutes / 60f) + " hours.");
+ }
+ }
+
+ private static boolean isMultipleOfHalfHour(float hours) {
+ float doubled = hours * 2f;
+ return Math.abs(doubled - Math.round(doubled)) < EPSILON;
+ }
+
+ private static boolean approximatelyEqual(float a, float b) {
+ return Math.abs(a - b) < EPSILON;
}
@@ -252,45 +362,32 @@ public Timeslot(int ID, String days, String startTime, String endTime, float lec
* as not continuous;
*/
public boolean isContinuous(){
- if(lecDays.size() != 1 || !nonLecDays.isEmpty()) return false;
-
- BitSet potentialBitSet = lectureBitSet;
- int indexFirstBit = potentialBitSet.nextSetBit(0);
- int cardinality = potentialBitSet.cardinality();
- BitSet mask = new BitSet();
- mask.set(indexFirstBit, indexFirstBit + cardinality);
- mask.and(potentialBitSet);
-
- if(cardinality <= 2) return false;
-
- return mask.cardinality() == cardinality;
-
+ return daysSlot1.size() == 1 && !hasSlot2 && bitSetSlot1.cardinality() > 2;
}
@Override
public String toString() {
- return this.toStringLec() + " ---- " + this.toStringLabAct();
+ return this.toStringSlot1() + " ---- " + this.toStringSlot2();
}
- public String toStringLec(){
- if(!this.hasLec) return "No lec time";
- return getString(lecDays, startTimeLec, endTimeLec);
+ private String toStringSlot1(){
+ return getString(daysSlot1, startTimeSlot1, endTimeSlot1);
}
- public String toStringLabAct(){
- if(!this.hasLabAct) return "No lab/act time";
+ private String toStringSlot2(){
+ if(!this.hasSlot2) return "No second slot";
- return getString(nonLecDays, startTimeLabAct, endTimeLabAct);
+ return getString(daysSlot2, startTimeSlot2, endTimeSlot2);
}
- private String getString(EnumSet nonLecDays, LocalTime startTime, LocalTime endTime) {
+ private String getString(EnumSet days, LocalTime startTime, LocalTime endTime) {
StringBuilder buildLecRep = new StringBuilder();
- if(nonLecDays.contains(Days.MONDAY)) buildLecRep.append('M');
- if(nonLecDays.contains(Days.TUESDAY)) buildLecRep.append('T');
- if(nonLecDays.contains(Days.WEDNESDAY)) buildLecRep.append('W');
- if(nonLecDays.contains(Days.THURSDAY)) buildLecRep.append('R');
- if(nonLecDays.contains(Days.FRIDAY)) buildLecRep.append('F');
+ if(days.contains(Days.MONDAY)) buildLecRep.append('M');
+ if(days.contains(Days.TUESDAY)) buildLecRep.append('T');
+ if(days.contains(Days.WEDNESDAY)) buildLecRep.append('W');
+ if(days.contains(Days.THURSDAY)) buildLecRep.append('R');
+ if(days.contains(Days.FRIDAY)) buildLecRep.append('F');
buildLecRep.append(" ");
buildLecRep.append(startTime.toString());
@@ -311,36 +408,32 @@ public int getID() {
return ID;
}
- public LocalTime getStartTimeLec() {
- return startTimeLec;
- }
-
- public LocalTime getEndTimeLec() {
- return endTimeLec;
+ public LocalTime getStartTimeSlot1() {
+ return startTimeSlot1;
}
- public BitSet getLectureBitSet() {
- return lectureBitSet;
+ public LocalTime getEndTimeSlot1() {
+ return endTimeSlot1;
}
- public boolean isHasLec() {
- return hasLec;
+ public BitSet getBitSetSlot1() {
+ return bitSetSlot1;
}
- public boolean isHasLabAct() {
- return hasLabAct;
+ public boolean isHasSlot2() {
+ return hasSlot2;
}
- public LocalTime getStartTimeLabAct() {
- return startTimeLabAct;
+ public LocalTime getStartTimeSlot2() {
+ return startTimeSlot2;
}
- public LocalTime getEndTimeLabAct() {
- return endTimeLabAct;
+ public LocalTime getEndTimeSlot2() {
+ return endTimeSlot2;
}
- public BitSet getLabActBitSet() {
- return labActBitSet;
+ public BitSet getBitSetSlot2() {
+ return bitSetSlot2;
}
public BitSet getAllTimesBitSet() {
@@ -351,24 +444,23 @@ public BitSet getAllTimesBitSet() {
/**
* @return number of lec hours the timeslot can accommodate per day
*/
- public float getLecHours() {
- if(!this.hasLec) return 0;
- else return lecHours;
+ public float getHoursSlot1() {
+ return hoursSlot1;
}
/**
* @return number of lab/act hours the timeslot can accommodate per day
*/
- public float getLabActHours(){
- if(!this.hasLabAct) return 0;
- else return labActHours;
+ public float getHoursSlot2(){
+ if(!this.hasSlot2) return 0;
+ else return hoursSlot2;
}
- public EnumSet getLecDays() {
- return lecDays;
+ public EnumSet getDaysSlot1() {
+ return daysSlot1;
}
- public EnumSet getNonLecDays() {
- return nonLecDays;
+ public EnumSet getDaysSlot2() {
+ return daysSlot2;
}
}
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/lesson/Lesson.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/lesson/Lesson.java
index 38c514b5..874b1811 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/lesson/Lesson.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/lesson/Lesson.java
@@ -221,6 +221,7 @@ public int getLecSection() {
return this.hasLecture ? this.sectionNumber : -1;
}
+ //TODO UPDATE
/**
* Retrieve a lec/act section if one exists
* @return lab/act section if one exists; otherwise -1
@@ -259,6 +260,7 @@ public boolean isStudio(){
return Constants.STUDIO_STYLE_COURSES.contains(this.courseName);
}
+
/**
* Masks the lecture bitset with {@link BitSetHelper#PRIME_TIME_MASK}
* @return a BitSet with any lecture bits set during primetime
@@ -266,7 +268,8 @@ public boolean isStudio(){
public BitSet maskInPT(){
//if the lesson has gone through the solver the timeslot will be null;
if(timeslot == null) return null;
- BitSet lecBitSet = timeslot.getLectureBitSet();
+ if(!hasLecture) return new BitSet();
+ BitSet lecBitSet = timeslot.getBitSetSlot1();
BitSet copy = lecBitSet.get(0, lecBitSet.length());
copy.and(BitSetHelper.PRIME_TIME_MASK);
return copy;
@@ -279,7 +282,8 @@ public BitSet maskInPT(){
public BitSet maskOutPT(){
//if the lesson has gone through the solver the timeslot will be null;
if(timeslot == null) return null;
- BitSet lecBitSet = timeslot.getLectureBitSet();
+ if(!hasLecture) return new BitSet();
+ BitSet lecBitSet = timeslot.getBitSetSlot1();
BitSet copy = lecBitSet.get(0, lecBitSet.length());
copy.and(BitSetHelper.NON_PRIME_TIME_MASK);
return copy;
@@ -293,8 +297,8 @@ public BitSet maskOutPT(){
public BitSet maskInCmprs(){//if the lesson has gone through the solver the timeslot will be null;
if(timeslot == null) return null;
BitSet copy = new BitSet();
- copy.or(timeslot.getLectureBitSet());
- copy.or(timeslot.getLabActBitSet());
+ copy.or(timeslot.getBitSetSlot1());
+ copy.or(timeslot.getBitSetSlot2());
copy.and(ScheduleConfig.getCompressInMask());
return copy;
}
@@ -309,8 +313,8 @@ public BitSet maskOutCmprs(){
//if the lesson has gone through the solver the timeslot will be null;
if(timeslot == null) return null;
BitSet copy = new BitSet();
- copy.or(timeslot.getLectureBitSet());
- copy.or(timeslot.getLabActBitSet());
+ copy.or(timeslot.getBitSetSlot1());
+ copy.or(timeslot.getBitSetSlot2());
copy.and(ScheduleConfig.getCompressOutMask());
return copy;
}
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/TimeslotGenerator.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/TimeslotGenerator.java
index 149deb8c..72fc0425 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/TimeslotGenerator.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/helperClasses/Generators/TimeslotGenerator.java
@@ -50,35 +50,35 @@ private static Timeslot generateTimeslot(int ID, HashMap timeslo
String days = Optional.ofNullable(timeslotMap.get("days"))
.filter(s -> !s.isBlank())
.orElse("").strip();
- String timeStart = timeslotMap.get("time_start");
- String timeEnd = timeslotMap.get("time_end");
- float lectureHours = Float.parseFloat(
- Optional.ofNullable(timeslotMap.get("lecture_hours"))
+ String startTime = timeslotMap.get("startTime");
+ String endTime = timeslotMap.get("endTime");
+ float startHours = Float.parseFloat(
+ Optional.ofNullable(timeslotMap.get("startHours"))
.filter(s -> !s.isBlank())
.orElse("-1"));
float totalHours = Float.parseFloat(
- Optional.ofNullable(timeslotMap.get("total_hours"))
+ Optional.ofNullable(timeslotMap.get("totalHours"))
.filter(s -> !s.isBlank())
.orElse("-1"));
- String labDays = Optional.ofNullable(timeslotMap.get("lab_days"))
+ String days2 = Optional.ofNullable(timeslotMap.get("days2"))
.filter(s -> !s.isBlank())
.orElse("").strip();
- String labStart = timeslotMap.get("lab_start");
- String labEnd = timeslotMap.get("lab_end");
- float labHours = Float.parseFloat(
- Optional.ofNullable(timeslotMap.get("lab_hours"))
+ String startTime2 = timeslotMap.get("startTime2");
+ String endTime2 = timeslotMap.get("endTime2");
+ float totalHours2 = Float.parseFloat(
+ Optional.ofNullable(timeslotMap.get("totalHours2"))
.filter(s -> !s.isBlank())
.orElse("-1")
);
try {
- newTimeslot = new Timeslot(ID, days, timeStart, timeEnd, lectureHours, totalHours,
- labDays, labStart, labEnd, labHours);
+ newTimeslot = new Timeslot(ID, days, startTime, endTime, startHours, totalHours,
+ days2, startTime2, endTime2, totalHours2);
}
catch (Exception e){
LOGGER.error("Terminating program. Failed to generate a timeslot with the following (check format):\n " +
- String.format("days: %s; timeStart: %s; timeEnd: %s; lecHours: %s; totalHours: %s; labDays: %s"
- ,days, timeStart, timeEnd, lectureHours, totalHours, labDays));
+ String.format("days: %s; startTime: %s; endTime: %s; startHours: %s; totalHours: %s; days2: %s"
+ ,days, startTime, endTime, startHours, totalHours, days2));
e.printStackTrace();
System.exit(ParseInput.PROGRAM_FAILURE);
}
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/solver/justifications/WrongHoursAmountJustification.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/solver/justifications/WrongHoursAmountJustification.java
index 761242f1..3a674c88 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/solver/justifications/WrongHoursAmountJustification.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/solver/justifications/WrongHoursAmountJustification.java
@@ -6,9 +6,9 @@
public record WrongHoursAmountJustification(Lesson lesson1, String description) implements ConstraintJustification{
public WrongHoursAmountJustification(Lesson lesson1){
this(lesson1,
- "Lesson name %s; Lesson has lab or act %B; timeslot has only lesson %B"
+ "Lesson name %s; Lesson has lab or act %B; timeslot has only one slot %B"
.formatted(lesson1.getCourseName(), lesson1.isHasLabAct(),
- lesson1.getTimeslot().isHasLec() && !lesson1.getTimeslot().isHasLabAct()
+ lesson1.getTimeslot().isHasSlot2()
));
}
}
diff --git a/java/hello-world/src/main/resources/constants/possibleTimes.csv b/java/hello-world/src/main/resources/constants/possibleTimes.csv
index 861de1c1..63986cfa 100644
--- a/java/hello-world/src/main/resources/constants/possibleTimes.csv
+++ b/java/hello-world/src/main/resources/constants/possibleTimes.csv
@@ -1,4 +1,4 @@
-days, time_start, time_end, lecture_hours, total_hours, lab_days, lab_start, lab_end, lab_hours
+days, startTime, endTime, startHours, totalHours, days2, startTime2, endTime2, totalHours2
MWF, 7:00AM, 9:00AM, 1, 2,,,,
MWF, 8:00AM, 10:00AM, 1, 2,,,,
MWF, 9:00AM, 11:00AM, 1, 2,,,,
diff --git a/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestTimeslot.java b/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestTimeslot.java
index 81d49146..8a37b2e1 100644
--- a/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestTimeslot.java
+++ b/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestTimeslot.java
@@ -1,19 +1,28 @@
package org.acme.schooltimetabling.TestClasses;
+import net.bytebuddy.asm.Advice;
+import org.acme.schooltimetabling.constants.Constants;
+import org.acme.schooltimetabling.constants.Days;
import org.acme.schooltimetabling.domain.Timeslot;
+import org.acme.schooltimetabling.helperClasses.BitSetHelper;
import org.acme.schooltimetabling.helperClasses.Generators.TimeslotGenerator;
import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
+import org.junit.Ignore;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.*;
public class TestTimeslot {
- final int LIST_LEN = 6;
+ final int LIST_LEN = 7;
+ private static final float EPSILON = 0.01f;
+ private static DateTimeFormatter FORMATTER;
static ArrayList timeslotList;
private static final int MONDAY_OFFSET = 0;
private static final int TUESDAY_OFFSET = 30;
@@ -27,6 +36,7 @@ static void setUp(){
String YAML_FILE_PATH = "constants/config.yaml";
ScheduleConfig.loadConfig(YAML_FILE_PATH);
timeslotList = TimeslotGenerator.generateTimeslots("possibletimes.csv");
+ FORMATTER = Constants.TIME_FORMATTER;
}
@Test
@@ -36,100 +46,303 @@ void checkLength(){
}
@Test
- @DisplayName("Checking for amount of labs")
- void checkLabs(){
- AtomicInteger numOfLabs = new AtomicInteger();
- timeslotList.forEach(timeslot -> {if(timeslot.hasLabAct) {
- numOfLabs.getAndIncrement();
- }});
- assertEquals(5, numOfLabs.get());
- }
-
- @Test
- @DisplayName("Checking # of only lectures")
+ @DisplayName("Checking # of slot2")
void checkLectures(){
- AtomicInteger numOfLabs = new AtomicInteger();
- timeslotList.forEach(timeslot -> {if(timeslot.hasLec && !timeslot.hasLabAct) {
- numOfLabs.getAndIncrement();
+ AtomicInteger numOfSlot2 = new AtomicInteger();
+ timeslotList.forEach(timeslot -> {if(
+ timeslot.hasSlot2) {
+ numOfSlot2.getAndIncrement();
}});
- assertEquals(1, numOfLabs.get());
+ assertEquals(5, numOfSlot2.get());
}
+ //DONE
@Test
@DisplayName("Timeslot 1")
void checkTimeslot1(){
Timeslot timeslot = timeslotList.get(0);
- assertEquals(timeslot.lecHours, 1);
- assertEquals(timeslot.labActBitSet.cardinality(), 6);
- assertEquals(timeslot.lectureBitSet.cardinality(), 6);
- assertEquals(timeslot.allTimesBitSet.cardinality(), 12);
- assertTrue(timeslot.hasLec);
- assertTrue(timeslot.hasLabAct);
+ EnumSet expectedDays = EnumSet.of(Days.MONDAY, Days.WEDNESDAY, Days.FRIDAY);
+ BitSet expected1 = BitSetHelper.timeSlotBitSet(
+ LocalTime.parse("7:00AM", FORMATTER),
+ 2,
+ expectedDays
+ );
+
+ BitSet expected2 = BitSetHelper.timeSlotBitSet(
+ LocalTime.parse("8:00AM", FORMATTER),
+ 2,
+ expectedDays
+ );
+
+ BitSet expectedTotal = new BitSet();
+ expectedTotal.xor(expected1);
+ expectedTotal.xor(expected2);
+ assertEquals(1, timeslot.getHoursSlot1(), EPSILON);
+ assertEquals(6, timeslot.bitSetSlot1.cardinality());
+ assertEquals(LocalTime.parse("7:00AM", FORMATTER), timeslot.getStartTimeSlot1());
+ assertEquals(LocalTime.parse("8:00AM", FORMATTER), timeslot.getEndTimeSlot1());
+ assertEquals(expectedDays, timeslot.getDaysSlot1());
+ assertEquals(expected1, timeslot.getBitSetSlot1());
+
+ assertEquals(1, timeslot.getHoursSlot2(), EPSILON);
+ assertEquals(6, timeslot.bitSetSlot2.cardinality());
+ assertEquals(LocalTime.parse("8:00AM", FORMATTER), timeslot.getStartTimeSlot2());
+ assertEquals(LocalTime.parse("9:00AM", FORMATTER), timeslot.getEndTimeSlot2());
+ assertEquals(expectedDays, timeslot.getDaysSlot2());
+ assertEquals(expected2, timeslot.getBitSetSlot2());
+
+ assertEquals(12, timeslot.allTimesBitSet.cardinality());
+ assertEquals(expectedTotal, timeslot.getAllTimesBitSet());
+ assertTrue(timeslot.isHasSlot2());
+ assertFalse(timeslot.isContinuous());
}
@Test
@DisplayName("Timeslot 2")
void checkTimeslot2(){
Timeslot timeslot = timeslotList.get(1);
- assertEquals(timeslot.lecHours, 1);
- assertEquals(timeslot.lectureBitSet.cardinality(), 4);
- assertEquals(timeslot.labActBitSet.cardinality(), 4);
- assertEquals(timeslot.allTimesBitSet.cardinality(), 8);
- assertTrue(timeslot.hasLec);
- assertTrue(timeslot.hasLabAct);
+ EnumSet days1 = EnumSet.of(Days.MONDAY, Days.WEDNESDAY);
+ BitSet bitSet1 = BitSetHelper.timeSlotBitSet(
+ LocalTime.parse("8:00AM", FORMATTER),
+ 2,
+ days1
+ );
+
+ EnumSet days2 = EnumSet.of(Days.FRIDAY);
+ BitSet bitSet2 = BitSetHelper.timeSlotBitSet(
+ LocalTime.parse("8:00AM", FORMATTER),
+ 4,
+ days2
+ );
+
+ BitSet expectedTotal = new BitSet();
+ expectedTotal.xor(bitSet1);
+ expectedTotal.xor(bitSet2);
+
+ assertEquals(1, timeslot.getHoursSlot1(), EPSILON);
+ assertEquals(4, timeslot.bitSetSlot1.cardinality());
+ assertEquals(LocalTime.parse("8:00AM", FORMATTER), timeslot.getStartTimeSlot1());
+ assertEquals(LocalTime.parse("9:00AM", FORMATTER), timeslot.getEndTimeSlot1());
+ assertEquals(days1, timeslot.getDaysSlot1());
+ assertEquals(bitSet1, timeslot.getBitSetSlot1());
+
+ assertEquals(2, timeslot.getHoursSlot2(), EPSILON);
+ assertEquals(4, timeslot.bitSetSlot2.cardinality());
+ assertEquals(LocalTime.parse("8:00AM", FORMATTER), timeslot.getStartTimeSlot2());
+ assertEquals(LocalTime.parse("10:00AM", FORMATTER), timeslot.getEndTimeSlot2());
+ assertEquals(days2, timeslot.getDaysSlot2());
+ assertEquals(bitSet2, timeslot.getBitSetSlot2());
+ assertEquals(8, timeslot.allTimesBitSet.cardinality());
+ assertEquals(expectedTotal, timeslot.getAllTimesBitSet());
+ assertTrue(timeslot.isHasSlot2());
+ assertFalse(timeslot.isContinuous());
}
@Test
@DisplayName("Timeslot 3")
void checkTimeslot3(){
Timeslot timeslot = timeslotList.get(2);
- assertEquals(timeslot.lecHours, 1);
- assertEquals(timeslot.lectureBitSet.cardinality(), 4);
- assertEquals(timeslot.labActBitSet.cardinality(), 9);
- assertEquals(timeslot.allTimesBitSet.cardinality(), 13);
- assertTrue(timeslot.hasLec);
- assertTrue(timeslot.hasLabAct);
+
+ EnumSet days1 = EnumSet.of(Days.MONDAY, Days.WEDNESDAY);
+ BitSet bitSet1 = BitSetHelper.timeSlotBitSet(
+ LocalTime.parse("8:00AM", FORMATTER),
+ 2,
+ days1
+ );
+
+ EnumSet days2 = EnumSet.of(Days.MONDAY, Days.TUESDAY, Days.FRIDAY);
+ BitSet bitSet2 = BitSetHelper.timeSlotBitSet(
+ LocalTime.parse("9:00AM", FORMATTER),
+ 3,
+ days2
+ );
+
+ BitSet expectedTotal = new BitSet();
+ expectedTotal.xor(bitSet1);
+ expectedTotal.xor(bitSet2);
+
+ assertEquals(1, timeslot.getHoursSlot1(), EPSILON);
+ assertEquals(4, timeslot.bitSetSlot1.cardinality());
+ assertEquals(LocalTime.parse("8:00AM", FORMATTER), timeslot.getStartTimeSlot1());
+ assertEquals(LocalTime.parse("9:00AM", FORMATTER), timeslot.getEndTimeSlot1());
+ assertEquals(days1, timeslot.getDaysSlot1());
+ assertEquals(bitSet1, timeslot.getBitSetSlot1());
+
+ assertEquals(1.5, timeslot.getHoursSlot2(), EPSILON);
+ assertEquals(9, timeslot.bitSetSlot2.cardinality());
+ assertEquals(LocalTime.parse("9:00AM", FORMATTER), timeslot.getStartTimeSlot2());
+ assertEquals(LocalTime.parse("10:30AM", FORMATTER), timeslot.getEndTimeSlot2());
+ assertEquals(days2, timeslot.getDaysSlot2());
+ assertEquals(bitSet2, timeslot.getBitSetSlot2());
+
+ assertEquals(13, timeslot.allTimesBitSet.cardinality());
+ assertEquals(expectedTotal, timeslot.getAllTimesBitSet());
+ assertTrue(timeslot.isHasSlot2());
+ assertFalse(timeslot.isContinuous());
}
@Test
@DisplayName("Timeslot 4")
void checkTimeslot4(){
Timeslot timeslot = timeslotList.get(3);
- assertEquals(timeslot.lecHours, 1);
- assertEquals(timeslot.lectureBitSet.cardinality(), 8);
- assertEquals(timeslot.labActBitSet.cardinality(), 0);
- assertEquals(timeslot.allTimesBitSet.cardinality(), 8);
- assertTrue(timeslot.hasLec);
- assertFalse(timeslot.hasLabAct);
+ EnumSet days1 = EnumSet.of(Days.MONDAY, Days.TUESDAY, Days.WEDNESDAY, Days.THURSDAY);
+ BitSet bitSet1 = BitSetHelper.timeSlotBitSet(
+ LocalTime.parse("7:00AM", FORMATTER),
+ 2,
+ days1
+ );
+
+ EnumSet days2 = EnumSet.noneOf(Days.class);
+ BitSet bitSet2 = new BitSet();
+
+ BitSet expectedTotal = new BitSet();
+ expectedTotal.xor(bitSet1);
+ expectedTotal.xor(bitSet2);
+
+ assertEquals(1, timeslot.getHoursSlot1(), EPSILON);
+ assertEquals(8, timeslot.bitSetSlot1.cardinality());
+ assertEquals(LocalTime.parse("7:00AM", FORMATTER), timeslot.getStartTimeSlot1());
+ assertEquals(LocalTime.parse("8:00AM", FORMATTER), timeslot.getEndTimeSlot1());
+ assertEquals(days1, timeslot.getDaysSlot1());
+ assertEquals(bitSet1, timeslot.getBitSetSlot1());
+
+ assertEquals(0, timeslot.getHoursSlot2(), EPSILON);
+ assertEquals(0, timeslot.bitSetSlot2.cardinality());
+ assertEquals(days2, timeslot.getDaysSlot2());
+ assertEquals(bitSet2, timeslot.getBitSetSlot2());
+
+ assertEquals(8, timeslot.allTimesBitSet.cardinality());
+ assertEquals(expectedTotal, timeslot.getAllTimesBitSet());
+ assertFalse(timeslot.isHasSlot2());
+ assertFalse(timeslot.isContinuous());
}
@Test
@DisplayName("Timeslot 5")
void checkTimeslot5(){
Timeslot timeslot = timeslotList.get(4);
- assertEquals(timeslot.lecHours, 1.5);
- assertEquals(timeslot.lectureBitSet.cardinality(), 6);
- assertEquals(timeslot.labActBitSet.cardinality(), 6);
- assertEquals(timeslot.allTimesBitSet.cardinality(), 16);
- assertTrue(timeslot.hasLec);
- assertTrue(timeslot.hasLabAct);
+ EnumSet days = EnumSet.of(Days.TUESDAY, Days.THURSDAY);
+
+ BitSet bitSet1 = BitSetHelper.timeSlotBitSet(
+ LocalTime.parse("9:30AM", FORMATTER),
+ 3,
+ days
+ );
+
+ BitSet bitSet2 = BitSetHelper.timeSlotBitSet(
+ LocalTime.parse("12:00PM", FORMATTER),
+ 3,
+ days
+ );
+
+
+
+ BitSet expectedTotal = BitSetHelper.timeSlotBitSet(
+ LocalTime.parse("9:30AM", FORMATTER),
+ 8,
+ days
+ );
+
+ assertEquals(1.5, timeslot.getHoursSlot1(), EPSILON);
+ assertEquals(6, timeslot.bitSetSlot1.cardinality());
+ assertEquals(LocalTime.parse("9:30AM", FORMATTER), timeslot.getStartTimeSlot1());
+ assertEquals(LocalTime.parse("11:00AM", FORMATTER), timeslot.getEndTimeSlot1());
+ assertEquals(days, timeslot.getDaysSlot1());
+ assertEquals(bitSet1, timeslot.getBitSetSlot1());
+
+ assertEquals(1.5, timeslot.getHoursSlot2(), EPSILON);
+ assertEquals(6, timeslot.bitSetSlot2.cardinality());
+ assertEquals(LocalTime.parse("12:00PM", FORMATTER), timeslot.getStartTimeSlot2());
+ assertEquals(LocalTime.parse("1:30PM", FORMATTER), timeslot.getEndTimeSlot2());
+ assertEquals(days, timeslot.getDaysSlot2());
+ assertEquals(bitSet2, timeslot.getBitSetSlot2());
+
+ assertEquals(16, timeslot.allTimesBitSet.cardinality());
+ assertEquals(expectedTotal, timeslot.getAllTimesBitSet());
+ assertTrue(timeslot.isHasSlot2());
+ assertFalse(timeslot.isContinuous());
}
@Test
@DisplayName("Timeslot 6")
void checkTimeslot6(){
Timeslot timeslot = timeslotList.get(5);
- assertEquals(timeslot.lecHours, 0);
- assertEquals(timeslot.lectureBitSet.cardinality(), 0);
- assertEquals(timeslot.labActBitSet.cardinality(), 6);
- assertEquals(timeslot.allTimesBitSet.cardinality(), 6);
- assertFalse(timeslot.hasLec);
- assertTrue(timeslot.hasLabAct);
+ EnumSet days1 = EnumSet.of(Days.FRIDAY);
+ BitSet bitSet1 = BitSetHelper.timeSlotBitSet(
+ LocalTime.parse("9:00AM", FORMATTER),
+ 6,
+ days1
+ );
+
+ EnumSet days2 = EnumSet.noneOf(Days.class);
+ BitSet bitSet2 = new BitSet();
+
+ BitSet expectedTotal = new BitSet();
+ expectedTotal.xor(bitSet1);
+ expectedTotal.xor(bitSet2);
+
+ assertEquals(3, timeslot.getHoursSlot1(), EPSILON);
+ assertEquals(6, timeslot.bitSetSlot1.cardinality());
+ assertEquals(LocalTime.parse("9:00AM", FORMATTER), timeslot.getStartTimeSlot1());
+ assertEquals(LocalTime.parse("12:00PM", FORMATTER), timeslot.getEndTimeSlot1());
+ assertEquals(days1, timeslot.getDaysSlot1());
+ assertEquals(bitSet1, timeslot.getBitSetSlot1());
+
+ assertEquals(0, timeslot.getHoursSlot2(), EPSILON);
+ assertEquals(0, timeslot.bitSetSlot2.cardinality());
+ assertEquals(days2, timeslot.getDaysSlot2());
+ assertEquals(bitSet2, timeslot.getBitSetSlot2());
+
+ assertEquals(6, timeslot.allTimesBitSet.cardinality());
+ assertEquals(expectedTotal, timeslot.getAllTimesBitSet());
+ assertFalse(timeslot.isHasSlot2());
+ assertTrue(timeslot.isContinuous());
+ }
+
+ @Test
+ @DisplayName("Timeslot 7")
+ void checkTimeslot7(){
+ Timeslot timeslot = timeslotList.get(6);
+
+ EnumSet days1 = EnumSet.of(Days.FRIDAY);
+ BitSet bitSet1 = BitSetHelper.timeSlotBitSet(
+ LocalTime.parse("9:00AM", FORMATTER),
+ 6,
+ days1
+ );
+
+ EnumSet days2 = EnumSet.of(Days.FRIDAY);
+ BitSet bitSet2 = BitSetHelper.timeSlotBitSet(
+ LocalTime.parse("12:00PM", FORMATTER),
+ 6,
+ days2
+ );
+
+ BitSet expectedTotal = new BitSet();
+ expectedTotal.xor(bitSet1);
+ expectedTotal.xor(bitSet2);
+
+ assertEquals(3, timeslot.getHoursSlot1(), EPSILON);
+ assertEquals(6, timeslot.bitSetSlot1.cardinality());
+ assertEquals(LocalTime.parse("9:00AM", FORMATTER), timeslot.getStartTimeSlot1());
+ assertEquals(LocalTime.parse("12:00PM", FORMATTER), timeslot.getEndTimeSlot1());
+ assertEquals(days1, timeslot.getDaysSlot1());
+ assertEquals(bitSet1, timeslot.getBitSetSlot1());
+
+ assertEquals(3, timeslot.getHoursSlot2(), EPSILON);
+ assertEquals(6, timeslot.bitSetSlot2.cardinality());
+ assertEquals(days2, timeslot.getDaysSlot2());
+ assertEquals(bitSet2, timeslot.getBitSetSlot2());
+
+ assertEquals(12, timeslot.allTimesBitSet.cardinality());
+ assertEquals(expectedTotal, timeslot.getAllTimesBitSet());
+ assertTrue(timeslot.isHasSlot2());
+ assertFalse(timeslot.isContinuous());
}
}
diff --git a/java/hello-world/src/test/resources/possibletimes.csv b/java/hello-world/src/test/resources/possibletimes.csv
index 863f8b33..07adde14 100644
--- a/java/hello-world/src/test/resources/possibletimes.csv
+++ b/java/hello-world/src/test/resources/possibletimes.csv
@@ -1,7 +1,8 @@
-days, time_start, time_end, lecture_hours, total_hours, lab_days, lab_start, lab_end, lab_hours
+days, startTime, endTime, startHours, totalHours, days2, startTime2, endTime2, totalHours2
MWF, 7:00AM, 9:00AM, 1, 2,,,,
MW, 8:00AM, 9:00AM, 1, 1, F, 8:00AM, 10:00AM, 2
MW, 8:00AM, 9:00AM, 1, 1, MTF, 9:00AM, 10:30AM, 1.5
MTWR, 7:00AM, 8:00AM, 1, 1,,,,
TR, 9:30AM, 1:30PM, 1.5, 4,,,,
-, , , , , F, 9:00AM, 12:00PM, 3
\ No newline at end of file
+F, 9:00AM, 12:00PM, 3, 3,,,,
+F, 9:00AM, 12:00PM, 3, 3,F,12:00PM,3:00PM,3
\ No newline at end of file
From fcaca1acfd492e5d8387b0adc9c2c11706a254ca Mon Sep 17 00:00:00 2001
From: RandomCyberCoder
Date: Sat, 30 May 2026 21:41:58 -0700
Subject: [PATCH 05/10] update to lesson creation
---
.../builders/lessons/LessonBuilder.java | 4 +--
.../Generators/LessonGenerator.java | 29 +++++++------------
2 files changed, 13 insertions(+), 20 deletions(-)
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/builders/lessons/LessonBuilder.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/builders/lessons/LessonBuilder.java
index f7d6d421..80638631 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/builders/lessons/LessonBuilder.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/builders/lessons/LessonBuilder.java
@@ -36,7 +36,7 @@ public class LessonBuilder {
public LessonBuilder id(int id){
if(id < 0) throw new IllegalArgumentException("the id must be greater then zero");
- this.id = courseName;
+ this.id = String.valueOf(id);
return this;
}
@@ -66,7 +66,7 @@ public LessonBuilder courseConfig(String courseConfig) {
}
private boolean validateConfig(String config){
- return courseConfig.matches("^(?=.*[1-9])[0-9]-[0-9]-[0-9]$");
+ return config.matches("^(?=.*[1-9])[0-9]-[0-9]-[0-9]$");
}
public LessonBuilder teacherObj(Teacher teacherObj) {
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 ef9a16fa..035c0a11 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
@@ -95,8 +95,8 @@ public static ArrayList generateLessons(List schedules,
/*loop through the schedule (list of courses) a teacher is planned
* to teach*/
for (ScheduleFormat schedule : schedules) {
- //TODO just make the tacher object and pass that instead of the teacher name
teacherName = schedule.getName();
+ Teacher teacher = getTeacher(teacherHashMap, teacherName);
/*This is a list courses that will be scheduled*/
List coursesToSchedule;
@@ -114,6 +114,7 @@ public static ArrayList generateLessons(List schedules,
String courseModifier = parsedCourse.getFirst();
String courseName = parsedCourse.getSecond();
String courseConfig = Constants.COURSE_CONFIGS.get(courseName);
+
if(courseConfig == null) throw new RuntimeException(String.format("Unable to find a configuration for " +
"the course '%s'", courseName));
@@ -127,7 +128,6 @@ public static ArrayList generateLessons(List schedules,
skippedLessons.add(newLesson);
}
else{
- //TODO here we will have to update when splitting
//check if we have to split
LabPatterns defaultPattern = getLabPattern(courseName, teacherName);
if(LabPatterns.ONE.equals(defaultPattern)){
@@ -141,7 +141,7 @@ public static ArrayList generateLessons(List schedules,
//check if this course has a lecture portion
if(courseUnits[LEC_POS] != 0){
lessons.add(
- generateLesson(teacherHashMap, course, teacherName,
+ generateLesson(teacher, course,
String.format("%d-0-0", courseUnits[LEC_POS]),
null)
);
@@ -150,21 +150,21 @@ public static ArrayList generateLessons(List schedules,
checks below:
-the lesson geneorator take into account the section numbers well
-Does the lesson object take this into account well? done
- + DO THIS BEFORE THE BELOW: Update the timeslots
+ - DO THIS BEFORE THE BELOW: Update the timeslots
+ +check over constraints
+how does the above effect print out? It doesn't I already check before printing if the
lesson has lecture or lab. ACUTALLY update the timeslots first. This will determine how
the print out and the constraints will have to be updated
- +check over constraints
*/
lessons.add(
- generateLesson(teacherHashMap, course, teacherName,
+ generateLesson(teacher, course,
String.format("0-%d-%d", courseUnits[LAB_POS], courseUnits[ACT_POS]),
LabPatterns.ONE)
);
}
//if not we do the below
else{
- newLesson = generateLesson(teacherHashMap, course, teacherName);
+ newLesson = generateLesson(teacher, course);
if(newLesson.isStudio()) proper_studio_detected = true;
lessons.add(newLesson);
}
@@ -176,22 +176,17 @@ public static ArrayList generateLessons(List schedules,
}
-
-
- //TODO lets update so we stop passing teacherHashMap and teacherName. No point since they both get combined here; normalize this
/**
* Generates a new lesson for the course that a teacher will teach.
*
- * @param teacherHashMap Hashmap of teacher's canon name mapped to its respective Teacher object
+ * @param teacher Object for teacher who will be scheduled
* @param course course slug for the lesson that will be created; expected format is modifier-courseName or
* courseName
- * @param teacherName name of the teacher who will teach the lesson
* @return returns a new lesson to be scheduled
* @see Constants#COURSE_CONFIGS
* @see Constants#COURSE_ID_BIMAP
*/
- private static Lesson generateLesson(Map teacherHashMap, String course, String teacherName){
- Teacher teacher = getTeacher(teacherHashMap, teacherName);;
+ private static Lesson generateLesson(Teacher teacher, String course){
//first element = modifier; second element = course name
Pair courseDetails = getCourseDetails(course);
String courseModifier = courseDetails.getFirst();
@@ -215,10 +210,8 @@ private static Lesson generateLesson(Map teacherHashMap, String
/**
* used only for when we split a course into two lesson objects
*/
- private static Lesson generateLesson(Map teacherHashMap, String course, String teacherName,
- String config, LabPatterns labPattern){
- Teacher teacher = getTeacher(teacherHashMap, teacherName);;
- //first element = modifier; second element = course name
+ private static Lesson generateLesson(Teacher teacher, String course, String config, LabPatterns labPattern){
+ //first element = modifier; second element = course name
Pair courseDetails = getCourseDetails(course);
String courseModifier = courseDetails.getFirst();
String courseName = courseDetails.getSecond();
From 7e4213eb9e6c921eb07d0027c0974a471ec0b95c Mon Sep 17 00:00:00 2001
From: RandomCyberCoder
Date: Mon, 1 Jun 2026 14:13:08 -0700
Subject: [PATCH 06/10] updated timeslot architecture to determine if it's
continuous to per slot
---
.../schooltimetabling/domain/Timeslot.java | 243 ++++++++++++------
.../TestClasses/TestTimeslot.java | 52 ++--
2 files changed, 187 insertions(+), 108 deletions(-)
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Timeslot.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Timeslot.java
index 3e82822e..fe53c705 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Timeslot.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Timeslot.java
@@ -12,6 +12,41 @@
import java.util.EnumSet;
public class Timeslot {
+ public class Partition{
+ private EnumSet days = EnumSet.noneOf(Days.class);
+ private LocalTime startTime = null;
+ private LocalTime endTime = null;
+ private BitSet bitSet = new BitSet();
+ /**
+ * Amount of hours per day for this partition
+ */
+ private float hours = 0f;
+
+ public LocalTime getStartTime() {
+ return startTime;
+ }
+
+ public LocalTime getEndTime() {
+ return endTime;
+ }
+
+ public EnumSet getDays() {
+ return days;
+ }
+
+ public BitSet getBitSet() {
+ return bitSet;
+ }
+
+ public float getHours() {
+ return hours;
+ }
+
+ public boolean isContinuous(){
+ return days.size() == 1 && bitSet.cardinality() > 2;
+ }
+ }
+
/*TODO checklist
* -convert names having lecture or lab/act to first slot and second slot; maybe rething names
* -update callers for getting previous lecture or lab/act bitsets
@@ -19,31 +54,18 @@ public class Timeslot {
* if we have only one slot and also returning lecture time if we have only one slot this will be messy
* This should be handle by callers to decide how they want to handle this info
* -we might also want to update the parsers to reflect this; this should just boil down to the header names
+ * -merge attributes into the Partition slot
* +Update the constraints that are accessing the times. We might want to look into adding another constraint
* so we can divided up responsibilities*/
@PlanningId
private String id;
private int ID;
- /*I should make these days into a class or something*/
- private EnumSet daysSlot1;
- public LocalTime startTimeSlot1;
- public LocalTime endTimeSlot1;
- public BitSet bitSetSlot1;
- /**
- * Amount of hours per day in portion one of this timeslot;
- * This slot can be either for lecture or lab/act time
- */
- public float hoursSlot1;
+
+ private Partition partition1;
+ private Partition partition2;
+
public boolean hasSlot2;
- private EnumSet daysSlot2;
- public LocalTime startTimeSlot2;
- public LocalTime endTimeSlot2;
- public BitSet bitSetSlot2;
- /**
- * Amount of hours per day in the second portion of this timeslot if any;
- * currently used only for lab/act
- */
- private float hoursSlot2;
+
public BitSet allTimesBitSet;
//static vars
@@ -103,31 +125,35 @@ public static Timeslot test_minSetUp(String id){
*/
private Timeslot(int ID, EnumSet daysSlot1, EnumSet daysSlot2){
if(daysSlot1.isEmpty()) throw new IllegalArgumentException("Slot1 must always include a time");
+ this.partition1 = new Partition();
+ this.partition2 = new Partition();
this.id = Integer.toString(ID);
- this.daysSlot1 = daysSlot1.clone();
- this.daysSlot2 = daysSlot2.clone();
- this.hasSlot2 = !this.daysSlot2.isEmpty();
+ this.partition1.days = daysSlot1.clone();
+ this.partition2.days = daysSlot2.clone();
+ this.hasSlot2 = !this.partition2.days.isEmpty();
}
/**
* test constructor
*/
- private Timeslot(int ID, BitSet lecBitSet, BitSet bitSetSlot2, EnumSet daysSlot1, EnumSet labActDays){
+ private Timeslot(int ID, BitSet bitset1, BitSet bitSetSlot2, EnumSet daysSlot1, EnumSet daysSlot2){
if(daysSlot1.isEmpty()) throw new IllegalArgumentException("Slot1 must always include a time");
+ this.partition1 = new Partition();
+ this.partition2 = new Partition();
this.id = Integer.toString(ID);
this.ID = ID;
- this.bitSetSlot1 = (BitSet) lecBitSet.clone();
- this.bitSetSlot2 = (BitSet) bitSetSlot2.clone();
- this.daysSlot1 = daysSlot1.clone();
- this.daysSlot2 = labActDays.clone();
- this.hasSlot2 = !this.daysSlot2.isEmpty();
+ this.partition1.bitSet = (BitSet) bitset1.clone();
+ this.partition2.bitSet = (BitSet) bitSetSlot2.clone();
+ this.partition1.days = daysSlot1.clone();
+ this.partition2.days = daysSlot2.clone();
+ this.hasSlot2 = !this.partition2.days.isEmpty();
BitSet allBitSet = new BitSet();
- allBitSet.or(this.bitSetSlot1);
- allBitSet.or(this.bitSetSlot2);
+ allBitSet.or(this.partition1.bitSet);
+ allBitSet.or(this.partition2.bitSet);
this.allTimesBitSet = allBitSet;
- this.hoursSlot1 = lecBitSet.cardinality() /(float) daysSlot1.size() / 2f;
- this.hoursSlot2 = bitSetSlot2.cardinality() /(float)labActDays.size() /2f;
+ this.partition1.hours = bitset1.cardinality() /(float) daysSlot1.size() / 2f;
+ this.partition2.hours = bitSetSlot2.cardinality() /(float)daysSlot2.size() /2f;
}
/**
@@ -168,6 +194,9 @@ public Timeslot(int ID, String days, String startTime, String endTime, float sta
validate(daysSlot1, startTimeSlot1, endTimeSlot1, startHours, totalHours,
daysSlot2, startTimeSlot2, endTimeSlot2, totalHours2);
+ partition1 = new Partition();
+ partition2 = new Partition();
+
this.ID = ID;
this.id = String.valueOf(ID);
//process parsed inputs after we have validated them;
@@ -179,71 +208,87 @@ public Timeslot(int ID, String days, String startTime, String endTime, float sta
if(startMinutes == totalMinutes){
this.hasSlot2 = false;
- this.daysSlot1 = daysSlot1;
- this.startTimeSlot1 = startTimeSlot1;
- this.endTimeSlot1 = endTimeSlot1;
- this.hoursSlot1 = totalHours;
- this.bitSetSlot1 = BitSetHelper.timeSlotBitSet(
- this.startTimeSlot1, Math.round(this.hoursSlot1 * 2f), this.daysSlot1
+ this.partition1.days = daysSlot1;
+ this.partition1.hours = totalHours;
+ this.partition1.startTime = startTimeSlot1;
+ this.partition1.endTime = endTimeSlot1;
+ this.partition1.bitSet = BitSetHelper.timeSlotBitSet(
+ this.partition1.startTime,
+ Math.round(this.partition1.hours * 2f),
+ this.partition1.days
);
- this.daysSlot2 = EnumSet.noneOf(Days.class);
- this.startTimeSlot2 = null;
- this.endTimeSlot2 = null;
- this.hoursSlot2 = 0;
- this.bitSetSlot2 = new BitSet();
+ this.partition2.days = EnumSet.noneOf(Days.class);
+ this.partition2.startTime = null;
+ this.partition2.endTime = null;
+ this.partition2.hours = 0f;
+ this.partition2.bitSet = new BitSet();
this.allTimesBitSet = new BitSet();
- this.allTimesBitSet.or(this.bitSetSlot1);
+ this.allTimesBitSet.or(this.partition1.bitSet);
}
//other scenario when it doesn't
//this means we have the slot1 and slot2 populated
else{
this.hasSlot2 = true;
- this.daysSlot1 = daysSlot1;
- this.hoursSlot1 = startHours;
- this.startTimeSlot1 = startTimeSlot1;
- this.endTimeSlot1 = this.startTimeSlot1.plusMinutes(Math.round(this.hoursSlot1 * 60f));
- this.bitSetSlot1 = BitSetHelper.timeSlotBitSet(
- this.startTimeSlot1, Math.round(this.hoursSlot1 * 2f), this.daysSlot1
+ this.partition1.days = daysSlot1;
+ this.partition1.hours = startHours;
+ this.partition1.startTime = startTimeSlot1;
+ this.partition1.endTime = this.partition1.startTime.plusMinutes(
+ Math.round(this.partition1.hours * 60f)
+ );
+ this.partition1.bitSet = BitSetHelper.timeSlotBitSet(
+ this.partition1.startTime,
+ Math.round(this.partition1.hours * 2f),
+ this.partition1.days
);
- this.daysSlot2 = daysSlot1;
- this.hoursSlot2 = startHours;
- this.startTimeSlot2 = endTimeSlot1.minusMinutes(Math.round(this.hoursSlot2 * 60f));
- this.endTimeSlot2 = endTimeSlot1;
- this.bitSetSlot2 = BitSetHelper.timeSlotBitSet(
- this.startTimeSlot2, Math.round(this.hoursSlot2 * 2f), this.daysSlot2
+ this.partition2.days = daysSlot1;
+ this.partition2.hours = startHours;
+ this.partition2.startTime = endTimeSlot1.minusMinutes(
+ Math.round(this.partition2.hours * 60f)
+ );
+ this.partition2.endTime = endTimeSlot1;
+ this.partition2.bitSet = BitSetHelper.timeSlotBitSet(
+ this.partition2.startTime,
+ Math.round(this.partition2.hours * 2f),
+ this.partition2.days
);
this.allTimesBitSet = BitSetHelper.timeSlotBitSet(
- this.startTimeSlot1, Math.round(totalHours * 2f), this.daysSlot1
+ this.partition1.startTime,
+ Math.round(totalHours * 2f),
+ this.partition1.days
);
}
}
else{
this.hasSlot2 = true;
- this.daysSlot1 = daysSlot1;
- this.startTimeSlot1 = startTimeSlot1;
- this.endTimeSlot1 = endTimeSlot1;
- this.hoursSlot1 = totalHours;
- this.bitSetSlot1 = BitSetHelper.timeSlotBitSet(
- this.startTimeSlot1, Math.round(this.hoursSlot1 * 2), this.daysSlot1
+ this.partition1.days = daysSlot1;
+ this.partition1.hours = totalHours;
+ this.partition1.startTime = startTimeSlot1;
+ this.partition1.endTime = endTimeSlot1;
+ this.partition1.bitSet = BitSetHelper.timeSlotBitSet(
+ this.partition1.startTime,
+ Math.round(this.partition1.hours * 2),
+ this.partition1.days
);
- this.daysSlot2 = daysSlot2;
- this.startTimeSlot2 = startTimeSlot2;
- this.endTimeSlot2 = endTimeSlot2;
- this.hoursSlot2 = totalHours2;
- this.bitSetSlot2 = BitSetHelper.timeSlotBitSet(
- this.startTimeSlot2, Math.round(this.hoursSlot2 * 2), this.daysSlot2
+ this.partition2.days = daysSlot2;
+ this.partition2.hours = totalHours2;
+ this.partition2.startTime = startTimeSlot2;
+ this.partition2.endTime = endTimeSlot2;
+ this.partition2.bitSet = BitSetHelper.timeSlotBitSet(
+ this.partition2.startTime,
+ Math.round(this.partition2.hours * 2),
+ this.partition2.days
);
this.allTimesBitSet = new BitSet();
- this.allTimesBitSet.or(this.bitSetSlot1);
- this.allTimesBitSet.or(this.bitSetSlot2);
+ this.allTimesBitSet.or(this.partition1.bitSet);
+ this.allTimesBitSet.or(this.partition2.bitSet);
}
}
@@ -361,8 +406,20 @@ private static boolean approximatelyEqual(float a, float b) {
* @return True if continuous; Otherwise false. One hour long (continuous) single day timeslots are marked
* as not continuous;
*/
- public boolean isContinuous(){
- return daysSlot1.size() == 1 && !hasSlot2 && bitSetSlot1.cardinality() > 2;
+ public boolean isContinuousSlot1(){
+ return partition1.isContinuous();
+ }
+
+ /**
+ * Checks that second partition has all its time in one block; all time in one day and all back to back
+ * @return true if continuous; otherwise false. It will throw an error if there is no second slot.
+ */
+ public boolean isContinuousSlot2(){
+ if(!hasSlot2) throw new RuntimeException(String.format(
+ "Timeslot with id '%d' has no second slot",
+ ID
+ ));
+ return partition2.isContinuous();
}
@Override
@@ -371,13 +428,21 @@ public String toString() {
}
private String toStringSlot1(){
- return getString(daysSlot1, startTimeSlot1, endTimeSlot1);
+ return getString(
+ this.partition1.days,
+ this.partition1.startTime,
+ this.partition1.endTime
+ );
}
private String toStringSlot2(){
if(!this.hasSlot2) return "No second slot";
- return getString(daysSlot2, startTimeSlot2, endTimeSlot2);
+ return getString(
+ this.partition2.days,
+ this.partition2.startTime,
+ this.partition2.endTime
+ );
}
private String getString(EnumSet days, LocalTime startTime, LocalTime endTime) {
@@ -409,15 +474,15 @@ public int getID() {
}
public LocalTime getStartTimeSlot1() {
- return startTimeSlot1;
+ return partition1.startTime;
}
public LocalTime getEndTimeSlot1() {
- return endTimeSlot1;
+ return partition1.endTime;
}
public BitSet getBitSetSlot1() {
- return bitSetSlot1;
+ return partition1.bitSet;
}
public boolean isHasSlot2() {
@@ -425,15 +490,15 @@ public boolean isHasSlot2() {
}
public LocalTime getStartTimeSlot2() {
- return startTimeSlot2;
+ return partition2.startTime;
}
public LocalTime getEndTimeSlot2() {
- return endTimeSlot2;
+ return partition2.endTime;
}
public BitSet getBitSetSlot2() {
- return bitSetSlot2;
+ return partition2.bitSet;
}
public BitSet getAllTimesBitSet() {
@@ -445,7 +510,7 @@ public BitSet getAllTimesBitSet() {
* @return number of lec hours the timeslot can accommodate per day
*/
public float getHoursSlot1() {
- return hoursSlot1;
+ return partition1.hours;
}
/**
@@ -453,14 +518,22 @@ public float getHoursSlot1() {
*/
public float getHoursSlot2(){
if(!this.hasSlot2) return 0;
- else return hoursSlot2;
+ else return partition2.hours;
}
public EnumSet getDaysSlot1() {
- return daysSlot1;
+ return partition1.days;
}
public EnumSet getDaysSlot2() {
- return daysSlot2;
+ return partition2.days;
+ }
+
+ public Partition getPartition1(){
+ return partition1;
+ }
+
+ public Partition getPartition2(){
+ return partition2;
}
}
diff --git a/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestTimeslot.java b/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestTimeslot.java
index 8a37b2e1..5cf74aa5 100644
--- a/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestTimeslot.java
+++ b/java/hello-world/src/test/java/org/acme/schooltimetabling/TestClasses/TestTimeslot.java
@@ -1,13 +1,11 @@
package org.acme.schooltimetabling.TestClasses;
-import net.bytebuddy.asm.Advice;
import org.acme.schooltimetabling.constants.Constants;
import org.acme.schooltimetabling.constants.Days;
import org.acme.schooltimetabling.domain.Timeslot;
import org.acme.schooltimetabling.helperClasses.BitSetHelper;
import org.acme.schooltimetabling.helperClasses.Generators.TimeslotGenerator;
import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
-import org.junit.Ignore;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -80,23 +78,24 @@ void checkTimeslot1(){
expectedTotal.xor(expected2);
assertEquals(1, timeslot.getHoursSlot1(), EPSILON);
- assertEquals(6, timeslot.bitSetSlot1.cardinality());
+ assertEquals(6, timeslot.getBitSetSlot1().cardinality());
assertEquals(LocalTime.parse("7:00AM", FORMATTER), timeslot.getStartTimeSlot1());
assertEquals(LocalTime.parse("8:00AM", FORMATTER), timeslot.getEndTimeSlot1());
assertEquals(expectedDays, timeslot.getDaysSlot1());
assertEquals(expected1, timeslot.getBitSetSlot1());
+ assertFalse(timeslot.isContinuousSlot1());
assertEquals(1, timeslot.getHoursSlot2(), EPSILON);
- assertEquals(6, timeslot.bitSetSlot2.cardinality());
+ assertEquals(6, timeslot.getBitSetSlot2().cardinality());
assertEquals(LocalTime.parse("8:00AM", FORMATTER), timeslot.getStartTimeSlot2());
assertEquals(LocalTime.parse("9:00AM", FORMATTER), timeslot.getEndTimeSlot2());
assertEquals(expectedDays, timeslot.getDaysSlot2());
assertEquals(expected2, timeslot.getBitSetSlot2());
+ assertFalse(timeslot.isContinuousSlot2());
assertEquals(12, timeslot.allTimesBitSet.cardinality());
assertEquals(expectedTotal, timeslot.getAllTimesBitSet());
assertTrue(timeslot.isHasSlot2());
- assertFalse(timeslot.isContinuous());
}
@Test
@@ -122,23 +121,24 @@ void checkTimeslot2(){
expectedTotal.xor(bitSet2);
assertEquals(1, timeslot.getHoursSlot1(), EPSILON);
- assertEquals(4, timeslot.bitSetSlot1.cardinality());
+ assertEquals(4, timeslot.getBitSetSlot1().cardinality());
assertEquals(LocalTime.parse("8:00AM", FORMATTER), timeslot.getStartTimeSlot1());
assertEquals(LocalTime.parse("9:00AM", FORMATTER), timeslot.getEndTimeSlot1());
assertEquals(days1, timeslot.getDaysSlot1());
assertEquals(bitSet1, timeslot.getBitSetSlot1());
+ assertFalse(timeslot.isContinuousSlot1());
assertEquals(2, timeslot.getHoursSlot2(), EPSILON);
- assertEquals(4, timeslot.bitSetSlot2.cardinality());
+ assertEquals(4, timeslot.getBitSetSlot2().cardinality());
assertEquals(LocalTime.parse("8:00AM", FORMATTER), timeslot.getStartTimeSlot2());
assertEquals(LocalTime.parse("10:00AM", FORMATTER), timeslot.getEndTimeSlot2());
assertEquals(days2, timeslot.getDaysSlot2());
assertEquals(bitSet2, timeslot.getBitSetSlot2());
+ assertTrue(timeslot.isContinuousSlot2());
assertEquals(8, timeslot.allTimesBitSet.cardinality());
assertEquals(expectedTotal, timeslot.getAllTimesBitSet());
assertTrue(timeslot.isHasSlot2());
- assertFalse(timeslot.isContinuous());
}
@Test
@@ -165,23 +165,24 @@ void checkTimeslot3(){
expectedTotal.xor(bitSet2);
assertEquals(1, timeslot.getHoursSlot1(), EPSILON);
- assertEquals(4, timeslot.bitSetSlot1.cardinality());
+ assertEquals(4, timeslot.getBitSetSlot1().cardinality());
assertEquals(LocalTime.parse("8:00AM", FORMATTER), timeslot.getStartTimeSlot1());
assertEquals(LocalTime.parse("9:00AM", FORMATTER), timeslot.getEndTimeSlot1());
assertEquals(days1, timeslot.getDaysSlot1());
assertEquals(bitSet1, timeslot.getBitSetSlot1());
+ assertFalse(timeslot.isContinuousSlot1());
assertEquals(1.5, timeslot.getHoursSlot2(), EPSILON);
- assertEquals(9, timeslot.bitSetSlot2.cardinality());
+ assertEquals(9, timeslot.getBitSetSlot2().cardinality());
assertEquals(LocalTime.parse("9:00AM", FORMATTER), timeslot.getStartTimeSlot2());
assertEquals(LocalTime.parse("10:30AM", FORMATTER), timeslot.getEndTimeSlot2());
assertEquals(days2, timeslot.getDaysSlot2());
assertEquals(bitSet2, timeslot.getBitSetSlot2());
+ assertFalse(timeslot.isContinuousSlot2());
assertEquals(13, timeslot.allTimesBitSet.cardinality());
assertEquals(expectedTotal, timeslot.getAllTimesBitSet());
assertTrue(timeslot.isHasSlot2());
- assertFalse(timeslot.isContinuous());
}
@Test
@@ -204,21 +205,23 @@ void checkTimeslot4(){
expectedTotal.xor(bitSet2);
assertEquals(1, timeslot.getHoursSlot1(), EPSILON);
- assertEquals(8, timeslot.bitSetSlot1.cardinality());
+ assertEquals(8, timeslot.getBitSetSlot1().cardinality());
assertEquals(LocalTime.parse("7:00AM", FORMATTER), timeslot.getStartTimeSlot1());
assertEquals(LocalTime.parse("8:00AM", FORMATTER), timeslot.getEndTimeSlot1());
assertEquals(days1, timeslot.getDaysSlot1());
assertEquals(bitSet1, timeslot.getBitSetSlot1());
+ assertFalse(timeslot.isContinuousSlot1());
assertEquals(0, timeslot.getHoursSlot2(), EPSILON);
- assertEquals(0, timeslot.bitSetSlot2.cardinality());
+ assertEquals(0, timeslot.getBitSetSlot2().cardinality());
assertEquals(days2, timeslot.getDaysSlot2());
assertEquals(bitSet2, timeslot.getBitSetSlot2());
+ assertThrows(RuntimeException.class, timeslot::isContinuousSlot2);
assertEquals(8, timeslot.allTimesBitSet.cardinality());
assertEquals(expectedTotal, timeslot.getAllTimesBitSet());
assertFalse(timeslot.isHasSlot2());
- assertFalse(timeslot.isContinuous());
+ assertFalse(timeslot.isContinuousSlot1());
}
@Test
@@ -249,23 +252,24 @@ void checkTimeslot5(){
);
assertEquals(1.5, timeslot.getHoursSlot1(), EPSILON);
- assertEquals(6, timeslot.bitSetSlot1.cardinality());
+ assertEquals(6, timeslot.getBitSetSlot1().cardinality());
assertEquals(LocalTime.parse("9:30AM", FORMATTER), timeslot.getStartTimeSlot1());
assertEquals(LocalTime.parse("11:00AM", FORMATTER), timeslot.getEndTimeSlot1());
assertEquals(days, timeslot.getDaysSlot1());
assertEquals(bitSet1, timeslot.getBitSetSlot1());
+ assertFalse(timeslot.isContinuousSlot1());
assertEquals(1.5, timeslot.getHoursSlot2(), EPSILON);
- assertEquals(6, timeslot.bitSetSlot2.cardinality());
+ assertEquals(6, timeslot.getBitSetSlot2().cardinality());
assertEquals(LocalTime.parse("12:00PM", FORMATTER), timeslot.getStartTimeSlot2());
assertEquals(LocalTime.parse("1:30PM", FORMATTER), timeslot.getEndTimeSlot2());
assertEquals(days, timeslot.getDaysSlot2());
assertEquals(bitSet2, timeslot.getBitSetSlot2());
+ assertFalse(timeslot.isContinuousSlot2());
assertEquals(16, timeslot.allTimesBitSet.cardinality());
assertEquals(expectedTotal, timeslot.getAllTimesBitSet());
assertTrue(timeslot.isHasSlot2());
- assertFalse(timeslot.isContinuous());
}
@Test
@@ -288,21 +292,22 @@ void checkTimeslot6(){
expectedTotal.xor(bitSet2);
assertEquals(3, timeslot.getHoursSlot1(), EPSILON);
- assertEquals(6, timeslot.bitSetSlot1.cardinality());
+ assertEquals(6, timeslot.getBitSetSlot1().cardinality());
assertEquals(LocalTime.parse("9:00AM", FORMATTER), timeslot.getStartTimeSlot1());
assertEquals(LocalTime.parse("12:00PM", FORMATTER), timeslot.getEndTimeSlot1());
assertEquals(days1, timeslot.getDaysSlot1());
assertEquals(bitSet1, timeslot.getBitSetSlot1());
+ assertTrue(timeslot.isContinuousSlot1());
assertEquals(0, timeslot.getHoursSlot2(), EPSILON);
- assertEquals(0, timeslot.bitSetSlot2.cardinality());
+ assertEquals(0, timeslot.getBitSetSlot2().cardinality());
assertEquals(days2, timeslot.getDaysSlot2());
assertEquals(bitSet2, timeslot.getBitSetSlot2());
+ assertThrows(RuntimeException.class, timeslot::isContinuousSlot2);
assertEquals(6, timeslot.allTimesBitSet.cardinality());
assertEquals(expectedTotal, timeslot.getAllTimesBitSet());
assertFalse(timeslot.isHasSlot2());
- assertTrue(timeslot.isContinuous());
}
@Test
@@ -329,20 +334,21 @@ void checkTimeslot7(){
expectedTotal.xor(bitSet2);
assertEquals(3, timeslot.getHoursSlot1(), EPSILON);
- assertEquals(6, timeslot.bitSetSlot1.cardinality());
+ assertEquals(6, timeslot.getBitSetSlot1().cardinality());
assertEquals(LocalTime.parse("9:00AM", FORMATTER), timeslot.getStartTimeSlot1());
assertEquals(LocalTime.parse("12:00PM", FORMATTER), timeslot.getEndTimeSlot1());
assertEquals(days1, timeslot.getDaysSlot1());
assertEquals(bitSet1, timeslot.getBitSetSlot1());
+ assertTrue(timeslot.isContinuousSlot1());
assertEquals(3, timeslot.getHoursSlot2(), EPSILON);
- assertEquals(6, timeslot.bitSetSlot2.cardinality());
+ assertEquals(6, timeslot.getBitSetSlot2().cardinality());
assertEquals(days2, timeslot.getDaysSlot2());
assertEquals(bitSet2, timeslot.getBitSetSlot2());
+ assertTrue(timeslot.isContinuousSlot2());
assertEquals(12, timeslot.allTimesBitSet.cardinality());
assertEquals(expectedTotal, timeslot.getAllTimesBitSet());
assertTrue(timeslot.isHasSlot2());
- assertFalse(timeslot.isContinuous());
}
}
From d1c73b5ab0503029ec96993d95ca423716d090d3 Mon Sep 17 00:00:00 2001
From: RandomCyberCoder
Date: Mon, 15 Jun 2026 16:33:17 -0700
Subject: [PATCH 07/10] feat: updated constraints and tests for revisions
---
.../builders/lessons/LessonBuilder.java | 60 +-
.../schooltimetabling/domain/Timeslot.java | 11 +-
.../domain/lesson/Lesson.java | 40 +-
.../fileObjects/ScheduleConfig.java | 8 +
.../Generators/LessonGenerator.java | 15 +-
.../Generators/TeacherGenerator.java | 3 +-
.../solver/TimetableConstraintProvider.java | 439 +++--
.../solver/ConstraintTestHelper.java | 26 +-
.../solver/TestConstraints.java | 1432 ++++++++++++-----
9 files changed, 1482 insertions(+), 552 deletions(-)
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/builders/lessons/LessonBuilder.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/builders/lessons/LessonBuilder.java
index 80638631..d9cb6afe 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/builders/lessons/LessonBuilder.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/builders/lessons/LessonBuilder.java
@@ -1,9 +1,12 @@
package org.acme.schooltimetabling.builders.lessons;
import org.acme.schooltimetabling.constants.Constants;
+import org.acme.schooltimetabling.domain.Room;
+import org.acme.schooltimetabling.domain.Timeslot;
import org.acme.schooltimetabling.domain.lesson.Lesson;
import org.acme.schooltimetabling.domain.teacher.Teacher;
import org.acme.schooltimetabling.fileObjects.LabPatterns;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
/**
@@ -32,8 +35,17 @@ public class LessonBuilder {
//optional build fields
private String modifier = "";
private LabPatterns labPattern = null;
+ private Integer linkerId = null;
+ //fields used only when testing
+ private Timeslot timeslot = null;
+ private Room room = null;
+ private int courseId = -1;
+
+ /**
+ * unique id of the Lesson object that will be built
+ */
public LessonBuilder id(int id){
if(id < 0) throw new IllegalArgumentException("the id must be greater then zero");
this.id = String.valueOf(id);
@@ -79,6 +91,36 @@ public LessonBuilder labPattern(LabPatterns labPattern) {
return this;
}
+ public LessonBuilder linkerId(Integer linkerId){
+ this.linkerId = linkerId;
+ return this;
+ }
+
+ public LessonBuilder timeslot(Timeslot timeslot){
+ if(!ScheduleConfig.isTesting()) throw new RuntimeException("This is only available during testing, Shouldn't be " +
+ "set manually");
+ this.timeslot = timeslot;
+ return this;
+ }
+
+ public LessonBuilder room(Room room){
+ if(!ScheduleConfig.isTesting()) throw new RuntimeException("This is only available during testing, Shouldn't be " +
+ "set manually");
+ this.room = room;
+ return this;
+ }
+
+ /**
+ * id for the course; i.e all lessons that are scheduling course 'csc1000' will share the same
+ * courseId, like '1';
+ */
+ public LessonBuilder courseId(int courseId){
+ if(!ScheduleConfig.isTesting()) throw new RuntimeException("This is only available during testing, Shouldn't be " +
+ "set manually");
+ this.courseId = courseId;
+ return this;
+ }
+
public LessonBuilder clear() {
id = null;
section = null;
@@ -87,16 +129,23 @@ public LessonBuilder clear() {
courseConfig = null;
teacherObj = null;
labPattern = null;
+ linkerId = null;
+ timeslot = null;
+ room = null;
+ courseId = -1;
return this;
}
public Lesson build() {
+ //mandatory fields
if (id == null || section == null || courseName == null || courseConfig == null || teacherObj == null) {
throw new IllegalCallerException(
"When calling build() you must have values set for id, section, courseName, courseConfig, and teacherObj"
);
}
+
+
//check if we need a default for the lab
String[] units = courseConfig.split("-");
int labUnits = Integer.parseInt(units[1]);
@@ -105,6 +154,15 @@ public Lesson build() {
if(modifier == null) modifier = "";
- return new Lesson(id, section, courseName, modifier, courseConfig, teacherObj, labPattern);
+ if(ScheduleConfig.isTesting()){
+ if(timeslot == null || room == null) throw new RuntimeException("When building a lesson during testing, " +
+ "a 'timeslot' and a 'room' or else the lesson won't be detected by the constraint");
+ return Lesson.test_buildLesson(
+ id, section, courseName, teacherObj.getName(), modifier, courseConfig, courseId, teacherObj,
+ timeslot, room, linkerId, labPattern
+ );
+ }
+
+ return new Lesson(id, section, courseName, modifier, courseConfig, teacherObj, labPattern, linkerId);
}
}
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Timeslot.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Timeslot.java
index fe53c705..86e5fddb 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Timeslot.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/Timeslot.java
@@ -47,16 +47,7 @@ public boolean isContinuous(){
}
}
- /*TODO checklist
- * -convert names having lecture or lab/act to first slot and second slot; maybe rething names
- * -update callers for getting previous lecture or lab/act bitsets
- * Note that we shouldn't try to do any smart behavior like returning lab/act time
- * if we have only one slot and also returning lecture time if we have only one slot this will be messy
- * This should be handle by callers to decide how they want to handle this info
- * -we might also want to update the parsers to reflect this; this should just boil down to the header names
- * -merge attributes into the Partition slot
- * +Update the constraints that are accessing the times. We might want to look into adding another constraint
- * so we can divided up responsibilities*/
+
@PlanningId
private String id;
private int ID;
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/lesson/Lesson.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/lesson/Lesson.java
index 874b1811..23326cfa 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/lesson/Lesson.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/domain/lesson/Lesson.java
@@ -51,6 +51,7 @@ public class Lesson {
public int lecHours, labActHours;
public Teacher teacherObj;
private LabPatterns labPattern;
+ private Integer linkerId;
/**Prevent default constructor use*/
private Lesson() {
@@ -59,10 +60,16 @@ private Lesson() {
/*TODO change all planning variable IDs to a int/Integer as mentioned in the documentation
* https://docs.timefold.ai/timefold-solver/latest/using-timefold-solver/modeling-planning-problems#planningId*/
-
- //TODO update to remove the teacherName field from all constructors. This will simplify the generators funcs
-
/* Test factory methods */
+ public static Lesson test_buildLesson(String Id, int lecSection, String courseName, String teacherName, String modifiers,
+ String courseConfig, int courseID, Teacher teacherObj, Timeslot timeslot, Room room,
+ Integer linkerId, LabPatterns labPattern){
+ Lesson lesson = new Lesson(Id, lecSection, courseName, teacherName, modifiers, courseConfig, courseID, teacherObj
+ , timeslot, room);
+ lesson.linkerId = linkerId;
+ lesson.labPattern = labPattern;
+ return lesson;
+ }
public static Lesson test_buildLesson(String Id, int lecSection, String courseName, String teacherName, String modifiers,
String courseConfig, int courseID, Teacher teacherObj, Timeslot timeslot, Room room){
return new Lesson(Id, lecSection, courseName, teacherName, modifiers, courseConfig, courseID, teacherObj
@@ -112,9 +119,11 @@ private Lesson(String Id, int sectionNumber, String courseName, String teacherNa
* @param modifiers any course modifiers. If no modifiers pass a "" string
* @param courseConfig configuration of the courseName for this lesson
* @param teacherObj teacher object associated with the teacherName
+ * @param linkerId ID used to link courses that got split into distinct lesson
+ * objects; Otherwise use null in on such split exists
*/
public Lesson(String Id, int sectionNumber, String courseName, String modifiers,
- String courseConfig, Teacher teacherObj){
+ String courseConfig, Teacher teacherObj, LabPatterns labPattern, Integer linkerId){
/*the courseConfig stream is assumed to come in the format
* E-L-A where E is the number of lecture units, L is the number of
* lab units, and A is the number of activity units */
@@ -136,15 +145,16 @@ public Lesson(String Id, int sectionNumber, String courseName, String modifiers,
this.courseName = courseName;
this.teacherObj = teacherObj;
this.modifiers = modifiers;
- this.labPattern = null;
- }
-
- public Lesson(String Id, int lecSection, String courseName, String modifiers,
- String courseConfig, Teacher teacherObj, LabPatterns labPattern){
- this(Id, lecSection, courseName, modifiers, courseConfig, teacherObj);
this.labPattern = labPattern;
+ this.linkerId = linkerId;
}
+// public Lesson(String Id, int lecSection, String courseName, String modifiers,
+// String courseConfig, Teacher teacherObj, LabPatterns labPattern){
+// this(Id, lecSection, courseName, modifiers, courseConfig, teacherObj, (Integer) null);
+// this.labPattern = labPattern;
+// }
+
/**
* Built using minimum fields needed for excel file print out
* @param courseName name of the course
@@ -221,7 +231,7 @@ public int getLecSection() {
return this.hasLecture ? this.sectionNumber : -1;
}
- //TODO UPDATE
+
/**
* Retrieve a lec/act section if one exists
* @return lab/act section if one exists; otherwise -1
@@ -231,8 +241,8 @@ public int getLabActSection() {
for the lecture and the next section number is assumed to be reserved for the lab/act portion of the lesson
*/
if(this.hasLabAct && this.hasLecture) return this.sectionNumber + 1;
- if(this.hasLabAct) return this.sectionNumber;
- return -1;
+ else if(this.hasLabAct) return this.sectionNumber;
+ else return -1;
}
//I guess we technically don't need this with the current setup up of getLecSection()
@@ -256,6 +266,10 @@ public Teacher getTeacherObj() {
return teacherObj;
}
+ public Integer getLinkerId(){
+ return this.linkerId;
+ }
+
public boolean isStudio(){
return Constants.STUDIO_STYLE_COURSES.contains(this.courseName);
}
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/ScheduleConfig.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/ScheduleConfig.java
index f4b5c269..4596c4b5 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/ScheduleConfig.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/fileObjects/ScheduleConfig.java
@@ -163,4 +163,12 @@ public static String getPatternsFileName(){
if(HOLDER.scheduleConfig == null) throw new IllegalStateException("Configuration must be loaded");
return HOLDER.scheduleConfig.patternsFileName;
}
+
+ public static void setCompressBits(BitSet compressIn, BitSet compressOut){
+ if(HOLDER.scheduleConfig == null) throw new IllegalStateException("Configuration must be loaded");
+ if(!HOLDER.scheduleConfig.testing) throw new IllegalStateException("This is only allowed during testing");
+
+ HOLDER.scheduleConfig.cpmrsInBs = compressIn;
+ HOLDER.scheduleConfig.cmprsOutBs = compressOut;
+ }
}
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 035c0a11..0e727a4b 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
@@ -24,7 +24,6 @@
import static org.acme.schooltimetabling.helperClasses.ParseInput.readCoursePatterns;
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 PrescheduleObject PRESCHED_TIMES = ScheduleConfig.getPrescheduledFileName() != null ?
@@ -39,6 +38,10 @@ public class LessonGenerator extends Generator{
* available lesson ID rather than using this attribute directly.
*/
private static int lessonID = 1;
+ /**
+ * contains the next available linker id
+ */
+ private static int nextLinkerId = 1;
private static List skippedLessons = new ArrayList<>();
static {
@@ -151,10 +154,13 @@ public static ArrayList generateLessons(List schedules,
-the lesson geneorator take into account the section numbers well
-Does the lesson object take this into account well? done
- DO THIS BEFORE THE BELOW: Update the timeslots
- +check over constraints
+ -check over constraints
+how does the above effect print out? It doesn't I already check before printing if the
lesson has lecture or lab. ACUTALLY update the timeslots first. This will determine how
the print out and the constraints will have to be updated
+ NOTE: THAT WHEN THINKING ABOUT THIS CHANGE THINK ABOUT LESSONS THAT COULD HAVE ONLY
+ LEC/ACT AS WELL. THIS CAN HELP US THINK ABOUT THE IMPLEMENTATION. ALSO THINK ABOUT HOW STUDIO
+ COURSES MAYBE COULD BE SPLIT AS WELL
*/
lessons.add(
generateLesson(teacher, course,
@@ -391,7 +397,6 @@ private static Teacher noSurveyTeacher(String name, DefaultTime defaultTime){
String[] nameFragments = name.split(",");
boolean isFaculty = Constants.FACULTY_LAST_NAMES.contains(nameFragments[LAST_NAME_POS].strip());
TeacherBuilder builder = new TeacherBuilder(new DefaultTeachingPolicy());
- //TODO; CHECK UPDATE
if(isFaculty){
LOGGER.info(String.format("Found teacher '%s' to be a faculty member. Promoting to Faculty"
, name));
@@ -438,6 +443,10 @@ public static int nextLessonID(){
return lessonID++;
}
+ public static int nextLinkerID(){
+ return nextLinkerId++;
+ }
+
/** This method requires the config to determine if the course will need a section for a lecture,
* lab/act, or both. This function should be used for pulling section numbers. In the case that the
* configuration has both a lecture and lab, the section number returned will be for the lesson and the
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 914313c6..ca1eafb0 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
@@ -270,7 +270,7 @@ private static Teacher generateTeacher(HashMap surveyEntry){
}
}
- //TODO; CHECK UPDATE
+
String[] splitName = canonName.split(",");
Preference gapPref = Preference.parsePref(surveyEntry.get("gap"));
boolean isFaculty = Constants.FACULTY_LAST_NAMES.contains(splitName[0].strip());
@@ -296,7 +296,6 @@ private static Teacher generateTeacher(HashMap surveyEntry){
private static void prescheduleUpdate(Map teacherMap,
Map> presched){
Iterator>> iterator = presched.entrySet().iterator();
- //TODO use builder here for teacher creation; we should use a default time here actually; lets update; CHECK UPDATE
while (iterator.hasNext()) {
Map.Entry> entry = iterator.next();
String name = entry.getKey();
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 980b8d00..199743a1 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
@@ -2,6 +2,7 @@
import ai.timefold.solver.core.api.score.buildin.hardmediumsoft.HardMediumSoftScore;
import ai.timefold.solver.core.api.score.stream.*;
+import org.acme.schooltimetabling.builders.teachers.policies.FacultyPolicy;
import org.acme.schooltimetabling.constants.Constants;
import org.acme.schooltimetabling.constants.Days;
import org.acme.schooltimetabling.constants.Preference;
@@ -9,20 +10,42 @@
import org.acme.schooltimetabling.domain.Room;
import org.acme.schooltimetabling.domain.Timeslot;
import org.acme.schooltimetabling.domain.teacher.Teacher;
+import org.acme.schooltimetabling.fileObjects.LabPatterns;
import org.acme.schooltimetabling.helperClasses.BitSetHelper;
import org.acme.schooltimetabling.helperClasses.Generators.LessonGenerator;
import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.acme.schooltimetabling.solver.justifications.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.acme.schooltimetabling.domain.teacher.Faculty;
+
import static ai.timefold.solver.core.api.score.stream.ConstraintCollectors.*;
import java.util.*;
+
public class TimetableConstraintProvider implements ConstraintProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(TimetableConstraintProvider.class);
private static final float FLOAT_TIME_DELTA = 0.01f;
@Override
+ /*TODO to consider now that we need to consider we can actually have lessons with only activities. Before this was
+ * technically possible already but the constraints or lesson creation never actually allowed this to happen.
+ * It might be worth making a new constraint for accommodating lab only lessons and those that need lab all in one
+ * block.
+ * Things to consider now in this new change that might not be in here already:
+ * -Lessons can now have lab/act only
+ * -We now have lessons whose lab may be required to be all in one block. Note such lessons will have lab only
+ * -For lessons whose lab days may be on multiple days, the lesson could have a lecture or be lab only
+ * -What the above means is that we now need to be careful how we assign timeslots to a lesson object and how
+ * we check for the correct amount of hours
+ * -timeslots with only one slot, can be used for lessons with either lab/act or lecture only now
+ * -for lecture blocks we have to be careful that it isn't given a timeslot that is continuous
+ * Summary:
+ * -lesson can be: lab + lab/act, lab/act only, or lecture only;
+ * -for lec + lab/act lessons, the lab/act will always be spread out
+ * -for lab/act only lessons, the time might either be all in one block or spread out
+ * -it might be worth making a constraint whose only responsibility is to handle that the right type of slot
+ * is given to a lesson; Actually lets start off doing this while updating all the other constraints
+ * and then figure out if we can merge it; This will be cognitively easier; make global flag for including
+ * only if we have a lesson with lab only that want all the time in one block????*/
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
Constraint[] studioConstraint = new Constraint[]{
@@ -38,12 +61,15 @@ public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
labActRoomConflict(constraintFactory),
wrongHoursAmount(constraintFactory),
wrongRoomType(constraintFactory),
+ timeslotPatternMatch(constraintFactory),
+ continuousTimeTaught(constraintFactory),
// Medium Constraints
prefTime(constraintFactory),
- compressTeachTime(constraintFactory),
+ compressIndividualTeachTime(constraintFactory),
rewardPreferredHourGap(constraintFactory),
- penalizeDislikedHourGap(constraintFactory)
+ penalizeDislikedHourGap(constraintFactory),
+ rewardCoursesDiffTimes(constraintFactory)
// Soft constraints
));
@@ -92,7 +118,7 @@ else if(AGGRESSIVE_CHOICE.equals("NONE")){
//add in the room prescheduling conflict constraint only if we prescheduled rooms detected
if(Room.hasPrescheduled || ScheduleConfig.isTesting()){
- LOGGER.info("At least on room has been found to be prescheduled. Including the room prescheduling constraint.");
+ LOGGER.info("At least one room has been found to be prescheduled. Including the room prescheduling constraint.");
solver_constraints.add(roomPreschedule(constraintFactory));
}
@@ -105,8 +131,8 @@ else if(AGGRESSIVE_CHOICE.equals("NONE")){
//-------------------------------------- Hard Constraints --------------------------------------
/**
- * This constraint will penalize solutions that have more then 50% of the lecture blocks (each block being
- * 30 minutes) in primetime
+ * This constraint will penalize solutions that have more than 50% of the lecture blocks (each block being
+ * 30 minutes) in prime time
* This constraint is an alternative to using the pair of constraints {@link #inPrimeTime} and
* {@link #outPrimeTime}.
* @param constraintFactory constraint factory
@@ -114,26 +140,30 @@ else if(AGGRESSIVE_CHOICE.equals("NONE")){
*/
Constraint primeTime50Plus(ConstraintFactory constraintFactory){
return constraintFactory.forEach(Lesson.class)
+ .filter(lesson -> lesson.hasLecture)
.groupBy(sum(lesson -> {
BitSet inPrime = lesson.maskInPT();
BitSet outPrime = lesson.maskOutPT();
return inPrime.cardinality() - outPrime.cardinality();
}))
.filter(
- //if the number of blocks in primetime is greater than those out; penalize by one hard
+ //if the number of blocks in prime time is greater than those out; penalize by one hard
blocks -> blocks > 0)
.penalize(HardMediumSoftScore.ONE_HARD)
.asConstraint("50%+ lecture time outside of primetime");
}
+ //TODO Later we must ask beard what about labs in the case that the lab time is all in one block
+ /*TODO MORE OF A NOTE BUT THE REASON TEACHER MIGHT GET A LONG DAY IS IF THEY HAVE MULTIPLE INSTANCE OF ONE COURSE
+ * THEN THE WILL HAVE TO TEACH ALL ISNTANCES OF TAHT COURSE (LESSOSN) ON THE SAME DAY; ask about this*/
+ //NOTE that I won't take labs into account because I'm not sure if labs matter, especially in the scenario of 3hour blocks
/**
* This constraint makes sure if an instructor is teaching multiple instances of a course that
- * they all land on the same day. Technically time should be taken into account; however, the timeslots that are available
- * naturally enforce this.This is essential because teaching different instances of a course
- * on different schedules is a nightmare for the instructor to plan out.
+ * they all land on the same day for the lecture only. I do not take labs into account.
+ * This is essential because teaching different instances of a course on different schedules is a
+ * nightmare for the instructor to plan out.
*
- * @param constraintFactory constraint factory
- * @return constraint
+ * @return Penalize by {@link HardMediumSoftScore#ONE_HARD}
*/
Constraint sameClassSameDays(ConstraintFactory constraintFactory){
return constraintFactory
@@ -142,32 +172,30 @@ Constraint sameClassSameDays(ConstraintFactory constraintFactory){
Joiners.equal(Lesson::getCourseID))
.filter((lesson, lesson2) -> {
/*Penalize courses not on the same day taught by the same professor */
- EnumSet l1Days = lesson.getTimeslot().getLecDays();
- EnumSet l2Days = lesson2.getTimeslot().getLecDays();
- return l1Days.contains(Days.MONDAY) != l2Days.contains(Days.MONDAY) ||
- l1Days.contains(Days.TUESDAY) != l2Days.contains(Days.TUESDAY) ||
- l1Days.contains(Days.WEDNESDAY) != l2Days.contains(Days.WEDNESDAY) ||
- l1Days.contains(Days.THURSDAY) != l2Days.contains(Days.THURSDAY) ||
- l1Days.contains(Days.FRIDAY) != l2Days.contains(Days.FRIDAY);
+ EnumSet lsnSlot1Days = lesson.getTimeslot().getDaysSlot1();
+ EnumSet lsn2Slot1Days = lesson2.getTimeslot().getDaysSlot1();
+ return lsnSlot1Days.contains(Days.MONDAY) != lsn2Slot1Days.contains(Days.MONDAY) ||
+ lsnSlot1Days.contains(Days.TUESDAY) != lsn2Slot1Days.contains(Days.TUESDAY) ||
+ lsnSlot1Days.contains(Days.WEDNESDAY) != lsn2Slot1Days.contains(Days.WEDNESDAY) ||
+ lsnSlot1Days.contains(Days.THURSDAY) != lsn2Slot1Days.contains(Days.THURSDAY) ||
+ lsnSlot1Days.contains(Days.FRIDAY) != lsn2Slot1Days.contains(Days.FRIDAY);
})
.penalize(HardMediumSoftScore.ONE_HARD)
.asConstraint("Teacher has same course on same days");
}
-
-
/**
* This constraint checks if the lesson timeslot overlaps with the instructors conflict
* bitset. If it does it will be penalized with ONE_HARD.
*
- * This only checks for the lecture and lab bitsets. This takes into account gaps.
+ * This only checks for the slot1 and slot2 bitsets. This takes into account gaps that are in a timeslot, represented
+ * if you unset bits in {@link Timeslot#allTimesBitSet} that are set in slot1 and slot2.
*
* NOTE: if all faculty should have conflicts during a certain time this is taken into
- * account in {@link Faculty#getConflict()}
+ * account in {@link FacultyPolicy}
*
- * @param constraintFactory constraint factory
- * @return constraint
+ * @return Penalize by {@link HardMediumSoftScore#ONE_HARD}
*/
Constraint teacherLessonConflict(ConstraintFactory constraintFactory){
return constraintFactory
@@ -176,8 +204,8 @@ Constraint teacherLessonConflict(ConstraintFactory constraintFactory){
BitSet bitset = new BitSet();
//Use lecture and lab/act bitset rather than the "All" bitset to not take into account gaps
- bitset.or(lesson.getTimeslot().getLectureBitSet());
- bitset.or(lesson.getTimeslot().getLabActBitSet());
+ bitset.or(lesson.getTimeslot().getBitSetSlot1());
+ bitset.or(lesson.getTimeslot().getBitSetSlot2());
bitset.and(lesson.getTeacherObj().getConflict());
return bitset.cardinality() > 0;
@@ -187,8 +215,6 @@ Constraint teacherLessonConflict(ConstraintFactory constraintFactory){
}
-
-
/**
* Checks that a professor isn't teaching two lessons at the same time.
*/
@@ -207,14 +233,9 @@ Constraint lessonConflict(ConstraintFactory constraintFactory){
}
-
-
/**
* This constraint will penalize any unique pair of lessons (w/ lab/act) that use the same room
* at the same time
- *
- * @param constraintFactory - constraint factory
- * @return constraint for one lesson in a room at a time
*/
Constraint labActRoomConflict(ConstraintFactory constraintFactory){
return constraintFactory
@@ -224,9 +245,11 @@ Constraint labActRoomConflict(ConstraintFactory constraintFactory){
Joiners.equal(Lesson::getRoom),
//that have a lab/activity
Joiners.filtering((lesson, lesson2) -> {
- /*make sure that the lesson requires a lab/activity room; just in case; don't think it's
- possible not to have both*/
- return lesson.isHasLabAct() && lesson2.isHasLabAct();
+ /*make sure that the lesson requires a lab/activity room.
+ * just in case the lesson can be split check if the is studio in case
+ * that the lesson's lecture and lab/act portion got split into separate portions*/
+ return (lesson.isHasLabAct() || lesson.isStudio()) &&
+ (lesson2.isHasLabAct() || lesson2.isStudio());
}))
.filter((lesson, lesson2) -> {
BitSet bitSet1;
@@ -235,18 +258,24 @@ Constraint labActRoomConflict(ConstraintFactory constraintFactory){
//studio courses occupy the room during lecture and lab
if(lesson.isStudio()){
bitSet1 = new BitSet();
- bitSet1.or(lesson.getTimeslot().getLectureBitSet());
- bitSet1.or(lesson.getTimeslot().getLabActBitSet());
+ bitSet1.or(lesson.getTimeslot().getBitSetSlot1());
+ bitSet1.or(lesson.getTimeslot().getBitSetSlot2());
}
- else bitSet1 = lesson.getTimeslot().getLabActBitSet();
+ /*If the split is for a non studio course or course simply has no lecture then the first slot
+ will contain the bitset we need*/
+ else if(!lesson.isHasLecture()) bitSet1 = lesson.getTimeslot().getBitSetSlot1();
+ else bitSet1 = lesson.getTimeslot().getBitSetSlot2();
/*same if else logic here for the studio room*/
if(lesson2.isStudio()){
bitSet2 = new BitSet();
- bitSet2.or(lesson2.getTimeslot().getLectureBitSet());
- bitSet2.or(lesson2.getTimeslot().getLabActBitSet());
+ bitSet2.or(lesson2.getTimeslot().getBitSetSlot1());
+ bitSet2.or(lesson2.getTimeslot().getBitSetSlot2());
}
- else bitSet2 = lesson2.getTimeslot().getLabActBitSet();
+ /*If the split is for a non studio course or course simply has no lecture then the first slot
+ will contain the bitset we need*/
+ else if(!lesson.isHasLecture()) bitSet2 = lesson2.getTimeslot().getBitSetSlot1();
+ else bitSet2 = lesson2.getTimeslot().getBitSetSlot2();
//penalize any overlap; aka room being occupied at the same time
return bitSet1.intersects(bitSet2);
@@ -255,53 +284,69 @@ Constraint labActRoomConflict(ConstraintFactory constraintFactory){
.asConstraint("Lab or Activity room conflict");
}
+
/**
* This constraint makes sure that the course a lesson represents has been given a timeslot
* that has the exact amount of hours for the lecture and/or lab/activity the course requires.
* i.e. a lesson that require 3 lecture hours and 3 lab hours should have a time slot that has
* no more or less than this amount of lecture and lab hours allocated.
- *
- * @param constraintFactory constraint factory
- * @return Constraint
+ *if a lesson has only lecture or lab/act. Then the timeslot should only have the first subslot
+ * populated
*/
Constraint wrongHoursAmount(ConstraintFactory constraintFactory){
return constraintFactory
.forEach(Lesson.class)
.filter(lesson -> {
- final Timeslot ts = lesson.getTimeslot();
+ Timeslot ts = lesson.getTimeslot();
/*tests course with only lecture; tests course that has lecture and lab that can be scheduled
* normally, basically not a studio course. Tests the lecture lesson portion of a studio course split*/
- EnumSet lDays = ts.getLecDays();
- EnumSet nonLDays = ts.getNonLecDays();
-
- float tsLecHrs = ts.getLecHours();
- tsLecHrs *= lDays.size();
- float tsLabActHrs = ts.getLabActHours();
- tsLabActHrs *= nonLDays.size();
+ EnumSet lecDays = EnumSet.noneOf(Days.class);
+ EnumSet labActDays = EnumSet.noneOf(Days.class);
+ float lecHours = 0f;
+ float labActHours = 0f;
+
+ //lesson can have both lec and lab/act
+ if(lesson.isHasLecture() && lesson.isHasLabAct()){
+ lecDays = ts.getDaysSlot1();
+ lecHours = ts.getHoursSlot1() * (float) lecDays.size();
+
+ labActDays = ts.getDaysSlot2();
+ labActHours = ts.getHoursSlot2() * (float) labActDays.size();
+ }
+ //it has lab/act only
+ else if(lesson.isHasLabAct()){
+ labActDays = ts.getDaysSlot1();
+ labActHours = ts.getHoursSlot1() * (float) labActDays.size();
+ }
+ //or it has lecture only
+ else{
+ lecDays = ts.getDaysSlot1();
+ lecHours = ts.getHoursSlot1() * (float) lecDays.size();
+ }
//return true of too many or not enough lec hours or lab/activity hours in the timeslot
- return Math.abs(lesson.getLecHours() - tsLecHrs) > FLOAT_TIME_DELTA
- || Math.abs(lesson.getLabActHours() - tsLabActHrs) > FLOAT_TIME_DELTA;
+ return Math.abs(lesson.getLecHours() - lecHours) > FLOAT_TIME_DELTA
+ || Math.abs(lesson.getLabActHours() - labActHours) > FLOAT_TIME_DELTA;
})
.penalize(HardMediumSoftScore.ONE_HARD)
.justifyWith((lesson, score) -> new WrongHoursAmountJustification(lesson))
.asConstraint("Lesson's timeslot must have exact time needed");
}
+
/**
- * Make sure the lessons are in the correct room. If the lesson has a lab/act we make sure it is placed
- * in a lab room. If the lesson is lecture only the we make sure it is placed in the lecture room (aka the
- * universal/general) room. Lecture rooms assumed to be "infinite"
- *
- * @param constraintFactory constraint factory
- * @return constraint penalizing lessons in the wrong room
+ * Checks that a lesson is in the correct room. If the lesson has a lab/act we make sure it is placed
+ * in a lab room. If the lesson is lecture only, then make sure it is placed in the lecture room (aka the
+ * universal/general) room, except if it got split, and it's a studio course. Lecture rooms are assumed to be
+ * "infinite"
*/
Constraint wrongRoomType(ConstraintFactory constraintFactory){
return constraintFactory.forEach(Lesson.class)
.filter(lesson -> {
Room room = lesson.getRoom();
- //if lesson has a lab/act
- if(lesson.isHasLabAct()){
+ //use same pattern as above. check if studio then check for lab/act
+ //if lesson has lab/act make sure it goes into lab type room
+ if(lesson.isHasLabAct() || lesson.isStudio()){
/*certain labs/acts can only be in certain rooms*/
if(Constants.COURSE_TO_ROOMS.containsKey(lesson.getCourseName())){
//check that the room the lesson is in the set of valid rooms
@@ -313,7 +358,7 @@ Constraint wrongRoomType(ConstraintFactory constraintFactory){
* lab/act room is valid for it; Penalize if it's in a lecture room*/
return Constants.ROOM_TO_ID_BIMAP.get(Constants.LEC_ONLY) == room.getID();
}
- //if lesson is lecture only
+ //if lesson is lecture only and the lecture doesn't have to go into a lab room
else{
//lecture only course should only be in the LEC_ONLY room
return Constants.ROOM_TO_ID_BIMAP.get(Constants.LEC_ONLY) != room.getID();
@@ -324,36 +369,44 @@ Constraint wrongRoomType(ConstraintFactory constraintFactory){
}
-
-
/**
- * This constraint will look at studio courses and penalize and studio lessons whose lecture blocks are not
- * immediately followed by a lab/act block
- * @param constraintFactory constraint factory
- * @return penalizes lessons that don't have timeslots that have lab time right after the lecture ends
+ * This constraint will look at studio courses and penalize any studio lessons whose lecture blocks are not
+ * immediately followed by a lab/act block and any lab time not immediately after lecture blocks
*/
Constraint studioLabAfterLec(ConstraintFactory constraintFactory){
return constraintFactory.forEach(Lesson.class)
.filter(lesson -> {
final int NO_NEXT_BIT_SET = -1;
- if(!lesson.isStudio()) return false;
- final BitSet lecBs = lesson.getTimeslot().getLectureBitSet();
- final BitSet labActBs = lesson.getTimeslot().getLabActBitSet();
- int end;
+ if(!lesson.isStudio() ||
+ //special case if we split up the lesson then this doesn't have to be true;
+ (lesson.isStudio() && (!lesson.isHasLecture() || !lesson.isHasLabAct()))
+ ) return false;
+ final BitSet lecBs = lesson.getTimeslot().getBitSetSlot1();
+ final BitSet labActBs = lesson.getTimeslot().getBitSetSlot2();
+ int endLec;
+ int labActScan = 0;
//for all lecture blocks, check that a lab starts right after it
- for(int idx = lecBs.nextSetBit(0); idx != NO_NEXT_BIT_SET; idx = lecBs.nextSetBit(end + 1)){
- end = lecBs.nextClearBit(idx) - 1;
- boolean labStart = labActBs.get(end + 1);
- //penalize this lesson for having a timeslot that doesn't have a lab right after lecture
- if(!labStart) return true;
+ for(int lecScan = lecBs.nextSetBit(0);
+ lecScan != NO_NEXT_BIT_SET;
+ lecScan = lecBs.nextSetBit(endLec + 1)
+ ){
+ endLec = lecBs.nextClearBit(lecScan) - 1;
+ boolean labStart = labActBs.get(endLec + 1);
+ int idxNextLab = labActBs.nextSetBit(labActScan);
+ /*penalize this lesson for having a timeslot that doesn't have a lab right after lecture UPDATE*/
+ if(!labStart || idxNextLab != endLec + 1) return true;
+
+ //set next bit we can search from to the first bit that is not set after the current lab/act block
+ labActScan = labActBs.nextClearBit(idxNextLab);
}
-
- return false;
+ //check that we don't have surplus lab time blocks
+ return labActBs.nextSetBit(labActScan) != NO_NEXT_BIT_SET;
})
.penalize(HardMediumSoftScore.ONE_HARD)
.asConstraint("Studio lab right after lecture");
}
+
/**
* constraint that will be added in when a prescheduled room is detected. The constraint will ensure that
* no lesson is scheduled in a room during its preschedule time
@@ -361,41 +414,166 @@ Constraint studioLabAfterLec(ConstraintFactory constraintFactory){
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
+ //1) skip lessons that are in the lecture only room
+ //2) take into account only lessons that have a room that has prescheduling times
+ //3) if lesson split of lec && lab/act, ignore the lesson portion if lesson isn't studio
if(Constants.LEC_ONLY.equals(lesson.getRoom().getName()) ||
- lesson.getRoom().getPrescheduled().isEmpty()) return false;
+ lesson.getRoom().getPrescheduled().isEmpty() ||
+ (lesson.isHasLecture() && !lesson.isHasLabAct() && !lesson.isStudio())) 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()));
+ Timeslot timeslot = lesson.getTimeslot();
+ BitSet usageTime = new BitSet();
+ //scenario where the lesson has both lec and lab
+ if(lesson.isHasLecture() && lesson.isHasLabAct()){
+ usageTime.or(timeslot.getBitSetSlot2());
+ if(lesson.isStudio()) usageTime.or(timeslot.getBitSetSlot1());
+ }
+ //scenario where we may have split a studio courses or the course only has a lab/act
+ else if(lesson.isHasLecture() && lesson.isStudio() || lesson.isHasLabAct()){
+ usageTime.or(timeslot.getBitSetSlot1());
+ }
+
+ return usageTime.intersects(prescheduled);
})
.penalize(HardMediumSoftScore.ONE_HARD)
.asConstraint("Penalize a lesson ");
}
+
+ /**
+ * Makes sure that if a lesson requires a lab/act time to be all in one block that it gets assigned a timeslot
+ * that meets this requirement. It also ensures that timeslots that have their time all in one block don't get
+ * assigned to lessons that don't want it.
+ */
+ Constraint timeslotPatternMatch(ConstraintFactory constraintFactory){
+ return constraintFactory.forEach(Lesson.class)
+ .filter(lesson -> {
+ Timeslot timeslot = lesson.getTimeslot();
+ //lab + lab/act
+ if(lesson.hasLecture && lesson.hasLabAct){
+ //in such a configuration the lab/act portion will be spread out; right now no lab with continuous
+ //time are in second slots
+ return !timeslot.hasSlot2 || timeslot.isContinuousSlot1() || timeslot.isContinuousSlot2();
+ }
+ //lab/act only
+ else if(lesson.hasLabAct){
+ return timeslot.isHasSlot2() ||
+ //the next portion depends on if the lab has to be all in one block
+ (lesson.getLabPattern() == LabPatterns.MULTIPLE ?
+ timeslot.isContinuousSlot1() :
+ !timeslot.isContinuousSlot1());
+ }
+ //lecture only
+ else{
+ //if this lesson is lecture only timeslot can have only one slot and the time can't be in one block
+ return timeslot.isHasSlot2() || timeslot.isContinuousSlot1();
+ }
+ })
+ .penalize(HardMediumSoftScore.ONE_HARD)
+ .asConstraint("Lesson has the wrong type of timeslot");
+ }
+
+
+ //used for the "continuousTimeTaught" constraint
+ private int countTeacherLongBlocks(Teacher teacher, List lessons){
+ //aggregate all time being taught into this BitSet
+ BitSet scheduledTime = new BitSet();
+ //aggregate all unique days taught
+ EnumSet daysTeaching = EnumSet.noneOf(Days.class);
+ //keeps track of what lesson pairs have already been counted towards the number of gaps
+ Set> used = new HashSet<>();
+ System.out.println(lessons.size());
+ for (Lesson lesson : lessons) {
+ Timeslot ts = lesson.getTimeslot();
+ scheduledTime.or(ts.getAllTimesBitSet());
+ daysTeaching.addAll(lesson.getTimeslot().getDaysSlot1());
+ daysTeaching.addAll(lesson.getTimeslot().getDaysSlot2());
+ }
+
+ int totalLongBlocks = 0;
+ for (Days day : daysTeaching) {
+ totalLongBlocks += countLongBlocks(day, scheduledTime, lessons, used);
+ }
+
+ //divide to account for potentially multiple classes counted
+ return totalLongBlocks;
+ }
+
+
+ //used for the "continuousTimeTaught" constraint
+ private int countLongBlocks(Days day, BitSet time, List lessons, Set> used){
+ //6 hours
+ final int MAX_TEACHING_CONTINUOUS_BLOCKS = 12;
+ final int OFFSET;
+ if(day == Days.MONDAY) OFFSET = BitSetHelper.MONDAY_OFFSET;
+ else if(day == Days.TUESDAY) OFFSET = BitSetHelper.TUESDAY_OFFSET;
+ else if(day == Days.WEDNESDAY) OFFSET = BitSetHelper.WEDNESDAY_OFFSET;
+ else if(day == Days.THURSDAY) OFFSET = BitSetHelper.THURSDAY_OFFSET;
+ else OFFSET = BitSetHelper.FRIDAY_OFFSET;
+ BitSet bs = time.get(OFFSET, OFFSET + BitSetHelper.MAX_BITS_PER_DAY);
+
+ int count = 0;
+ int end;
+ for(int start = bs.nextSetBit(0);
+ start != -1;
+ start = bs.nextSetBit(end + 1)
+ ){
+ end = bs.nextClearBit(start) - 1;
+ if(end - start + 1 > MAX_TEACHING_CONTINUOUS_BLOCKS){
+ int first = -1;
+ int second = -1;
+ for(int i = 0; i < lessons.size(); i++){
+ BitSet lsBs = lessons.get(i).getTimeslot().getAllTimesBitSet();
+ if(lsBs.get(OFFSET + end)) first = i;
+ if(lsBs.get(OFFSET + start)) second = i;
+ }
+ //need to check that they aren't the same or else the creation of inline set will error
+ if(first != second && used.add(Set.of(first, second))) count++;
+ }
+ }
+
+ return count;
+ }
+
+
+ /**
+ * Penalize teachers who teache for more than 6 consecutive hours in one day. Note this only penalizes
+ * when courses combined results in more than 6 hours of time back to back. It will not penalize individual
+ * timeslots that result in more than 6 hours of continuous time taught.
+ */
+ Constraint continuousTimeTaught(ConstraintFactory constraintFactory){
+ return constraintFactory.forEach(Lesson.class)
+ .groupBy(Lesson::getTeacherObj, toList())
+ .penalize(HardMediumSoftScore.ONE_HARD,
+ this::countTeacherLongBlocks)
+ .asConstraint("Penalize long teaching blocks");
+ }
+
//-------------------------------------- Medium Constraints --------------------------------------
+ /**
+ * reward 30-minute blocks that are in an instructor's preferred time
+ */
Constraint prefTime(ConstraintFactory constraintFactory){
return constraintFactory.forEach(Lesson.class)
.reward(HardMediumSoftScore.ONE_MEDIUM, lesson -> {
final Timeslot ts = lesson.getTimeslot();
final Teacher teacher = lesson.getTeacherObj();
final BitSet tmp = new BitSet();
- tmp.or(ts.getLectureBitSet());
- tmp.or(ts.getLabActBitSet());
+ tmp.or(ts.getBitSetSlot1());
+ tmp.or(ts.getBitSetSlot2());
tmp.and(teacher.getPreferences());
//reward per hour rather than per 30-minute block
return tmp.cardinality() / 2;
})
- .asConstraint("Reward 30min blocks in prof's pref times");
+ .asConstraint("Reward hour blocks in prof's pref times");
}
- //cap at 8 hour days
+ //used by "compressIndividualTeachTime" constraint
private static final int MAX_DAY_LEN = 16;
private static final int MAX_GAP = 6;
private boolean dayHasLrgGap(Days day, BitSet time){
@@ -414,7 +592,8 @@ private boolean dayHasLrgGap(Days day, BitSet time){
int end = bs.nextClearBit(start) - 1;
for(int nxtStrt = bs.nextSetBit(end + 1);
nxtStrt != NO_NEXT_BIT_SET;
- nxtStrt = bs.nextSetBit(end + 1)){
+ nxtStrt = bs.nextSetBit(end + 1)
+ ){
int localDiff = nxtStrt - end - 1;
//to large a gap between lessons on the given day
@@ -427,12 +606,13 @@ private boolean dayHasLrgGap(Days day, BitSet time){
return end - start + 1 > MAX_DAY_LEN;
}
+
/**
* This constraint penalizes a teacher if there is a gap between lessons more than {@link #MAX_GAP} amount of 30-minute
* blocks between the end of a lesson and the start of another lesson. It will also penalize a teacher that has
* a day longer than 8 hours; i.e. the time from when their first lesson starts to when their last lesson ends.
*/
- Constraint compressTeachTime(ConstraintFactory constraintFactory){
+ Constraint compressIndividualTeachTime(ConstraintFactory constraintFactory){
return constraintFactory.forEach(Lesson.class)
.groupBy(Lesson::getTeacherObj, toList())
.filter((teacher, lessons) -> {
@@ -446,14 +626,14 @@ Constraint compressTeachTime(ConstraintFactory constraintFactory){
for(Lesson lesson: lessons){
Timeslot ts = lesson.getTimeslot();
//flag if we find any two lessons that share a day
- if(Collections.disjoint(daysTeaching, ts.getLecDays())) lsSharedDay = true;
- if(Collections.disjoint(daysTeaching, ts.getNonLecDays())) lsSharedDay = true;
+ if(Collections.disjoint(daysTeaching, ts.getDaysSlot1())) lsSharedDay = true;
+ if(Collections.disjoint(daysTeaching, ts.getDaysSlot2())) lsSharedDay = true;
//collect the days being taught
- daysTeaching.addAll(ts.getLecDays());
- daysTeaching.addAll(ts.getNonLecDays());
+ daysTeaching.addAll(ts.getDaysSlot1());
+ daysTeaching.addAll(ts.getDaysSlot2());
//accumulate time taught
- scheduledTime.or(ts.getLectureBitSet());
- scheduledTime.or(ts.getLabActBitSet());
+ scheduledTime.or(ts.getBitSetSlot1());
+ scheduledTime.or(ts.getBitSetSlot2());
}
//if no lessons share days, then there is nothing to penalize
@@ -470,8 +650,18 @@ Constraint compressTeachTime(ConstraintFactory constraintFactory){
.asConstraint("Penalize teacher schedules with large gaps or long days");
}
- //------------------------
-
+ //used by constraints penalizing/rewarding hour gap preference
+ /**
+ * count number of gaps between lessons for the given day. If a pair of lessons is found in used,
+ * it will not be counted towards the total number of gaps in the day.
+ *
+ * @param day used to mask what bits will be looked at
+ * @param time bitset that will be masked according to the day parameter
+ * @param lessons list all lessons a teacher has been scheduled
+ * @param used set of lesson pairs that have already been used; elements are sets rather than pairs
+ * because the order of the lesson ids in a pair shouldn't matter
+ * @return number of gaps found in the day
+ */
private int countHourGaps(Days day, BitSet time, List lessons, Set> used) {
// Determine the day's offset
final int OFFSET;
@@ -518,16 +708,20 @@ private int countHourGaps(Days day, BitSet time, List lessons, Set lessons) {
+ //aggregate all time being taught into this BitSet
BitSet scheduledTime = new BitSet();
+ //aggregate all unique days taught
EnumSet daysTeaching = EnumSet.noneOf(Days.class);
+ //keeps track of what lesson pairs have already been counted towards the number of gaps
Set> used = new HashSet<>();
for (Lesson lesson : lessons) {
Timeslot ts = lesson.getTimeslot();
scheduledTime.or(ts.getAllTimesBitSet());
- daysTeaching.addAll(lesson.getTimeslot().getLecDays());
- daysTeaching.addAll(lesson.getTimeslot().getNonLecDays());
+ daysTeaching.addAll(lesson.getTimeslot().getDaysSlot1());
+ daysTeaching.addAll(lesson.getTimeslot().getDaysSlot2());
}
int totalGaps = 0;
@@ -539,6 +733,9 @@ private int countTeacherHourGaps(List lessons) {
return totalGaps;
}
+ /**
+ * reward hour gaps in an instructor's teaching schedule if they prefer hour gaps between courses
+ */
Constraint rewardPreferredHourGap(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Lesson.class)
.groupBy(Lesson::getTeacherObj, toList())
@@ -549,6 +746,10 @@ Constraint rewardPreferredHourGap(ConstraintFactory constraintFactory) {
.asConstraint("Reward teachers who prefer 1-hour gaps");
}
+
+ /**
+ * penalize hour gaps in an instructor's teaching schedule if they dislike hour gaps between courses
+ */
Constraint penalizeDislikedHourGap(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Lesson.class)
.groupBy(Lesson::getTeacherObj, toList())
@@ -559,6 +760,30 @@ Constraint penalizeDislikedHourGap(ConstraintFactory constraintFactory) {
.asConstraint("Penalize teachers who dislike 1-hour gaps");
}
+
+ /**
+ * reward different instances of a courses being taught that don't overlap
+ */
+ Constraint rewardCoursesDiffTimes(ConstraintFactory constraintFactory) {
+ return constraintFactory.forEachUniquePair(Lesson.class,
+ Joiners.filtering((lesson1, lesson2) -> {
+ Integer lesson1LinkerId = lesson1.getLinkerId();
+ Integer lesson2LinkerId = lesson2.getLinkerId();
+
+ return lesson1LinkerId != null
+ && !lesson1LinkerId.equals(lesson2LinkerId)
+ && lesson1.getCourseID() == lesson2.getCourseID();
+ }))
+ .filter(((lesson1, lesson2) -> {
+ BitSet bitSet1 = lesson1.getTimeslot().getAllTimesBitSet();
+ BitSet bitSet2 = lesson2.getTimeslot().getAllTimesBitSet();
+
+ return !bitSet1.intersects(bitSet2);
+ }))
+ .reward(HardMediumSoftScore.ONE_MEDIUM)
+ .asConstraint("Reward linked courses at different times");
+ }
+
//-------------------------------------- Soft Constraints --------------------------------------
/*at least 50 percent of the time for scheduled Department courses should be outside Prime Time hours
@@ -567,9 +792,6 @@ Constraint penalizeDislikedHourGap(ConstraintFactory constraintFactory) {
/**
* Rewards 30-minute blocks of lecture time outside prime time.
- *
- * @param constraintFactory constraint factory
- * @return constraint rewarding lecture time out of prime time
*/
Constraint outPrimeTime(ConstraintFactory constraintFactory){
return constraintFactory.forEach(Lesson.class)
@@ -586,9 +808,6 @@ Constraint outPrimeTime(ConstraintFactory constraintFactory){
/**
* Penalizes 30-minute blocks of lecture time in prime time
- *
- * @param constraintFactory constraint factory
- * @return constraint penalizing lecture time in prime time
*/
Constraint inPrimeTime(ConstraintFactory constraintFactory){
return constraintFactory.forEach(Lesson.class)
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 142baeed..67adcb24 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
@@ -8,8 +8,8 @@
import org.acme.schooltimetabling.domain.Room;
import org.acme.schooltimetabling.domain.Timeslot;
import org.acme.schooltimetabling.domain.teacher.Teacher;
+import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.acme.schooltimetabling.helperClasses.BitSetHelper;
-import org.glassfish.jaxb.runtime.v2.runtime.reflect.opt.Const;
import java.time.LocalTime;
import java.util.BitSet;
@@ -28,9 +28,9 @@ public class ConstraintTestHelper {
.gapPref(Preference.NEUTRAL)
.canon("dummyInstructor")
.build();
- public final static Timeslot DUMMY_TS = Timeslot.test_lecLabBitAndDays(999, EMPTY_BS, EMPTY_BS, NO_DAYS, NO_DAYS);
+ public final static Timeslot DUMMY_TS = Timeslot.test_minSetUp("999");
/**
- * Test: room name; will be used by a specific course {@link #TEST_L_W_LAB_SPECIFIC}
+ * Test: room name; will be used by a specific course {@link #TEST_STUDIO_SPECIFIC}
*/
public final static String TEST_NAME_LAB_ROOM_SPECIFIC = "LabRoom";
/**
@@ -44,7 +44,7 @@ public class ConstraintTestHelper {
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";
+ public final static String TEST_STUDIO_SPECIFIC = "Lab/Act course";
/**
* Room that can be used by any lab
*/
@@ -54,7 +54,7 @@ public class ConstraintTestHelper {
*/
public static Room TEST_ROOM_SPECIFIC;
/**
- * Room prescheduled
+ * Room prescheduled: currently MWF 9-10am
*/
public static Room TEST_ROOM_PRESCHEDULED;
public static String NON_STUDIO_SPECIFIC = "non studio course with a specific room";
@@ -76,10 +76,10 @@ public class ConstraintTestHelper {
//add which lessons are studios
Constants.STUDIO_STYLE_COURSES.add(DUMMY_STUDIO);
- Constants.STUDIO_STYLE_COURSES.add(TEST_L_W_LAB_SPECIFIC);
+ Constants.STUDIO_STYLE_COURSES.add(TEST_STUDIO_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(TEST_STUDIO_SPECIFIC, TEST_SET_LAB_ROOMS);
Constants.COURSE_TO_ROOMS.put(NON_STUDIO_SPECIFIC, TEST_SET_LAB_ROOMS);
//create the room objects
@@ -95,9 +95,21 @@ public class ConstraintTestHelper {
BitSet roomBlocked = BitSetHelper.timeSlotBitSet(LocalTime.parse("9:00AM", Constants.TIME_FORMATTER), 2, MWF);
TEST_ROOM_PRESCHEDULED.prescheduleUpdate(roomBlocked);
+ //add
+ EnumSet MTWRF = EnumSet.of(Days.MONDAY, Days.TUESDAY, Days.WEDNESDAY, Days.THURSDAY, Days.FRIDAY);
+ BitSet compressIn = new BitSet(150);
+ compressIn.or(
+ BitSetHelper.timeSlotBitSet(LocalTime.parse("10:00AM", Constants.TIME_FORMATTER),
+ 8, MTWRF)
+ );
+ BitSet compressOut = (BitSet) compressIn.clone();
+ compressOut.flip(0,150);
+ ScheduleConfig.setCompressBits(compressIn, compressOut);
Constants.TESTING = true;
}
+
+
public static void load(){
}
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 68e8f334..b2adb95c 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
@@ -1,5 +1,7 @@
package org.acme.schooltimetabling.solver;
+import ai.timefold.solver.core.api.score.stream.Constraint;
+import org.acme.schooltimetabling.builders.lessons.LessonBuilder;
import org.acme.schooltimetabling.builders.teachers.TeacherBuilder;
import org.acme.schooltimetabling.builders.teachers.policies.DefaultTeachingPolicy;
import org.acme.schooltimetabling.builders.teachers.policies.FacultyPolicy;
@@ -11,9 +13,10 @@
import org.acme.schooltimetabling.domain.Timeslot;
import org.acme.schooltimetabling.domain.Timetable;
import org.acme.schooltimetabling.domain.teacher.Teacher;
+import org.acme.schooltimetabling.fileObjects.LabPatterns;
import org.acme.schooltimetabling.helperClasses.BitSetHelper;
-import org.acme.schooltimetabling.helperClasses.Generators.LessonGenerator;
import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
+import org.acme.schooltimetabling.helperClasses.Generators.LessonGenerator;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -28,13 +31,12 @@ public class TestConstraints {
ConstraintVerifier constraintVerifier = ConstraintVerifier.build(
new TimetableConstraintProvider(), Timetable.class, Lesson.class);
final static int NO_PENALTY = 0;
- DateTimeFormatter formatter = DateTimeFormatter.ofPattern("h:mma");
+ final DateTimeFormatter FORMATTER = Constants.TIME_FORMATTER;
@BeforeAll
static void setUp(){
String YAML_FILE_PATH = "constants/config.yaml";
ScheduleConfig.loadConfig(YAML_FILE_PATH);
- LessonGenerator.OLD_studio_detected = true;
ConstraintTestHelper.load();
}
@@ -42,6 +44,7 @@ static void setUp(){
@Test
@DisplayName("Testing same teacher same course constraint")
void sameLessonNTeacher(){
+ LessonBuilder builder = new LessonBuilder();
EnumSet MW = EnumSet.of(Days.MONDAY, Days.WEDNESDAY);
EnumSet MT = EnumSet.of(Days.MONDAY, Days.TUESDAY);
EnumSet MWF = EnumSet.of(Days.MONDAY, Days.WEDNESDAY, Days.FRIDAY);
@@ -55,14 +58,32 @@ void sameLessonNTeacher(){
Lesson ls2 = Lesson.test_buildLesson("2", 3, "csc201", "dummyInstructor", "",
"3-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER, timeslot2, ConstraintTestHelper.DUMMY_ROOM);
- Timeslot timeslot3 = Timeslot.test_CreateWithDaysOnly(2, MWF, MWF);
+ Timeslot timeslot3 = Timeslot.test_CreateWithDaysOnly(3, MWF, MWF);
Lesson ls3 = Lesson.test_buildLesson("3", 3, "csc201", "dummyInstructor", "",
"3-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER, timeslot3, ConstraintTestHelper.DUMMY_ROOM);
+
+
+ Teacher teacher2 = new Teacher(2, "rand",
+ ConstraintTestHelper.EMPTY_BS, ConstraintTestHelper.EMPTY_BS, ConstraintTestHelper.EMPTY_BS);
+ Lesson ls5 = Lesson.test_buildLesson("1", 1, "csc201", "dummyInstructor", "",
+ "3-1-0", 1, teacher2, timeslot1, ConstraintTestHelper.DUMMY_ROOM);
+ Lesson ls4 = builder
+ .id(4)
+ .section(5)
+ .courseName("csc201")
+ .courseConfig("3-1-0")
+ .teacherObj(teacher2)
+ .courseId(1)
+ .timeslot(Timeslot.test_CreateWithDaysOnly(4, MWF, ConstraintTestHelper.NO_DAYS))
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .build();
+
+
constraintVerifier.verifyThat(TimetableConstraintProvider::sameClassSameDays)
- .given(ls1, ls2, ls3)
+ .given(ls1, ls2, ls3, ls4, ls5)
/*Note this takes into account weight of rewards*/
- .penalizesBy(2);
+ .penalizesBy(3);
}
@@ -72,7 +93,7 @@ void sameLessonNTeacher(){
void teacherAndTimeslot(){
//No penalty for teacher1
EnumSet MW = EnumSet.of(Days.MONDAY, Days.WEDNESDAY);
- BitSet teacher1Bits = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", formatter)
+ BitSet teacher1Bits = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", FORMATTER)
, 4, MW);
Teacher teacher1 = new TeacherBuilder(new DefaultTeachingPolicy())
.preference(ConstraintTestHelper.EMPTY_BS)
@@ -81,15 +102,15 @@ void teacherAndTimeslot(){
.canon("instructor1")
.gapPref(Preference.NEUTRAL)
.build();
- BitSet ts1 = BitSetHelper.timeSlotBitSet(LocalTime.parse("10:00AM", formatter)
+ BitSet ts1 = BitSetHelper.timeSlotBitSet(LocalTime.parse("10:00AM", FORMATTER)
, 4, MW);
- Timeslot timeslot1 = Timeslot.test_lecLabBitAndDays(1, ts1, ConstraintTestHelper.EMPTY_BS,
+ Timeslot ts_10AM_MW = Timeslot.test_lecLabBitAndDays(1, ts1, ConstraintTestHelper.EMPTY_BS,
MW, ConstraintTestHelper.NO_DAYS);
Lesson lesson1 = Lesson.test_buildLesson("1", 1, "dummyName", "noName",
- "", "3-1-0", 2, teacher1, timeslot1, ConstraintTestHelper.DUMMY_ROOM);
+ "", "3-1-0", 2, teacher1, ts_10AM_MW, ConstraintTestHelper.DUMMY_ROOM);
//No Penalty 1+ for teacher2
- BitSet teacher2Bits = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", formatter)
+ BitSet teacher2Bits = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", FORMATTER)
, 4, MW);
Teacher teacher2 = new TeacherBuilder(new DefaultTeachingPolicy())
.preference(ConstraintTestHelper.EMPTY_BS)
@@ -98,8 +119,8 @@ void teacherAndTimeslot(){
.canon("instructor2")
.gapPref(Preference.NEUTRAL)
.build();
- BitSet bs_MW_7AM_blck2 = BitSetHelper.timeSlotBitSet(LocalTime.parse("7:00AM", formatter), 2, MW);
- BitSet bs_MW_8AM_blcks2 = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", formatter), 2, MW);
+ BitSet bs_MW_7AM_blck2 = BitSetHelper.timeSlotBitSet(LocalTime.parse("7:00AM", FORMATTER), 2, MW);
+ BitSet bs_MW_8AM_blcks2 = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", FORMATTER), 2, MW);
Timeslot timeslot2 = Timeslot.test_lecLabBitAndDays(1, bs_MW_7AM_blck2, bs_MW_8AM_blcks2, MW, MW);
Lesson lesson2 = Lesson.test_buildLesson("1", 1, "dummyName", "noName",
"", "3-1-0", 2, teacher2, timeslot2, ConstraintTestHelper.DUMMY_ROOM);
@@ -116,9 +137,9 @@ void facultyAndTimeslot(){
//NOTE: the faculty class sets bits from 9pm-10pm MWF a faculty time only during testing
EnumSet MW = EnumSet.of(Days.MONDAY, Days.WEDNESDAY);
- BitSet bs_9pm_2blcks_MW = BitSetHelper.timeSlotBitSet(LocalTime.parse("9:00PM", formatter)
+ BitSet bs_9pm_2blcks_MW = BitSetHelper.timeSlotBitSet(LocalTime.parse("9:00PM", FORMATTER)
, 2, MW);
- BitSet bs_8pm_2blcks_MW = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00PM", formatter)
+ BitSet bs_8pm_2blcks_MW = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00PM", FORMATTER)
, 2, MW);
Teacher teacher = new TeacherBuilder(new FacultyPolicy(bs_9pm_2blcks_MW))
@@ -154,7 +175,7 @@ void facultyAndTimeslot(){
@DisplayName("Test: teacher can't teach two lessons at the same time")
void teacherLessonSameTime(){
EnumSet enumSet = EnumSet.of(Days.MONDAY, Days.WEDNESDAY);
- BitSet ts1 = BitSetHelper.timeSlotBitSet(LocalTime.parse("1:00PM", formatter)
+ BitSet ts1 = BitSetHelper.timeSlotBitSet(LocalTime.parse("1:00PM", FORMATTER)
, 6, enumSet);
//1PM-4 MW
Timeslot timeslot1 = Timeslot.test_lecLabBitAndDays(1, ts1, ConstraintTestHelper.EMPTY_BS, enumSet, enumSet);
@@ -164,25 +185,23 @@ void teacherLessonSameTime(){
EnumSet enumSet2 = EnumSet.of(Days.WEDNESDAY);
- BitSet ts2 = BitSetHelper.timeSlotBitSet(LocalTime.parse("3:30PM", formatter)
+ BitSet ts2 = BitSetHelper.timeSlotBitSet(LocalTime.parse("3:30PM", FORMATTER)
, 2, enumSet2);
//3:30PM-4:30PM W
- Timeslot timeslot2 = Timeslot.test_lecLabBitAndDays(2, ConstraintTestHelper.EMPTY_BS, ts2,
- ConstraintTestHelper.NO_DAYS, enumSet2);
+ Timeslot timeslot2 = Timeslot.test_lecLabBitAndDays(2, ts2, ConstraintTestHelper.EMPTY_BS,
+ enumSet2, ConstraintTestHelper.NO_DAYS);
Lesson lesson2 = Lesson.test_buildLesson("2", 2, "dummyName", "noName",
- "", "3-1-0", 3, ConstraintTestHelper.DUMMY_TEACHER, timeslot2,
+ "", "1-0-0", 3, ConstraintTestHelper.DUMMY_TEACHER, timeslot2,
ConstraintTestHelper.DUMMY_ROOM);
- EnumSet soloDay = EnumSet.of(Days.MONDAY);
- BitSet bs3 = BitSetHelper.timeSlotBitSet(LocalTime.parse("2:00PM", formatter), 2, soloDay);
EnumSet MWF = EnumSet.of(Days.MONDAY, Days.WEDNESDAY, Days.FRIDAY);
- BitSet bs_1PM_MWF = BitSetHelper.timeSlotBitSet(LocalTime.parse("1:00PM", formatter), 2, soloDay);
- BitSet bs_2PM_MWF = BitSetHelper.timeSlotBitSet(LocalTime.parse("2:00PM", formatter), 2, soloDay);
+ BitSet bs_1PM_MWF = BitSetHelper.timeSlotBitSet(LocalTime.parse("1:00PM", FORMATTER), 2, MWF);
+ BitSet bs_2PM_MWF = BitSetHelper.timeSlotBitSet(LocalTime.parse("2:00PM", FORMATTER), 2, MWF);
Timeslot ts_1PM_MWF_blks4 = Timeslot.test_lecLabBitAndDays(1, bs_1PM_MWF, bs_2PM_MWF, MWF
, MWF);
- Timeslot ts3 = Timeslot.test_lecLabBitAndDays(1, bs3, ConstraintTestHelper.EMPTY_BS, soloDay, ConstraintTestHelper.NO_DAYS);
+
Teacher teacher2 = new TeacherBuilder(new DefaultTeachingPolicy())
.preference(ConstraintTestHelper.EMPTY_BS)
.acceptable(ConstraintTestHelper.EMPTY_BS)
@@ -191,7 +210,8 @@ void teacherLessonSameTime(){
.gapPref(Preference.NEUTRAL)
.build();
Lesson lesson3 = Lesson.test_buildLesson("3", 3, "dummyName", "noName",
- "", "3-1-0", 3, teacher2, ts_1PM_MWF_blks4, ConstraintTestHelper.DUMMY_ROOM);
+ "", "3-1-0", 3, teacher2, ts_1PM_MWF_blks4,
+ ConstraintTestHelper.DUMMY_ROOM);
//penalizes lesson1 and lesson2 grouping
constraintVerifier.verifyThat(TimetableConstraintProvider::lessonConflict)
@@ -205,126 +225,165 @@ void teacherLessonSameTime(){
void roomMultiLessons(){
EnumSet MWF = EnumSet.of(Days.MONDAY, Days.WEDNESDAY,Days.FRIDAY);
//9-11 MWF
- BitSet bs_MWF_9AM_4blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("9:00AM", formatter)
+ BitSet bs_MWF_8AM_4blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", FORMATTER)
, 4, MWF);
//9-10 MWF
- BitSet bs_MWF_9AM_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("9:00AM", formatter)
+ BitSet bs_MWF_9AM_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("9:00AM", FORMATTER)
, 2, MWF);
//10-11 MWF
- BitSet bs_MWF_10_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("10:00AM", formatter),
+ BitSet bs_MWF_10_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("10:00AM", FORMATTER),
2, MWF);
//9-11:30 TR
- BitSet bs_MWF_11_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("11:00AM", formatter),
+ BitSet bs_MWF_11_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("11:00AM", FORMATTER),
2, MWF);
- Timeslot ts_lab_mwf_9_4blcks = Timeslot.test_lecLabBitAndDays(1, ConstraintTestHelper.EMPTY_BS, bs_MWF_9AM_4blcks,
- ConstraintTestHelper.NO_DAYS, MWF);
- Timeslot ts_lab_mwf_10_2blcks = Timeslot.test_lecLabBitAndDays(2, bs_MWF_9AM_2blcks, bs_MWF_10_2blcks,
- ConstraintTestHelper.NO_DAYS, MWF);
+ Timeslot ts_mwf_8_4blcks = Timeslot.test_lecLabBitAndDays(1, bs_MWF_8AM_4blcks, ConstraintTestHelper.EMPTY_BS,
+ MWF, ConstraintTestHelper.NO_DAYS);
+ Timeslot ts_mwf_9_10_2blcks = Timeslot.test_lecLabBitAndDays(2, bs_MWF_9AM_2blcks, bs_MWF_10_2blcks,
+ MWF, MWF);
//no conflict with either
Timeslot ts_lec_mwf_10_lab_mwf_11 = Timeslot.test_lecLabBitAndDays(3, bs_MWF_10_2blcks, bs_MWF_11_2blcks,
MWF, MWF);
+ Timeslot ts_mwf_11 = Timeslot.test_lecLabBitAndDays(4, bs_MWF_11_2blcks, ConstraintTestHelper.EMPTY_BS,
+ MWF, ConstraintTestHelper.NO_DAYS);
Lesson lesson1 = Lesson.test_buildLesson("1", 1, "dummyName", "noName"
- , "", "0-0-2", 1, ConstraintTestHelper.DUMMY_TEACHER, ts_lab_mwf_9_4blcks,
+ , "", "0-0-1", 1, ConstraintTestHelper.DUMMY_TEACHER, ts_mwf_8_4blcks,
ConstraintTestHelper.DUMMY_ROOM);
Lesson lesson2 = Lesson.test_buildLesson("2", 2, "dummyName", "noName"
- , "", "3-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER, ts_lab_mwf_10_2blcks,
+ , "", "3-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER, ts_mwf_9_10_2blcks,
ConstraintTestHelper.DUMMY_ROOM);
Lesson lesson3 = Lesson.test_buildLesson("3", 3, "dummyName", "noName"
, "", "3-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER, ts_lec_mwf_10_lab_mwf_11,
ConstraintTestHelper.DUMMY_ROOM);
+ Lesson lesson4studio = Lesson.test_buildLesson("4", 1, ConstraintTestHelper.DUMMY_STUDIO, "", "",
+ "3-0-0", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_mwf_11,
+ ConstraintTestHelper.DUMMY_ROOM);
constraintVerifier.verifyThat(TimetableConstraintProvider::labActRoomConflict)
- .given(lesson1, lesson2, lesson3)
- .penalizesBy(1);
+ .given(lesson1, lesson2, lesson3, lesson4studio)
+ .penalizesBy(2);
}
@Test
@DisplayName("Correct time slot hours for course type")
void timeslotAndLessonTimeMatch(){
- EnumSet MWF = EnumSet.of(Days.MONDAY, Days.WEDNESDAY,Days.FRIDAY);
- EnumSet MW = EnumSet.of(Days.MONDAY, Days.WEDNESDAY);
- EnumSet MTWR = EnumSet.of(Days.MONDAY, Days.TUESDAY, Days.WEDNESDAY, Days.THURSDAY);
- Room room = new Room("1", "dummyRoom", 1);
- Teacher teacher = new TeacherBuilder(new DefaultTeachingPolicy())
- .preference(ConstraintTestHelper.EMPTY_BS)
- .acceptable(ConstraintTestHelper.EMPTY_BS)
- .conflict(ConstraintTestHelper.EMPTY_BS)
- .canon("instructor1")
- .gapPref(Preference.NEUTRAL)
+ LessonBuilder builder = new LessonBuilder();
+ Timeslot ts_mwf_9am_12pm = new Timeslot(1,
+ "MWF", "9:00AM", "12:00PM", 1f, 3f,
+ "", "", "", 0);
+
+ Timeslot ts_tr_10_1pm = new Timeslot(2,
+ "tr", "10:00AM", "1:00PM", 1f, 3f,
+ "", "", "" , 0);
+
+ Timeslot ts_m_7_10am = new Timeslot(3,
+ "m", "7:00AM", "10:00AM", 3, 3,
+ "", "", "", 0);
+
+
+ Lesson lec_lab = builder
+ .clear()
+ .id(1)
+ .section(1)
+ .courseName("")
+ .courseConfig("3-1-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .timeslot(ts_mwf_9am_12pm)
+ .build();
+
+ Lesson lec_lab2 = builder
+ .clear()
+ .id(2)
+ .section(3)
+ .courseName("")
+ .courseConfig("2-0-1")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .timeslot(ts_tr_10_1pm)
+ .build();
+
+ Lesson lab = builder
+ .clear()
+ .id(3)
+ .section(1)
+ .courseName("continuous")
+ .courseConfig("0-1-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .timeslot(ts_m_7_10am)
+ .build();
+
+ Lesson lec = builder
+ .clear()
+ .id(3)
+ .section(1)
+ .courseName("continuous")
+ .courseConfig("3-0-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .timeslot(ts_m_7_10am)
+ .build();
+
+
+
+
+ constraintVerifier.verifyThat(TimetableConstraintProvider::wrongHoursAmount)
+ .given(lec_lab, lec_lab2, lab ,lec)
+ .penalizesBy(0);
+ }
+
+
+ @Test
+ @DisplayName("Wrong time slot hours for course type")
+ void timeslotAndLessonTimeMismatch(){
+ LessonBuilder builder = new LessonBuilder();
+ Timeslot ts_tr_9_11am = new Timeslot(1,
+ "TR", "9:00AM", "11:00AM", 1f, 2f,
+ "", "", "", 0);
+ Timeslot ts_m_7_10am = new Timeslot(3,
+ "m", "7:00AM", "10:00AM", 3, 3,
+ "", "", "", 0);
+
+ //lesson with lab and lec
+ Lesson lec_lab = builder
+ .clear()
+ .id(1)
+ .section(1)
+ .courseName("")
+ .courseConfig("3-1-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .timeslot(ts_tr_9_11am)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .build();
+ //lesson with only lab
+ Lesson act = builder
+ .clear()
+ .id(2)
+ .section(3)
+ .courseName("")
+ .courseConfig("0-0-1")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .timeslot(ts_m_7_10am)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .build();
+ //lesson with only lec
+ Lesson lec = builder
+ .clear()
+ .id(3)
+ .section(4)
+ .courseName("")
+ .courseConfig("1-0-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .timeslot(ts_m_7_10am)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
.build();
- /*9-10 MWF*/
- BitSet bs_mwf_9_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("9:00AM", formatter)
- , 2, MWF);
- /*10-11 MWF*/
- BitSet bs_mwf_10_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("10:00AM", formatter),
- 2, MWF);
- /*10-11:30 MWF*/
- BitSet bs_mwf_10_3blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("10:00AM", formatter),
- 3, MWF);
- /*8:30-10:00 MW*/
- BitSet bs_mw_830_3blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:30AM", formatter),
- 3, MW);
- /*9-10 MTWR*/
- BitSet bs_mtwr_9_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("9:00AM", formatter),
- 2, MTWR);
- /*10-11:30 MW*/
- BitSet bs_mw_10_3blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("10:00AM", formatter),
- 3, MW);
- /*7-8 MW*/
- BitSet bs_mw_7_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("7:00AM", formatter),
- 2, MW);
- /*8-9 MW*/
- BitSet bs_mw_8_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", formatter),
- 2, MW);
-
- /*right amount of hours*/
- Timeslot timeslot1 = Timeslot.test_lecLabBitAndDays(1, bs_mwf_9_2blcks, bs_mwf_10_2blcks, MWF, MWF);
- /*wrong lab/act hours*/
- Timeslot timeslot2 = Timeslot.test_lecLabBitAndDays(2, bs_mwf_9_2blcks, bs_mwf_10_3blcks, MWF, MWF);
- /*wrong lec hours*/
- Timeslot timeslot3 = Timeslot.test_lecLabBitAndDays(3, bs_mwf_10_3blcks, bs_mwf_10_2blcks, MWF, MWF);
- /*right amount of hours*/
- /*1hr lec 4 days*/
- Timeslot timeslot4 = Timeslot.test_lecLabBitAndDays(4, bs_mtwr_9_2blcks, ConstraintTestHelper.EMPTY_BS,
- MTWR, ConstraintTestHelper.NO_DAYS);
- /*right amount of hours*/
- /*1.5 hours lec & lab 2 days*/
- Timeslot timeslot5 = Timeslot.test_lecLabBitAndDays(5, bs_mw_830_3blcks, bs_mw_10_3blcks, MW, MW);
- /*right amount of hours of 1 activity unit and 2 lec units*/
- Timeslot timeslot6 = Timeslot.test_lecLabBitAndDays(5, bs_mw_7_2blcks, bs_mw_8_2blcks, MW, MW);
- Timeslot ts_lab_mw_830_3blcks = Timeslot.test_lecLabBitAndDays(7, ConstraintTestHelper.EMPTY_BS, bs_mw_830_3blcks,
- ConstraintTestHelper.NO_DAYS, MW);
-
-
- /*For this test only the timeslot and course configuration matter*/
- /*3hrs lec & 3hrs lab per week*/
- Lesson lesson1 = Lesson.test_buildLesson("1", 1, "dummyName1", "noName"
- , "", "3-1-0", 1, teacher, timeslot1, room);
- /*3hrs lec & 3hrs lab per week*/
- Lesson lesson2 = Lesson.test_buildLesson("2", 1, "dummyName2", "noName"
- , "", "3-1-0", 1, teacher, timeslot2, room);
- /*3hrs lec & 3hrs lab per week*/
- Lesson lesson3 = Lesson.test_buildLesson("3", 1, "dummyName3", "noName"
- , "", "3-1-0", 1, teacher, timeslot3, room);
- /*4hrs lec per week*/
- Lesson lesson4 = Lesson.test_buildLesson("4", 1, "dummyName4", "noName"
- , "", "4-0-0", 1, teacher, timeslot4, room);
- /*3hrs lec & 3hrs lab per week*/
- Lesson lesson5 = Lesson.test_buildLesson("5", 1, "dummyName5", "noName"
- , "", "3-1-0", 1, teacher, timeslot5, room);
- /*lecture with activity 2hrs lec and 2 act hrs per week*/
- Lesson lesson6 = Lesson.test_buildLesson("6", 1, "dummyName6", "noName"
- , "", "0-1-0", 1, teacher, timeslot6, room);
- Lesson lsLabGood = Lesson.test_buildLesson("7", 1, "", ""
- , "", "0-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER, ts_lab_mw_830_3blcks,
- ConstraintTestHelper.DUMMY_ROOM);
constraintVerifier.verifyThat(TimetableConstraintProvider::wrongHoursAmount)
- .given(lesson1, lesson2, lesson3, lesson4, lesson5, lesson6, lsLabGood)
+ .given(lec_lab, act, lec)
.penalizesBy(3);
}
@@ -332,8 +391,12 @@ void timeslotAndLessonTimeMatch(){
@Test
@DisplayName("Penalty: Lesson should be in the right Room")
void penLessonRoomCheck(){
+ //recreating the lecture only room
+ final int LEC_ONLY_ID = Constants.ROOM_TO_ID_BIMAP.get(Constants.LEC_ONLY);
+ final Room LEC_ONLY_ROOM = new Room(Integer.toString(LEC_ONLY_ID), "LEC_ONLY", LEC_ONLY_ID);
+
//Lesson requires a specific room
- Lesson lesson1 = Lesson.test_buildLesson("1", 1, ConstraintTestHelper.NON_STUDIO_SPECIFIC, "",
+ Lesson lesson1 = Lesson.test_buildLesson("0", 1, ConstraintTestHelper.NON_STUDIO_SPECIFIC, "",
"", "2-1-0", Constants.COURSE_ID_BIMAP.get(ConstraintTestHelper.NON_STUDIO_SPECIFIC),
ConstraintTestHelper.DUMMY_TEACHER, ConstraintTestHelper.DUMMY_TS, ConstraintTestHelper.TEST_ROOM_RANDO_LAB);
@@ -342,13 +405,17 @@ void penLessonRoomCheck(){
"", "1-0-0", 1111, ConstraintTestHelper.DUMMY_TEACHER,
ConstraintTestHelper.DUMMY_TS, ConstraintTestHelper.TEST_ROOM_RANDO_LAB);
- Lesson lsLabOnly = Lesson.test_buildLesson("2", 1, Constants.LEC_ONLY,
- "noName", "", "0-0-1", 1, ConstraintTestHelper.DUMMY_TEACHER,
- ConstraintTestHelper.DUMMY_TS, ConstraintTestHelper.DUMMY_ROOM);
+ Lesson lsLabOnly = Lesson.test_buildLesson("2", 1, ConstraintTestHelper.TEST_STUDIO_SPECIFIC,
+ "noName", "", "2-0-1", 1, ConstraintTestHelper.DUMMY_TEACHER,
+ ConstraintTestHelper.DUMMY_TS, ConstraintTestHelper.TEST_ROOM_RANDO_LAB);
+
+ Lesson lecOnlyStudioSplit = Lesson.test_buildLesson("3", 1, ConstraintTestHelper.DUMMY_STUDIO,
+ "", "", "1-0-0", 1, ConstraintTestHelper.DUMMY_TEACHER,
+ ConstraintTestHelper.DUMMY_TS, LEC_ONLY_ROOM);
constraintVerifier.verifyThat(TimetableConstraintProvider::wrongRoomType)
- .given(lesson1, lesson2, lsLabOnly)
- .penalizesBy(2);
+ .given(lesson1, lesson2, lsLabOnly, lecOnlyStudioSplit)
+ .penalizesBy(4);
}
@@ -359,6 +426,7 @@ void noPenLessonRoomCheck(){
//recreating the lecture only room
final int LEC_ONLY_ID = Constants.ROOM_TO_ID_BIMAP.get(Constants.LEC_ONLY);
final Room LEC_ONLY_ROOM = new Room(Integer.toString(LEC_ONLY_ID), "LEC_ONLY", LEC_ONLY_ID);
+
Lesson lesson1 = Lesson.test_buildLesson("1", 1, ConstraintTestHelper.NON_STUDIO_SPECIFIC, "",
"", "2-1-0", Constants.COURSE_ID_BIMAP.get(ConstraintTestHelper.NON_STUDIO_SPECIFIC),
ConstraintTestHelper.DUMMY_TEACHER, ConstraintTestHelper.DUMMY_TS, ConstraintTestHelper.TEST_ROOM_SPECIFIC);
@@ -367,12 +435,19 @@ 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_NAME_RANDOM_LAB_ROOM,
+ Lesson lsLabOnly = Lesson.test_buildLesson("2", 1, "activity course",
"noName", "", "0-0-1", 1, ConstraintTestHelper.DUMMY_TEACHER,
ConstraintTestHelper.DUMMY_TS, ConstraintTestHelper.DUMMY_ROOM);
+ Lesson studioLecSplit = Lesson.test_buildLesson("3", 1, ConstraintTestHelper.TEST_STUDIO_SPECIFIC,
+ "noName", "", "0-0-1", 1, ConstraintTestHelper.DUMMY_TEACHER,
+ ConstraintTestHelper.DUMMY_TS, ConstraintTestHelper.TEST_ROOM_SPECIFIC);
+ Lesson studioLabSplit = Lesson.test_buildLesson("4", 1, ConstraintTestHelper.TEST_STUDIO_SPECIFIC,
+ "noName", "", "2-0-0", 1, ConstraintTestHelper.DUMMY_TEACHER,
+ ConstraintTestHelper.DUMMY_TS, ConstraintTestHelper.TEST_ROOM_SPECIFIC);
+
constraintVerifier.verifyThat(TimetableConstraintProvider::wrongRoomType)
- .given(lesson1, lesson2, lsLabOnly)
+ .given(lesson1, lesson2, lsLabOnly, studioLecSplit, studioLabSplit)
.penalizesBy(NO_PENALTY);
}
@@ -381,183 +456,198 @@ void noPenLessonRoomCheck(){
@Test
@DisplayName("PrimeTime reward")
void primeTimeReward(){
- EnumSet MWF = EnumSet.of(Days.MONDAY, Days.WEDNESDAY,Days.FRIDAY);
- EnumSet MW = EnumSet.of(Days.MONDAY, Days.WEDNESDAY);
- /*8-9 MWF; Lecture time completely in prime time*/
- BitSet bs_MWF_8_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", formatter)
- , 2, MWF);
- /*10-11 MWF*/
- BitSet bs_MWF_10_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("10:00AM", formatter),
- 2, MWF);
- /*8-9:30 MWF; Lecture time partially outside of prime time*/
- BitSet bs_MWF_8_3blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", formatter)
- , 3, MWF);
- BitSet bs_MW_8_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", formatter)
- , 2, MW);
- Timeslot timeslot = Timeslot.test_lecLabBitAndDays(1, bs_MWF_8_2blcks, bs_MWF_10_2blcks, MWF, MWF);
- Lesson lesson = Lesson.test_buildLesson("1", 1, "someCourse", "noName", "",
- "1-0-0", 1, ConstraintTestHelper.DUMMY_TEACHER, timeslot, ConstraintTestHelper.DUMMY_ROOM);
-
- Timeslot timeslot2 = Timeslot.test_lecLabBitAndDays(1, bs_MWF_8_3blcks, bs_MWF_10_2blcks, MWF, MWF);
- Lesson lesson2 = Lesson.test_buildLesson("2", 1, "someCourse", "noName", "",
- "1-0-0", 1, ConstraintTestHelper.DUMMY_TEACHER, timeslot2, ConstraintTestHelper.DUMMY_ROOM);
- Timeslot ts3 = Timeslot.test_lecLabBitAndDays(3, ConstraintTestHelper.EMPTY_BS, bs_MW_8_2blcks,
- ConstraintTestHelper.NO_DAYS, MW);
- Lesson lsLabOnly = Lesson.test_buildLesson("3", 1, "someCourse", "noName", "",
- "0-0-1", 1, ConstraintTestHelper.DUMMY_TEACHER, ts3, ConstraintTestHelper.DUMMY_ROOM);
+ //mwf 4- 2 in 2 out
+ Timeslot ts_1in1out = new Timeslot(1,
+ "MF", "8:00AM", "12:00PM", 2f, 4f,
+ "", "", "", 0);
+ //add 4 to the reward
+ Lesson lesson1 = Lesson.test_buildLesson("1", 1, "", "", "",
+ "4-0-2", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_1in1out,
+ ConstraintTestHelper.DUMMY_ROOM);
+ Timeslot ts_0out = new Timeslot(2,
+ "M", "9:00AM", "10:00AM", 1f, 1f,
+ "", "", "", 0);
+ //add 0 to the reward
+ Lesson lesson2 = Lesson.test_buildLesson("2", 1, "", "", "",
+ "1-0-0", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_0out,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ Timeslot ts_5out = new Timeslot(3,
+ "MWF", "4:00PM", "6:00PM", 2f,2f,
+ "", "" , "", 0);
+ //add 0 to reward
+ Lesson lesson3 = Lesson.test_buildLesson("3", 1, "", "", "",
+ "0-2-0", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_5out,
+ ConstraintTestHelper.DUMMY_ROOM);
+ //add 12 to reward
+ Lesson lesson4 = Lesson.test_buildLesson("4", 1, ConstraintTestHelper.TEST_STUDIO_SPECIFIC,
+ "", "", "6-0-0", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_5out,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ //f 8 hours
+ //lec only 6
constraintVerifier.verifyThat(TimetableConstraintProvider::outPrimeTime)
- .given(lesson, lesson2, lsLabOnly)
+ .given(lesson1, lesson2, lesson3, lesson4)
/*Note this takes into account weight of rewards*/
- .rewardsWith(12 + 0);
+ .rewardsWith(16);
}
@Test
@DisplayName("PrimeTime penalty")
void primeTimePenalty(){
- EnumSet MW = EnumSet.of(Days.MONDAY, Days.WEDNESDAY);
- EnumSet MWF = EnumSet.of(Days.MONDAY, Days.WEDNESDAY, Days.FRIDAY);
+ //mwf 4- 2 in 2 out
+ Timeslot ts_1in1out = new Timeslot(1,
+ "MF", "8:00AM", "12:00PM", 2f, 4f,
+ "", "", "", 0);
+ //add 4 to the penalty
+ Lesson lesson1 = Lesson.test_buildLesson("1", 1, "", "", "",
+ "4-0-2", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_1in1out,
+ ConstraintTestHelper.DUMMY_ROOM);
- /*7-9:30 MW; Lecture time completely in prime time*/
- BitSet bs_MW_7_5blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("7:00AM", formatter)
- , 5, MW);
- /*10-11 MW*/
- BitSet bs_MW_10_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("10:00AM", formatter),
- 2, MW);
- /*8:30-10:00 MWF; Lecture time partially outside of prime time*/
- BitSet bs_MWF_830_3blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:30AM", formatter)
- , 3, MWF);
- Timeslot timeslot = Timeslot.test_lecLabBitAndDays(1, bs_MW_7_5blcks, bs_MW_10_2blcks, MW, MW);
- Lesson lesson1 = Lesson.test_buildLesson("1", 1, "someCourse", "noName", "",
- "1-0-0", 1, ConstraintTestHelper.DUMMY_TEACHER, timeslot, ConstraintTestHelper.DUMMY_ROOM);
-
- Timeslot timeslot2 = Timeslot.test_lecLabBitAndDays(1, bs_MWF_830_3blcks, bs_MW_10_2blcks, MWF, MW);
- Lesson lesson2 = Lesson.test_buildLesson("2", 1, "someCourse", "noName", "",
- "1-0-0", 1, ConstraintTestHelper.DUMMY_TEACHER, timeslot2, ConstraintTestHelper.DUMMY_ROOM);
- Timeslot ts3 = Timeslot.test_lecLabBitAndDays(3, ConstraintTestHelper.EMPTY_BS, bs_MW_10_2blcks,
- ConstraintTestHelper.NO_DAYS, MW);
- Lesson lsLabOnly = Lesson.test_buildLesson("3", 1, "someCourse", "noName", "",
- "0-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER, ts3, ConstraintTestHelper.DUMMY_ROOM);
+ Timeslot ts_2in = new Timeslot(2,
+ "MF", "11:00AM", "12:00PM", 1f, 1f,
+ "", "", "", 0);
+ //lab in 0 towards penalty
+ Lesson lesson2 = Lesson.test_buildLesson("2", 1, "", "", "",
+ "0-0-1", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_2in,
+ ConstraintTestHelper.DUMMY_ROOM);
+ //all time in; 4 towards penalty
+ Lesson lesson3 = Lesson.test_buildLesson("3", 1, "", "", "",
+ "2-0-0", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_2in,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ //all time out
+ Timeslot ts_8out = new Timeslot(3,
+ "MTWR", "12:00PM", "3:00PM", 3f, 3f,
+ "", "", "", 0);
+ //24 towards penalty
+ Lesson lesson4 = Lesson.test_buildLesson("4", 1, "", "", "",
+ "12-0-0", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_8out,
+ ConstraintTestHelper.DUMMY_ROOM);
constraintVerifier.verifyThat(TimetableConstraintProvider::inPrimeTime)
- .given(lesson1, lesson2, lsLabOnly)
+ .given(lesson1, lesson2, lesson3, lesson4)
/*Note this takes into account weight of rewards*/
- .penalizesBy(2 + 6);
+ .penalizesBy(32);
}
+
@Test
@DisplayName("no penalty Primetime hard constraint")
void hardPrimeTimeNoPenalty(){
- EnumSet MWF = EnumSet.of(Days.MONDAY, Days.WEDNESDAY, Days.FRIDAY);
- BitSet bs_8am_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", formatter), 2, MWF);
- BitSet bs_9am_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("9:00AM", formatter), 2, MWF);
- BitSet bs_2pm_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("2:00PM", formatter), 2, MWF);
+ Timeslot ts_1in1out = new Timeslot(1,
+ "MF", "8:00AM", "12:00PM", 2f, 4f,
+ "", "", "", 0);
+ //add 4 to the penalty
+ Lesson lesson1 = Lesson.test_buildLesson("1", 1, "", "", "",
+ "4-0-2", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_1in1out,
+ ConstraintTestHelper.DUMMY_ROOM);
- Timeslot ts_8am_2blcks_9am_2blcks = Timeslot.test_lecLabBitAndDays(1, bs_8am_2blcks, bs_9am_2blcks, MWF, MWF);
- Timeslot ts_2pm_2blcks = Timeslot.test_lecLabBitAndDays(2, bs_2pm_2blcks, ConstraintTestHelper.EMPTY_BS,
- MWF, ConstraintTestHelper.NO_DAYS);
- Timeslot ts_lab_2pm_2blcks = Timeslot.test_lecLabBitAndDays(3, ConstraintTestHelper.EMPTY_BS, bs_2pm_2blcks,
- ConstraintTestHelper.NO_DAYS, MWF);
+ Timeslot ts_4NHalfIn = new Timeslot(2,
+ "M", "9:00AM", "1:30PM", 4.5f, 4.5f,
+ "", "", "",0);
+ Lesson lesson2 = Lesson.test_buildLesson("2", 1, "", "", "",
+ "4-4-4", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_4NHalfIn,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ Timeslot ts_5Out = new Timeslot(3,
+ "M", "3:00PM", "8:00PM", 5f, 5f,
+ "", "", "",0);
+ Lesson lesson3 = Lesson.test_buildLesson("3", 1, "", "", "",
+ "4-4-4", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_5Out,
+ ConstraintTestHelper.DUMMY_ROOM);
- Lesson lessonOutPrimeTime = Lesson.test_buildLesson("1", 1, "", "",
- "", "3-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER,
- ts_8am_2blcks_9am_2blcks, ConstraintTestHelper.DUMMY_ROOM);
- Lesson lessonInPrimeTime = Lesson.test_buildLesson("2", 1, "", "",
- "", "3-0-0", 1, ConstraintTestHelper.DUMMY_TEACHER,
- ts_2pm_2blcks, ConstraintTestHelper.DUMMY_ROOM);
- Lesson lsLabOnly = Lesson.test_buildLesson("3", 1, "someCourse", "noName", "",
- "0-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER, ts_lab_2pm_2blcks,
+ Lesson lesson4_LabOnly = Lesson.test_buildLesson("4", 1, "", "", "",
+ "0-0-4", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_4NHalfIn,
ConstraintTestHelper.DUMMY_ROOM);
+
constraintVerifier.verifyThat(TimetableConstraintProvider::primeTime50Plus)
- .given(lessonOutPrimeTime, lessonInPrimeTime, lsLabOnly)
+ .given(lesson1, lesson2, lesson3, lesson4_LabOnly)
.penalizesBy(0);
}
@Test
@DisplayName("penalty Primetime hard constraint")
void hardPrimeTimePenalty(){
- EnumSet MWF = EnumSet.of(Days.MONDAY, Days.WEDNESDAY, Days.FRIDAY);
- BitSet bs_8am_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", formatter), 2, MWF);
- BitSet bs_9am_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("9:00AM", formatter), 2, MWF);
- BitSet bs_130pm_3blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("1:30PM", formatter), 3, MWF);
+ Timeslot ts_1in1out = new Timeslot(1,
+ "MF", "8:00AM", "12:00PM", 2f, 4f,
+ "", "", "", 0);
+ //add 4 to the penalty
+ Lesson lesson1 = Lesson.test_buildLesson("1", 1, "", "", "",
+ "4-0-2", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_1in1out,
+ ConstraintTestHelper.DUMMY_ROOM);
- Timeslot ts_8am_2blcks_9am_2blcks = Timeslot.test_lecLabBitAndDays(1, bs_8am_2blcks, bs_9am_2blcks, MWF, MWF);
- Timeslot ts_130pm_3blcks = Timeslot.test_lecLabBitAndDays(2, bs_130pm_3blcks, ConstraintTestHelper.EMPTY_BS,
- MWF, ConstraintTestHelper.NO_DAYS);
- Timeslot ts_lab_MWF_8am_2blcks = Timeslot.test_lecLabBitAndDays(1, ConstraintTestHelper.EMPTY_BS,
- bs_9am_2blcks, ConstraintTestHelper.NO_DAYS, MWF);
+ Timeslot ts_5In = new Timeslot(2,
+ "M", "9:00AM", "2:00PM", 5f, 5f,
+ "", "", "",0);
+ Lesson lesson2 = Lesson.test_buildLesson("1", 1, "", "", "",
+ "4-4-4", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_5In,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ Timeslot ts_4NHalfOut = new Timeslot(3,
+ "M", "3:00PM", "7:30PM", 4.5f, 4.5f,
+ "", "", "",0);
+ Lesson lesson3 = Lesson.test_buildLesson("3", 1, "", "", "",
+ "4-4-4", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_4NHalfOut,
+ ConstraintTestHelper.DUMMY_ROOM);
- Lesson lessonOutPrimeTime = Lesson.test_buildLesson("1", 1, "", "",
- "", "3-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER,
- ts_8am_2blcks_9am_2blcks, ConstraintTestHelper.DUMMY_ROOM);
- Lesson lessonInPrimeTime = Lesson.test_buildLesson("2", 1, "", "",
- "", "3-0-0", 1, ConstraintTestHelper.DUMMY_TEACHER,
- ts_130pm_3blcks, ConstraintTestHelper.DUMMY_ROOM);
- Lesson lsLabOnly = Lesson.test_buildLesson("3", 1, "someCourse", "noName", "",
- "0-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER, ts_lab_MWF_8am_2blcks,
+ Lesson lesson4_LabOnly = Lesson.test_buildLesson("4", 1, "", "", "",
+ "0-0-4", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_4NHalfOut,
ConstraintTestHelper.DUMMY_ROOM);
+
constraintVerifier.verifyThat(TimetableConstraintProvider::primeTime50Plus)
- .given(lessonOutPrimeTime, lessonInPrimeTime, lsLabOnly)
+ .given(lesson1, lesson2, lesson3, lesson4_LabOnly)
.penalizesBy(1);
}
+
@Test
- @DisplayName("no Pen: teacher with small gaps")
+ @DisplayName("no Pen: discourage large schedule gaps or long days")
void teacherNoLargeGaps(){
- EnumSet MWF = EnumSet.of(Days.MONDAY, Days.WEDNESDAY, Days.FRIDAY);
- EnumSet TR = EnumSet.of(Days.TUESDAY, Days.THURSDAY);
-
- BitSet bs_mwf_8am_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", formatter), 2, MWF);
- BitSet bs_mwf_9am_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("9:00AM", formatter), 2, MWF);
- BitSet bs_mwf_1PM_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("1:00PM", formatter), 2, MWF);
- BitSet bs_tr_830pm_3blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:30PM", formatter), 3, TR);
- BitSet bs_mwf_3pm_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("3:00PM", formatter), 2, MWF);
-
- Timeslot ts_mwf_8am_2blcks_9am_2blcks = Timeslot.test_lecLabBitAndDays(1, bs_mwf_8am_2blcks, bs_mwf_9am_2blcks, MWF, MWF);
- Timeslot ts_mwf_1pm_2blcks = Timeslot.test_lecLabBitAndDays(2, bs_mwf_1PM_2blcks, ConstraintTestHelper.EMPTY_BS,
- ConstraintTestHelper.NO_DAYS, ConstraintTestHelper.NO_DAYS);
- Timeslot ts_tr_830pm_3blcks = Timeslot.test_lecLabBitAndDays(3, ConstraintTestHelper.EMPTY_BS, bs_tr_830pm_3blcks,
- ConstraintTestHelper.NO_DAYS, ConstraintTestHelper.NO_DAYS);
- Timeslot ts_mwf_3pm_2blcks =
- Timeslot.test_lecLabBitAndDays(4, bs_mwf_3pm_2blcks, ConstraintTestHelper.EMPTY_BS,
- MWF, ConstraintTestHelper.NO_DAYS);
+ Teacher TEACHER2 = new TeacherBuilder(new DefaultTeachingPolicy())
+ .preference(ConstraintTestHelper.EMPTY_BS)
+ .acceptable(ConstraintTestHelper.EMPTY_BS)
+ .conflict(ConstraintTestHelper.EMPTY_BS)
+ .canon("teacher2")
+ .gapPref(Preference.NEUTRAL)
+ .build();
- Lesson ls1 = Lesson.test_buildLesson("1", 1, "", "", "",
- "0-0-0", 1, ConstraintTestHelper.DUMMY_TEACHER , ts_mwf_8am_2blcks_9am_2blcks,
- ConstraintTestHelper.DUMMY_ROOM);
- Lesson ls2 = Lesson.test_buildLesson("2", 1, "", "", "",
- "0-0-0", 1, ConstraintTestHelper.DUMMY_TEACHER , ts_mwf_1pm_2blcks,
+ Timeslot timeslot1 = new Timeslot(1,
+ "MW", "8:00AM", "10:00AM", 2f, 2f,
+ "F", "9:00AM", "11:00AM", 2f);
+ Lesson lesson1 = Lesson.test_buildLesson("1", 1, "", "", "",
+ "4-0-1", 2, ConstraintTestHelper.DUMMY_TEACHER, timeslot1,
ConstraintTestHelper.DUMMY_ROOM);
- Lesson ls3 = Lesson.test_buildLesson("3", 1, "", "", "",
- "0-0-0", 1, ConstraintTestHelper.DUMMY_TEACHER , ts_tr_830pm_3blcks,
+
+ Timeslot timeslot2 = new Timeslot(2,
+ "MTWR", "1:00PM", "4:00PM", 3f, 3f,
+ "", "", "", 0);
+ Lesson lesson2 = Lesson.test_buildLesson("2", 1, "", "", "",
+ "12-0-0", 2, ConstraintTestHelper.DUMMY_TEACHER, timeslot2,
ConstraintTestHelper.DUMMY_ROOM);
- Lesson ls4 = Lesson.test_buildLesson("4", 1, "", "", "",
- "0-0-0", 1, ConstraintTestHelper.DUMMY_TEACHER , ts_mwf_3pm_2blcks,
+
+ Timeslot timeslot3 = new Timeslot(3,
+ "M", "7:00PM", "9:00PM", 2f, 2f,
+ "", "", "", 0);
+ Lesson lesson3 = Lesson.test_buildLesson("3", 1, "", "", "",
+ "0-0-1", 2, TEACHER2, timeslot3,
ConstraintTestHelper.DUMMY_ROOM);
- constraintVerifier.verifyThat(TimetableConstraintProvider::compressTeachTime)
- .given(ls1, ls2, ls3, ls4)
+ constraintVerifier.verifyThat(TimetableConstraintProvider::compressIndividualTeachTime)
+ .given(lesson1, lesson2, lesson3)
.penalizesBy(0);
}
@Test
- @DisplayName("Penalties: large gaps and long days")
+ @DisplayName("Penalty: discourage large schedule gaps or long days")
void penalizeLargeGapsAndLongDays(){
-
- EnumSet MWF = EnumSet.of(Days.MONDAY, Days.WEDNESDAY, Days.FRIDAY);
- Teacher TEACHER1 = new TeacherBuilder(new DefaultTeachingPolicy())
- .preference(ConstraintTestHelper.EMPTY_BS)
- .acceptable(ConstraintTestHelper.EMPTY_BS)
- .conflict(ConstraintTestHelper.EMPTY_BS)
- .canon("teacher1")
- .gapPref(Preference.NEUTRAL)
- .build();
Teacher TEACHER2 = new TeacherBuilder(new DefaultTeachingPolicy())
.preference(ConstraintTestHelper.EMPTY_BS)
.acceptable(ConstraintTestHelper.EMPTY_BS)
@@ -566,223 +656,176 @@ void penalizeLargeGapsAndLongDays(){
.gapPref(Preference.NEUTRAL)
.build();
+ //3 hour gap check
+ Timeslot timeslot1 = new Timeslot(1,
+ "MW", "8:00AM", "10:00AM", 2f, 2f,
+ "", "", "", 0);
+ Lesson lesson1 = Lesson.test_buildLesson("1", 1, "", "", "",
+ "4-0-0", 2, ConstraintTestHelper.DUMMY_TEACHER, timeslot1,
+ ConstraintTestHelper.DUMMY_ROOM);
- /* ---------------------------
- Teacher 1: gap > 3 hours
- --------------------------- */
-
- BitSet t1_8am = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", formatter), 2, MWF);
- BitSet t1_1230pm = BitSetHelper.timeSlotBitSet(LocalTime.parse("12:30PM", formatter), 2, MWF); // 3.5 hr gap
-
- Timeslot ts_t1_a = Timeslot.test_lecLabBitAndDays(1, t1_8am, ConstraintTestHelper.EMPTY_BS,
- MWF, ConstraintTestHelper.NO_DAYS);
-
- Timeslot ts_t1_b = Timeslot.test_lecLabBitAndDays(1, t1_1230pm, ConstraintTestHelper.EMPTY_BS,
- MWF, ConstraintTestHelper.NO_DAYS);
-
- Lesson t1_l1 = Lesson.test_buildLesson("t1_l1", 1,"","","","0-0-0",1,
- TEACHER1, ts_t1_a, ConstraintTestHelper.DUMMY_ROOM);
-
- Lesson t1_l2 = Lesson.test_buildLesson("t1_l2", 1,"","","","0-0-0",1,
- TEACHER1, ts_t1_b, ConstraintTestHelper.DUMMY_ROOM);
-
-
-/* ---------------------------
- Teacher 2: day > 8 hours (8.5h) with NO >3hr gaps
- --------------------------- */
-
- BitSet t2_8am = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", formatter), 2, MWF);
- BitSet t2_12pm = BitSetHelper.timeSlotBitSet(LocalTime.parse("12:00PM", formatter), 2, MWF);
- BitSet t2_430pm = BitSetHelper.timeSlotBitSet(LocalTime.parse("4:30PM", formatter), 1, MWF);
-
- Timeslot ts_t2_a = Timeslot.test_lecLabBitAndDays(1, t2_8am, ConstraintTestHelper.EMPTY_BS,
- MWF, ConstraintTestHelper.NO_DAYS);
-
- Timeslot ts_t2_b = Timeslot.test_lecLabBitAndDays(1, t2_12pm, ConstraintTestHelper.EMPTY_BS,
- MWF, ConstraintTestHelper.NO_DAYS);
-
- Timeslot ts_t2_c = Timeslot.test_lecLabBitAndDays(1, t2_430pm, ConstraintTestHelper.EMPTY_BS,
- MWF, ConstraintTestHelper.NO_DAYS);
-
- Lesson t2_l1 = Lesson.test_buildLesson("t2_l1",1,"","","","0-0-0",1,
- TEACHER2, ts_t2_a, ConstraintTestHelper.DUMMY_ROOM);
-
- Lesson t2_l2 = Lesson.test_buildLesson("t2_l2",1,"","","","0-0-0",1,
- TEACHER2, ts_t2_b, ConstraintTestHelper.DUMMY_ROOM);
-
- Lesson t2_l3 = Lesson.test_buildLesson("t2_l3",1,"","","","0-0-0",1,
- TEACHER2, ts_t2_c, ConstraintTestHelper.DUMMY_ROOM);
-
-
-
- /* ---------------------------
- Teacher 5: multiple large gaps
- still only one penalty
- --------------------------- */
-
- BitSet t5_8am = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", formatter), 2, MWF);
- BitSet t5_1230pm = BitSetHelper.timeSlotBitSet(LocalTime.parse("12:30PM", formatter), 2, MWF);
- BitSet t5_6pm = BitSetHelper.timeSlotBitSet(LocalTime.parse("6:00PM", formatter), 2, MWF);
-
- Timeslot ts_t5_a = Timeslot.test_lecLabBitAndDays(1, t5_8am, ConstraintTestHelper.EMPTY_BS,
- MWF, ConstraintTestHelper.NO_DAYS);
-
- Timeslot ts_t5_b = Timeslot.test_lecLabBitAndDays(1, t5_1230pm, ConstraintTestHelper.EMPTY_BS,
- MWF, ConstraintTestHelper.NO_DAYS);
-
- Timeslot ts_t5_c = Timeslot.test_lecLabBitAndDays(1, t5_6pm, ConstraintTestHelper.EMPTY_BS,
- MWF, ConstraintTestHelper.NO_DAYS);
+ Timeslot timeslot2 = new Timeslot(2,
+ "MW", "1:30PM", "3:00PM", 1.5f, 1.5f,
+ "", "", "", 0);
+ Lesson lesson2 = Lesson.test_buildLesson("2", 1, "", "", "",
+ "0-1-0", 2, ConstraintTestHelper.DUMMY_TEACHER, timeslot2,
+ ConstraintTestHelper.DUMMY_ROOM);
- Lesson t5_l1 = Lesson.test_buildLesson("t5_l1",1,"","","","0-0-0",1,
- ConstraintTestHelper.DUMMY_TEACHER, ts_t5_a, ConstraintTestHelper.DUMMY_ROOM);
+ //8+ hour time in one day check
+ Timeslot timeslot3 = new Timeslot(3,
+ "R", "1:30PM", "7:30PM", 6f, 6f,
+ "", "", "", 0);
+ Lesson lesson3 = Lesson.test_buildLesson("3", 1, "", "", "",
+ "0-2-0", 2, TEACHER2, timeslot3,
+ ConstraintTestHelper.DUMMY_ROOM);
- Lesson t5_l2 = Lesson.test_buildLesson("t5_l2",1,"","","","0-0-0",1,
- ConstraintTestHelper.DUMMY_TEACHER, ts_t5_b, ConstraintTestHelper.DUMMY_ROOM);
- Lesson t5_l3 = Lesson.test_buildLesson("t5_l3",1,"","","","0-0-0",1,
- ConstraintTestHelper.DUMMY_TEACHER, ts_t5_c, ConstraintTestHelper.DUMMY_ROOM);
+ Timeslot timeslot4 = new Timeslot(4,
+ "RF", "7:00AM", "12:00PM", 2f, 5f,
+ "", "", "", 0);
+ Lesson lesson4 = Lesson.test_buildLesson("4", 1, "", "", "",
+ "4-0-2", 2, TEACHER2, timeslot4,
+ ConstraintTestHelper.DUMMY_ROOM);
- constraintVerifier.verifyThat(TimetableConstraintProvider::compressTeachTime)
- .given(
- t1_l1, t1_l2,
- t2_l1, t2_l2, t2_l3,
- t5_l1, t5_l2, t5_l3
- )
- .penalizesBy(3);
+ constraintVerifier.verifyThat(TimetableConstraintProvider::compressIndividualTeachTime)
+ .given(lesson1, lesson2, lesson3, lesson4)
+ .penalizesBy(2);
}
+
@Test
@DisplayName("Reward: teacher prefers one-hour gaps")
void rewardPreferredHourGap() {
- EnumSet MWF = EnumSet.of(Days.MONDAY, Days.WEDNESDAY, Days.FRIDAY);
- Teacher teacher = new TeacherBuilder(new DefaultTeachingPolicy())
+ //no reward if gap is within the timeslot
+ Teacher TEACHER1 = new TeacherBuilder(new DefaultTeachingPolicy())
.preference(ConstraintTestHelper.EMPTY_BS)
.acceptable(ConstraintTestHelper.EMPTY_BS)
.conflict(ConstraintTestHelper.EMPTY_BS)
- .canon("prefers gaps")
+ .canon("teacher2")
.gapPref(Preference.AGREE)
.build();
+ Timeslot timeslot1 = new Timeslot(1,
+ "MW", "8:00AM", "11:00AM", 1f, 3f,
+ "", "", "", 0);
+ Lesson lesson1 = Lesson.test_buildLesson("1", 1, "", "", "",
+ "2-0-2", 2, TEACHER1, timeslot1,
+ ConstraintTestHelper.DUMMY_ROOM);
- BitSet early = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", formatter), 2, MWF);
- BitSet late = BitSetHelper.timeSlotBitSet(LocalTime.parse("10:00AM", formatter), 2, MWF);
-
- Timeslot tsEarly = Timeslot.test_lecLabBitAndDays(1, early, ConstraintTestHelper.EMPTY_BS, MWF, ConstraintTestHelper.NO_DAYS);
- Timeslot tsLate = Timeslot.test_lecLabBitAndDays(2, ConstraintTestHelper.EMPTY_BS, late, ConstraintTestHelper.NO_DAYS, MWF);
-
- Lesson lesson1 = Lesson.test_buildLesson("gap1", 1, "", "", "",
- "0-0-0", 1, teacher, tsEarly, ConstraintTestHelper.DUMMY_ROOM);
- Lesson lesson2 = Lesson.test_buildLesson("gap2", 1, "", "", "",
- "0-0-0", 1, teacher, tsLate, ConstraintTestHelper.DUMMY_ROOM);
-
- //------ no reward (hour gap between lec and lab)
- EnumSet RF = EnumSet.of(Days.THURSDAY, Days.FRIDAY);
- BitSet rfLecture = BitSetHelper.timeSlotBitSet(LocalTime.parse("3:00PM", formatter), 2, RF);
- BitSet rfLab = BitSetHelper.timeSlotBitSet(LocalTime.parse("5:00PM", formatter), 2, RF);
- Timeslot rfTimeslot = Timeslot.test_lecLabBitAndDays(6, rfLecture, rfLab, RF, RF);
- Teacher singleCourseTeacher = new TeacherBuilder(new DefaultTeachingPolicy())
+ //-------- penalty
+ Teacher TEACHER2 = new TeacherBuilder(new DefaultTeachingPolicy())
.preference(ConstraintTestHelper.EMPTY_BS)
.acceptable(ConstraintTestHelper.EMPTY_BS)
.conflict(ConstraintTestHelper.EMPTY_BS)
- .canon("single slot")
+ .canon("teacher2")
.gapPref(Preference.AGREE)
.build();
- Lesson singleCourse = Lesson.test_buildLesson("gap3", 1, "", "", "",
- "0-0-0", 1, singleCourseTeacher, rfTimeslot, ConstraintTestHelper.DUMMY_ROOM);
- //------ no reward
- Teacher disagreeTeacher = new TeacherBuilder(new DefaultTeachingPolicy())
+ Timeslot timeslot2 = new Timeslot(2,
+ "M", "12:00PM", "1:00PM", 1f, 1f,
+ "W", "12:00PM", "1:00PM", 1f);
+ Lesson lesson2 = Lesson.test_buildLesson("2", 1, "", "", "",
+ "2-0-2", 2, TEACHER2, timeslot1,
+ ConstraintTestHelper.DUMMY_ROOM);
+ Lesson lesson3 = Lesson.test_buildLesson("3", 1, "", "", "",
+ "1-0-2", 2, TEACHER2, timeslot2,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ //------ no reward if teacher doesn't care
+ Teacher TEACHER3 = new TeacherBuilder(new DefaultTeachingPolicy())
.preference(ConstraintTestHelper.EMPTY_BS)
.acceptable(ConstraintTestHelper.EMPTY_BS)
.conflict(ConstraintTestHelper.EMPTY_BS)
- .canon("hates gaps")
- .gapPref(Preference.DISAGREE)
+ .canon("teacher2")
+ .gapPref(Preference.NEUTRAL)
.build();
- Lesson disagreeLesson1 = Lesson.test_buildLesson("gapDisagree1", 1, "", "", "",
- "0-0-0", 1, disagreeTeacher, tsEarly, ConstraintTestHelper.DUMMY_ROOM);
- Lesson disagreeLesson2 = Lesson.test_buildLesson("gapDisagree2", 1, "", "", "",
- "0-0-0", 1, disagreeTeacher, tsLate, ConstraintTestHelper.DUMMY_ROOM);
+ Lesson lesson4 = Lesson.test_buildLesson("4", 1, "", "", "",
+ "2-0-2", 2, TEACHER3, timeslot1,
+ ConstraintTestHelper.DUMMY_ROOM);
+ Lesson lesson5 = Lesson.test_buildLesson("5", 1, "", "", "",
+ "1-0-2", 2, TEACHER3, timeslot2,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+
constraintVerifier.verifyThat(TimetableConstraintProvider::rewardPreferredHourGap)
- .given(lesson1, lesson2,
- singleCourse,
- disagreeLesson1, disagreeLesson2)
+ .given(lesson1,
+ lesson2, lesson3,
+ lesson4, lesson5)
.rewardsWith(1);
}
@Test
@DisplayName("Penalty: teacher dislikes one-hour gaps")
void penalizeDislikedHourGap() {
- EnumSet TR = EnumSet.of(Days.TUESDAY, Days.THURSDAY);
- EnumSet T = EnumSet.of(Days.TUESDAY);
- Teacher teacher = new TeacherBuilder(new DefaultTeachingPolicy())
+ //no penalty if gap is within the timeslot
+ Teacher TEACHER1 = new TeacherBuilder(new DefaultTeachingPolicy())
.preference(ConstraintTestHelper.EMPTY_BS)
.acceptable(ConstraintTestHelper.EMPTY_BS)
.conflict(ConstraintTestHelper.EMPTY_BS)
- .canon("hates gaps")
+ .canon("teacher2")
.gapPref(Preference.DISAGREE)
.build();
+ Timeslot timeslot1 = new Timeslot(1,
+ "MW", "8:00AM", "11:00AM", 1f, 3f,
+ "", "", "", 0);
+ Lesson lesson1 = Lesson.test_buildLesson("1", 1, "", "", "",
+ "2-0-2", 2, TEACHER1, timeslot1,
+ ConstraintTestHelper.DUMMY_ROOM);
- BitSet blockA = BitSetHelper.timeSlotBitSet(LocalTime.parse("9:00AM", formatter), 2, TR);
- BitSet blockB = BitSetHelper.timeSlotBitSet(LocalTime.parse("11:00AM", formatter), 2, TR);
- BitSet blockC = BitSetHelper.timeSlotBitSet(LocalTime.parse("1:00PM", formatter), 2, T);
-
- Timeslot tsA = Timeslot.test_lecLabBitAndDays(3, blockA, ConstraintTestHelper.EMPTY_BS, TR, ConstraintTestHelper.NO_DAYS);
- Timeslot tsB = Timeslot.test_lecLabBitAndDays(4, blockB, ConstraintTestHelper.EMPTY_BS, TR, ConstraintTestHelper.NO_DAYS);
- Timeslot tsC = Timeslot.test_lecLabBitAndDays(5, ConstraintTestHelper.EMPTY_BS, blockC, ConstraintTestHelper.NO_DAYS, T);
-
- Lesson lessonA = Lesson.test_buildLesson("penalty1", 1, "", "", "",
- "0-0-0", 1, teacher, tsA, ConstraintTestHelper.DUMMY_ROOM);
- Lesson lessonB = Lesson.test_buildLesson("penalty2", 1, "", "", "",
- "0-0-0", 1, teacher, tsB, ConstraintTestHelper.DUMMY_ROOM);
- Lesson lessonC = Lesson.test_buildLesson("penalty2", 1, "", "", "",
- "0-0-0", 1, teacher, tsC, ConstraintTestHelper.DUMMY_ROOM);
-
- //------ No penalty
- EnumSet RF = EnumSet.of(Days.THURSDAY, Days.FRIDAY);
- BitSet lectureRf = BitSetHelper.timeSlotBitSet(LocalTime.parse("3:00PM", formatter), 2, RF);
- BitSet labRf = BitSetHelper.timeSlotBitSet(LocalTime.parse("5:00PM", formatter), 2, RF);
- Timeslot rfTimeslot = Timeslot.test_lecLabBitAndDays(6, lectureRf, labRf, RF, RF);
- Teacher singleCourseTeacher = new TeacherBuilder(new DefaultTeachingPolicy())
+ //-------- penalty
+ Teacher TEACHER2 = new TeacherBuilder(new DefaultTeachingPolicy())
.preference(ConstraintTestHelper.EMPTY_BS)
.acceptable(ConstraintTestHelper.EMPTY_BS)
.conflict(ConstraintTestHelper.EMPTY_BS)
- .canon("single course")
+ .canon("teacher2")
.gapPref(Preference.DISAGREE)
.build();
- Lesson singleCourse = Lesson.test_buildLesson("singleCourse", 1, "", "", "",
- "0-0-0", 1, singleCourseTeacher, rfTimeslot, ConstraintTestHelper.DUMMY_ROOM);
- //-------- No penalty
- Teacher gapPrefTeacher = new TeacherBuilder(new DefaultTeachingPolicy())
+
+ Timeslot timeslot2 = new Timeslot(2,
+ "M", "12:00PM", "1:00PM", 1f, 1f,
+ "W", "12:00PM", "1:00PM", 1f);
+ Lesson lesson2 = Lesson.test_buildLesson("2", 1, "", "", "",
+ "2-0-2", 2, TEACHER2, timeslot1,
+ ConstraintTestHelper.DUMMY_ROOM);
+ Lesson lesson3 = Lesson.test_buildLesson("3", 1, "", "", "",
+ "1-0-2", 2, TEACHER2, timeslot2,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ //------ no reward if teacher doesn't care
+ Teacher TEACHER3 = new TeacherBuilder(new DefaultTeachingPolicy())
.preference(ConstraintTestHelper.EMPTY_BS)
.acceptable(ConstraintTestHelper.EMPTY_BS)
.conflict(ConstraintTestHelper.EMPTY_BS)
- .canon("likes gaps")
- .gapPref(Preference.AGREE)
+ .canon("teacher2")
+ .gapPref(Preference.NEUTRAL)
.build();
- Lesson likedGap1 = Lesson.test_buildLesson("agree1", 1, "", "", "",
- "0-0-0", 1, gapPrefTeacher, tsA, ConstraintTestHelper.DUMMY_ROOM);
- Lesson likedGap2 = Lesson.test_buildLesson("agree2", 1, "", "", "",
- "0-0-0", 1, gapPrefTeacher, tsB, ConstraintTestHelper.DUMMY_ROOM);
+ Lesson lesson4 = Lesson.test_buildLesson("4", 1, "", "", "",
+ "2-0-2", 2, TEACHER3, timeslot1,
+ ConstraintTestHelper.DUMMY_ROOM);
+ Lesson lesson5 = Lesson.test_buildLesson("5", 1, "", "", "",
+ "1-0-2", 2, TEACHER3, timeslot2,
+ ConstraintTestHelper.DUMMY_ROOM);
constraintVerifier.verifyThat(TimetableConstraintProvider::penalizeDislikedHourGap)
- .given(lessonA, lessonB, lessonC,
- singleCourse,
- likedGap1, likedGap2)
- .penalizesBy(2);
+ .given(lesson1,
+ lesson2, lesson3,
+ lesson4, lesson5)
+ .penalizesBy(1);
}
+
@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);
+ 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,
@@ -794,10 +837,13 @@ void roomPrescheduleConflict() {
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);
+ Lesson blockedLabOnly = 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);
+ .given(blockedLessonLab, blockedStudio, blockedLabOnly)
+ .penalizesBy(3);
}
@Test
@@ -805,8 +851,8 @@ void roomPrescheduleConflict() {
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);
+ 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);
@@ -827,4 +873,578 @@ void noRoomPrescheduleConflict() {
.given(openLessonLec, lecOnlyRoomLesson, emptyRoomLesson)
.penalizesBy(0);
}
+
+ @Test
+ @DisplayName("Teacher preferred time reward")
+ void teacherPrefReward(){
+ EnumSet MWF = EnumSet.of(Days.MONDAY, Days.WEDNESDAY, Days.FRIDAY);
+ BitSet preference = BitSetHelper.timeSlotBitSet(LocalTime.parse("10:00AM", FORMATTER), 4, MWF);
+ Teacher TEACHER1 = new TeacherBuilder(new DefaultTeachingPolicy())
+ .preference(preference)
+ .acceptable(ConstraintTestHelper.EMPTY_BS)
+ .conflict(ConstraintTestHelper.EMPTY_BS)
+ .canon("teacher with a preference 10-12pm MWF")
+ .gapPref(Preference.NEUTRAL)
+ .build();
+
+ Timeslot timeslot1 = new Timeslot(1,
+ "M", "10:00AM", "11:00AM", 1f, 1f,
+ "W", "10:00AM", "11:30AM", 1.5f);
+ Lesson lesson1 = Lesson.test_buildLesson("1", 1, "", "", "",
+ "9-9-9", 2, TEACHER1, timeslot1,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ constraintVerifier.verifyThat(TimetableConstraintProvider::prefTime)
+ .given(lesson1)
+ .rewardsWith(2);
+ }
+
+ @Test
+ @DisplayName("Reward: compress global schedule")
+ void compressGlobalReward(){
+ Timeslot timeslot1 = new Timeslot(1,
+ "M", "9:00AM", "10:00AM", 1f, 1f,
+ "W", "10:00AM", "11:30AM", 1.5f);
+ Lesson lesson1 = Lesson.test_buildLesson("1", 1, "", "", "",
+ "9-9-9", 2, ConstraintTestHelper.DUMMY_TEACHER, timeslot1,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ Timeslot timeslot2 = new Timeslot(2,
+ "MW", "10:00AM", "11:00AM", 1f, 1f,
+ "", "", "", 0f);
+ Lesson lesson2 = Lesson.test_buildLesson("2", 1, "", "", "",
+ "2-0-0", 2, ConstraintTestHelper.DUMMY_TEACHER, timeslot2,
+ ConstraintTestHelper.DUMMY_ROOM);
+ Lesson lesson3 = Lesson.test_buildLesson("3", 1, "", "", "",
+ "0-0-1", 2, ConstraintTestHelper.DUMMY_TEACHER, timeslot2,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ //in bad time
+ Timeslot timeslot3 = new Timeslot(2,
+ "MW", "9:00PM", "10:00PM", 1f, 1f,
+ "", "", "", 0f);
+ Lesson lesson4 = Lesson.test_buildLesson("4", 1, "", "", "",
+ "0-0-1", 2, ConstraintTestHelper.DUMMY_TEACHER, timeslot3,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ constraintVerifier.verifyThat(TimetableConstraintProvider::inBestTime)
+ .given(lesson1, lesson2, lesson3, lesson4)
+ .rewardsWith(3);
+ }
+
+ @Test
+ @DisplayName("Penalty: compress global schedule")
+ void compressGlobalPenalty(){
+ Timeslot timeslot1 = new Timeslot(1,
+ "M", "9:00AM", "10:00AM", 1f, 1f,
+ "W", "10:00AM", "11:30AM", 1.5f);
+ Lesson lesson1 = Lesson.test_buildLesson("1", 1, "", "", "",
+ "9-9-9", 2, ConstraintTestHelper.DUMMY_TEACHER, timeslot1,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ Timeslot timeslot2 = new Timeslot(2,
+ "MW", "2:00PM", "3:00PM", 1f, 1f,
+ "", "", "", 0f);
+ Lesson lesson2 = Lesson.test_buildLesson("2", 1, "", "", "",
+ "2-0-0", 2, ConstraintTestHelper.DUMMY_TEACHER, timeslot2,
+ ConstraintTestHelper.DUMMY_ROOM);
+ Lesson lesson3 = Lesson.test_buildLesson("3", 1, "", "", "",
+ "0-0-1", 2, ConstraintTestHelper.DUMMY_TEACHER, timeslot2,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ //in good time
+ Timeslot timeslot3 = new Timeslot(2,
+ "MW", "10:00AM", "11:00AM", 1f, 1f,
+ "", "", "", 0f);
+ Lesson lesson4 = Lesson.test_buildLesson("4", 1, "", "", "",
+ "0-0-1", 2, ConstraintTestHelper.DUMMY_TEACHER, timeslot3,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ constraintVerifier.verifyThat(TimetableConstraintProvider::outBestTime)
+ .given(lesson1, lesson2, lesson3, lesson4)
+ .penalizesBy(3);
+ }
+
+ @Test
+ @DisplayName("No penalty: timeslot pattern match")
+ void noPenTimeslotPattern(){
+ LessonBuilder builder = new LessonBuilder();
+
+ Timeslot timeslot1 = new Timeslot(1,
+ "MTWR", "7:00AM", "8:00AM", 1f, 1f,
+ "", "" ,"", 0f);
+ Lesson lesson1 = Lesson.test_buildLesson("1", 1, "", "", "",
+ "4-0-0", 2, ConstraintTestHelper.DUMMY_TEACHER, timeslot1,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ Timeslot timeslot2 = new Timeslot(2,
+ "TR", "7:00AM", "11:00AM", 1.5f, 4f,
+ "", "" ,"", 0f);
+ Lesson lesson2 = Lesson.test_buildLesson("2", 1, "", "", "",
+ "3-1-0", 2, ConstraintTestHelper.DUMMY_TEACHER, timeslot2,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ Timeslot timeslot3 = new Timeslot(3,
+ "MWF", "7:00AM", "8:00AM", 1f, 1f,
+ "", "" ,"", 0f);
+ Lesson lesson3 = builder
+ .clear()
+ .id(3)
+ .section(10)
+ .courseName("lab multiple blocks")
+ .courseConfig("0-1-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .courseId(1)
+ .timeslot(timeslot3)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .labPattern(LabPatterns.MULTIPLE)
+ .build();
+
+ Timeslot timeslot4 = new Timeslot(4,
+ "M", "7:00AM", "10:00AM", 3f, 3f,
+ "", "" ,"", 0f);
+ Lesson lesson4 = builder
+ .clear()
+ .id(4)
+ .section(11)
+ .courseName("lab one block")
+ .courseConfig("0-1-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .courseId(1)
+ .timeslot(timeslot4)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .labPattern(LabPatterns.ONE)
+ .build();
+
+ constraintVerifier.verifyThat(TimetableConstraintProvider::timeslotPatternMatch)
+ .given(lesson1, lesson2, lesson3, lesson4)
+ .penalizesBy(NO_PENALTY);
+ }
+
+
+ @Test
+ @DisplayName("No penalty: studios must have lab right after lec")
+ void noPenStudioLabSpacing(){
+ LessonBuilder builder = new LessonBuilder();
+ //course with gap but not studio
+ Timeslot timeslot1 = new Timeslot(1,
+ "MWF", "12:00PM", "3:00PM", 1f, 3f,
+ "", "" ,"", 0f);
+ Lesson lesson1 = builder
+ .clear()
+ .id(1)
+ .section(1)
+ .courseName("")
+ .courseConfig("3-1-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .timeslot(timeslot1)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .build();
+ //course split but do only lec part
+ int linkerId = LessonGenerator.nextLinkerID();
+ Timeslot timeslot2 = new Timeslot(2,
+ "MWF", "12:00PM", "1:00PM", 1f, 1f,
+ "", "" ,"", 0f);
+ Lesson lesson2 = builder
+ .clear()
+ .id(2)
+ .section(1)
+ .courseName(ConstraintTestHelper.DUMMY_STUDIO)
+ .courseConfig("3-0-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .timeslot(timeslot2)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .linkerId(linkerId)
+ .build();
+ Timeslot timeslot3 = new Timeslot(3,
+ "T", "12:00PM", "3:00PM", 3f, 3f,
+ "", "" ,"", 0f);
+ Lesson lesson3 = builder
+ .clear()
+ .id(3)
+ .section(1)
+ .courseName(ConstraintTestHelper.DUMMY_STUDIO)
+ .courseConfig("0-1-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .timeslot(timeslot3)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .linkerId(linkerId)
+ .labPattern(LabPatterns.ONE)
+ .build();
+
+ //do a studio with the right spacing
+ Timeslot timeslot4 = new Timeslot(4,
+ "MWF", "12:00PM", "2:00PM", 1f, 2f,
+ "", "" ,"", 0f);
+ Lesson lesson4 = builder
+ .clear()
+ .id(4)
+ .section(1)
+ .courseName(ConstraintTestHelper.DUMMY_STUDIO)
+ .courseConfig("3-1-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .timeslot(timeslot4)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .build();
+
+ constraintVerifier.verifyThat(TimetableConstraintProvider::timeslotPatternMatch)
+ .given(lesson1, lesson2, lesson3, lesson4)
+ .penalizesBy(NO_PENALTY);
+ }
+
+ @Test
+ @DisplayName("Penalty: studios must have lab right after lec")
+ void penStudioLabSpacing(){
+ LessonBuilder builder = new LessonBuilder();
+ //do course with lab days < lec
+ Timeslot timeslot1 = new Timeslot(1,
+ "MWF", "12:00PM", "1:00PM", 1f, 1f,
+ "MW", "1:00PM" ,"2:30PM", 1.5f);
+ Lesson lesson1 = builder
+ .clear()
+ .id(1)
+ .section(1)
+ .courseName("")
+ .courseConfig("3-1-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .timeslot(timeslot1)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .build();
+ //do course with lab days > lec
+ Timeslot timeslot2 = new Timeslot(2,
+ "MWF", "12:00PM", "1:00PM", 1f, 1f,
+ "MTWF", "1:00PM" ,"2:00PM", 1f);
+ Lesson lesson2 = builder
+ .clear()
+ .id(2)
+ .section(1)
+ .courseName(ConstraintTestHelper.DUMMY_STUDIO)
+ .courseConfig("4-1-2")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .timeslot(timeslot2)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .build();
+ //do with days == for both but with spacing
+ Timeslot timeslot3 = new Timeslot(3,
+ "MWF", "12:00PM", "3:00PM", 1f, 3f,
+ "", "" ,"", 0f);
+ Lesson lesson3 = builder
+ .clear()
+ .id(3)
+ .section(1)
+ .courseName(ConstraintTestHelper.DUMMY_STUDIO)
+ .courseConfig("3-1-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .timeslot(timeslot3)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .build();
+
+ constraintVerifier.verifyThat(TimetableConstraintProvider::timeslotPatternMatch)
+ .given(lesson1, lesson2, lesson3)
+ .penalizesBy(NO_PENALTY);
+ }
+
+ //new constraints from latest update
+ @Test
+ @DisplayName("Penalty: timeslot pattern match")
+ void penTimeslotPatternMatch(){
+ LessonBuilder builder = new LessonBuilder();
+ Timeslot timeslot1 = new Timeslot(1,
+ "R", "7:00AM", "10:00AM", 3f, 3f,
+ "", "" ,"", 0f);
+ Lesson lesson1 = builder
+ .clear()
+ .id(1)
+ .section(10)
+ .courseName("lab one block timeslot")
+ .courseConfig("0-1-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .courseId(1)
+ .timeslot(timeslot1)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .labPattern(LabPatterns.MULTIPLE)
+ .build();
+
+ Timeslot timeslot2 = new Timeslot(2,
+ "MWF", "7:00AM", "8:00AM", 1f, 1f,
+ "", "" ,"", 0f);
+ Lesson lesson2 = builder
+ .clear()
+ .id(2)
+ .section(11)
+ .courseName("lab multiple blocks timeslot")
+ .courseConfig("0-1-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .courseId(1)
+ .timeslot(timeslot2)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .labPattern(LabPatterns.ONE)
+ .build();
+
+ Lesson lesson3 = builder
+ .clear()
+ .id(3)
+ .section(12)
+ .courseName("lec with cont. timeslot")
+ .courseConfig("3-0-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .courseId(1)
+ .timeslot(timeslot1)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .labPattern(null)
+ .build();
+
+ Timeslot timeslot3 = new Timeslot(3,
+ "MWF", "7:00AM", "8:00AM", 1f, 1f,
+ "M", "8:00AM" ,"11:00AM", 3f);
+ Lesson lesson4 = builder
+ .clear()
+ .id(4)
+ .section(12)
+ .courseName("lec/lab with lab time continuous => bad")
+ .courseConfig("3-1-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .courseId(1)
+ .timeslot(timeslot3)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .labPattern(null)
+ .build();
+
+ constraintVerifier.verifyThat(TimetableConstraintProvider::timeslotPatternMatch)
+ .given(lesson1, lesson2, lesson3, lesson4)
+ .penalizesBy(4);
+ }
+
+ @Test
+ @DisplayName("No penalty: prevent long time blocks taught")
+ void noPenaltyLongTeaching(){
+ Teacher TEACHER1 = new TeacherBuilder(new DefaultTeachingPolicy())
+ .preference(ConstraintTestHelper.EMPTY_BS)
+ .acceptable(ConstraintTestHelper.EMPTY_BS)
+ .conflict(ConstraintTestHelper.EMPTY_BS)
+ .canon("instructor1")
+ .gapPref(Preference.NEUTRAL)
+ .build();
+
+ Timeslot timeslot1 = new Timeslot(1,
+ "MTWR", "7:00AM", "1:00PM", 6f, 6f,
+ "", "" ,"", 0f);
+ Lesson lesson1 = Lesson.test_buildLesson("1", 1, "", "", "",
+ "0-2-0", 2, TEACHER1, timeslot1,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+
+ Timeslot timeslot2 = new Timeslot(2,
+ "MTWR", "2:00PM", "8:00PM", 6f, 6f,
+ "", "" ,"", 0f);
+ Lesson lesson2 = Lesson.test_buildLesson("2", 1, "", "", "",
+ "0-2-0", 2, TEACHER1, timeslot2,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+
+ Timeslot timeslot3 = new Timeslot(3,
+ "MTWR", "9:00PM", "10:00PM", 1f, 1f,
+ "", "" ,"", 0f);
+ Lesson lesson3 = Lesson.test_buildLesson("3", 1, "", "", "",
+ "0-0-2", 2, TEACHER1, timeslot3,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ Teacher TEACHER2 = new TeacherBuilder(new DefaultTeachingPolicy())
+ .preference(ConstraintTestHelper.EMPTY_BS)
+ .acceptable(ConstraintTestHelper.EMPTY_BS)
+ .conflict(ConstraintTestHelper.EMPTY_BS)
+ .canon("instructor2")
+ .gapPref(Preference.NEUTRAL)
+ .build();
+ Timeslot timeslot4 = new Timeslot(4,
+ "MTWR", "1:00PM", "2:00PM", 1f, 1f,
+ "MTWR", "8:00PM", "9:00PM", 1f);
+ Lesson lesson4 = Lesson.test_buildLesson("4", 1, "", "", "",
+ "4-0-2", 2, TEACHER2, timeslot4,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+
+ constraintVerifier.verifyThat(TimetableConstraintProvider::continuousTimeTaught)
+ .given(lesson1, lesson2, lesson3, lesson4)
+ .penalizesBy(0);
+ }
+
+ @Test
+ @DisplayName("Penalty: long time blocks taught found")
+ void penaltyLongTeaching(){
+ Teacher TEACHER1 = new TeacherBuilder(new DefaultTeachingPolicy())
+ .preference(ConstraintTestHelper.EMPTY_BS)
+ .acceptable(ConstraintTestHelper.EMPTY_BS)
+ .conflict(ConstraintTestHelper.EMPTY_BS)
+ .canon("instructor1")
+ .gapPref(Preference.NEUTRAL)
+ .build();
+
+ Timeslot timeslot1 = new Timeslot(1,
+ "MTWR", "7:00AM", "1:00PM", 6f, 6f,
+ "", "" ,"", 0f);
+ Lesson lesson1 = Lesson.test_buildLesson("1", 1, "", "", "",
+ "1-1-1", 2, TEACHER1, timeslot1,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+
+ Timeslot timeslot2 = new Timeslot(2,
+ "MTWR", "3:00PM", "8:00PM", 5f, 5f,
+ "", "" ,"", 0f);
+ Lesson lesson2 = Lesson.test_buildLesson("2", 1, "", "", "",
+ "1-1-1", 2, TEACHER1, timeslot2,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ Timeslot timeslot3 = new Timeslot(3,
+ "MTWR", "1:00PM", "2:00PM", 1f, 1f,
+ "", "" ,"", 0f);
+ Lesson lesson3 = Lesson.test_buildLesson("3", 1, "", "", "",
+ "1-1-1", 2, TEACHER1, timeslot3,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ Timeslot timeslot4 = new Timeslot(4,
+ "MTWR", "8:00PM", "10:00PM", 2f, 2f,
+ "", "" ,"", 0f);
+ Lesson lesson4 = Lesson.test_buildLesson("4", 1, "", "", "",
+ "1-1-1", 2, TEACHER1, timeslot4,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+
+ constraintVerifier.verifyThat(TimetableConstraintProvider::continuousTimeTaught)
+ .given(lesson1, lesson2, lesson3, lesson4)
+ .penalizesBy(2);
+ }
+
+
+ @Test
+ @DisplayName("No reward: check for course different times")
+ void noRewardCourseInstances(){
+ LessonBuilder builder = new LessonBuilder();
+ final int COMMON_COURSE_ID = 1000;
+
+ Timeslot timeslot1 = new Timeslot(1,
+ "MWF", "7:00AM", "8:00AM", 1f, 1f,
+ "", "" ,"", 0f);
+ int link1 = LessonGenerator.nextLinkerID();
+ Lesson lesson1 = builder
+ .clear()
+ .id(1)
+ .section(1)
+ .courseName("link1")
+ .courseConfig("1-0-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .timeslot(timeslot1)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .linkerId(link1)
+ .courseId(COMMON_COURSE_ID)
+ .build();
+
+
+ Timeslot timeslot2 = new Timeslot(2,
+ "TR", "3:00PM", "6:00PM", 3f, 3f,
+ "", "" ,"", 0f);
+ Lesson lesson2 = builder
+ .clear()
+ .id(2)
+ .section(1)
+ .courseName("link1")
+ .courseConfig("0-1-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .timeslot(timeslot2)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .linkerId(link1)
+ .courseId(COMMON_COURSE_ID)
+ .labPattern(LabPatterns.ONE)
+ .build();
+
+ Timeslot timeslot3 = new Timeslot(3,
+ "F", "12:00PM", "3:00PM", 3f, 3f,
+ "", "" ,"", 0f);
+ Lesson lesson3 = builder
+ .clear()
+ .id(3)
+ .section(1)
+ .courseName("some other course")
+ .courseConfig("3-1-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .timeslot(timeslot3)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .courseId(COMMON_COURSE_ID + 1)
+ .build();
+
+ constraintVerifier.verifyThat(TimetableConstraintProvider::rewardCoursesDiffTimes)
+ .given(lesson1, lesson2, lesson3)
+ .rewardsWith(0);
+ }
+
+ @Test
+ @DisplayName("Reward: check for course different times")
+ void rewardCourseInstances(){
+ LessonBuilder builder = new LessonBuilder();
+ final int COMMON_COURSE_ID = 1000;
+ Teacher TEACHER2 = new TeacherBuilder(new DefaultTeachingPolicy())
+ .preference(ConstraintTestHelper.EMPTY_BS)
+ .acceptable(ConstraintTestHelper.EMPTY_BS)
+ .conflict(ConstraintTestHelper.EMPTY_BS)
+ .canon("instructor1")
+ .gapPref(Preference.NEUTRAL)
+ .build();
+
+ Timeslot timeslot1 = new Timeslot(1,
+ "MWF", "7:00AM", "8:00AM", 1f, 1f,
+ "", "" ,"", 0f);
+ int link1 = LessonGenerator.nextLinkerID();
+ Lesson lesson1 = builder
+ .clear()
+ .id(1)
+ .section(1)
+ .courseName("course1")
+ .courseConfig("1-0-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .timeslot(timeslot1)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .linkerId(link1)
+ .courseId(COMMON_COURSE_ID)
+ .build();
+
+
+ Timeslot timeslot2 = new Timeslot(2,
+ "TR", "3:00PM", "6:00PM", 3f, 3f,
+ "", "" ,"", 0f);
+ Lesson lesson2 = builder
+ .clear()
+ .id(2)
+ .section(2)
+ .courseName("course1")
+ .courseConfig("0-1-0")
+ .teacherObj(ConstraintTestHelper.DUMMY_TEACHER)
+ .timeslot(timeslot2)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .linkerId(link1)
+ .courseId(COMMON_COURSE_ID)
+ .labPattern(LabPatterns.ONE)
+ .build();
+
+ Timeslot timeslot3 = new Timeslot(3,
+ "MWF", "12:00PM", "3:00PM", 1f, 3f,
+ "", "" ,"", 0f);
+ Lesson lesson3 = builder
+ .clear()
+ .id(3)
+ .section(3)
+ .courseName("course1")
+ .courseConfig("3-1-0")
+ .teacherObj(TEACHER2)
+ .timeslot(timeslot3)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .courseId(COMMON_COURSE_ID)
+ .build();
+
+ constraintVerifier.verifyThat(TimetableConstraintProvider::rewardCoursesDiffTimes)
+ .given(lesson1, lesson2, lesson3)
+ .rewardsWith(2);
+ }
+
+
+
}
From 3d99e3ece0b03c25c3bdc95e80bab1f62325e445 Mon Sep 17 00:00:00 2001
From: RandomCyberCoder
Date: Wed, 17 Jun 2026 01:28:24 -0700
Subject: [PATCH 08/10] feat: result print out done, fixes made, full run
validation
---
.../acme/schooltimetabling/TimetableApp.java | 5 +
.../Generators/LessonGenerator.java | 25 ++--
.../helperClasses/ResultSaver.java | 80 ++++++++----
.../solver/TimetableConstraintProvider.java | 36 +++---
.../resources/constants/possibleTimes.csv | 122 ++++++++++++++++++
.../solver/TestConstraints.java | 52 +++++++-
6 files changed, 254 insertions(+), 66 deletions(-)
diff --git a/java/hello-world/src/main/java/org/acme/schooltimetabling/TimetableApp.java b/java/hello-world/src/main/java/org/acme/schooltimetabling/TimetableApp.java
index aee071eb..a4fd443a 100644
--- a/java/hello-world/src/main/java/org/acme/schooltimetabling/TimetableApp.java
+++ b/java/hello-world/src/main/java/org/acme/schooltimetabling/TimetableApp.java
@@ -14,6 +14,7 @@
import org.acme.schooltimetabling.domain.Timeslot;
import org.acme.schooltimetabling.domain.Timetable;
import org.acme.schooltimetabling.domain.teacher.Teacher;
+import org.acme.schooltimetabling.fileObjects.CoursePatterns;
import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
import org.acme.schooltimetabling.fileObjects.ScheduleFormat;
import org.acme.schooltimetabling.helperClasses.*;
@@ -41,6 +42,10 @@ public static void main(String[] args) throws Exception{
ScheduleConfig.getSeasonTerm()));
LOGGER.info("Loading critical constants");
Constants.load();
+ if(ScheduleConfig.isTesting()){
+ LOGGER.error("Configuration must not have testing set to true when creating a solution. Exiting...");
+ return;
+ }
Map teacherMap = TeacherGenerator.teacherGenDriver();
/*generate timeslots*/
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 0e727a4b..e45d07bf 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
@@ -140,32 +140,21 @@ public static ArrayList generateLessons(List schedules,
final int LEC_POS = 0;
final int LAB_POS = 1;
final int ACT_POS = 2;
+ final Integer linkerId = courseUnits[LEC_POS] != 0 ? nextLinkerID() : null;
//check if this course has a lecture portion
if(courseUnits[LEC_POS] != 0){
lessons.add(
generateLesson(teacher, course,
String.format("%d-0-0", courseUnits[LEC_POS]),
- null)
+ null, linkerId)
);
}
- /*TODO make sure we can make a lab only lesson.
- checks below:
- -the lesson geneorator take into account the section numbers well
- -Does the lesson object take this into account well? done
- - DO THIS BEFORE THE BELOW: Update the timeslots
- -check over constraints
- +how does the above effect print out? It doesn't I already check before printing if the
- lesson has lecture or lab. ACUTALLY update the timeslots first. This will determine how
- the print out and the constraints will have to be updated
- NOTE: THAT WHEN THINKING ABOUT THIS CHANGE THINK ABOUT LESSONS THAT COULD HAVE ONLY
- LEC/ACT AS WELL. THIS CAN HELP US THINK ABOUT THE IMPLEMENTATION. ALSO THINK ABOUT HOW STUDIO
- COURSES MAYBE COULD BE SPLIT AS WELL
- */
+
lessons.add(
generateLesson(teacher, course,
String.format("0-%d-%d", courseUnits[LAB_POS], courseUnits[ACT_POS]),
- LabPatterns.ONE)
+ LabPatterns.ONE, linkerId)
);
}
//if not we do the below
@@ -209,14 +198,15 @@ private static Lesson generateLesson(Teacher teacher, String course){
.courseConfig(courseConfig)
.modifier(courseModifier)
.teacherObj(teacher)
- .labPattern(getLabPattern(courseName, courseModifier))
+ .labPattern(getLabPattern(courseName, teacher.getName()))
.build();
}
/**
* used only for when we split a course into two lesson objects
*/
- private static Lesson generateLesson(Teacher teacher, String course, String config, LabPatterns labPattern){
+ private static Lesson generateLesson(Teacher teacher, String course, String config, LabPatterns labPattern,
+ Integer linkerId){
//first element = modifier; second element = course name
Pair courseDetails = getCourseDetails(course);
String courseModifier = courseDetails.getFirst();
@@ -230,6 +220,7 @@ private static Lesson generateLesson(Teacher teacher, String course, String conf
.modifier(courseModifier)
.teacherObj(teacher)
.labPattern(labPattern)
+ .linkerId(linkerId)
.build();
}
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 593b04e8..cd103474 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
@@ -14,6 +14,7 @@
import org.acme.schooltimetabling.constants.Days;
import org.acme.schooltimetabling.domain.lesson.Lesson;
import org.acme.schooltimetabling.domain.Timeslot;
+import org.acme.schooltimetabling.domain.Timeslot.Partition;
import org.acme.schooltimetabling.domain.Timetable;
import org.acme.schooltimetabling.domain.teacher.Teacher;
import org.acme.schooltimetabling.fileObjects.ScheduleConfig;
@@ -40,6 +41,7 @@
public class ResultSaver {
private static final Logger LOGGER = LoggerFactory.getLogger(ResultSaver.class);
private static final DateTimeFormatter LOCALTIME_FORMATTER = DateTimeFormatter.ofPattern("h:mma");
+ private static final String UNIVERSITY_ROOM = "University Room";
private static final int COLUMN_SPACING = 5;
private static final int START_COLUMN = 1;
private static final Map ROOM_COL_MAP;
@@ -67,6 +69,7 @@ public class ResultSaver {
localTime = localTime.plusMinutes(30);
}
}
+
public ResultSaver(Timetable solution){
solToPrint = solution;
buildTeacherMap();
@@ -77,6 +80,10 @@ public void useNewSolution(Timetable solution){
buildTeacherMap();
}
+ /**
+ * builds an internal map that is used to determine the starting column that belongs to an instructor
+ * in the Excel file. Used only for the "Room Usage" and "Teacher Schedule" sheets
+ */
private void buildTeacherMap(){
List lessonList = solToPrint.getLessons();
int rowNum = 0;
@@ -265,23 +272,23 @@ private void listView(XSSFSheet listSheet, List validLessons, List lessons, int rowIdx) {
lessons = sortLessons(lessons);
-
for(Lesson lesson: lessons){
- final Timeslot lsTs = lesson.getTimeslot();
+ final Timeslot ts = lesson.getTimeslot();
if(lesson.isHasLecture()){
Row row = listSheet.createRow(rowIdx++);
- String roomName = lesson.isStudio() ? lesson.getRoom().getName() : "University Room";
+ String roomName = lesson.isStudio() ? lesson.getRoom().getName() : UNIVERSITY_ROOM;
Object[] vals = new Object[]{lesson.getCourseName(), lesson.getLecSection(), lesson.getModifiers(),
- lesson.getTeacherObj().getName(), roomName, lsTs.getLecDays().toString(),
- lsTs.getStartTimeLec().toString(), lsTs.getEndTimeLec().toString(), true};
+ lesson.getTeacherObj().getName(), roomName, ts.getDaysSlot1().toString(),
+ ts.getStartTimeSlot1().toString(), ts.getEndTimeSlot1().toString(), true};
lstViewRowHelper(row, vals);
}
if(lesson.isHasLabAct()){
- //lab print out
+ //slot/partition we access depends on if the lesson has a lecture associated with it
+ Partition partition = lesson.isHasLecture() ? ts.getPartition2() : ts.getPartition1();
Row row = listSheet.createRow(rowIdx++);
Object[] vals = new Object[]{lesson.getCourseName(), lesson.getLabActSection(), lesson.getModifiers(),
- lesson.getTeacherObj().getName(), lesson.getRoom().getName(), lsTs.getNonLecDays().toString(),
- lsTs.getStartTimeLabAct().toString(), lsTs.getEndTimeLabAct().toString(), false};
+ lesson.getTeacherObj().getName(), lesson.getRoom().getName(), partition.getDays().toString(),
+ partition.getStartTime().toString(), partition.getEndTime().toString(), false};
lstViewRowHelper(row, vals);
}
}
@@ -324,16 +331,20 @@ else if (val instanceof Boolean){
}
}
-
private List sortLessons(List list){
- //sort the list so it look neat to look at; sort by course name
+ //sort the list so it look neat to look at; sort by course name and by id in case we split or no lecture
//make the list mutable for sorting
List toSort = new ArrayList<>(list);
toSort.sort((a, b) -> {
String nameA = a.getCourseName();
String nameB = b.getCourseName();
-
- return nameA.compareTo(nameB);
+ //lecture section will be smaller if lesson has both lec and lab/act
+ int sectionA = a.isHasLecture() ? a.getLecSection() : a.getLabActSection();
+ int sectionB = b.isHasLecture() ? b.getLecSection() : b.getLabActSection();
+ int order;
+ if(nameA.equals(nameB)) order = Integer.compare(sectionA, sectionB);
+ else order = nameA.compareTo(nameB);
+ return order;
});
return toSort;
@@ -352,19 +363,22 @@ private void teacherView(XSSFSheet teacherSheet, List validLessons){
//lecture
if(lesson.isHasLecture()){
String lecStr = lecToStr(lesson);
- fillTimeCell(teacherSheet, rowsBuilt, lecStr, TEACHER_COL_MAP.get(teacher.getName()), ts.getLecDays(),
- ts.getStartTimeLec(), ts.getEndTimeLec());
+ fillTimeCell(teacherSheet, rowsBuilt, lecStr, TEACHER_COL_MAP.get(teacher.getName()), ts.getDaysSlot1(),
+ ts.getStartTimeSlot1(), ts.getEndTimeSlot1());
}
- //lab
+ //lab/act
if(lesson.isHasLabAct()){
String labStr = labToStr(lesson);
- fillTimeCell(teacherSheet, rowsBuilt, labStr, TEACHER_COL_MAP.get(teacher.getName()), ts.getNonLecDays(),
- ts.getStartTimeLabAct(), ts.getEndTimeLabAct());
+ //check in the case that we split or no lecture exists
+ Partition partition = lesson.isHasLecture() ? ts.getPartition2() : ts.getPartition1();
+ fillTimeCell(teacherSheet, rowsBuilt, labStr, TEACHER_COL_MAP.get(teacher.getName()), partition.getDays(),
+ partition.getStartTime(), partition.getEndTime());
}
}
}
+
/**
* Driver function for printing out the room sheet
*/
@@ -375,12 +389,14 @@ private void roomView(XSSFSheet roomSheet, List validLessons){
Timeslot ts = lesson.getTimeslot();
if(lesson.getRoom().getName().equals(Constants.LEC_ONLY)) continue;
//if the course is a studio course also print out the lecture that is also occupying the room
- if(lesson.isStudio()){
- fillTimeCell(roomSheet, rowsBuilt, lecToStr(lesson), ROOM_COL_MAP.get(lesson.getRoom().getName()), ts.getLecDays(),
- ts.getStartTimeLec(), ts.getEndTimeLec());
+ if(lesson.isStudio() && lesson.isHasLecture()){
+ fillTimeCell(roomSheet, rowsBuilt, lecToStr(lesson), ROOM_COL_MAP.get(lesson.getRoom().getName()),
+ ts.getDaysSlot1(), ts.getStartTimeSlot1(), ts.getEndTimeSlot1());
}
- fillTimeCell(roomSheet, rowsBuilt, labToStr(lesson), ROOM_COL_MAP.get(lesson.getRoom().getName()), ts.getNonLecDays(),
- ts.getStartTimeLabAct(), ts.getEndTimeLabAct());
+
+ Partition partition = lesson.isHasLecture() ? ts.getPartition2() : ts.getPartition1();
+ fillTimeCell(roomSheet, rowsBuilt, labToStr(lesson), ROOM_COL_MAP.get(lesson.getRoom().getName()),
+ partition.getDays(), partition.getStartTime(), partition.getEndTime());
}
}
@@ -500,28 +516,38 @@ private void saveSolution(XSSFWorkbook workbook){
}
}
+
private String labToStr(Lesson lesson){
Timeslot ts = lesson.getTimeslot();
+ Partition partition = lesson.isHasLecture() ? ts.getPartition2() : ts.getPartition1();
return String.format("%s\n", lesson.getTeacherObj().getName()) +
String.format("%s\n", lesson.getCourseName()) +
String.format("%s\n", lesson.getRoom().getName()) +
String.format("Lab Sec Num: %s\n", lesson.getLabActSection()) +
- String.format("%s %s-%s", ts.getLecDays().toString(), ts.getStartTimeLabAct().format(LOCALTIME_FORMATTER),
- ts.getEndTimeLabAct().format(LOCALTIME_FORMATTER));
+ String.format("%s %s-%s", partition.getDays().toString(),
+ partition.getStartTime().format(LOCALTIME_FORMATTER),
+ partition.getEndTime().format(LOCALTIME_FORMATTER)
+ );
}
+
private String lecToStr(Lesson lesson){
Timeslot ts = lesson.getTimeslot();
return String.format("%s\n", lesson.getTeacherObj().getName()) +
String.format("%s\n", lesson.getCourseName()) +
- String.format("%s\n", lesson.isStudio() ? lesson.getRoom().getName() : "University Room") +
+ String.format("%s\n", lesson.isStudio() ? lesson.getRoom().getName() : UNIVERSITY_ROOM) +
String.format("Lecture Sec Num: %s\n", lesson.getLecSection()) +
- String.format("%s %s-%s", ts.getLecDays().toString(), ts.getStartTimeLec().format(LOCALTIME_FORMATTER),
- ts.getEndTimeLec().format(LOCALTIME_FORMATTER));
+ String.format("%s %s-%s", ts.getDaysSlot1().toString(), ts.getStartTimeSlot1().format(LOCALTIME_FORMATTER),
+ ts.getEndTimeSlot1().format(LOCALTIME_FORMATTER));
}
+
+ /**
+ * Dumps times a teacher has been scheduled during for the given solution as JSON.
+ * @throws IOException
+ */
public void teacherTimesToJson() throws IOException {
Pair, List> extracted = extractLessons();
//we only consider lessons that don't violate any hard constraints as actually being scheduled
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 199743a1..7dc394d7 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
@@ -170,15 +170,15 @@ Constraint sameClassSameDays(ConstraintFactory constraintFactory){
.forEachUniquePair(Lesson.class,
Joiners.equal(lesson -> lesson.getTeacherObj().getId()),
Joiners.equal(Lesson::getCourseID))
- .filter((lesson, lesson2) -> {
- /*Penalize courses not on the same day taught by the same professor */
- EnumSet lsnSlot1Days = lesson.getTimeslot().getDaysSlot1();
- EnumSet lsn2Slot1Days = lesson2.getTimeslot().getDaysSlot1();
- return lsnSlot1Days.contains(Days.MONDAY) != lsn2Slot1Days.contains(Days.MONDAY) ||
- lsnSlot1Days.contains(Days.TUESDAY) != lsn2Slot1Days.contains(Days.TUESDAY) ||
- lsnSlot1Days.contains(Days.WEDNESDAY) != lsn2Slot1Days.contains(Days.WEDNESDAY) ||
- lsnSlot1Days.contains(Days.THURSDAY) != lsn2Slot1Days.contains(Days.THURSDAY) ||
- lsnSlot1Days.contains(Days.FRIDAY) != lsn2Slot1Days.contains(Days.FRIDAY);
+ .filter((lesson1, lesson2) -> {
+ if(!lesson1.isHasLecture() || !lesson2.isHasLecture()) return false;
+ /*Make sure they are on the same time pattern; note the same pattern is guaranteed when you
+ * take this constraint and the constraint ensuring the exact hours in the timeslot match that
+ * of the lesson into account. Remember the timeslot architecture results in each day having
+ * the same amount of hours for each subpartition the timeslot has*/
+ EnumSet lesson1Days = lesson1.getTimeslot().getPartition1().getDays();
+ EnumSet lesson2Days = lesson2.getTimeslot().getPartition1().getDays();
+ return lesson1Days.size() != lesson2Days.size();
})
.penalize(HardMediumSoftScore.ONE_HARD)
.asConstraint("Teacher has same course on same days");
@@ -274,7 +274,7 @@ Constraint labActRoomConflict(ConstraintFactory constraintFactory){
}
/*If the split is for a non studio course or course simply has no lecture then the first slot
will contain the bitset we need*/
- else if(!lesson.isHasLecture()) bitSet2 = lesson2.getTimeslot().getBitSetSlot1();
+ else if(!lesson2.isHasLecture()) bitSet2 = lesson2.getTimeslot().getBitSetSlot1();
else bitSet2 = lesson2.getTimeslot().getBitSetSlot2();
//penalize any overlap; aka room being occupied at the same time
@@ -452,13 +452,13 @@ Constraint timeslotPatternMatch(ConstraintFactory constraintFactory){
return constraintFactory.forEach(Lesson.class)
.filter(lesson -> {
Timeslot timeslot = lesson.getTimeslot();
- //lab + lab/act
+ //lab + lab/act; this will always require lab to be spread out
if(lesson.hasLecture && lesson.hasLabAct){
//in such a configuration the lab/act portion will be spread out; right now no lab with continuous
//time are in second slots
return !timeslot.hasSlot2 || timeslot.isContinuousSlot1() || timeslot.isContinuousSlot2();
}
- //lab/act only
+ //lab/act only; lab could be either be spread or all in one block
else if(lesson.hasLabAct){
return timeslot.isHasSlot2() ||
//the next portion depends on if the lab has to be all in one block
@@ -485,7 +485,6 @@ private int countTeacherLongBlocks(Teacher teacher, List lessons){
EnumSet daysTeaching = EnumSet.noneOf(Days.class);
//keeps track of what lesson pairs have already been counted towards the number of gaps
Set> used = new HashSet<>();
- System.out.println(lessons.size());
for (Lesson lesson : lessons) {
Timeslot ts = lesson.getTimeslot();
scheduledTime.or(ts.getAllTimesBitSet());
@@ -770,9 +769,12 @@ Constraint rewardCoursesDiffTimes(ConstraintFactory constraintFactory) {
Integer lesson1LinkerId = lesson1.getLinkerId();
Integer lesson2LinkerId = lesson2.getLinkerId();
- return lesson1LinkerId != null
- && !lesson1LinkerId.equals(lesson2LinkerId)
- && lesson1.getCourseID() == lesson2.getCourseID();
+ //pass through if no linker id; if linker id exists allow if they are not the same
+ return (
+ lesson1LinkerId == null && lesson2LinkerId == null ||
+ lesson1LinkerId != null && !lesson1LinkerId.equals(lesson2LinkerId) ||
+ !lesson2LinkerId.equals(lesson1LinkerId)
+ ) && lesson1.getCourseID() == lesson2.getCourseID();
}))
.filter(((lesson1, lesson2) -> {
BitSet bitSet1 = lesson1.getTimeslot().getAllTimesBitSet();
@@ -781,7 +783,7 @@ Constraint rewardCoursesDiffTimes(ConstraintFactory constraintFactory) {
return !bitSet1.intersects(bitSet2);
}))
.reward(HardMediumSoftScore.ONE_MEDIUM)
- .asConstraint("Reward linked courses at different times");
+ .asConstraint("Reward lesson instances of a course scheduled at different times");
}
//-------------------------------------- Soft Constraints --------------------------------------
diff --git a/java/hello-world/src/main/resources/constants/possibleTimes.csv b/java/hello-world/src/main/resources/constants/possibleTimes.csv
index 63986cfa..77eaef92 100644
--- a/java/hello-world/src/main/resources/constants/possibleTimes.csv
+++ b/java/hello-world/src/main/resources/constants/possibleTimes.csv
@@ -169,3 +169,125 @@ TR, 3:00PM, 4:30PM, 1.5, 1.5,,,,
TR, 4:30PM, 6:00PM, 1.5, 1.5,,,,
TR, 6:00PM, 7:30PM, 1.5, 1.5,,,,
TR, 7:30PM, 9:00PM, 1.5, 1.5,,,,
+M, 8:00AM, 10:00AM, 2, 2,,,,
+M, 10:00AM, 12:00PM, 2, 2,,,,
+M, 12:00PM, 2:00PM, 2, 2,,,,
+M, 2:00PM, 4:00PM, 2, 2,,,,
+M, 4:00PM, 6:00PM, 2, 2,,,,
+M, 6:00PM, 8:00PM, 2, 2,,,,
+T, 8:00AM, 10:00AM, 2, 2,,,,
+T, 10:00AM, 12:00PM, 2, 2,,,,
+T, 12:00PM, 2:00PM, 2, 2,,,,
+T, 2:00PM, 4:00PM, 2, 2,,,,
+T, 4:00PM, 6:00PM, 2, 2,,,,
+T, 6:00PM, 8:00PM, 2, 2,,,,
+W, 8:00AM, 10:00AM, 2, 2,,,,
+W, 10:00AM, 12:00PM, 2, 2,,,,
+W, 12:00PM, 2:00PM, 2, 2,,,,
+W, 2:00PM, 4:00PM, 2, 2,,,,
+W, 4:00PM, 6:00PM, 2, 2,,,,
+W, 6:00PM, 8:00PM, 2, 2,,,,
+R, 8:00AM, 10:00AM, 2, 2,,,,
+R, 10:00AM, 12:00PM, 2, 2,,,,
+R, 12:00PM, 2:00PM, 2, 2,,,,
+R, 2:00PM, 4:00PM, 2, 2,,,,
+R, 4:00PM, 6:00PM, 2, 2,,,,
+R, 6:00PM, 8:00PM, 2, 2,,,,
+F, 8:00AM, 10:00AM, 2, 2,,,,
+F, 12:00PM, 2:00PM, 2, 2,,,,
+F, 2:00PM, 4:00PM, 2, 2,,,,
+F, 4:00PM, 6:00PM, 2, 2,,,,
+F, 6:00PM, 8:00PM, 2, 2,,,,
+M, 8:00AM, 11:00AM, 3, 3,,,,
+M, 9:00AM, 12:00PM, 3, 3,,,,
+M, 11:00AM, 2:00PM, 3, 3,,,,
+M, 12:00PM, 3:00PM, 3, 3,,,,
+M, 1:00PM, 4:00PM, 3, 3,,,,
+M, 2:00PM, 5:00PM, 3, 3,,,,
+M, 3:00PM, 6:00PM, 3, 3,,,,
+M, 4:00PM, 7:00PM, 3, 3,,,,
+M, 5:00PM, 8:00PM, 3, 3,,,,
+T, 8:00AM, 11:00AM, 3, 3,,,,
+T, 9:00AM, 12:00PM, 3, 3,,,,
+T, 11:00AM, 2:00PM, 3, 3,,,,
+T, 12:00PM, 3:00PM, 3, 3,,,,
+T, 1:00PM, 4:00PM, 3, 3,,,,
+T, 2:00PM, 5:00PM, 3, 3,,,,
+T, 3:00PM, 6:00PM, 3, 3,,,,
+T, 4:00PM, 7:00PM, 3, 3,,,,
+T, 5:00PM, 8:00PM, 3, 3,,,,
+W, 8:00AM, 11:00AM, 3, 3,,,,
+W, 9:00AM, 12:00PM, 3, 3,,,,
+W, 11:00AM, 2:00PM, 3, 3,,,,
+W, 12:00PM, 3:00PM, 3, 3,,,,
+W, 1:00PM, 4:00PM, 3, 3,,,,
+W, 2:00PM, 5:00PM, 3, 3,,,,
+W, 3:00PM, 6:00PM, 3, 3,,,,
+W, 4:00PM, 7:00PM, 3, 3,,,,
+W, 5:00PM, 8:00PM, 3, 3,,,,
+R, 8:00AM, 11:00AM, 3, 3,,,,
+R, 9:00AM, 12:00PM, 3, 3,,,,
+R, 11:00AM, 2:00PM, 3, 3,,,,
+R, 12:00PM, 3:00PM, 3, 3,,,,
+R, 1:00PM, 4:00PM, 3, 3,,,,
+R, 2:00PM, 5:00PM, 3, 3,,,,
+R, 3:00PM, 6:00PM, 3, 3,,,,
+R, 4:00PM, 7:00PM, 3, 3,,,,
+R, 5:00PM, 8:00PM, 3, 3,,,,
+F, 8:00AM, 11:00AM, 3, 3,,,,
+F, 12:00PM, 3:00PM, 3, 3,,,,
+F, 1:00PM, 4:00PM, 3, 3,,,,
+F, 2:00PM, 5:00PM, 3, 3,,,,
+F, 3:00PM, 6:00PM, 3, 3,,,,
+F, 4:00PM, 7:00PM, 3, 3,,,,
+F, 5:00PM, 8:00PM, 3, 3,,,,
+MW, 7:00AM, 8:00AM, 1, 1,,,,
+MW, 8:00AM, 9:00AM, 1, 1,,,,
+MW, 9:00AM, 10:00AM, 1, 1,,,,
+MW, 10:00AM, 11:00AM, 1, 1,,,,
+MW, 11:00AM, 12:00PM, 1, 1,,,,
+MW, 12:00PM, 1:00PM, 1, 1,,,,
+MW, 1:00PM, 2:00PM, 1, 1,,,,
+MW, 2:00PM, 3:00PM, 1, 1,,,,
+MW, 3:00PM, 4:00PM, 1, 1,,,,
+MW, 4:00PM, 5:00PM, 1, 1,,,,
+MW, 5:00PM, 6:00PM, 1, 1,,,,
+MW, 6:00PM, 7:00PM, 1, 1,,,,
+MW, 7:00PM, 8:00PM, 1, 1,,,,
+MF, 7:00AM, 8:00AM, 1, 1,,,,
+MF, 8:00AM, 9:00AM, 1, 1,,,,
+MF, 9:00AM, 10:00AM, 1, 1,,,,
+MF, 10:00AM, 11:00AM, 1, 1,,,,
+MF, 12:00PM, 1:00PM, 1, 1,,,,
+MF, 1:00PM, 2:00PM, 1, 1,,,,
+MF, 2:00PM, 3:00PM, 1, 1,,,,
+MF, 3:00PM, 4:00PM, 1, 1,,,,
+MF, 4:00PM, 5:00PM, 1, 1,,,,
+MF, 5:00PM, 6:00PM, 1, 1,,,,
+MF, 6:00PM, 7:00PM, 1, 1,,,,
+MF, 7:00PM, 8:00PM, 1, 1,,,,
+WF, 7:00AM, 8:00AM, 1, 1,,,,
+WF, 8:00AM, 9:00AM, 1, 1,,,,
+WF, 9:00AM, 10:00AM, 1, 1,,,,
+WF, 10:00AM, 11:00AM, 1, 1,,,,
+WF, 12:00PM, 1:00PM, 1, 1,,,,
+WF, 1:00PM, 2:00PM, 1, 1,,,,
+WF, 2:00PM, 3:00PM, 1, 1,,,,
+WF, 3:00PM, 4:00PM, 1, 1,,,,
+WF, 4:00PM, 5:00PM, 1, 1,,,,
+WF, 5:00PM, 6:00PM, 1, 1,,,,
+WF, 6:00PM, 7:00PM, 1, 1,,,,
+WF, 7:00PM, 8:00PM, 1, 1,,,,
+TR, 7:00AM, 8:00AM, 1, 1,,,,
+TR, 8:00AM, 9:00AM, 1, 1,,,,
+TR, 9:00AM, 10:00AM, 1, 1,,,,
+TR, 10:00AM, 11:00AM, 1, 1,,,,
+TR, 11:00AM, 12:00PM, 1, 1,,,,
+TR, 12:00PM, 1:00PM, 1, 1,,,,
+TR, 1:00PM, 2:00PM, 1, 1,,,,
+TR, 2:00PM, 3:00PM, 1, 1,,,,
+TR, 3:00PM, 4:00PM, 1, 1,,,,
+TR, 4:00PM, 5:00PM, 1, 1,,,,
+TR, 5:00PM, 6:00PM, 1, 1,,,,
+TR, 6:00PM, 7:00PM, 1, 1,,,,
+TR, 7:00PM, 8:00PM, 1, 1,,,,
\ No newline at end of file
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 b2adb95c..377e4648 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
@@ -225,7 +225,7 @@ void teacherLessonSameTime(){
void roomMultiLessons(){
EnumSet MWF = EnumSet.of(Days.MONDAY, Days.WEDNESDAY,Days.FRIDAY);
//9-11 MWF
- BitSet bs_MWF_8AM_4blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("8:00AM", FORMATTER)
+ BitSet bs_MWF_9AM_4blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("9:00AM", FORMATTER)
, 4, MWF);
//9-10 MWF
BitSet bs_MWF_9AM_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("9:00AM", FORMATTER)
@@ -237,7 +237,7 @@ void roomMultiLessons(){
BitSet bs_MWF_11_2blcks = BitSetHelper.timeSlotBitSet(LocalTime.parse("11:00AM", FORMATTER),
2, MWF);
- Timeslot ts_mwf_8_4blcks = Timeslot.test_lecLabBitAndDays(1, bs_MWF_8AM_4blcks, ConstraintTestHelper.EMPTY_BS,
+ Timeslot ts_mwf_9_4blcks = Timeslot.test_lecLabBitAndDays(1, bs_MWF_9AM_4blcks, ConstraintTestHelper.EMPTY_BS,
MWF, ConstraintTestHelper.NO_DAYS);
Timeslot ts_mwf_9_10_2blcks = Timeslot.test_lecLabBitAndDays(2, bs_MWF_9AM_2blcks, bs_MWF_10_2blcks,
MWF, MWF);
@@ -248,15 +248,19 @@ void roomMultiLessons(){
MWF, ConstraintTestHelper.NO_DAYS);
+ //potential time conflict: 8-10am
Lesson lesson1 = Lesson.test_buildLesson("1", 1, "dummyName", "noName"
- , "", "0-0-1", 1, ConstraintTestHelper.DUMMY_TEACHER, ts_mwf_8_4blcks,
+ , "", "0-0-1", 1, ConstraintTestHelper.DUMMY_TEACHER, ts_mwf_9_4blcks,
ConstraintTestHelper.DUMMY_ROOM);
+ //potential time conflict: mwf 10-11pm
Lesson lesson2 = Lesson.test_buildLesson("2", 2, "dummyName", "noName"
, "", "3-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER, ts_mwf_9_10_2blcks,
ConstraintTestHelper.DUMMY_ROOM);
+ //potential time conflict: mwf 11-12pm
Lesson lesson3 = Lesson.test_buildLesson("3", 3, "dummyName", "noName"
, "", "3-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER, ts_lec_mwf_10_lab_mwf_11,
ConstraintTestHelper.DUMMY_ROOM);
+ //potential time conflict mwf: 11-12pm
Lesson lesson4studio = Lesson.test_buildLesson("4", 1, ConstraintTestHelper.DUMMY_STUDIO, "", "",
"3-0-0", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_mwf_11,
ConstraintTestHelper.DUMMY_ROOM);
@@ -266,6 +270,28 @@ void roomMultiLessons(){
.penalizesBy(2);
}
+ @Test
+ @DisplayName("A room accommodates only one lesson at a time; test2")
+ void roomMultiLessons2(){
+ Timeslot timeslot1 = new Timeslot(1,
+ "M", "9:00AM", "12:00PM", 3f, 3f,
+ "", "", "", 0);
+ Lesson lesson1 = Lesson.test_buildLesson("1", 1, "dummyName", "noName"
+ , "", "0-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER, timeslot1,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ Timeslot timeslot2 = new Timeslot(2,
+ "MWF", "8:00AM", "10:00AM", 1f, 2f,
+ "", "", "", 0);
+ Lesson lesson2 = Lesson.test_buildLesson("2", 2, "dummyName", "noName"
+ , "", "3-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER, timeslot2,
+ ConstraintTestHelper.DUMMY_ROOM);
+
+ constraintVerifier.verifyThat(TimetableConstraintProvider::labActRoomConflict)
+ .given(lesson1, lesson2)
+ .penalizesBy(1);
+ }
+
@Test
@DisplayName("Correct time slot hours for course type")
@@ -1440,11 +1466,27 @@ void rewardCourseInstances(){
.courseId(COMMON_COURSE_ID)
.build();
+ Timeslot timeslot4 = new Timeslot(4,
+ "MWF", "7:00AM", "8:00AM", 1f, 1f,
+ "TR", "3:00PM" ,"4:00PM", 1f);
+ Lesson lesson4 = builder
+ .clear()
+ .id(4)
+ .section(4)
+ .courseName("course1")
+ .courseConfig("3-1-0")
+ .teacherObj(TEACHER2)
+ .timeslot(timeslot4)
+ .room(ConstraintTestHelper.DUMMY_ROOM)
+ .courseId(COMMON_COURSE_ID)
+ .build();
+
constraintVerifier.verifyThat(TimetableConstraintProvider::rewardCoursesDiffTimes)
- .given(lesson1, lesson2, lesson3)
- .rewardsWith(2);
+ .given(lesson1, lesson2, lesson3, lesson4)
+ .rewardsWith(3);
}
+
}
From 59c22e1cc42d7fc5f08f018b8449b5377da6c3d9 Mon Sep 17 00:00:00 2001
From: RandomCyberCoder
Date: Wed, 17 Jun 2026 13:53:43 -0700
Subject: [PATCH 09/10] documentation update
---
README.md | 4 +++
java/hello-world/README.md | 28 +++++++++++++++++--
.../solver/TimetableConstraintProvider.java | 20 -------------
.../resources/constants/config.example.yaml | 3 +-
4 files changed, 32 insertions(+), 23 deletions(-)
diff --git a/README.md b/README.md
index 59ddd9ce..13dca27e 100644
--- a/README.md
+++ b/README.md
@@ -56,6 +56,10 @@ __Optional Files:__
An example file has been given for you to understand the formatting.
This file can be included if you want to add in prescheduled times.
For example, an instructor was scheduled in another department.
+- `your_name_choice.json` [java/hello-world/src/main/resources/input/](java/hello-world/src/main/resources/input/).
+An example file is given for you to understand th formatting.
+This file can be used if you want to specify if a courses lab or activity time should be spread out across multiple
+days or if the time should be all in one block.
Note I'll give you some of the files that aren't included.
diff --git a/java/hello-world/README.md b/java/hello-world/README.md
index 763a5cca..83b421bc 100644
--- a/java/hello-world/README.md
+++ b/java/hello-world/README.md
@@ -86,7 +86,31 @@ If instructors teach in multiple departments, you can include the times they hav
The file should be a `json` placed in the [./src/main/resources/input/](./src/main/resources/input/) directory.
An example of the file has been provided in the same directory your file should be placed.
-Once your file containing preshceulded times is placed, create the key in your `config.yaml` named `prescheduledFileName` mapped to the value of your file name, as seen in this example [file](./src/main/resources/constants/config.example.yaml)
+Once your file containing prescheduled times is placed, create the key in your `config.yaml` named `prescheduledFileName` mapped to the value of your file name, as seen in this example [file](./src/main/resources/constants/config.example.yaml)
+
+
+# Setting lab/act pattern
+If a course contains a lab or activity you can set if the course should have all it's lab/act time all in one block of time
+or spread out amongst various days.
+You can set the pattern for a course globally and/or per instructor.
+Note, instructor choice takes precedence over global.
+If a course has a lab/act and no pattern is given to the course, the default is **"Multiple"**
+
+Time use this option you must include in your `config.yaml` they key name `patternsFileName` mapped the value of your
+file name, which must be a JSON file. An example of the JSON format that is expected is given
+[here](./src/main/resources/input/coursePatterns.example.json).
+
+The only patterns available for a course are **"One"** and **"Multiple"**.
+If the pattern **"One"** is chosen and the course has lecture portion to it, the course will be split into two lessons.
+One lesson will have only the lecture units while the other lesson contains only the lab/act units.
+If the pattern **"Multiple"** is selected or defaulted to and the course has a lecture portion,
+the lecture units and lab/act units will be bundled together into a lesson instance.
+
+Other things to consider:
+- If a studio course instance is scheduled to have all it's lab/act time to be in **one** block, then the constraint
+enforcing that lab/act time must be immediately after lecture will not be enforced for this lesson.
+
+
@@ -107,6 +131,6 @@ If there aren't any or there is a test you want to inlcude, follow the image gui
**Example setup in intellij**
On step 1 you will see pop up. Select `edit` under configuration.
-On step 2 you will see a menu and you should choose the `JUnit` option.
+On step 2 you will see a menu, and you should choose the `JUnit` option.
On step 3 you will choose the same first 2 options and then choose the test class you want to run.

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 7dc394d7..59b70432 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
@@ -26,26 +26,6 @@ public class TimetableConstraintProvider implements ConstraintProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(TimetableConstraintProvider.class);
private static final float FLOAT_TIME_DELTA = 0.01f;
@Override
- /*TODO to consider now that we need to consider we can actually have lessons with only activities. Before this was
- * technically possible already but the constraints or lesson creation never actually allowed this to happen.
- * It might be worth making a new constraint for accommodating lab only lessons and those that need lab all in one
- * block.
- * Things to consider now in this new change that might not be in here already:
- * -Lessons can now have lab/act only
- * -We now have lessons whose lab may be required to be all in one block. Note such lessons will have lab only
- * -For lessons whose lab days may be on multiple days, the lesson could have a lecture or be lab only
- * -What the above means is that we now need to be careful how we assign timeslots to a lesson object and how
- * we check for the correct amount of hours
- * -timeslots with only one slot, can be used for lessons with either lab/act or lecture only now
- * -for lecture blocks we have to be careful that it isn't given a timeslot that is continuous
- * Summary:
- * -lesson can be: lab + lab/act, lab/act only, or lecture only;
- * -for lec + lab/act lessons, the lab/act will always be spread out
- * -for lab/act only lessons, the time might either be all in one block or spread out
- * -it might be worth making a constraint whose only responsibility is to handle that the right type of slot
- * is given to a lesson; Actually lets start off doing this while updating all the other constraints
- * and then figure out if we can merge it; This will be cognitively easier; make global flag for including
- * only if we have a lesson with lab only that want all the time in one block????*/
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
Constraint[] studioConstraint = new Constraint[]{
diff --git a/java/hello-world/src/main/resources/constants/config.example.yaml b/java/hello-world/src/main/resources/constants/config.example.yaml
index 9e353f4d..a432acbd 100644
--- a/java/hello-world/src/main/resources/constants/config.example.yaml
+++ b/java/hello-world/src/main/resources/constants/config.example.yaml
@@ -9,4 +9,5 @@ aggressiveChoice: "AGGRESSIVE_PENALTY"
# this setting changes how we model the PrimeTime constraint
compressStart: "7:00AM"
compressEnd: "6:00PM"
-prescheduledFileName: "PrescheduledTimes_ExampleFile.example.json"
\ No newline at end of file
+prescheduledFileName: "PrescheduledTimes_ExampleFile.example.json"
+patternsFileName: "coursePatterns.json"
\ No newline at end of file
From d6dd7245aeb81398d1bf5586231eabae21f144e4 Mon Sep 17 00:00:00 2001
From: RandomCyberCoder
Date: Wed, 17 Jun 2026 16:36:27 -0700
Subject: [PATCH 10/10] doc updated and constraint renames
---
java/hello-world/README.md | 1 +
.../solver/TimetableConstraintProvider.java | 8 ++++----
.../solver/TestConstraintPropStudio.java | 8 ++++----
.../acme/schooltimetabling/solver/TestConstraints.java | 7 +++----
4 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/java/hello-world/README.md b/java/hello-world/README.md
index 83b421bc..bc4d10e7 100644
--- a/java/hello-world/README.md
+++ b/java/hello-world/README.md
@@ -109,6 +109,7 @@ the lecture units and lab/act units will be bundled together into a lesson insta
Other things to consider:
- If a studio course instance is scheduled to have all it's lab/act time to be in **one** block, then the constraint
enforcing that lab/act time must be immediately after lecture will not be enforced for this lesson.
+- When setting the instructor preference, the key (aka the instructor's name) should be their canonical name.
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 59b70432..57e42598 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
@@ -35,10 +35,10 @@ public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
//universal constraints
List solver_constraints = new ArrayList<>(Arrays.asList(
// Hard constraints
- sameClassSameDays(constraintFactory),
+ teacherSameCourseSamePattern(constraintFactory),
teacherLessonConflict(constraintFactory),
lessonConflict(constraintFactory),
- labActRoomConflict(constraintFactory),
+ roomConflict(constraintFactory),
wrongHoursAmount(constraintFactory),
wrongRoomType(constraintFactory),
timeslotPatternMatch(constraintFactory),
@@ -145,7 +145,7 @@ Constraint primeTime50Plus(ConstraintFactory constraintFactory){
*
* @return Penalize by {@link HardMediumSoftScore#ONE_HARD}
*/
- Constraint sameClassSameDays(ConstraintFactory constraintFactory){
+ Constraint teacherSameCourseSamePattern(ConstraintFactory constraintFactory){
return constraintFactory
.forEachUniquePair(Lesson.class,
Joiners.equal(lesson -> lesson.getTeacherObj().getId()),
@@ -217,7 +217,7 @@ Constraint lessonConflict(ConstraintFactory constraintFactory){
* This constraint will penalize any unique pair of lessons (w/ lab/act) that use the same room
* at the same time
*/
- Constraint labActRoomConflict(ConstraintFactory constraintFactory){
+ Constraint roomConflict(ConstraintFactory constraintFactory){
return constraintFactory
//for each lesson
.forEachUniquePair(Lesson.class,
diff --git a/java/hello-world/src/test/java/org/acme/schooltimetabling/solver/TestConstraintPropStudio.java b/java/hello-world/src/test/java/org/acme/schooltimetabling/solver/TestConstraintPropStudio.java
index 882376f9..628ef5c3 100644
--- a/java/hello-world/src/test/java/org/acme/schooltimetabling/solver/TestConstraintPropStudio.java
+++ b/java/hello-world/src/test/java/org/acme/schooltimetabling/solver/TestConstraintPropStudio.java
@@ -46,7 +46,7 @@ void pen_PropDiffTchPttrn(){
ConstraintTestHelper.DUMMY_TEACHER, ts_TR_TR, ConstraintTestHelper.DUMMY_ROOM);
- constraintVerifier.verifyThat(TimetableConstraintProvider::sameClassSameDays)
+ constraintVerifier.verifyThat(TimetableConstraintProvider::teacherSameCourseSamePattern)
.given(ls1_MWF_TR, ls2_TR_TR
)
/*Note this takes into account weight of rewards*/
@@ -67,7 +67,7 @@ void noPen_PropSameTchPttrn(){
"", "3-1-0", Constants.COURSE_ID_BIMAP.get(ConstraintTestHelper.DUMMY_STUDIO),
ConstraintTestHelper.DUMMY_TEACHER, ts_MWF_MWF, ConstraintTestHelper.DUMMY_ROOM);
- constraintVerifier.verifyThat(TimetableConstraintProvider::sameClassSameDays)
+ constraintVerifier.verifyThat(TimetableConstraintProvider::teacherSameCourseSamePattern)
.given(ls1_MWF_TR, ls2_MWF_MWF
)
/*Note this takes into account weight of rewards*/
@@ -116,7 +116,7 @@ void pen_PropCoursesSameRoom() throws Exception{
"", "3-1-0", Constants.COURSE_ID_BIMAP.get(ConstraintTestHelper.DUMMY_STUDIO),
ConstraintTestHelper.DUMMY_TEACHER, ts_TR_830AM_3blcks_10AM_3blcks, ConstraintTestHelper.DUMMY_ROOM);
- constraintVerifier.verifyThat(TimetableConstraintProvider::labActRoomConflict)
+ constraintVerifier.verifyThat(TimetableConstraintProvider::roomConflict)
.given(combo1_st1, combo1_non_st1,
combo2_st2, combo2_st3)
.penalizesBy(2);
@@ -147,7 +147,7 @@ void noPen_PropCoursesSameRoom() throws Exception{
ConstraintTestHelper.DUMMY_ROOM);
//reasoning: proper studios use lab room for lec and lab/act portions, but non proper doesn't use it during lec
- constraintVerifier.verifyThat(TimetableConstraintProvider::labActRoomConflict)
+ constraintVerifier.verifyThat(TimetableConstraintProvider::roomConflict)
.given(combo1_st1, combo1_non_st1)
.penalizesBy(NO_PENALTY);
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 377e4648..11698d8b 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
@@ -1,6 +1,5 @@
package org.acme.schooltimetabling.solver;
-import ai.timefold.solver.core.api.score.stream.Constraint;
import org.acme.schooltimetabling.builders.lessons.LessonBuilder;
import org.acme.schooltimetabling.builders.teachers.TeacherBuilder;
import org.acme.schooltimetabling.builders.teachers.policies.DefaultTeachingPolicy;
@@ -80,7 +79,7 @@ void sameLessonNTeacher(){
.build();
- constraintVerifier.verifyThat(TimetableConstraintProvider::sameClassSameDays)
+ constraintVerifier.verifyThat(TimetableConstraintProvider::teacherSameCourseSamePattern)
.given(ls1, ls2, ls3, ls4, ls5)
/*Note this takes into account weight of rewards*/
.penalizesBy(3);
@@ -265,7 +264,7 @@ void roomMultiLessons(){
"3-0-0", 2, ConstraintTestHelper.DUMMY_TEACHER, ts_mwf_11,
ConstraintTestHelper.DUMMY_ROOM);
- constraintVerifier.verifyThat(TimetableConstraintProvider::labActRoomConflict)
+ constraintVerifier.verifyThat(TimetableConstraintProvider::roomConflict)
.given(lesson1, lesson2, lesson3, lesson4studio)
.penalizesBy(2);
}
@@ -287,7 +286,7 @@ void roomMultiLessons2(){
, "", "3-1-0", 1, ConstraintTestHelper.DUMMY_TEACHER, timeslot2,
ConstraintTestHelper.DUMMY_ROOM);
- constraintVerifier.verifyThat(TimetableConstraintProvider::labActRoomConflict)
+ constraintVerifier.verifyThat(TimetableConstraintProvider::roomConflict)
.given(lesson1, lesson2)
.penalizesBy(1);
}