diff --git a/ortools/scheduling/BUILD.bazel b/ortools/scheduling/BUILD.bazel index 6e27790607..3e2dccf061 100644 --- a/ortools/scheduling/BUILD.bazel +++ b/ortools/scheduling/BUILD.bazel @@ -17,7 +17,9 @@ load("@protobuf//bazel:cc_proto_library.bzl", "cc_proto_library") load("@protobuf//bazel:proto_library.bzl", "proto_library") load("@protobuf//bazel:py_proto_library.bzl", "py_proto_library") +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") package(default_visibility = ["//visibility:public"]) @@ -51,6 +53,29 @@ cc_library( ], ) +cc_test( + name = "rcpsp_parser_test", + size = "small", + srcs = ["rcpsp_parser_test.cc"], + data = [ + "//ortools/scheduling/testdata:c1510_1.mm.txt", + "//ortools/scheduling/testdata:j301_1.sm", + "//ortools/scheduling/testdata:mmlib100_j100100_1.mm.txt", + "//ortools/scheduling/testdata:psp1.sch", + "//ortools/scheduling/testdata:psp10_1.sch", + "//ortools/scheduling/testdata:rg300_1.rcp", + "//ortools/scheduling/testdata:rg30_set1_pat1.rcp", + "//ortools/scheduling/testdata:rip1.sch", + "//ortools/scheduling/testdata:ubo_10_psp2.sch", + ], + deps = [ + ":rcpsp_parser", + "//ortools/base:gmock_main", + "//ortools/base:path", + "@abseil-cpp//absl/strings:string_view", + ], +) + ### Jobshop Scheduling ### proto_library( name = "jobshop_scheduling_proto", @@ -81,6 +106,27 @@ cc_library( ], ) +cc_test( + name = "jobshop_scheduling_parser_test", + size = "small", + srcs = ["jobshop_scheduling_parser_test.cc"], + data = [ + "//ortools/scheduling/testdata:02a.fjs", + "//ortools/scheduling/testdata:1010_1_3", + "//ortools/scheduling/testdata:50_10_01_ta041.txt", + "//ortools/scheduling/testdata:SDST10_ta001.txt", + "//ortools/scheduling/testdata:ft06", + "//ortools/scheduling/testdata:jb1.txt", + "//ortools/scheduling/testdata:taillard-jobshop-15_15-1_225_100_150-1", + ], + deps = [ + ":jobshop_scheduling_parser", + "//ortools/base:gmock_main", + "//ortools/base:path", + "@abseil-cpp//absl/strings:string_view", + ], +) + ### Course Scheduling ### proto_library( name = "course_scheduling_proto", @@ -91,3 +137,47 @@ cc_proto_library( name = "course_scheduling_cc_proto", deps = [":course_scheduling_proto"], ) + +cc_library( + name = "course_scheduling", + srcs = ["course_scheduling.cc"], + hdrs = ["course_scheduling.h"], + deps = [ + ":course_scheduling_cc_proto", + "//ortools/base:mathutil", + "//ortools/linear_solver:linear_solver_base", + "//ortools/linear_solver:linear_solver_scip", + "//ortools/sat:cp_model_cc_proto", + "@abseil-cpp//absl/container:flat_hash_set", + "@abseil-cpp//absl/log", + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/strings:str_format", + "@abseil-cpp//absl/types:span", + ], +) + +cc_binary( + name = "course_scheduling_run", + srcs = ["course_scheduling_run.cc"], + deps = [ + ":course_scheduling", + ":course_scheduling_cc_proto", + "//ortools/base", + "//ortools/base:file", + "//ortools/base:timer", + "@abseil-cpp//absl/flags:flag", + "@abseil-cpp//absl/log", + ], +) + +cc_test( + name = "course_scheduling_test", + srcs = ["course_scheduling_test.cc"], + deps = [ + ":course_scheduling", + ":course_scheduling_cc_proto", + "//ortools/base:gmock_main", + "//ortools/base:mutable_memfile", + "//ortools/base:parse_test_proto", + ], +) diff --git a/ortools/scheduling/CMakeLists.txt b/ortools/scheduling/CMakeLists.txt index 48953bca47..4b8285a637 100644 --- a/ortools/scheduling/CMakeLists.txt +++ b/ortools/scheduling/CMakeLists.txt @@ -11,15 +11,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -file(GLOB _SRCS "*.h" "*.cc") +list(APPEND _SRCS + course_scheduling.cc + course_scheduling.h + jobshop_scheduling_parser.cc + jobshop_scheduling_parser.h + rcpsp_parser.cc + rcpsp_parser.h +) set(NAME ${PROJECT_NAME}_scheduling) # Will be merge in libortools.so -#add_library(${NAME} STATIC ${_SRCS}) add_library(${NAME} OBJECT ${_SRCS}) -set_target_properties(${NAME} PROPERTIES - POSITION_INDEPENDENT_CODE ON - ) +set_target_properties(${NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) target_include_directories(${NAME} PRIVATE ${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR}) diff --git a/examples/cpp/course_scheduling.cc b/ortools/scheduling/course_scheduling.cc similarity index 99% rename from examples/cpp/course_scheduling.cc rename to ortools/scheduling/course_scheduling.cc index 264acc5268..e1f0a6d0e8 100644 --- a/examples/cpp/course_scheduling.cc +++ b/ortools/scheduling/course_scheduling.cc @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "examples/cpp/course_scheduling.h" +#include "ortools/scheduling/course_scheduling.h" #include #include @@ -21,10 +21,10 @@ #include #include "absl/container/flat_hash_set.h" +#include "absl/log/log.h" #include "absl/status/status.h" #include "absl/strings/str_format.h" #include "absl/types/span.h" -#include "ortools/base/logging.h" #include "ortools/base/mathutil.h" #include "ortools/linear_solver/linear_solver.h" #include "ortools/scheduling/course_scheduling.pb.h" diff --git a/examples/cpp/course_scheduling.h b/ortools/scheduling/course_scheduling.h similarity index 95% rename from examples/cpp/course_scheduling.h rename to ortools/scheduling/course_scheduling.h index cc8247f0b6..0ab8640163 100644 --- a/examples/cpp/course_scheduling.h +++ b/ortools/scheduling/course_scheduling.h @@ -11,8 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef ORTOOLS_EXAMPLES_COURSE_SCHEDULING_H_ -#define ORTOOLS_EXAMPLES_COURSE_SCHEDULING_H_ +#ifndef ORTOOLS_SCHEDULING_COURSE_SCHEDULING_H_ +#define ORTOOLS_SCHEDULING_COURSE_SCHEDULING_H_ #include #include @@ -83,4 +83,4 @@ class CourseSchedulingSolver { } // namespace operations_research -#endif // ORTOOLS_EXAMPLES_COURSE_SCHEDULING_H_ +#endif // ORTOOLS_SCHEDULING_COURSE_SCHEDULING_H_ diff --git a/examples/cpp/course_scheduling_run.cc b/ortools/scheduling/course_scheduling_run.cc similarity index 98% rename from examples/cpp/course_scheduling_run.cc rename to ortools/scheduling/course_scheduling_run.cc index 2aef942952..2b30cdf5f7 100644 --- a/examples/cpp/course_scheduling_run.cc +++ b/ortools/scheduling/course_scheduling_run.cc @@ -23,11 +23,11 @@ #include "absl/flags/flag.h" #include "absl/log/log.h" -#include "examples/cpp/course_scheduling.h" #include "ortools/base/helpers.h" #include "ortools/base/init_google.h" #include "ortools/base/options.h" #include "ortools/base/timer.h" +#include "ortools/scheduling/course_scheduling.h" #include "ortools/scheduling/course_scheduling.pb.h" ABSL_FLAG(std::string, input, "", diff --git a/ortools/scheduling/course_scheduling_test.cc b/ortools/scheduling/course_scheduling_test.cc new file mode 100644 index 0000000000..9525f909a5 --- /dev/null +++ b/ortools/scheduling/course_scheduling_test.cc @@ -0,0 +1,1204 @@ +// 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 diff --git a/ortools/scheduling/jobshop_scheduling_parser_test.cc b/ortools/scheduling/jobshop_scheduling_parser_test.cc new file mode 100644 index 0000000000..d5cbc7ca96 --- /dev/null +++ b/ortools/scheduling/jobshop_scheduling_parser_test.cc @@ -0,0 +1,102 @@ +// 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/jobshop_scheduling_parser.h" + +#include + +#include "absl/strings/string_view.h" +#include "gtest/gtest.h" +#include "ortools/base/path.h" + +namespace operations_research { +namespace scheduling { +namespace jssp { +namespace { + +std::string GetPath(absl::string_view filename) { + constexpr absl::string_view kTestDataDir = + "_main/ortools/scheduling/testdata/"; + return file::JoinPath(::testing::SrcDir(), kTestDataDir, filename); +} + +TEST(RcpspParserTest, Jssp) { + JsspParser parser; + ASSERT_TRUE(parser.ParseFile(GetPath("ft06"))); + const JsspInputProblem problem = parser.problem(); + EXPECT_EQ(6, problem.jobs_size()); + EXPECT_EQ(6, problem.machines_size()); +} + +TEST(RcpspParserTest, Taillard) { + JsspParser parser; + ASSERT_TRUE(parser.ParseFile(GetPath("50_10_01_ta041.txt"))); + const JsspInputProblem problem = parser.problem(); + EXPECT_EQ(50, problem.jobs_size()); + EXPECT_EQ(10, problem.machines_size()); +} + +TEST(RcpspParserTest, Flexible) { + JsspParser parser; + ASSERT_TRUE(parser.ParseFile(GetPath("02a.fjs"))); + const JsspInputProblem problem = parser.problem(); + EXPECT_EQ(10, problem.jobs_size()); + EXPECT_EQ(5, problem.machines_size()); +} + +TEST(RcpspParserTest, Sdst) { + JsspParser parser; + ASSERT_TRUE(parser.ParseFile(GetPath("SDST10_ta001.txt"))); + const JsspInputProblem problem = parser.problem(); + EXPECT_EQ(20, problem.jobs_size()); + EXPECT_EQ(5, problem.machines_size()); + for (const Machine& m : problem.machines()) { + ASSERT_TRUE(m.has_transition_time_matrix()); + EXPECT_EQ(m.transition_time_matrix().transition_time_size(), + problem.jobs_size() * problem.jobs_size()); + } +} + +TEST(RcpspParserTest, Tardiness) { + JsspParser parser; + ASSERT_TRUE(parser.ParseFile(GetPath("jb1.txt"))); + const JsspInputProblem problem = parser.problem(); + EXPECT_EQ(10, problem.jobs_size()); + EXPECT_EQ(5, problem.machines_size()); +} + +TEST(RcpspParserTest, Pss) { + JsspParser parser; + ASSERT_TRUE( + parser.ParseFile(GetPath("taillard-jobshop-15_15-1_225_100_150-1"))); + const JsspInputProblem problem = parser.problem(); + EXPECT_EQ(15, problem.jobs_size()); + EXPECT_EQ(15, problem.machines_size()); +} + +TEST(RcpspParserTest, EarlyTardy) { + JsspParser parser; + ASSERT_TRUE(parser.ParseFile(GetPath("1010_1_3"))); + const JsspInputProblem problem = parser.problem(); + EXPECT_EQ(10, problem.jobs_size()); + EXPECT_EQ(10, problem.machines_size()); + EXPECT_EQ(1033, problem.jobs(0).early_due_date()); + EXPECT_EQ(1033, problem.jobs(0).late_due_date()); + EXPECT_EQ(3, problem.jobs(0).earliness_cost_per_time_unit()); + EXPECT_EQ(10, problem.jobs(0).lateness_cost_per_time_unit()); +} + +} // namespace +} // namespace jssp +} // namespace scheduling +} // namespace operations_research diff --git a/ortools/scheduling/rcpsp_parser_test.cc b/ortools/scheduling/rcpsp_parser_test.cc new file mode 100644 index 0000000000..36486e7ddb --- /dev/null +++ b/ortools/scheduling/rcpsp_parser_test.cc @@ -0,0 +1,115 @@ +// 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/rcpsp_parser.h" + +#include + +#include "absl/strings/string_view.h" +#include "gtest/gtest.h" +#include "ortools/base/path.h" + +namespace operations_research { +namespace scheduling { +namespace rcpsp { + +namespace { + +std::string GetPath(absl::string_view filename) { + constexpr absl::string_view kTestDataDir = + "_main/ortools/scheduling/testdata/"; + return file::JoinPath(::testing::SrcDir(), kTestDataDir, filename); +} + +TEST(RcpspParserTest, SingleMode) { + RcpspParser parser; + ASSERT_TRUE(parser.ParseFile(GetPath("j301_1.sm"))); + const RcpspProblem problem = parser.problem(); + EXPECT_EQ(32, problem.tasks_size()); + EXPECT_EQ(4, problem.resources_size()); +} + +TEST(RcpspParserTest, MultiMode) { + RcpspParser parser; + ASSERT_TRUE(parser.ParseFile(GetPath("c1510_1.mm.txt"))); + const RcpspProblem problem = parser.problem(); + EXPECT_EQ(18, problem.tasks_size()); + EXPECT_EQ(4, problem.resources_size()); +} + +TEST(RcpspParserTest, MultiModeMax) { + RcpspParser parser; + ASSERT_TRUE(parser.ParseFile(GetPath("psp1.sch"))); + const RcpspProblem problem = parser.problem(); + EXPECT_EQ(12, problem.tasks_size()); + EXPECT_EQ(7, problem.resources_size()); + EXPECT_TRUE(problem.is_rcpsp_max()); +} + +TEST(RcpspParserTest, SingleModeMax) { + RcpspParser parser; + ASSERT_TRUE(parser.ParseFile(GetPath("ubo_10_psp2.sch"))); + const RcpspProblem problem = parser.problem(); + EXPECT_EQ(12, problem.tasks_size()); + EXPECT_EQ(5, problem.resources_size()); + EXPECT_TRUE(problem.is_rcpsp_max()); + EXPECT_FALSE(problem.is_consumer_producer()); +} + +TEST(RcpspParserTest, SingleModeMaxReservoir) { + RcpspParser parser; + ASSERT_TRUE(parser.ParseFile(GetPath("psp10_1.sch"))); + const RcpspProblem problem = parser.problem(); + EXPECT_EQ(12, problem.tasks_size()); + EXPECT_EQ(5, problem.resources_size()); + EXPECT_TRUE(problem.is_rcpsp_max()); + EXPECT_TRUE(problem.is_consumer_producer()); +} + +TEST(RcpspParserTest, SingleModeInvestment) { + RcpspParser parser; + ASSERT_TRUE(parser.ParseFile(GetPath("rip1.sch"))); + const RcpspProblem problem = parser.problem(); + EXPECT_EQ(12, problem.tasks_size()); + EXPECT_EQ(1, problem.resources_size()); + EXPECT_TRUE(problem.is_resource_investment()); + EXPECT_EQ(19, problem.deadline()); +} + +TEST(RcpspParserTest, SingleModePatterson) { + RcpspParser parser; + ASSERT_TRUE(parser.ParseFile(GetPath("rg30_set1_pat1.rcp"))); + const RcpspProblem problem = parser.problem(); + EXPECT_EQ(32, problem.tasks_size()); + EXPECT_EQ(4, problem.resources_size()); +} + +TEST(RcpspParserTest, SingleModeLargePatterson) { + RcpspParser parser; + ASSERT_TRUE(parser.ParseFile(GetPath("rg300_1.rcp"))); + const RcpspProblem problem = parser.problem(); + EXPECT_EQ(302, problem.tasks_size()); + EXPECT_EQ(4, problem.resources_size()); +} + +TEST(RcpspParserTest, MultiModeMmLib) { + RcpspParser parser; + ASSERT_TRUE(parser.ParseFile(GetPath("mmlib100_j100100_1.mm.txt"))); + const RcpspProblem problem = parser.problem(); + EXPECT_EQ(102, problem.tasks_size()); + EXPECT_EQ(4, problem.resources_size()); +} +} // namespace +} // namespace rcpsp +} // namespace scheduling +} // namespace operations_research