diff --git a/examples/cpp/rcpsp_sat.cc b/examples/cpp/rcpsp_sat.cc index 5ecbf68549..5a17772107 100644 --- a/examples/cpp/rcpsp_sat.cc +++ b/examples/cpp/rcpsp_sat.cc @@ -26,18 +26,19 @@ #include "ortools/sat/optimization.h" #include "ortools/sat/precedences.h" #include "ortools/util/rcpsp_parser.h" +#include "ortools/util/rcpsp.pb.h" DEFINE_string(input, "", "Input file."); DEFINE_string(params, "", "Sat parameters in text proto format."); namespace operations_research { namespace sat { -int ComputeNaiveHorizon(const RcpspParser& parser) { +int ComputeNaiveHorizon(const util::RcpspProblem& problem) { int horizon = 0; - for (const RcpspParser::Task& task : parser.tasks()) { + for (const util::Task& task : problem.tasks()) { int max_duration = 0; - for (const RcpspParser::Recipe& recipe : task.recipes) { - max_duration = std::max(max_duration, recipe.duration); + for (const util::Recipe& recipe : task.recipes()) { + max_duration = std::max(max_duration, recipe.duration()); } horizon += max_duration; } @@ -54,24 +55,27 @@ void LoadAndSolve(const std::string& file_name) { RcpspParser parser; CHECK(parser.LoadFile(file_name)); LOG(INFO) << "Successfully read '" << file_name << "'."; + util::RcpspProblem problem = parser.problem(); + LOG(INFO) << problem.DebugString(); const std::string problem_type = - parser.is_rcpsp_max() - ? (parser.is_resource_investment() ? "Resource investment/Max" - : "RCPSP/Max") - : (parser.is_resource_investment() ? "Resource investment" : "RCPSP"); - LOG(INFO) << problem_type << " problem with " << parser.resources().size() - << " resources, and " << parser.tasks().size() << " tasks."; + problem.is_rcpsp_max() + ? (problem.is_resource_investment() ? "Resource investment/Max" + : "RCPSP/Max") + : (problem.is_resource_investment() ? "Resource investment" + : "RCPSP"); + LOG(INFO) << problem_type << " problem with " << problem.resources_size() + << " resources, and " << problem.tasks_size() << " tasks."; Model model; model.Add(NewSatParameters(FLAGS_params)); - const int num_tasks = parser.tasks().size(); - const int num_resources = parser.resources().size(); + const int num_tasks = problem.tasks().size(); + const int num_resources = problem.resources().size(); const int horizon = - parser.deadline() == -1 - ? (parser.horizon() == -1 ? ComputeNaiveHorizon(parser) - : parser.horizon()) - : parser.deadline(); + problem.deadline() == -1 + ? (problem.horizon() == -1 ? ComputeNaiveHorizon(problem) + : problem.horizon()) + : problem.deadline(); LOG(INFO) << "Horizon = " << horizon; std::vector> intervals_per_resources( @@ -86,39 +90,37 @@ void LoadAndSolve(const std::string& file_name) { std::vector> alternatives_per_task(num_tasks); for (int t = 1; t < num_tasks - 1; ++t) { // Ignore both sentinels. - const RcpspParser::Task& task = parser.tasks()[t]; + const util::Task& task = problem.tasks(t); - if (task.recipes.size() == 1) { + if (task.recipes_size() == 1) { // Create the master interval. - const RcpspParser::Recipe& recipe = task.recipes.front(); - CHECK_EQ(num_resources, recipe.demands_per_resource.size()); + const util::Recipe& recipe = task.recipes(0); const IntervalVariable interval = - model.Add(NewInterval(0, horizon, recipe.duration)); + model.Add(NewInterval(0, horizon, recipe.duration())); task_starts[t] = model.Get(StartVar(interval)); task_ends[t] = model.Get(EndVar(interval)); alternatives_per_task[t].push_back(interval); // Add intervals to the resources. - for (int r = 0; r < num_resources; ++r) { - const int demand = recipe.demands_per_resource[r]; - if (demand == 0) continue; + for (int i = 0; i < recipe.demands_size(); ++i) { + const int demand = recipe.demands(i); + const int res = recipe.resources(i); - consumptions_per_resources[r].push_back(demand); - if (parser.resources()[r].renewable) { - intervals_per_resources[r].push_back(interval); - demands_per_resources[r].push_back( + consumptions_per_resources[res].push_back(demand); + if (problem.resources(res).renewable()) { + intervals_per_resources[res].push_back(interval); + demands_per_resources[res].push_back( model.Add(ConstantIntegerVariable(demand))); } else { - presences_per_resources[r].push_back( + presences_per_resources[res].push_back( model.Add(ConstantIntegerVariable(1))); } } } else { int min_size = kint32max; int max_size = 0; - for (const RcpspParser::Recipe& recipe : task.recipes) { - CHECK_EQ(num_resources, recipe.demands_per_resource.size()); - const int duration = recipe.duration; + for (const util::Recipe& recipe : task.recipes()) { + const int duration = recipe.duration(); min_size = std::min(min_size, duration); max_size = std::max(max_size, duration); const Literal is_present = @@ -129,17 +131,17 @@ void LoadAndSolve(const std::string& file_name) { const IntegerVariable presence_var = model.Add(NewIntegerVariableFromLiteral(is_present)); - for (int r = 0; r < num_resources; ++r) { - const int demand = recipe.demands_per_resource[r]; - if (demand == 0) continue; + for (int i = 0; i < recipe.demands_size(); ++i) { + const int demand = recipe.demands(i); + const int res = recipe.resources(i); - consumptions_per_resources[r].push_back(demand); - if (parser.resources()[r].renewable) { - intervals_per_resources[r].push_back(interval); - demands_per_resources[r].push_back( + consumptions_per_resources[res].push_back(demand); + if (problem.resources(res).renewable()) { + intervals_per_resources[res].push_back(interval); + demands_per_resources[res].push_back( model.Add(ConstantIntegerVariable(demand))); } else { - presences_per_resources[r].push_back(presence_var); + presences_per_resources[res].push_back(presence_var); } } } @@ -158,29 +160,31 @@ void LoadAndSolve(const std::string& file_name) { const IntegerVariable makespan = model.Add(NewIntegerVariable(0, horizon)); // Add precedences. - if (parser.is_rcpsp_max()) { + if (problem.is_rcpsp_max()) { for (int t = 1; t < num_tasks - 1; ++t) { - const RcpspParser::Task& task = parser.tasks()[t]; - const int num_modes = task.recipes.size(); + const util::Task& task = problem.tasks(t); + const int num_modes = task.recipes_size(); - for (int s = 0; s < task.successors.size(); ++s) { - const int n = task.successors[s]; - const std::vector>& delay_matrix = task.delays[s]; - CHECK_EQ(delay_matrix.size(), num_modes); - const int num_other_modes = parser.tasks()[n].recipes.size(); + for (int s = 0; s < task.successors_size(); ++s) { + const int n = task.successors(s); + const auto& delay_matrix = task.successor_delays(s); + CHECK_EQ(delay_matrix.recipe_delays_size(), num_modes); + const int num_other_modes = problem.tasks(n).recipes_size(); for (int m1 = 0; m1 < num_modes; ++m1) { const IntegerVariable s1 = model.Get(StartVar(alternatives_per_task[t][m1])); - CHECK_EQ(num_other_modes, delay_matrix[m1].size()); + CHECK_EQ(num_other_modes, + delay_matrix.recipe_delays(m1).min_delays_size()); if (n == num_tasks - 1) { CHECK_EQ(1, num_other_modes); - const int delay = delay_matrix[m1][0]; + const int delay = delay_matrix.recipe_delays(m1).min_delays(0); model.Add(LowerOrEqualWithOffset(s1, makespan, delay)); } else { for (int m2 = 0; m2 < num_other_modes; ++m2) { - const int delay = delay_matrix[m1][m2]; + const int delay = + delay_matrix.recipe_delays(m1).min_delays(m2); const IntegerVariable s2 = model.Get(StartVar(alternatives_per_task[n][m2])); model.Add(LowerOrEqualWithOffset(s1, s2, delay)); @@ -191,7 +195,7 @@ void LoadAndSolve(const std::string& file_name) { } } else { for (int t = 1; t < num_tasks - 1; ++t) { - for (int n : parser.tasks()[t].successors) { + for (int n : problem.tasks(t).successors()) { if (n == num_tasks - 1) { // By construction, we do not need to add the precedence // constraint between all tasks and the makespan, just the one @@ -209,21 +213,21 @@ void LoadAndSolve(const std::string& file_name) { // Create resources. for (int r = 0; r < num_resources; ++r) { - const RcpspParser::Resource& res = parser.resources()[r]; - const int64 c = res.max_capacity == -1 + const util::Resource& res = problem.resources(r); + const int64 c = res.max_capacity() == -1 ? VectorSum(consumptions_per_resources[r]) - : res.max_capacity; + : res.max_capacity(); const IntegerVariable capacity = model.Add(ConstantIntegerVariable(c)); model.Add(Cumulative(intervals_per_resources[r], demands_per_resources[r], capacity)); - if (parser.is_resource_investment()) { + if (problem.is_resource_investment()) { const IntegerVariable capacity = model.Add(NewIntegerVariable(0, c)); model.Add(Cumulative(intervals_per_resources[r], demands_per_resources[r], capacity)); capacities.push_back(capacity); - weights.push_back(res.unit_cost); - } else if (res.renewable) { + weights.push_back(res.unit_cost()); + } else if (res.renewable()) { if (intervals_per_resources[r].empty()) continue; const IntegerVariable capacity = model.Add(ConstantIntegerVariable(c)); model.Add(Cumulative(intervals_per_resources[r], demands_per_resources[r], @@ -237,7 +241,7 @@ void LoadAndSolve(const std::string& file_name) { // Create objective var. IntegerVariable objective_var; - if (parser.is_resource_investment()) { + if (problem.is_resource_investment()) { objective_var = model.Add(NewWeightedSum(weights, capacities)); } else { objective_var = makespan; diff --git a/makefiles/Makefile.gen.mk b/makefiles/Makefile.gen.mk index bedbceb927..9ab83bf17a 100644 --- a/makefiles/Makefile.gen.mk +++ b/makefiles/Makefile.gen.mk @@ -315,7 +315,8 @@ UTIL_LIB_OBJS = \ $(OBJ_DIR)/util/sorted_interval_list.$O \ $(OBJ_DIR)/util/stats.$O \ $(OBJ_DIR)/util/time_limit.$O \ - $(OBJ_DIR)/util/xml_helper.$O + $(OBJ_DIR)/util/xml_helper.$O \ + $(OBJ_DIR)/util/rcpsp.pb.$O $(SRC_DIR)/ortools/util/affine_relation.h: \ $(SRC_DIR)/ortools/base/iterator_adaptors.h \ @@ -504,6 +505,7 @@ $(OBJ_DIR)/util/rcpsp_parser.$O: \ $(SRC_DIR)/ortools/util/rcpsp_parser.cc \ $(SRC_DIR)/ortools/util/filelineiter.h \ $(SRC_DIR)/ortools/util/rcpsp_parser.h \ + $(GEN_DIR)/ortools/util/rcpsp.pb.h \ $(SRC_DIR)/ortools/base/numbers.h \ $(SRC_DIR)/ortools/base/split.h \ $(SRC_DIR)/ortools/base/stringpiece_utils.h \ @@ -541,6 +543,14 @@ $(OBJ_DIR)/util/xml_helper.$O: \ $(SRC_DIR)/ortools/base/strutil.h $(CCC) $(CFLAGS) -c $(SRC_DIR)$Sortools$Sutil$Sxml_helper.cc $(OBJ_OUT)$(OBJ_DIR)$Sutil$Sxml_helper.$O +$(GEN_DIR)/ortools/util/rcpsp.pb.cc: $(SRC_DIR)/ortools/util/rcpsp.proto + $(PROTOBUF_DIR)/bin/protoc --proto_path=$(INC_DIR) --cpp_out=$(GEN_DIR) $(SRC_DIR)/ortools/util/rcpsp.proto + +$(GEN_DIR)/ortools/util/rcpsp.pb.h: $(GEN_DIR)/ortools/util/rcpsp.pb.cc + +$(OBJ_DIR)/util/rcpsp.pb.$O: $(GEN_DIR)/ortools/util/rcpsp.pb.cc + $(CCC) $(CFLAGS) -c $(GEN_DIR)/ortools/util/rcpsp.pb.cc $(OBJ_OUT)$(OBJ_DIR)$Sutil$Srcpsp.pb.$O + LP_DATA_DEPS = \ $(SRC_DIR)/ortools/lp_data/lp_data.h \ $(SRC_DIR)/ortools/lp_data/lp_types.h \ @@ -3575,4 +3585,3 @@ $(GEN_DIR)/ortools/constraint_solver/solver_parameters.pb.h: $(GEN_DIR)/ortools/ $(OBJ_DIR)/constraint_solver/solver_parameters.pb.$O: $(GEN_DIR)/ortools/constraint_solver/solver_parameters.pb.cc $(CCC) $(CFLAGS) -c $(GEN_DIR)/ortools/constraint_solver/solver_parameters.pb.cc $(OBJ_OUT)$(OBJ_DIR)$Sconstraint_solver$Ssolver_parameters.pb.$O - diff --git a/ortools/util/rcpsp.proto b/ortools/util/rcpsp.proto new file mode 100644 index 0000000000..3dc243bcae --- /dev/null +++ b/ortools/util/rcpsp.proto @@ -0,0 +1,143 @@ +// Copyright 2010-2017 Google +// 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. + +// RCPSP problem. +// +// The problem description is as follows: +// +// You have a set of resources. They all have a maximum capacity, and +// can be renewable or not. +// +// You have a set of tasks. Each task has a list of successors, and a +// list of recipes. Each recipe consists of a duration, and a list of +// demands, one per resource. +// +// The tasks dependencies form a DAG with a single source and a single end. +// Both source and end tasks have a zero duration, and no resource consumption. +// +// In case the problem is of type RCPSP/Max. The data contains an additional +// array of delays per task. This flattened array contains the following +// information for task i with mode mi and successor j with mode mj, then +// start(i) + delay[i, mi, j, mj] <= start(j). This subsumes the normal +// successor predecence of the non RCPSP/Max variation, i.e.: +// start(i) + duration(i, mi) <= start(j). +// +// In the normal case, the objective is to minimize the makespan of the problem. +// +// In the resource investment problem, there is no makespan. It is +// replaced by a strict deadline, and each task must finish before +// this deadline. In that case, resources have a unit cost, and the +// objective is to minimize the sum of resource cost. +// +// In the consumer/producer case, tasks have a zero duration, and demands can be +// negative. The constraint states that at each time point, the sum of demands +// happening before or during this time must be between the min and max +// capacity. Note that in that case, both min and max capacity can be negative. +// Furthermore, if 0 is not in [min_capacity, max_capacity], then a sufficient +// set of events must happen at time 0 such that the sum of their demands must +// fall inside the capacity interval. +// +// The supported file formats are: +// - standard psplib (.sm and .mm): +// http://www.om-db.wi.tum.de/psplib/data.html +// - rcpsp problem in the patterson format (.rcp): +// http://www.om-db.wi.tum.de/psplib/dataob.html +// - rcpsp/max (.sch): +// https://www.wiwi.tu-clausthal.de/de/abteilungen/produktion/forschung/ +// schwerpunkte/project-generator/rcpspmax/ +// https://www.wiwi.tu-clausthal.de/de/abteilungen/produktion/forschung/ +// schwerpunkte/project-generator/mrcpspmax/ +// - resource investment problem with max delay (.sch): +// https://www.wiwi.tu-clausthal.de/de/abteilungen/produktion/forschung/ +// schwerpunkte/project-generator/ripmax/ + +syntax = "proto3"; + +option java_package = "com.google.ortools.util"; +option java_multiple_files = true; +option csharp_namespace = "Google.OrTools.Util"; + +package operations_research.util; + +message Resource { + // The max capacity of the cumulative. + int32 max_capacity = 1; + + // This field is used only in the consumer/producer case. It states the + // minimum capacity that must be valid at each time point. + int32 min_capacity = 2; + + // Indicates if the resource is renewable, that is if a task demands + // d from this resource, then the available capacity decreases by d at the + // start of the task and increases by d at the end of the task. + bool renewable = 3; + + // If non zero, then each unit of capacity will incur a cost of unit_cost. + int32 unit_cost = 4; +} + +message Recipe { + // The duration of the task when this recipe is selected. + int32 duration = 1; + + // In the general case, demand must be >= 0. In the consumer/producer case, + // it can be < 0. Note that in this case, the tasks always have a duration + // of zero. Thus the effect of the demand (increase or decrease of the + // current usage) happens at the start of the task. + repeated int32 demands = 2; + + // This parallel list indicates which resource index (in the main problem) + // the above demand corresponds to. + repeated int32 resources = 3; +} + +message PerRecipeDelays { + repeated int32 min_delays = 1; +} + +message PerSuccessorDelays { + repeated PerRecipeDelays recipe_delays = 1; +} + +message Task { + // The indices of the successors in the main problem. + repeated int32 successors = 1; + + // The list of possible ways to execute the task. + repeated Recipe recipes = 2; + + // If the current task has n successors and m modes then this is + // an n x m matrix where each entry at line i is a vector with the + // same length as the number of recipes for the task successor[i]. If + // mode m1 is chosen for the current task, and mode m2 is chosen + // for its successor i, we have: + // start(current_task) + delay[i][m1][m2] <= start(successor_task). + repeated PerSuccessorDelays successor_delays = 3; +} + +message RcpspProblem { + repeated Resource resources = 1; + repeated Task tasks = 2; + bool is_consumer_producer = 3; + bool is_resource_investment = 4; + bool is_rcpsp_max = 5; + int32 deadline = 6; + int32 horizon = 7; + int32 release_date = 8; + int32 tardiness_cost = 9; + int32 mpm_time = 10; + int64 seed = 11; + string basedata = 12; + int32 due_date = 13; + string name = 14; +} diff --git a/ortools/util/rcpsp_parser.cc b/ortools/util/rcpsp_parser.cc index db7086e786..eac1673ded 100644 --- a/ortools/util/rcpsp_parser.cc +++ b/ortools/util/rcpsp_parser.cc @@ -25,32 +25,26 @@ using ::strings::delimiter::AnyOf; RcpspParser::RcpspParser() : seed_(-1), - horizon_(-1), - release_date_(-1), - due_date_(-1), - tardiness_cost_(-1), - mpm_time_(-1), - deadline_(-1), load_status_(NOT_STARTED), declared_tasks_(-1), current_task_(-1), - is_rcpsp_max_(false), - is_resource_investment_(false), - is_consumer_producer_(false), - unreads_(0) {} + unreads_(0) { + rcpsp_.set_deadline(-1); + rcpsp_.set_horizon(-1); +} bool RcpspParser::LoadFile(const std::string& file_name) { if (load_status_ != NOT_STARTED) { return false; } - is_rcpsp_max_ = strings::EndsWith(file_name, ".sch") || - strings::EndsWith(file_name, ".SCH"); + const bool is_rcpsp_max = strings::EndsWith(file_name, ".sch") || + strings::EndsWith(file_name, ".SCH"); const bool is_patterson = strings::EndsWith(file_name, ".rcp"); load_status_ = HEADER_SECTION; for (const std::string& line : FileLines(file_name)) { - if (is_rcpsp_max_) { + if (is_rcpsp_max) { ProcessRcpspMaxLine(line); } else if (is_patterson) { ProcessPattersonLine(line); @@ -58,11 +52,12 @@ bool RcpspParser::LoadFile(const std::string& file_name) { ProcessRcpspLine(line); } if (load_status_ == ERROR_FOUND) { + LOG(INFO) << rcpsp_.DebugString(); return false; } } // Count the extra start and end tasks. - return declared_tasks_ + 2 == tasks_.size() && + return declared_tasks_ + 2 == rcpsp_.tasks_size() && load_status_ == PARSING_FINISHED; } @@ -87,9 +82,9 @@ void RcpspParser::ProcessRcpspLine(const std::string& line) { } case HEADER_SECTION: { if (words[0] == "file") { - basedata_ = words[3]; + rcpsp_.set_basedata(words[3]); } else if (words[0] == "initial") { - seed_ = atoi64(words[4]); + rcpsp_.set_seed(atoi64(words[4])); load_status_ = PROJECT_SECTION; } else if (words[0] == "jobs") { // Workaround for the mmlib files which has less headers. @@ -107,16 +102,24 @@ void RcpspParser::ProcessRcpspLine(const std::string& line) { // This declaration counts the 2 sentinels. declared_tasks_ = atoi32(words[4]) - 2; } else if (words[0] == "horizon") { - horizon_ = atoi32(words[1]); + rcpsp_.set_horizon(atoi32(words[1])); } else if (words[0] == "RESOURCES") { // Nothing to do. } else if (words.size() > 1 && words[1] == "renewable") { for (int i = 0; i < atoi32(words[2]); ++i) { - resources_.push_back({-1, -1, true, 0}); + util::Resource* const res = rcpsp_.add_resources(); + res->set_max_capacity(-1); + res->set_min_capacity(-1); + res->set_renewable(true); + res->set_unit_cost(0); } } else if (words.size() > 1 && words[1] == "nonrenewable") { for (int i = 0; i < atoi32(words[2]); ++i) { - resources_.push_back({-1, -1, false, 0}); + util::Resource* const res = rcpsp_.add_resources(); + res->set_max_capacity(-1); + res->set_min_capacity(-1); + res->set_renewable(false); + res->set_unit_cost(0); } } else if (words.size() > 1 && words[1] == "doubly") { // Nothing to do. @@ -135,10 +138,10 @@ void RcpspParser::ProcessRcpspLine(const std::string& line) { // Nothing to do. } else if (words.size() == 6) { declared_tasks_ = atoi32(words[1]); - release_date_ = atoi32(words[2]); - due_date_ = atoi32(words[3]); - tardiness_cost_ = atoi32(words[4]); - mpm_time_ = atoi32(words[5]); + rcpsp_.set_release_date(atoi32(words[2])); + rcpsp_.set_due_date(atoi32(words[3])); + rcpsp_.set_tardiness_cost(atoi32(words[4])); + rcpsp_.set_mpm_time(atoi32(words[5])); } else if (words.size() == 2 && words[0] == "PRECEDENCE") { load_status_ = PRECEDENCE_SECTION; } else { @@ -151,9 +154,12 @@ void RcpspParser::ProcessRcpspLine(const std::string& line) { // Nothing to do. } else if (words.size() >= 3) { const int task_index = atoi32(words[0]); - CHECK_EQ(task_index, tasks_.size() + 1); - tasks_.resize(task_index); - tasks_.back().recipes.resize(atoi32(words[1])); + CHECK_EQ(task_index, rcpsp_.tasks_size() + 1); + util::Task* const task = rcpsp_.add_tasks(); + const int num_recipes = atoi32(words[1]); + for (int i = 0; i < num_recipes; ++i) { + task->add_recipes(); + } const int num_successors = atoi32(words[2]); if (words.size() != 3 + num_successors) { ReportError(line); @@ -161,7 +167,7 @@ void RcpspParser::ProcessRcpspLine(const std::string& line) { } for (int i = 0; i < num_successors; ++i) { // The array of tasks is 0-based for us. - tasks_.back().successors.push_back(atoi32(words[3 + i]) - 1); + task->add_successors(atoi32(words[3 + i]) - 1); } } else if (words[0] == "REQUESTS/DURATIONS") { load_status_ = REQUEST_SECTION; @@ -173,28 +179,38 @@ void RcpspParser::ProcessRcpspLine(const std::string& line) { case REQUEST_SECTION: { if (words[0] == "jobnr.") { // Nothing to do. - } else if (words.size() == 3 + resources_.size()) { + } else if (words.size() == 3 + rcpsp_.resources_size()) { // Start of a new task (index is 0-based for us). current_task_ = atoi32(words[0]) - 1; const int current_mode = atoi32(words[1]) - 1; - CHECK_LT(current_mode, tasks_[current_task_].recipes.size()); + CHECK_LT(current_mode, rcpsp_.tasks(current_task_).recipes_size()); if (current_mode != 0) { ReportError(line); break; } - Recipe& recipe = tasks_[current_task_].recipes[current_mode]; - recipe.duration = atoi32(words[2]); - for (int i = 0; i < resources_.size(); ++i) { - recipe.demands_per_resource.push_back(atoi32(words[3 + i])); + util::Recipe* const recipe = + rcpsp_.mutable_tasks(current_task_)->mutable_recipes(current_mode); + recipe->set_duration(atoi32(words[2])); + for (int i = 0; i < rcpsp_.resources_size(); ++i) { + const int demand = atoi32(words[3 + i]); + if (demand != 0) { + recipe->add_demands(demand); + recipe->add_resources(i); + } } - } else if (words.size() == 2 + resources_.size()) { + } else if (words.size() == 2 + rcpsp_.resources_size()) { // New mode for a current task. const int current_mode = atoi32(words[0]) - 1; - CHECK_LT(current_mode, tasks_[current_task_].recipes.size()); - Recipe& recipe = tasks_[current_task_].recipes[current_mode]; - recipe.duration = atoi32(words[1]); - for (int i = 0; i < resources_.size(); ++i) { - recipe.demands_per_resource.push_back(atoi32(words[2 + i])); + CHECK_LT(current_mode, rcpsp_.tasks(current_task_).recipes_size()); + util::Recipe* const recipe = + rcpsp_.mutable_tasks(current_task_)->mutable_recipes(current_mode); + recipe->set_duration(atoi32(words[1])); + for (int i = 0; i < rcpsp_.resources_size(); ++i) { + const int demand = atoi32(words[2 + i]); + if (demand != 0) { + recipe->add_demands(demand); + recipe->add_resources(i); + } } } else if (words[0] == "RESOURCEAVAILABILITIES" || (words[0] == "RESOURCE" && words[1] == "AVAILABILITIES")) { @@ -205,11 +221,11 @@ void RcpspParser::ProcessRcpspLine(const std::string& line) { break; } case RESOURCE_SECTION: { - if (words.size() == 2 * resources_.size()) { + if (words.size() == 2 * rcpsp_.resources_size()) { // Nothing to do. - } else if (words.size() == resources_.size()) { + } else if (words.size() == rcpsp_.resources_size()) { for (int i = 0; i < words.size(); ++i) { - resources_[i].max_capacity = atoi32(words[i]); + rcpsp_.mutable_resources(i)->set_max_capacity(atoi32(words[i])); } load_status_ = PARSING_FINISHED; } else { @@ -240,32 +256,50 @@ void RcpspParser::ProcessRcpspMaxLine(const std::string& line) { break; } case HEADER_SECTION: { + rcpsp_.set_is_rcpsp_max(true); if (words.size() == 2) { - is_consumer_producer_ = true; + rcpsp_.set_is_consumer_producer(true); } else if (words.size() < 4 || atoi32(words[3]) != 0) { ReportError(line); break; } if (words.size() == 5) { - deadline_ = atoi32(words[4]); - is_resource_investment_ = true; + rcpsp_.set_deadline(atoi32(words[4])); + rcpsp_.set_is_resource_investment(true); } declared_tasks_ = atoi32(words[0]); - tasks_.resize(declared_tasks_ + 2); // 2 sentinels. temp_delays_.resize(declared_tasks_ + 2); + recipe_sizes_.resize(declared_tasks_ + 2, 0); // Creates resources. - if (is_consumer_producer_) { + if (rcpsp_.is_consumer_producer()) { const int num_nonrenewable_resources = atoi32(words[1]); - resources_.resize(num_nonrenewable_resources, {-1, -1, false, 0}); + for (int i = 0; i < num_nonrenewable_resources; ++i) { + util::Resource* const res = rcpsp_.add_resources(); + res->set_max_capacity(-1); + res->set_min_capacity(-1); + res->set_renewable(false); + res->set_unit_cost(0); + } } else { const int num_renewable_resources = atoi32(words[1]); const int num_nonrenewable_resources = atoi32(words[2]); - resources_.resize(num_renewable_resources, {-1, -1, true, 0}); - resources_.resize(num_renewable_resources + num_nonrenewable_resources, - {-1, -1, false, 0}); + for (int i = 0; i < num_renewable_resources; ++i) { + util::Resource* const res = rcpsp_.add_resources(); + res->set_max_capacity(-1); + res->set_min_capacity(-1); + res->set_renewable(true); + res->set_unit_cost(0); + } + for (int i = 0; i < num_nonrenewable_resources; ++i) { + util::Resource* const res = rcpsp_.add_resources(); + res->set_max_capacity(-1); + res->set_min_capacity(-1); + res->set_renewable(false); + res->set_unit_cost(0); + } } // Set up for the next section. @@ -296,14 +330,14 @@ void RcpspParser::ProcessRcpspMaxLine(const std::string& line) { } const int num_modes = atoi32(words[1]); + recipe_sizes_[task_id] = num_modes; const int num_successors = atoi32(words[2]); - RcpspParser::Task& task = tasks_[task_id]; - task.recipes.resize(num_modes); + util::Task* const task = rcpsp_.add_tasks(); // Read successors. for (int i = 0; i < num_successors; ++i) { - task.successors.push_back(atoi32(words[3 + i])); + task->add_successors(atoi32(words[3 + i])); } // Read flattened delays into temp_delays_. @@ -315,17 +349,19 @@ void RcpspParser::ProcessRcpspMaxLine(const std::string& line) { // Convert the flattened delays into structured delays (1 vector per // successor) in the task_size. for (int t = 1; t <= declared_tasks_; ++t) { - const int num_modes = tasks_[t].recipes.size(); - const int num_successors = tasks_[t].successors.size(); - tasks_[t].delays.resize(num_successors); + const int num_modes = recipe_sizes_[t]; + const int num_successors = rcpsp_.tasks(t).successors_size(); int count = 0; for (int s = 0; s < num_successors; ++s) { - tasks_[t].delays[s].resize(num_modes); + util::PerSuccessorDelays* const succ_delays = + rcpsp_.mutable_tasks(t)->add_successor_delays(); for (int m1 = 0; m1 < num_modes; ++m1) { - const int other = tasks_[t].successors[s]; - const int num_other_modes = tasks_[other].recipes.size(); + util::PerRecipeDelays* const recipe_delays = + succ_delays->add_recipe_delays(); + const int other = rcpsp_.tasks(t).successors(s); + const int num_other_modes = recipe_sizes_[other]; for (int m2 = 0; m2 < num_other_modes; ++m2) { - tasks_[t].delays[s][m1].push_back(temp_delays_[t][count++]); + recipe_delays->add_min_delays(temp_delays_[t][count++]); } } } @@ -339,47 +375,59 @@ void RcpspParser::ProcessRcpspMaxLine(const std::string& line) { break; } case REQUEST_SECTION: { - if (words.size() == 3 + resources_.size()) { + if (words.size() == 3 + rcpsp_.resources_size()) { // Start of a new task. current_task_ = atoi32(words[0]); // 0 based indices for the mode. const int current_mode = atoi32(words[1]) - 1; - CHECK_LT(current_mode, tasks_[current_task_].recipes.size()); if (current_mode != 0) { ReportError(line); break; } - Recipe& recipe = tasks_[current_task_].recipes[current_mode]; - recipe.duration = atoi32(words[2]); - for (int i = 0; i < resources_.size(); ++i) { - recipe.demands_per_resource.push_back(atoi32(words[3 + i])); + util::Recipe* const recipe = + rcpsp_.mutable_tasks(current_task_)->add_recipes(); + recipe->set_duration(atoi32(words[2])); + for (int i = 0; i < rcpsp_.resources_size(); ++i) { + const int demand = atoi32(words[3 + i]); + if (demand != 0) { + recipe->add_demands(demand); + recipe->add_resources(i); + } } - } else if (words.size() == 2 + resources_.size() && - is_consumer_producer_) { + } else if (words.size() == 2 + rcpsp_.resources_size() && + rcpsp_.is_consumer_producer()) { // Start of a new task. current_task_ = atoi32(words[0]); // 0 based indices for the mode. const int current_mode = atoi32(words[1]) - 1; - CHECK_LT(current_mode, tasks_[current_task_].recipes.size()); if (current_mode != 0) { ReportError(line); break; } - Recipe& recipe = tasks_[current_task_].recipes[current_mode]; - recipe.duration = 0; - for (int i = 0; i < resources_.size(); ++i) { - recipe.demands_per_resource.push_back(atoi32(words[2 + i])); + util::Recipe* const recipe = + rcpsp_.mutable_tasks(current_task_)->add_recipes(); + recipe->set_duration(0); + for (int i = 0; i < rcpsp_.resources_size(); ++i) { + const int demand = atoi32(words[2 + i]); + if (demand != 0) { + recipe->add_demands(demand); + recipe->add_resources(i); + } } - } else if (words.size() == 2 + resources_.size()) { + } else if (words.size() == 2 + rcpsp_.resources_size()) { // New mode for a current task. const int current_mode = atoi32(words[0]) - 1; - CHECK_LT(current_mode, tasks_[current_task_].recipes.size()); - Recipe& recipe = tasks_[current_task_].recipes[current_mode]; - recipe.duration = atoi32(words[1]); - for (int i = 0; i < resources_.size(); ++i) { - recipe.demands_per_resource.push_back(atoi32(words[2 + i])); + util::Recipe* const recipe = + rcpsp_.mutable_tasks(current_task_)->add_recipes(); + recipe->set_duration(atoi32(words[1])); + for (int i = 0; i < rcpsp_.resources_size(); ++i) { + const int demand = atoi32(words[2 + i]); + if (demand != 0) { + recipe->add_demands(demand); + recipe->add_resources(i); + } } } if (current_task_ == declared_tasks_ + 1) { @@ -388,15 +436,15 @@ void RcpspParser::ProcessRcpspMaxLine(const std::string& line) { break; } case RESOURCE_SECTION: { - if (words.size() == resources_.size()) { + if (words.size() == rcpsp_.resources_size()) { for (int i = 0; i < words.size(); ++i) { - if (is_resource_investment_) { - resources_[i].unit_cost = atoi32(words[i]); + if (rcpsp_.is_resource_investment()) { + rcpsp_.mutable_resources(i)->set_unit_cost(atoi32(words[i])); } else { - resources_[i].max_capacity = atoi32(words[i]); + rcpsp_.mutable_resources(i)->set_max_capacity(atoi32(words[i])); } } - if (is_consumer_producer_) { + if (rcpsp_.is_consumer_producer()) { load_status_ = RESOURCE_MIN_SECTION; } else { load_status_ = PARSING_FINISHED; @@ -407,9 +455,9 @@ void RcpspParser::ProcessRcpspMaxLine(const std::string& line) { break; } case RESOURCE_MIN_SECTION: { - if (words.size() == resources_.size()) { + if (words.size() == rcpsp_.resources_size()) { for (int i = 0; i < words.size(); ++i) { - resources_[i].min_capacity = atoi32(words[i]); + rcpsp_.mutable_resources(i)->set_min_capacity(atoi32(words[i])); } load_status_ = PARSING_FINISHED; } else { @@ -443,11 +491,19 @@ void RcpspParser::ProcessPattersonLine(const std::string& line) { break; } declared_tasks_ = atoi32(words[0]) - 2; // Remove the 2 sentinels. - tasks_.resize(declared_tasks_ + 2); // 2 sentinels. + for (int i = 0; i < declared_tasks_ + 2; ++i) { // Add back 2 sentinels. + rcpsp_.add_tasks(); + } // Creates resources. const int num_renewable_resources = atoi32(words[1]); - resources_.resize(num_renewable_resources, {-1, -1, true, 0}); + for (int i = 0; i < num_renewable_resources; ++i) { + util::Resource* const res = rcpsp_.add_resources(); + res->set_max_capacity(-1); + res->set_min_capacity(-1); + res->set_renewable(true); + res->set_unit_cost(0); + } // Set up for the next section. load_status_ = RESOURCE_SECTION; @@ -464,31 +520,35 @@ void RcpspParser::ProcessPattersonLine(const std::string& line) { case PRECEDENCE_SECTION: { if (unreads_ > 0) { for (int i = 0; i < words.size(); ++i) { - tasks_[current_task_].successors.push_back(atoi32(words[i]) - 1); + rcpsp_.mutable_tasks(current_task_)->add_successors( + atoi32(words[i]) - 1); unreads_--; CHECK_GE(unreads_, 0); } } else { - if (words.size() < 2 + resources_.size()) { + if (words.size() < 2 + rcpsp_.resources_size()) { ReportError(line); break; } - RcpspParser::Task& task = tasks_[current_task_]; - task.recipes.resize(1); + util::Task* const task = rcpsp_.mutable_tasks(current_task_); + util::Recipe* const recipe = task->add_recipes(); - const int num_resources = resources_.size(); + const int num_resources = rcpsp_.resources_size(); - RcpspParser::Recipe& recipe = task.recipes.front(); - recipe.duration = atoi32(words[0]); + recipe->set_duration(atoi32(words[0])); for (int i = 1; i <= num_resources; ++i) { - recipe.demands_per_resource.push_back(atoi32(words[i])); + const int demand = atoi32(words[i]); + if (demand != 0) { + recipe->add_demands(demand); + recipe->add_resources(i - 1); + } } unreads_ = atoi32(words[1 + num_resources]); for (int i = 2 + num_resources; i < words.size(); ++i) { // Successors are 1 based in the data file. - task.successors.push_back(atoi32(words[i]) - 1); + task->add_successors(atoi32(words[i]) - 1); unreads_--; CHECK_GE(unreads_, 0); } @@ -504,9 +564,9 @@ void RcpspParser::ProcessPattersonLine(const std::string& line) { break; } case RESOURCE_SECTION: { - if (words.size() == resources_.size()) { + if (words.size() == rcpsp_.resources_size()) { for (int i = 0; i < words.size(); ++i) { - resources_[i].max_capacity = atoi32(words[i]); + rcpsp_.mutable_resources(i)->set_max_capacity(atoi32(words[i])); } load_status_ = PRECEDENCE_SECTION; current_task_ = 0; diff --git a/ortools/util/rcpsp_parser.h b/ortools/util/rcpsp_parser.h index 4993c13bf8..94206b02bc 100644 --- a/ortools/util/rcpsp_parser.h +++ b/ortools/util/rcpsp_parser.h @@ -21,6 +21,7 @@ #include #include "ortools/base/integral_types.h" +#include "ortools/util/rcpsp.pb.h" namespace operations_research { @@ -75,60 +76,9 @@ namespace operations_research { // schwerpunkte/project-generator/ripmax/ class RcpspParser { public: - struct Resource { - // The max capacity of the cumulative. - int max_capacity; - // This field is used only in the consumer/producer case. It states the - // minimum capacity that must be valid at each time point. - int min_capacity; - bool renewable; - // If non zero, then each unit of capacity will incur a cost of unit_cost. - int unit_cost; - }; - - struct Recipe { - int duration; - // In the general case, demand must be >= 0. In the consumer/producer case, - // it can be < 0. Note that in this case, the tasks always have a duration - // of zero. Thus the effect of the demand (increase or decrease of the - // current usage) happens at the start of the task. - std::vector demands_per_resource; - }; - - struct Task { - std::vector successors; - // If the current task has n successors and m modes then this is - // an n x m matrix where each entry at line i is a vector with the - // same length as the number of modes for the task successor[i]. If - // mode m1 is chosen for the current task, and mode m2 is chosen - // for its successor i, we have: - // start(current_task) + delay[i][m1][m2] <= start(successor_task). - std::vector>> delays; - std::vector recipes; - }; - RcpspParser(); - std::string name() const { return name_; } - const std::vector& resources() const { return resources_; } - const std::vector& tasks() const { return tasks_; } - // The horizon is a date where we are sure that all tasks can fit before it. - int horizon() const { return horizon_; } - // The release date is defined in the rcpsp base format, but is not used. - int release_date() const { return release_date_; } - // The due date is defined in the rcpsp base format, but is not used. - int due_date() const { return due_date_; } - // The tardiness cost is defined in the rcpsp base format, but is not used. - int tardiness_cost() const { return tardiness_cost_; } - // The mpm_time is defined in the rcpsp base format, but is not used. - // It is defined as the minimum makespan in case of interruptible tasks. - int mpm_time() const { return mpm_time_; } - // If set, it defines a strict date, and each task must finish before this. - int deadline() const { return deadline_; } - // Define the problem type. - bool is_rcpsp_max() const { return is_rcpsp_max_; } - bool is_resource_investment() const { return is_resource_investment_; } - bool is_consumer_producer() const { return is_consumer_producer_; } + util::RcpspProblem problem() const { return rcpsp_; } bool LoadFile(const std::string& file_name); @@ -151,25 +101,15 @@ class RcpspParser { void ProcessRcpspMaxLine(const std::string& line); void ReportError(const std::string& line); - std::string name_; std::string basedata_; int64 seed_; - std::vector resources_; - std::vector tasks_; - int horizon_; - int release_date_; - int due_date_; - int tardiness_cost_; - int mpm_time_; - int deadline_; LoadStatus load_status_; int declared_tasks_; int current_task_; - bool is_rcpsp_max_; - bool is_resource_investment_; - bool is_consumer_producer_; std::vector> temp_delays_; + std::vector recipe_sizes_; int unreads_; + util::RcpspProblem rcpsp_; }; } // namespace operations_research