Files
ortools-clone/ortools/scheduling/course_scheduling_test.cc

1205 lines
34 KiB
C++
Raw Normal View History

// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ortools/scheduling/course_scheduling.h"
#include "gtest/gtest.h"
#include "ortools/base/gmock.h"
#include "ortools/base/parse_test_proto.h"
#include "ortools/scheduling/course_scheduling.pb.h"
namespace operations_research {
namespace {
using ::google::protobuf::contrib::parse_proto::ParseTestProto;
TEST(CourseSchedulingTest, CheckMultipleSectionsCorrectlyScheduled) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 2
daily_time_slot_count: 2
courses {
meetings_count: 2
consecutive_slots_count: 1
teacher_indices: 0
teacher_indices: 1
teacher_section_counts: 1
teacher_section_counts: 2
}
teachers {}
teachers { restricted_time_slots: 1 }
)pb");
const CourseSchedulingResult infeasible_result_ = ParseTestProto(R"pb(
solver_status: SOLVER_INFEASIBLE
message: "The problem is infeasible with the given courses."
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
EXPECT_THAT(result, testing::EqualsProto(infeasible_result_));
}
TEST(CourseSchedulingTest, CheckDailyMaximumForCoursesIsNotViolated) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 3
daily_time_slot_count: 2
courses {
meetings_count: 4
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
}
teachers {}
)pb");
const CourseSchedulingResult infeasible_result_ = ParseTestProto(R"pb(
solver_status: SOLVER_INFEASIBLE
message: "The problem is infeasible with the given courses."
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
EXPECT_THAT(result, testing::EqualsProto(infeasible_result_));
}
TEST(CourseSchedulingTest, CheckTeacherIsNotDoubleBooked) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 1
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
}
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
}
teachers {}
)pb");
const CourseSchedulingResult infeasible_result_ = ParseTestProto(R"pb(
solver_status: SOLVER_INFEASIBLE
message: "The problem is infeasible with the given courses."
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
EXPECT_THAT(result, testing::EqualsProto(infeasible_result_));
}
TEST(CourseSchedulingTest, CheckRoomAssignmentsForTimeSlotNotViolated) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 1
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
room_indices: 0
}
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 1
teacher_section_counts: 1
room_indices: 0
}
teachers {}
teachers {}
rooms {}
)pb");
const CourseSchedulingResult infeasible_result_ = ParseTestProto(R"pb(
solver_status: SOLVER_INFEASIBLE
message: "The problem is infeasible with the given courses."
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
EXPECT_THAT(result, testing::EqualsProto(infeasible_result_));
}
TEST(CourseSchedulingTest,
CheckConsecutiveTimeslotsValuesStartOfDayNotViolated) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 2
daily_time_slot_count: 4
courses {
meetings_count: 1
consecutive_slots_count: 2
teacher_indices: 0
teacher_section_counts: 1
}
teachers {
restricted_time_slots: 1
restricted_time_slots: 2
restricted_time_slots: 3
restricted_time_slots: 5
restricted_time_slots: 6
restricted_time_slots: 7
}
)pb");
const CourseSchedulingResult infeasible_result_ = ParseTestProto(R"pb(
solver_status: SOLVER_INFEASIBLE
message: "The problem is infeasible with the given courses."
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
EXPECT_THAT(result, testing::EqualsProto(infeasible_result_));
}
TEST(CourseSchedulingTest, CheckConsecutiveTimeslotsValuesEndOfDayNotViolated) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 2
daily_time_slot_count: 4
courses {
meetings_count: 1
consecutive_slots_count: 2
teacher_indices: 0
teacher_section_counts: 1
}
teachers {
restricted_time_slots: 0
restricted_time_slots: 1
restricted_time_slots: 2
restricted_time_slots: 4
restricted_time_slots: 5
restricted_time_slots: 6
}
)pb");
const CourseSchedulingResult infeasible_result_ = ParseTestProto(R"pb(
solver_status: SOLVER_INFEASIBLE
message: "The problem is infeasible with the given courses."
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
EXPECT_THAT(result, testing::EqualsProto(infeasible_result_));
}
TEST(CourseSchedulingTest, CheckSingletonCoursesNotScheduledForSameTime) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 1
courses {
meetings_count: 1
consecutive_slots_count: 1
min_capacity: 0
max_capacity: 5
teacher_indices: 0
teacher_section_counts: 1
}
courses {
meetings_count: 1
consecutive_slots_count: 1
min_capacity: 0
max_capacity: 5
teacher_indices: 1
teacher_section_counts: 1
}
teachers {}
teachers {}
students { course_indices: 0 course_indices: 1 }
)pb");
const CourseSchedulingResult infeasible_result_ = ParseTestProto(R"pb(
solver_status: SOLVER_INFEASIBLE
message: "The problem is infeasible with the given courses."
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
EXPECT_THAT(result, testing::EqualsProto(infeasible_result_));
}
TEST(CourseSchedulingTest, CheckMinimumCapacityForCourseNotViolated) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 1
courses {
meetings_count: 1
consecutive_slots_count: 1
min_capacity: 6
max_capacity: 10
teacher_indices: 0
teacher_section_counts: 1
}
teachers {}
students { course_indices: 0 }
)pb");
const CourseSchedulingResult infeasible_result_ = ParseTestProto(R"pb(
solver_status: SOLVER_INFEASIBLE
message: "Check the minimum or maximum capacity constraints for your "
"classes."
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
EXPECT_THAT(result, testing::EqualsProto(infeasible_result_));
}
TEST(CourseSchedulingTest, CheckMaximumCapacityForCourseNotViolated) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 1
courses {
meetings_count: 1
consecutive_slots_count: 1
min_capacity: 0
max_capacity: 2
teacher_indices: 0
teacher_section_counts: 1
}
teachers {}
students { course_indices: 0 }
students { course_indices: 0 }
students { course_indices: 0 }
)pb");
const CourseSchedulingResult infeasible_result_ = ParseTestProto(R"pb(
solver_status: SOLVER_INFEASIBLE
message: "Check the minimum or maximum capacity constraints for your "
"classes."
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
EXPECT_THAT(result, testing::EqualsProto(infeasible_result_));
}
TEST(CourseSchedulingTest, CheckStudentsAreNotDoubleBooked) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 2
courses {
meetings_count: 1
consecutive_slots_count: 1
min_capacity: 0
max_capacity: 5
teacher_indices: 0
teacher_indices: 1
teacher_section_counts: 1
teacher_section_counts: 1
}
courses {
meetings_count: 1
consecutive_slots_count: 1
min_capacity: 0
max_capacity: 5
teacher_indices: 2
teacher_section_counts: 1
}
teachers { restricted_time_slots: 1 }
teachers { restricted_time_slots: 1 }
teachers { restricted_time_slots: 1 }
students { course_indices: 0 course_indices: 1 }
)pb");
const CourseSchedulingResult infeasible_result = ParseTestProto(R"pb(
solver_status: SOLVER_INFEASIBLE
message: "The problem is infeasible with the given courses."
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
EXPECT_THAT(result, testing::EqualsProto(infeasible_result));
}
TEST(CourseSchedulingTest, CheckStudentsAreNotDoubleBooked_TooManyCourses) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 2
courses {
meetings_count: 1
consecutive_slots_count: 1
min_capacity: 0
max_capacity: 5
teacher_indices: 0
teacher_section_counts: 2
}
courses {
meetings_count: 1
consecutive_slots_count: 1
min_capacity: 0
max_capacity: 5
teacher_indices: 1
teacher_indices: 2
teacher_section_counts: 1
teacher_section_counts: 1
}
courses {
meetings_count: 1
consecutive_slots_count: 1
min_capacity: 0
max_capacity: 5
teacher_indices: 2
teacher_section_counts: 1
}
teachers {}
teachers {}
teachers {}
students { course_indices: 0 course_indices: 1 course_indices: 2 }
)pb");
const CourseSchedulingResult infeasible_result = ParseTestProto(R"pb(
solver_status: SOLVER_INFEASIBLE
message: "The problem is infeasible with the given courses."
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
EXPECT_THAT(result, testing::EqualsProto(infeasible_result));
}
TEST(CourseSchedulingTest, CheckTeacherNotScheduledForRestrictedSlot) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 2
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 0
teacher_indices: 1
teacher_section_counts: 1
teacher_section_counts: 1
}
teachers { restricted_time_slots: 1 }
teachers { restricted_time_slots: 0 }
)pb");
const CourseSchedulingResult expected_result = ParseTestProto(R"pb(
solver_status: SOLVER_OPTIMAL
class_assignments { course_index: 0 section_number: 0 time_slots: 0 }
class_assignments { course_index: 0 section_number: 1 time_slots: 1 }
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
EXPECT_THAT(result, testing::EqualsProto(expected_result));
}
TEST(CourseSchedulingTest, CheckConsecutiveTimeSlotsValuesNotViolated) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 2
daily_time_slot_count: 2
courses {
meetings_count: 1
consecutive_slots_count: 2
teacher_indices: 0
teacher_section_counts: 1
}
teachers { restricted_time_slots: 1 }
)pb");
const CourseSchedulingResult expected_result = ParseTestProto(R"pb(
solver_status: SOLVER_OPTIMAL
class_assignments {
course_index: 0
section_number: 0
time_slots: 2
time_slots: 3
}
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
EXPECT_THAT(result, testing::EqualsProto(expected_result));
}
TEST(CourseSchedulingTest, CheckStudentsCorrectlyAssignedToSingletonCourses) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 2
courses {
meetings_count: 1
consecutive_slots_count: 1
min_capacity: 0
max_capacity: 5
teacher_indices: 0
teacher_section_counts: 1
}
courses {
meetings_count: 1
consecutive_slots_count: 1
min_capacity: 0
max_capacity: 5
teacher_indices: 1
teacher_section_counts: 1
}
teachers { restricted_time_slots: 0 }
teachers {}
students { course_indices: 0 course_indices: 1 }
students { course_indices: 1 course_indices: 0 }
)pb");
const CourseSchedulingResult expected_result = ParseTestProto(R"pb(
solver_status: SOLVER_OPTIMAL
class_assignments { course_index: 0 section_number: 0 time_slots: 1 }
class_assignments { course_index: 1 section_number: 0 time_slots: 0 }
student_assignments {
student_index: 0
course_indices: 0
course_indices: 1
section_indices: 0
section_indices: 0
}
student_assignments {
student_index: 1
course_indices: 1
course_indices: 0
section_indices: 0
section_indices: 0
}
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
EXPECT_THAT(result, testing::EqualsProto(expected_result));
}
TEST(CourseSchedulingTest, CheckStudentsNotDoubleBookedForTimeSlot) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 2
courses {
meetings_count: 1
consecutive_slots_count: 1
min_capacity: 0
max_capacity: 5
teacher_indices: 0
teacher_indices: 1
teacher_indices: 2
teacher_section_counts: 1
teacher_section_counts: 1
teacher_section_counts: 1
}
courses {
meetings_count: 1
consecutive_slots_count: 1
min_capacity: 0
max_capacity: 5
teacher_indices: 3
teacher_section_counts: 1
}
teachers { restricted_time_slots: 1 }
teachers { restricted_time_slots: 1 }
teachers { restricted_time_slots: 0 }
teachers { restricted_time_slots: 1 }
students { course_indices: 0 course_indices: 1 }
)pb");
const CourseSchedulingResult expected_result = ParseTestProto(R"pb(
solver_status: SOLVER_OPTIMAL
class_assignments { course_index: 0 section_number: 0 time_slots: 0 }
class_assignments { course_index: 0 section_number: 1 time_slots: 0 }
class_assignments { course_index: 0 section_number: 2 time_slots: 1 }
class_assignments { course_index: 1 section_number: 0 time_slots: 0 }
student_assignments {
student_index: 0
course_indices: 0
course_indices: 1
section_indices: 2
section_indices: 0
}
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
EXPECT_THAT(result, testing::EqualsProto(expected_result));
}
TEST(CourseSchedulingTest,
AssertErrorWhenTeacherIndexNumberDoesNotMatchNumSectionsNumber) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 3
daily_time_slot_count: 2
courses {
display_name: "English"
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 0
teacher_indices: 1
teacher_section_counts: 2
}
teachers {}
teachers {}
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
ASSERT_EQ(result.solver_status(), SOLVER_MODEL_INVALID);
ASSERT_EQ(result.message(),
"The course titled English should have the same number of teacher "
"indices and section numbers.");
}
TEST(CourseSchedulingTest,
AssertErrorWhenCourseIsAllottedRoomIndexThatDoesNotExist) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 3
daily_time_slot_count: 2
courses {
display_name: "English"
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
room_indices: 1
}
teachers {}
rooms {}
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
ASSERT_EQ(result.solver_status(), SOLVER_MODEL_INVALID);
ASSERT_EQ(result.message(),
"The course titled English is slotted for room index 1 but there "
"are only 1 rooms.");
}
TEST(CourseSchedulingTest,
AssertErrorWhenCourseIsGivenTeacherIndexThatDoesNotExist) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 3
daily_time_slot_count: 2
courses {
display_name: "English"
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 1
teacher_section_counts: 1
}
teachers {}
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
ASSERT_EQ(result.solver_status(), SOLVER_MODEL_INVALID);
ASSERT_EQ(result.message(),
"The course titled English has teacher 1 assigned to it but there "
"are only 1 teachers.");
}
TEST(CourseSchedulingTest,
AssertErrorWhenConsecutiveTimeSlotNumberIsMoreThanTwo) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 3
daily_time_slot_count: 2
courses {
display_name: "English"
meetings_count: 1
consecutive_slots_count: 3
teacher_indices: 0
teacher_section_counts: 1
}
teachers {}
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
ASSERT_EQ(result.solver_status(), SOLVER_MODEL_INVALID);
ASSERT_EQ(result.message(),
"The course titled English has 3 consecutive time slots specified "
"when it can only have 1 or 2.");
}
TEST(CourseSchedulingTest,
AssertErrorWhenTeacherHasRestrictedTimeSlotThatDoesNotExist) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 1
courses {
display_name: "English"
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
}
teachers { display_name: "A" restricted_time_slots: 1 }
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
ASSERT_EQ(result.solver_status(), SOLVER_MODEL_INVALID);
ASSERT_EQ(result.message(),
"Teacher with name A has restricted time slot 1 but there are only "
"1 time slots.");
}
TEST(CourseSchedulingTest,
AssertErrorWhenStudentHasCourseIndexThatDoesNotExist) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 1
courses {
display_name: "English"
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
}
teachers {}
students { display_name: "Marvin" course_indices: 1 }
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
ASSERT_EQ(result.solver_status(), SOLVER_MODEL_INVALID);
ASSERT_EQ(result.message(),
"Student with name Marvin has course index 1 but there are only 1 "
"courses.");
}
class CourseSchedulingVerifierTestSolver : public CourseSchedulingSolver {
public:
void SetResultToReturn(CourseSchedulingResult result_to_return) {
result_to_return_ = result_to_return;
}
protected:
CourseSchedulingResult SolveModel(
const CourseSchedulingModel& model,
const ConflictPairs& class_conflicts) override {
return result_to_return_;
}
private:
CourseSchedulingResult result_to_return_;
};
TEST(CourseSchedulingTest, CheckVerifierErrorWhenTwoClassesAssignedToSameRoom) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 3
daily_time_slot_count: 2
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
room_indices: 0
}
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 1
teacher_section_counts: 1
room_indices: 0
}
teachers {}
teachers {}
rooms { display_name: "Zaphod" }
)pb");
const CourseSchedulingResult error_result = ParseTestProto(R"pb(
solver_status: SOLVER_OPTIMAL
class_assignments {
course_index: 0
section_number: 0
time_slots: 0
room_indices: 0
}
class_assignments {
course_index: 1
section_number: 0
time_slots: 0
room_indices: 0
}
)pb");
CourseSchedulingVerifierTestSolver solver;
solver.SetResultToReturn(error_result);
const CourseSchedulingResult result = solver.Solve(model);
ASSERT_EQ(result.solver_status(), ABNORMAL);
ASSERT_EQ(result.message(),
"Verification failed: Multiple classes have been assigned to room "
"Zaphod during time slot 0.");
}
TEST(CourseSchedulingTest,
CheckVerifierErrorWhenClassDoesNotMeetCorrectNumberOfTimes) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 3
daily_time_slot_count: 2
courses {
display_name: "English"
meetings_count: 3
consecutive_slots_count: 2
teacher_indices: 0
teacher_section_counts: 2
}
teachers {}
)pb");
const CourseSchedulingResult error_result = ParseTestProto(R"pb(
solver_status: SOLVER_OPTIMAL
class_assignments {
course_index: 0
section_number: 0
time_slots: 0
time_slots: 1
time_slots: 2
time_slots: 3
time_slots: 4
time_slots: 5
}
class_assignments {
course_index: 0
section_number: 1
time_slots: 0
time_slots: 1
time_slots: 2
time_slots: 3
time_slots: 5
}
)pb");
CourseSchedulingVerifierTestSolver solver;
solver.SetResultToReturn(error_result);
const CourseSchedulingResult result = solver.Solve(model);
ASSERT_EQ(result.solver_status(), ABNORMAL);
ASSERT_EQ(result.message(),
"Verification failed: The course titled English and section number "
"1 meets 5 times when it should meet 6 times.");
}
TEST(CourseSchedulingTest,
CheckVerifierErrorWhenClassMeetsMoreThanConsecutiveSlotCountPerDay) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 2
daily_time_slot_count: 3
courses {
display_name: "English"
meetings_count: 2
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
}
teachers {}
)pb");
const CourseSchedulingResult error_result = ParseTestProto(R"pb(
solver_status: SOLVER_OPTIMAL
class_assignments {
course_index: 0
section_number: 0
time_slots: 3
time_slots: 4
}
)pb");
CourseSchedulingVerifierTestSolver solver;
solver.SetResultToReturn(error_result);
const CourseSchedulingResult result = solver.Solve(model);
ASSERT_EQ(result.solver_status(), ABNORMAL);
ASSERT_EQ(result.message(),
"Verification failed: The course titled English does not meet the "
"correct number of "
"times in day 1.");
}
TEST(CourseSchedulingTest,
CheckVerifierErrorWhenClassIsNotScheduledForConsecutiveSlots) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 2
daily_time_slot_count: 3
courses {
display_name: "English"
meetings_count: 2
consecutive_slots_count: 2
teacher_indices: 0
teacher_section_counts: 1
}
teachers {}
)pb");
const CourseSchedulingResult error_result = ParseTestProto(R"pb(
solver_status: SOLVER_OPTIMAL
class_assignments {
course_index: 0
section_number: 0
time_slots: 1
time_slots: 2
time_slots: 3
time_slots: 5
}
)pb");
CourseSchedulingVerifierTestSolver solver;
solver.SetResultToReturn(error_result);
const CourseSchedulingResult result = solver.Solve(model);
ASSERT_EQ(result.solver_status(), ABNORMAL);
ASSERT_EQ(result.message(),
"Verification failed: The course titled English is not scheduled "
"for consecutive time "
"slots in day 1.");
}
TEST(CourseSchedulingTest,
CheckVerifierErrorWhenTeacherIsDoubleBookedForATimeSlot) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 2
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
}
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
}
teachers { display_name: "Marvin" }
)pb");
const CourseSchedulingResult error_result = ParseTestProto(R"pb(
solver_status: SOLVER_OPTIMAL
class_assignments { course_index: 0 section_number: 0 time_slots: 0 }
class_assignments { course_index: 1 section_number: 0 time_slots: 0 }
)pb");
CourseSchedulingVerifierTestSolver solver;
solver.SetResultToReturn(error_result);
const CourseSchedulingResult result = solver.Solve(model);
ASSERT_EQ(result.solver_status(), ABNORMAL);
ASSERT_EQ(result.message(),
"Verification failed: Teacher with name Marvin has been assigned "
"to multiple classes at time slot 0.");
}
TEST(CourseSchedulingTest,
CheckVerifierErrorWhenTeacherIsAssignedToRestrictedTimeSlot) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 2
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
}
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
}
teachers { display_name: "Marvin" restricted_time_slots: 1 }
)pb");
const CourseSchedulingResult error_result = ParseTestProto(R"pb(
solver_status: SOLVER_OPTIMAL
class_assignments { course_index: 0 section_number: 0 time_slots: 0 }
class_assignments { course_index: 1 section_number: 0 time_slots: 1 }
)pb");
CourseSchedulingVerifierTestSolver solver;
solver.SetResultToReturn(error_result);
const CourseSchedulingResult result = solver.Solve(model);
ASSERT_EQ(result.solver_status(), ABNORMAL);
ASSERT_EQ(result.message(),
"Verification failed: Teacher with name Marvin has been assigned "
"to restricted time slot 1.");
}
TEST(CourseSchedulingTest,
CheckVerifierErrorWhenStudentNotAssignedToCorrectCourses) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 3
daily_time_slot_count: 2
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
}
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 1
teacher_section_counts: 1
}
teachers {}
teachers {}
students { display_name: "Marvin" course_indices: 0 }
)pb");
const CourseSchedulingResult error_result = ParseTestProto(R"pb(
solver_status: SOLVER_OPTIMAL
class_assignments { course_index: 0 section_number: 0 time_slots: 0 }
class_assignments { course_index: 1 section_number: 0 time_slots: 0 }
student_assignments {
student_index: 0
course_indices: 1
section_indices: 0
}
)pb");
CourseSchedulingVerifierTestSolver solver;
solver.SetResultToReturn(error_result);
const CourseSchedulingResult result = solver.Solve(model);
ASSERT_EQ(result.solver_status(), ABNORMAL);
ASSERT_EQ(result.message(),
"Verification failed: Student with name Marvin has not been "
"assigned the correct courses.");
}
TEST(CourseSchedulingTest,
CheckVerifierErrorWhenStudentIsDoubleBookedForATimeSlot) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 2
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
}
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 1
teacher_section_counts: 1
}
teachers {}
teachers {}
students { display_name: "Marvin" course_indices: 0 course_indices: 1 }
)pb");
const CourseSchedulingResult error_result = ParseTestProto(R"pb(
solver_status: SOLVER_OPTIMAL
class_assignments { course_index: 0 section_number: 0 time_slots: 0 }
class_assignments { course_index: 1 section_number: 0 time_slots: 0 }
student_assignments {
student_index: 0
course_indices: 0
course_indices: 1
section_indices: 0
section_indices: 0
}
)pb");
CourseSchedulingVerifierTestSolver solver;
solver.SetResultToReturn(error_result);
const CourseSchedulingResult result = solver.Solve(model);
ASSERT_EQ(result.solver_status(), ABNORMAL);
ASSERT_EQ(result.message(),
"Verification failed: Student with name Marvin has been assigned "
"to multiple classes at time slot 0.");
}
TEST(CourseSchedulingTest,
CheckVerifierErrorWhenClassSizeDoesNotReachMinCapacity) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 2
courses {
display_name: "English"
meetings_count: 1
min_capacity: 3
max_capacity: 10
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
}
teachers {}
students { course_indices: 0 }
students { course_indices: 0 }
)pb");
const CourseSchedulingResult error_result = ParseTestProto(R"pb(
solver_status: SOLVER_OPTIMAL
class_assignments { course_index: 0 section_number: 0 time_slots: 0 }
student_assignments {
student_index: 0
course_indices: 0
section_indices: 0
}
student_assignments {
student_index: 1
course_indices: 0
section_indices: 0
}
)pb");
CourseSchedulingVerifierTestSolver solver;
solver.SetResultToReturn(error_result);
const CourseSchedulingResult result = solver.Solve(model);
ASSERT_EQ(result.solver_status(), ABNORMAL);
ASSERT_EQ(result.message(),
"Verification failed: The course titled English has 2 students "
"when it should have at least 3 students.");
}
TEST(CourseSchedulingTest, CheckVerifierErrorWhenClassSizeExceedsMaxCapacity) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 2
courses {
display_name: "English"
meetings_count: 1
min_capacity: 0
max_capacity: 2
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
}
teachers {}
students { course_indices: 0 }
students { course_indices: 0 }
students { course_indices: 0 }
)pb");
const CourseSchedulingResult error_result = ParseTestProto(R"pb(
solver_status: SOLVER_OPTIMAL
class_assignments { course_index: 0 section_number: 0 time_slots: 0 }
student_assignments {
student_index: 0
course_indices: 0
section_indices: 0
}
student_assignments {
student_index: 1
course_indices: 0
section_indices: 0
}
student_assignments {
student_index: 2
course_indices: 0
section_indices: 0
}
)pb");
CourseSchedulingVerifierTestSolver solver;
solver.SetResultToReturn(error_result);
const CourseSchedulingResult result = solver.Solve(model);
ASSERT_EQ(result.solver_status(), ABNORMAL);
ASSERT_EQ(result.message(),
"Verification failed: The course titled English has 3 students "
"when it should have no more than 2 students.");
}
TEST(CourseSchedulingTest, CheckRoomAssignmentsForCourseNotViolated) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 1
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
room_indices: 1
}
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 1
teacher_section_counts: 1
room_indices: 0
}
teachers {}
teachers {}
rooms {}
rooms {}
)pb");
const CourseSchedulingResult expected_result = ParseTestProto(R"pb(
solver_status: SOLVER_OPTIMAL
class_assignments {
course_index: 0
section_number: 0
time_slots: 0
room_indices: 1
}
class_assignments {
course_index: 1
section_number: 0
time_slots: 0
room_indices: 0
}
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
EXPECT_THAT(result, testing::EqualsProto(expected_result));
}
TEST(CourseSchedulingTest, CheckConsecutiveTimeSlotsScheduledForSameRoom) {
const CourseSchedulingModel model = ParseTestProto(R"pb(
days_count: 1
daily_time_slot_count: 2
courses {
meetings_count: 1
consecutive_slots_count: 1
teacher_indices: 0
teacher_section_counts: 1
room_indices: 0
}
courses {
meetings_count: 1
consecutive_slots_count: 2
teacher_indices: 1
teacher_section_counts: 1
room_indices: 0
room_indices: 1
}
teachers { restricted_time_slots: 0 }
teachers {}
rooms {}
rooms {}
)pb");
const CourseSchedulingResult expected_result = ParseTestProto(R"pb(
solver_status: SOLVER_OPTIMAL
class_assignments {
course_index: 0
section_number: 0
time_slots: 1
room_indices: 0
}
class_assignments {
course_index: 1
section_number: 0
time_slots: 0
room_indices: 1
time_slots: 1
room_indices: 1
}
)pb");
CourseSchedulingSolver solver;
const CourseSchedulingResult result = solver.Solve(model);
EXPECT_THAT(result, testing::EqualsProto(expected_result));
}
} // namespace
} // namespace operations_research